mirror of
https://github.com/rizsotto/Bear.git
synced 2026-10-24 07:15:08 +02:00
With the config dependency gone, the analysis core can live where the compiler enforces its boundaries. `crates/semantic` now owns compiler recognition, flag parsing, and the compilers/*.yaml definitions with their codegen build script; `crates/bear` keeps the application layer and gains a dependency on it. A back-edge from the driver into the analysis core is now a dependency cycle rather than a convention. The crate states a deliberate public API instead of inheriting the module's blanket visibility. `interpreters::compilers` narrowed to `pub(crate)`, so `CompilerType`, `CompilerId`, `CompilerInterpreter`, the interpreter combinators and `IgnoreByPath` are unreachable from outside; the driver sees only what it uses. The manifest lists what analysis actually needs, which turned out to exclude serde entirely. The `testing` module is feature-gated rather than `cfg(test)`, because a dependency's test code is not compiled for its dependents' tests. The gate is `any(test, feature = "testing")` so the crate's own fast test loop still covers the module. `bear` enables the feature as a dev-dependency; release builds carry no test factories. Build scripts and scripts that read the YAML directory follow it: intercept's env-key codegen, the codegen snapshot tests, the docs-site generator, and the integration build script's rerun trigger. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
256 lines
10 KiB
Rust
256 lines
10 KiB
Rust
/*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* This build script is responsible for setting up environment variables and
|
|
* cfg flags required by the integration tests, mirroring the logic from the
|
|
* original bear/build.rs.
|
|
*/
|
|
|
|
/// Driver executable name (platform-dependent)
|
|
#[cfg(windows)]
|
|
const DRIVER_NAME: &str = "bear-driver.exe";
|
|
#[cfg(not(windows))]
|
|
const DRIVER_NAME: &str = "bear-driver";
|
|
|
|
/// Wrapper executable name (platform-dependent)
|
|
#[cfg(windows)]
|
|
const WRAPPER_NAME: &str = "bear-wrapper.exe";
|
|
#[cfg(not(windows))]
|
|
const WRAPPER_NAME: &str = "bear-wrapper";
|
|
|
|
/// Preload library name (platform-dependent)
|
|
#[cfg(target_os = "macos")]
|
|
const PRELOAD_NAME: &str = "libexec.dylib";
|
|
#[cfg(not(target_os = "macos"))]
|
|
const PRELOAD_NAME: &str = "libexec.so";
|
|
|
|
fn main() {
|
|
// Re-run build script if env changes
|
|
println!("cargo:rerun-if-env-changed=PATH");
|
|
println!("cargo:rerun-if-env-changed=CARGO_TARGET_DIR");
|
|
println!("cargo:rerun-if-env-changed=PROFILE");
|
|
println!("cargo:rerun-if-env-changed=INTERCEPT_LIBDIR");
|
|
|
|
// Forward INTERCEPT_LIBDIR so integration tests use the same value as bear-driver
|
|
let intercept_libdir = std::env::var("INTERCEPT_LIBDIR").unwrap_or_else(|_| "lib".to_string());
|
|
validate_intercept_libdir(&intercept_libdir);
|
|
println!("cargo:rustc-env=INTERCEPT_LIBDIR={}", intercept_libdir);
|
|
|
|
// Re-run if bear or intercept-preload artifacts change
|
|
println!("cargo:rerun-if-changed=../../crates/bear/src");
|
|
println!("cargo:rerun-if-changed=../../crates/semantic/src");
|
|
println!("cargo:rerun-if-changed=../../crates/intercept-preload/src");
|
|
|
|
// Locate install script and repo root for integration tests.
|
|
// CARGO_MANIFEST_DIR is <root>/tests/integration, two levels below root.
|
|
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
|
let repo_root = std::path::Path::new(&manifest_dir).ancestors().nth(2).unwrap();
|
|
let install_script = repo_root.join("scripts").join("install.sh");
|
|
println!("cargo:rerun-if-changed={}", install_script.display());
|
|
println!("cargo:rustc-env=REPO_ROOT={}", repo_root.display());
|
|
println!("cargo:rustc-env=INSTALL_SCRIPT_PATH={}", install_script.display());
|
|
|
|
// Set up paths for driver, wrapper and preload artifacts
|
|
let (driver_path, wrapper_path, preload_path) = find_intercept_artifacts();
|
|
println!("cargo:rustc-env=DRIVER_EXECUTABLE={}", DRIVER_NAME);
|
|
println!("cargo:rustc-env=DRIVER_EXECUTABLE_PATH={}", driver_path);
|
|
println!("cargo:rustc-env=WRAPPER_EXECUTABLE={}", WRAPPER_NAME);
|
|
println!("cargo:rustc-env=WRAPPER_EXECUTABLE_PATH={}", wrapper_path);
|
|
println!("cargo:rustc-env=PRELOAD_LIBRARY={}", PRELOAD_NAME);
|
|
println!("cargo:rustc-env=PRELOAD_LIBRARY_PATH={}", preload_path);
|
|
|
|
// Replay platform-checks results as cfg directives for this crate
|
|
platform_checks::emit_cfg();
|
|
platform_checks::emit_check_cfg();
|
|
|
|
// Perform additional checks for executables
|
|
check_executable_exists("true");
|
|
check_executable_exists("false");
|
|
check_executable_exists("echo");
|
|
check_executable_exists("sleep");
|
|
check_executable_exists("setsid");
|
|
check_executable_exists("cat");
|
|
check_executable_exists("ls");
|
|
check_executable_exists("mkdir");
|
|
check_executable_exists("rm");
|
|
check_executable_exists("nm");
|
|
check_one_executable_exists("shell", &["sh", "zsh", "bash"]);
|
|
check_one_executable_exists("make", &["make", "gmake", "mingw32-make"]);
|
|
check_one_executable_exists("compiler_c", &["gcc", "clang", "cc"]);
|
|
check_one_executable_exists("compiler_cxx", &["g++", "clang++", "c++"]);
|
|
check_ccache_masquerade_dir();
|
|
check_one_executable_exists("compiler_fortran", &["gfortran", "flang"]);
|
|
check_one_executable_exists("compiler_cuda", &["nvcc"]);
|
|
check_executable_exists("libtool");
|
|
check_executable_exists("fakeroot");
|
|
check_executable_exists("valgrind");
|
|
check_executable_exists("ar");
|
|
check_executable_exists("env");
|
|
|
|
// Check for preload library availability
|
|
check_preload_library_availability(&preload_path);
|
|
}
|
|
|
|
fn find_intercept_artifacts() -> (String, String, String) {
|
|
let out_dir = std::env::var("OUT_DIR").unwrap();
|
|
let target_dir = profile_dir_of(std::path::Path::new(&out_dir));
|
|
|
|
let driver_path = target_dir.join(DRIVER_NAME);
|
|
let wrapper_path = target_dir.join(WRAPPER_NAME);
|
|
let preload_path = target_dir.join(PRELOAD_NAME);
|
|
|
|
(
|
|
format!("{}", driver_path.display()),
|
|
format!("{}", wrapper_path.display()),
|
|
format!("{}", preload_path.display()),
|
|
)
|
|
}
|
|
|
|
/// Climb from a build script's `OUT_DIR` to the profile directory that holds
|
|
/// the linked binaries (`target/debug` or `target/release`).
|
|
///
|
|
/// Counting components is not safe here: cargo has shipped two `OUT_DIR`
|
|
/// layouts, `<profile>/build/<pkg>-<hash>/out` and, on newer toolchains,
|
|
/// `<profile>/build/<pkg>/<hash>/out`. Both put exactly one `build` component
|
|
/// under the profile directory, so anchor on that instead of on a depth.
|
|
fn profile_dir_of(out_dir: &std::path::Path) -> &std::path::Path {
|
|
out_dir
|
|
.ancestors()
|
|
.find(|a| a.file_name().and_then(|n| n.to_str()) == Some("build"))
|
|
.and_then(|build| build.parent())
|
|
.unwrap_or_else(|| panic!("no 'build' component in OUT_DIR: {}", out_dir.display()))
|
|
}
|
|
|
|
fn check_executable_exists(executable: &str) {
|
|
match which::which(executable) {
|
|
Ok(path) => {
|
|
println!("cargo:rustc-cfg=has_executable_{}", executable);
|
|
println!("cargo:rustc-check-cfg=cfg(has_executable_{})", executable);
|
|
println!("cargo:rustc-env={}_PATH={}", executable.to_uppercase(), path.display());
|
|
println!("cargo:warning=Checking for executable: {} ... {}", executable, path.display());
|
|
}
|
|
Err(_) => {
|
|
println!("cargo:warning=Checking for executable: {} ... missing", executable);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn check_one_executable_exists(define: &str, executables: &[&str]) {
|
|
for executable in executables {
|
|
if let Ok(path) = which::which(executable) {
|
|
println!("cargo:rustc-cfg=has_executable_{}", define);
|
|
println!("cargo:rustc-check-cfg=cfg(has_executable_{})", define);
|
|
println!("cargo:rustc-env={}_PATH={}", define.to_uppercase(), path.display());
|
|
println!("cargo:warning=Checking for executable: {} ... {}", define, path.display());
|
|
return;
|
|
}
|
|
}
|
|
println!("cargo:warning=Checking for executable: {} ... missing", define);
|
|
}
|
|
|
|
/// Locate a ccache masquerade directory on this host, independent of PATH.
|
|
/// When one is found, expose it via the `CCACHE_MASQUERADE_DIR` env var and
|
|
/// set `cfg(host_has_ccache_masquerade)`. The dedicated recursion test (see
|
|
/// `interception-wrapper-recursion`) prepends that dir to its own PATH at
|
|
/// runtime so the masquerade setup is exercised regardless of whether the
|
|
/// host's default PATH already includes it. CI installs ccache so the dir
|
|
/// exists on the Ubuntu matrix entry.
|
|
fn check_ccache_masquerade_dir() {
|
|
println!("cargo:rustc-check-cfg=cfg(host_has_ccache_masquerade)");
|
|
let candidates = [
|
|
// Debian/Ubuntu
|
|
"/usr/lib/ccache",
|
|
// Fedora/Arch/Gentoo (lib64 multilib)
|
|
"/usr/lib64/ccache",
|
|
// Some BSDs / older distros
|
|
"/usr/libexec/ccache",
|
|
// Homebrew on Apple Silicon
|
|
"/opt/homebrew/opt/ccache/libexec",
|
|
// Homebrew on Intel macOS / Linuxbrew
|
|
"/usr/local/opt/ccache/libexec",
|
|
];
|
|
for dir in candidates {
|
|
if let Some(path) = detect_ccache_masquerade_dir(dir) {
|
|
println!("cargo:rustc-cfg=host_has_ccache_masquerade");
|
|
println!("cargo:rustc-env=CCACHE_MASQUERADE_DIR={}", path);
|
|
println!("cargo:warning=ccache masquerade directory found at {}", path);
|
|
return;
|
|
}
|
|
}
|
|
println!("cargo:warning=no ccache masquerade directory detected");
|
|
}
|
|
|
|
/// A directory qualifies as a ccache masquerade dir if it contains a `gcc`,
|
|
/// `cc`, `g++`, `c++`, `clang`, or `clang++` entry whose ultimate target's
|
|
/// file name is `ccache`.
|
|
fn detect_ccache_masquerade_dir(dir: &str) -> Option<String> {
|
|
let path = std::path::Path::new(dir);
|
|
if !path.is_dir() {
|
|
return None;
|
|
}
|
|
for name in ["gcc", "cc", "g++", "c++", "clang", "clang++"] {
|
|
let candidate = path.join(name);
|
|
if !candidate.exists() {
|
|
continue;
|
|
}
|
|
if let Ok(target) = std::fs::canonicalize(&candidate)
|
|
&& target.file_name().and_then(|n| n.to_str()) == Some("ccache")
|
|
{
|
|
return Some(dir.to_string());
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn check_preload_library_availability(preload_path: &str) {
|
|
// Check if we're on a platform that supports LD_PRELOAD (Unix-like systems)
|
|
let platform_supports_preload = !cfg!(windows);
|
|
|
|
// Check if the preload library file exists
|
|
let preload_file_exists = std::path::Path::new(preload_path).exists();
|
|
|
|
// Check for platform-specific restrictions
|
|
let runtime_supports_preload = is_preload_supported_at_runtime();
|
|
|
|
if platform_supports_preload && preload_file_exists && runtime_supports_preload {
|
|
println!("cargo:rustc-cfg=has_preload_library");
|
|
println!("cargo:rustc-check-cfg=cfg(has_preload_library)");
|
|
println!("cargo:warning=Preload library available at: {}", preload_path);
|
|
} else {
|
|
println!(
|
|
"cargo:warning=Preload library not available (platform_supports: {}, file_exists: {}, runtime_supports: {})",
|
|
platform_supports_preload, preload_file_exists, runtime_supports_preload
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Check if preload is supported at runtime, considering platform-specific restrictions.
|
|
fn is_preload_supported_at_runtime() -> bool {
|
|
#[cfg(windows)]
|
|
{
|
|
// Windows doesn't support LD_PRELOAD
|
|
false
|
|
}
|
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
|
{
|
|
// Disable integration test on macOS, because could not find out how to compile
|
|
// the libexec.dylib that works on arm64e, and that makes the CI build broken.
|
|
false
|
|
}
|
|
#[cfg(not(any(target_os = "macos", target_os = "ios", windows)))]
|
|
{
|
|
// Other Unix-like systems should support preload
|
|
true
|
|
}
|
|
}
|
|
|
|
fn validate_intercept_libdir(value: &str) {
|
|
if value.trim().is_empty() {
|
|
panic!("INTERCEPT_LIBDIR must not be empty or whitespace-only");
|
|
}
|
|
let path = std::path::Path::new(value);
|
|
if path.is_absolute() {
|
|
panic!("INTERCEPT_LIBDIR must be a relative path, got: {}", value);
|
|
}
|
|
}
|