use fish_build_helper::{ env_var, fish_build_dir, target_os, target_os_is_apple, target_os_is_bsd, target_os_is_cygwin, workspace_root, }; use rsconf::Target; use std::path::{Path, PathBuf}; fn main() { setup_paths(); // Add our default to enable tools that don't go through CMake, like "cargo test" and the // language server. rsconf::set_env_value( "FISH_RESOLVED_BUILD_DIR", // If set by CMake, this might include symlinks. Since we want to compare this to the // dir fish is executed in we need to canonicalize it. fish_build_dir().canonicalize().unwrap().to_str().unwrap(), ); // We need to canonicalize (i.e. realpath) the manifest dir because we want to be able to // compare it directly as a string at runtime. rsconf::set_env_value( "CARGO_MANIFEST_DIR", workspace_root().canonicalize().unwrap().to_str().unwrap(), ); // Some build info rsconf::set_env_value("BUILD_TARGET_TRIPLE", &env_var("TARGET").unwrap()); rsconf::set_env_value("BUILD_HOST_TRIPLE", &env_var("HOST").unwrap()); rsconf::set_env_value("BUILD_PROFILE", &env_var("PROFILE").unwrap()); // Per https://doc.rust-lang.org/cargo/reference/build-scripts.html#inputs-to-the-build-script, // the source directory is the current working directory of the build script rsconf::set_env_value("FISH_BUILD_VERSION", &get_version()); fish_build_helper::rebuild_if_embedded_path_changed("share"); let build = cc::Build::new(); let mut target = Target::new_from(build).unwrap(); // Keep verbose mode on until we've ironed out rust build script stuff target.set_verbose(true); detect_cfgs(&mut target); #[cfg(all(target_env = "gnu", target_feature = "crt-static"))] compile_error!( "Statically linking against glibc has unavoidable crashes and is unsupported. Use dynamic linking or link statically against musl." ); } /// Check target system support for certain functionality dynamically when the build is invoked, /// without their having to be explicitly enabled in the `cargo build --features xxx` invocation. /// /// We are using [`rsconf::enable_cfg()`] instead of [`rsconf::enable_feature()`] as rust features /// should be used for things that a user can/would reasonably enable or disable to tweak or coerce /// behavior, but here we are testing for whether or not things are supported altogether. /// /// 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_cfgs(target: &mut Target) { for (name, handler) in [ // Ignore the first entry, it just sets up the type inference. ("", &(|_: &Target| false) as &dyn Fn(&Target) -> bool), ("apple", &(|_| target_os_is_apple())), ("bsd", &(|_| target_os_is_bsd())), ("cygwin", &(|_| target_os_is_cygwin())), ("have_eventfd", &|target| { // FIXME: NetBSD 10 has eventfd, but the libc crate does not expose it. if target_os() == "netbsd" { false } else { target.has_header("sys/eventfd.h") } }), ("have_localeconv_l", &|target| { target.has_symbol("localeconv_l") }), ("have_pipe2", &|target| target.has_symbol("pipe2")), ("have_posix_spawn", &|target| { if matches!(target_os().as_str(), "openbsd" | "android") { // OpenBSD's posix_spawn returns status 127 instead of erroring with ENOEXEC when faced with a // shebang-less script. Disable posix_spawn on OpenBSD. // // Android is broken for unclear reasons false } else { target.has_header("spawn.h") } }), ("small_main_stack", &has_small_stack), ("using_cmake", &|_| { option_env!("FISH_CMAKE_BINARY_DIR").is_some() }), ("waitstatus_signal_ret", &|target| { target.r#if("WEXITSTATUS(0x007f) == 0x7f", &["sys/wait.h"]) }), ] { rsconf::declare_cfg(name, handler(target)); } } /// Rust sets the stack size of newly created threads to a sane value, but is at at the mercy of the /// OS when it comes to the size of the main stack. Some platforms we support default to a tiny /// 0.5 MiB main stack, which is insufficient for fish's MAX_EVAL_DEPTH/MAX_STACK_DEPTH values. /// /// 0.5 MiB is small enough that we'd have to drastically reduce MAX_STACK_DEPTH to less than 10, so /// we instead use a workaround to increase the main thread size. fn has_small_stack(_: &Target) -> bool { #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "netbsd")))] return false; // NetBSD 10 also needs this but can't find pthread_get_stacksize_np. #[cfg(target_os = "netbsd")] return true; #[cfg(any(target_os = "ios", target_os = "macos"))] { use core::ffi; unsafe extern "C" { unsafe fn pthread_get_stacksize_np(thread: *const ffi::c_void) -> usize; unsafe fn pthread_self() -> *const ffi::c_void; } // build.rs is executed on the main thread, so we are getting the main thread's stack size. // Modern macOS versions default to an 8 MiB main stack but legacy OS X have a 0.5 MiB one. let stack_size = unsafe { pthread_get_stacksize_np(pthread_self()) }; const TWO_MIB: usize = 2 * 1024 * 1024 - 1; stack_size <= TWO_MIB } } fn setup_paths() { #[cfg(windows)] use unix_path::{Path, PathBuf}; fn overridable_path( env_var_name: &str, f: impl FnOnce(Option) -> Option, ) -> Option { rsconf::rebuild_if_env_changed(env_var_name); let maybe_path = f(env_var(env_var_name)); if let Some(path) = maybe_path.as_ref() { rsconf::set_env_value(env_var_name, path.to_str().unwrap()); } maybe_path } fn join_if_relative(parent_if_relative: &Path, path: String) -> PathBuf { let path = PathBuf::from(path); if path.is_relative() { parent_if_relative.join(path) } else { path } } let prefix = overridable_path("PREFIX", |env_prefix| { Some(PathBuf::from(env_prefix.unwrap_or("/usr/local".to_owned()))) }) .unwrap(); overridable_path("SYSCONFDIR", |env_sysconfdir| { Some(join_if_relative( &prefix, env_sysconfdir.unwrap_or("/etc/".to_owned()), )) }); let datadir = overridable_path("DATADIR", |env_datadir| { env_datadir.map(|p| join_if_relative(&prefix, p)) }); overridable_path("BINDIR", |env_bindir| { env_bindir.map(|p| join_if_relative(&prefix, p)) }); overridable_path("DOCDIR", |env_docdir| { env_docdir.map(|p| { join_if_relative( &datadir .expect("Setting DOCDIR without setting DATADIR is not currently supported"), p, ) }) }); } fn get_version() -> String { use std::process::Command; String::from_utf8( Command::new("build_tools/git_version_gen.sh") .output() .unwrap() .stdout, ) .unwrap() .trim_ascii_end() .to_owned() }