use semver::Version; use std::env; use std::ffi::OsStr; use std::io::{self}; use std::path::{Path, PathBuf}; use std::process::Command; lazy_static::lazy_static! { /// LLVM version used by this version of the crate. static ref CRATE_VERSION: Version = { let crate_version = Version::parse(env!("CARGO_PKG_VERSION")) .expect("Crate version is somehow not valid semver"); Version { major: crate_version.major / 10, minor: crate_version.major % 10, .. crate_version } }; /// Filesystem path to an llvm-config binary for the correct version. static ref LLVM_CONFIG_PATH: PathBuf = { if let Some(path) = env::var_os(format!("DEP_LLVM_{}_CONFIG_PATH", CRATE_VERSION.major)) { return path.into() } println!("No suitable version of LLVM was found for lld-rs. lld-rs uses llvm-sys to locate the `llvm-config` binary."); panic!("Could not find a compatible version of LLVM"); }; } fn target_env_is(name: &str) -> bool { match env::var_os("CARGO_CFG_TARGET_ENV") { Some(s) => s == name, None => false, } } fn target_os_is(name: &str) -> bool { match env::var_os("CARGO_CFG_TARGET_OS") { Some(s) => s == name, None => false, } } /// Get the output from running `llvm-config` with the given argument. /// /// Lazily searches for or compiles LLVM as configured by the environment /// variables. fn llvm_config(arg: &str) -> String { llvm_config_ex(&*LLVM_CONFIG_PATH, arg).expect("Surprising failure from llvm-config") } /// Invoke the specified binary as llvm-config. /// /// Explicit version of the `llvm_config` function that bubbles errors /// up. fn llvm_config_ex>(binary: S, arg: &str) -> io::Result { Command::new(binary) .arg(arg) .arg("--link-static") // Don't use dylib for >= 3.9 .arg("core") // We only need core component things. .output() .and_then(|output| { if output.stdout.is_empty() { Err(io::Error::new( io::ErrorKind::NotFound, "llvm-config returned empty output", )) } else { Ok(String::from_utf8(output.stdout) .expect("Output from llvm-config was not valid UTF-8")) } }) } /// Get the names of the dylibs required by LLVM, including the C++ standard /// library. fn get_system_libraries() -> Vec { llvm_config("--system-libs") .split(&[' ', '\n'] as &[char]) .filter(|s| !s.is_empty()) .filter(|s| !s.starts_with("/")) .map(|flag| { if target_env_is("msvc") { // Same as --libnames, foo.lib assert!( flag.ends_with(".lib"), "system library {:?} does not appear to be a MSVC library file", flag ); &flag[..flag.len() - 4] } else { if flag.starts_with("-l") { // Linker flags style, -lfoo if target_os_is("macos") && flag.starts_with("-llib") && flag.ends_with(".tbd") { // .tdb libraries are "text-based stub" files that provide lists of symbols, // which refer to libraries shipped with a given system and aren't shipped // as part of the corresponding SDK. They're named like the underlying // library object, including the 'lib' prefix that we need to strip. return flag[5..flag.len() - 4].to_owned(); } return flag[2..].to_owned(); } let maybe_lib = Path::new(&flag); if maybe_lib.is_file() { // Library on disk, likely an absolute path to a .so. We'll add its location to // the library search path and specify the file as a link target. println!( "cargo:rustc-link-search={}", maybe_lib.parent().unwrap().display() ); // Expect a file named something like libfoo.so, or with a version libfoo.so.1. // Trim everything after and including the last .so and remove the leading 'lib' let soname = maybe_lib .file_name() .unwrap() .to_str() .expect("Shared library path must be a valid string"); let stem = soname .rsplit_once(target_dylib_extension()) .expect("Shared library should be a .so file") .0; stem.trim_start_matches("lib") } else { panic!( "Unable to parse result of llvm-config --system-libs: was {:?}", flag ) } } .to_owned() }) .chain(get_system_libcpp().map(str::to_owned)) .collect::>() } fn target_dylib_extension() -> &'static str { if target_os_is("macos") { ".dylib" } else { ".so" } } /// Get the library that must be linked for C++, if any. fn get_system_libcpp() -> Option<&'static str> { if target_env_is("msvc") { // MSVC doesn't need an explicit one. None } else if target_os_is("macos") { // On OS X 10.9 and later, LLVM's libc++ is the default. On earlier // releases GCC's libstdc++ is default. Unfortunately we can't // reasonably detect which one we need (on older ones libc++ is // available and can be selected with -stdlib=lib++), so assume the // latest, at the cost of breaking the build on older OS releases // when LLVM was built against libstdc++. Some("c++") } else if target_os_is("freebsd") { Some("c++") } else { // Otherwise assume GCC's libstdc++. // This assumption is probably wrong on some platforms, but would need // testing on them. Some("stdc++") } } /// Get the names of libraries to link against. fn get_link_libraries() -> Vec { // Using --libnames in conjunction with --libdir is particularly important // for MSVC when LLVM is in a path with spaces, but it is generally less of // a hack than parsing linker flags output from --libs and --ldflags. llvm_config("--libnames") .split(&[' ', '\n'] as &[char]) .filter(|s| !s.is_empty()) .map(|name| { // --libnames gives library filenames. Extract only the name that // we need to pass to the linker. if target_env_is("msvc") { // LLVMfoo.lib assert!( name.ends_with(".lib"), "library name {:?} does not appear to be a MSVC library file", name ); &name[..name.len() - 4] } else { // libLLVMfoo.a assert!( name.starts_with("lib") && name.ends_with(".a"), "library name {:?} does not appear to be a static library", name ); &name[3..name.len() - 2] } }) .map(str::to_owned) .collect::>() } fn get_llvm_cxxflags() -> String { let output = llvm_config("--cxxflags"); // llvm-config includes cflags from its own compilation with --cflags that // may not be relevant to us. In particularly annoying cases, these might // include flags that aren't understood by the default compiler we're // using. Unless requested otherwise, clean CFLAGS of options that are // known to be possibly-harmful. let no_clean = env::var_os(format!( "LLVM_SYS_{}_NO_CLEAN_CFLAGS", env!("CARGO_PKG_VERSION_MAJOR") )) .is_some(); if no_clean || target_env_is("msvc") { // MSVC doesn't accept -W... options, so don't try to strip them and // possibly strip something that should be retained. Also do nothing if // the user requests it. return output; } llvm_config("--cxxflags") .split(&[' ', '\n'][..]) .filter(|word| !word.starts_with("-W")) .collect::>() .join(" ") } fn is_llvm_debug() -> bool { // Has to be either Debug or Release llvm_config("--build-mode").contains("Debug") } fn main() { // Build the extra wrapper functions. std::env::set_var("CXXFLAGS", get_llvm_cxxflags()); let mut build = cc::Build::new(); build .cpp(true) .file("wrapper/lld-c.cpp"); if build.get_compiler().is_like_msvc() { build.flag("/std:c++17"); } else { build.flag("-std=c++17"); } build.compile("lldwrapper"); println!("cargo:rerun-if-changed=wrapper/lld-c.cpp"); if cfg!(feature = "no-llvm-linking") { return; } let libdir = llvm_config("--libdir"); // Export information to other crates println!("cargo:config_path={}", LLVM_CONFIG_PATH.display()); // will be DEP_LLVM_CONFIG_PATH println!("cargo:libdir={}", libdir); // DEP_LLVM_LIBDIR // Link LLVM libraries println!("cargo:rustc-link-search=native={}", libdir); let blacklist = vec!["LLVMLineEditor"]; for name in get_link_libraries() .iter() .filter(|n| !blacklist.iter().any(|blacklisted| n.contains(*blacklisted))) { println!("cargo:rustc-link-lib=static={}", name); } // Link system libraries for name in get_system_libraries() { println!("cargo:rustc-link-lib=dylib={}", name); } let use_debug_msvcrt = env::var_os(format!( "LLVM_SYS_{}_USE_DEBUG_MSVCRT", env!("CARGO_PKG_VERSION_MAJOR") )) .is_some(); if cfg!(target_env = "msvc") && (use_debug_msvcrt || is_llvm_debug()) { println!("cargo:rustc-link-lib=msvcrtd"); } println!("cargo:rustc-link-lib=static=lldCOFF"); println!("cargo:rustc-link-lib=static=lldCommon"); println!("cargo:rustc-link-lib=static=lldELF"); println!("cargo:rustc-link-lib=static=lldMachO"); println!("cargo:rustc-link-lib=static=lldMinGW"); println!("cargo:rustc-link-lib=static=lldWasm"); if cfg!(target_os = "macos") { println!("cargo:rustc-link-lib=dylib=xar"); } if cfg!(not(target_os = "windows")) { println!("cargo:rustc-link-lib=dylib=ffi"); } }