use miette::miette; fn main() -> miette::Result<()> { cc::Build::new().file("src/compat.c").compile("libcompat.a"); let rust_dir = std::env::var("CARGO_MANIFEST_DIR").expect("Env var CARGO_MANIFEST_DIR missing"); let target_dir = std::env::var("FISH_RUST_TARGET_DIR").unwrap_or(format!("{}/{}", rust_dir, "target/")); let fish_src_dir = format!("{}/{}", rust_dir, "../src/"); // Where cxx emits its header. let cxx_include_dir = format!("{}/{}", target_dir, "cxxbridge/rust/"); // If FISH_BUILD_DIR is given by CMake, then use it; otherwise assume it's at ../build. let fish_build_dir = std::env::var("FISH_BUILD_DIR").unwrap_or(format!("{}/{}", rust_dir, "../build/")); // Where autocxx should put its stuff. let autocxx_gen_dir = std::env::var("FISH_AUTOCXX_GEN_DIR") .unwrap_or(format!("{}/{}", fish_build_dir, "fish-autocxx-gen/")); detect_features(); // Emit cxx junk. // This allows "Rust to be used from C++" // This must come before autocxx so that cxx can emit its cxx.h header. let source_files = vec![ "src/abbrs.rs", "src/event.rs", "src/common.rs", "src/fd_monitor.rs", "src/fd_readable_set.rs", "src/fds.rs", "src/ffi_init.rs", "src/ffi_tests.rs", "src/future_feature_flags.rs", "src/job_group.rs", "src/parse_constants.rs", "src/redirection.rs", "src/smoke.rs", "src/termsize.rs", "src/timer.rs", "src/tokenizer.rs", "src/topic_monitor.rs", "src/trace.rs", "src/util.rs", "src/wait_handle.rs", "src/builtins/shared.rs", ]; cxx_build::bridges(&source_files) .flag_if_supported("-std=c++11") .include(&fish_src_dir) .include(&fish_build_dir) // For config.h .include(&cxx_include_dir) // For cxx.h .flag("-Wno-comment") .compile("fish-rust"); // Emit autocxx junk. // This allows "C++ to be used from Rust." let include_paths = [&fish_src_dir, &fish_build_dir, &cxx_include_dir]; let mut builder = autocxx_build::Builder::new("src/ffi.rs", include_paths); // Use autocxx's custom output directory unless we're being called by `rust-analyzer` and co., // in which case stick to the default target directory so code intelligence continues to work. if std::env::var("RUSTC_WRAPPER").map_or(true, |wrapper| { !(wrapper.contains("rust-analyzer") || wrapper.contains("intellij-rust-native-helper")) }) { // We need this reassignment because of how the builder pattern works builder = builder.custom_gendir(autocxx_gen_dir.into()); } let mut b = builder.build()?; b.flag_if_supported("-std=c++11") .flag("-Wno-comment") .compile("fish-rust-autocxx"); for file in source_files { println!("cargo:rerun-if-changed={file}"); } Ok(()) } /// Dynamically enables certain features at build-time, without their having to be explicitly /// enabled in the `cargo build --features xxx` invocation. /// /// This can be used to enable features that we check for and conditionally compile according to in /// our own codebase, but [can't be used to pull in dependencies](0) even if they're gated (in /// `Cargo.toml`) behind a feature we just enabled. /// /// [0]: https://github.com/rust-lang/cargo/issues/5499 fn detect_features() { for (feature, detector) in [ // Ignore the first line, it just sets up the type inference. Model new entries after the // second line. ("", &(|| Ok(false)) as &dyn Fn() -> miette::Result), ("bsd", &detect_bsd), ] { match detector() { Err(e) => eprintln!("{feature} detect: {e}"), Ok(true) => println!("cargo:rustc-cfg=feature=\"{feature}\""), Ok(false) => (), } } } /// Detect if we're being compiled on a BSD-derived OS. Does not yet play nicely with /// cross-compilation. /// /// Rust offers fine-grained conditional compilation per-os for the popular operating systems, but /// doesn't necessarily include less-popular forks nor does it group them into families more /// specific than "windows" vs "unix" so we can conditionally compile code for BSD systems. fn detect_bsd() -> miette::Result { let uname = std::process::Command::new("uname") .output() .map_err(|_| miette!("Error executing uname!"))?; Ok(std::str::from_utf8(&uname.stdout) .map(|s| s.to_ascii_lowercase()) .map(|s| s.contains("bsd")) .unwrap_or(false)) }