diff options
Diffstat (limited to 'cc')
-rw-r--r-- | cc/.github/workflows/main.yml | 124 | ||||
-rw-r--r-- | cc/Cargo.toml | 10 | ||||
-rw-r--r-- | cc/README.md | 8 | ||||
-rw-r--r-- | cc/azure-pipelines.yml | 105 | ||||
-rw-r--r-- | cc/cc-test/Cargo.toml | 1 | ||||
-rw-r--r-- | cc/cc-test/build.rs | 2 | ||||
-rw-r--r-- | cc/cc-test/tests/all.rs | 2 | ||||
-rw-r--r-- | cc/ci/azure-install-rust.yml | 38 | ||||
-rw-r--r-- | cc/ci/azure-steps.yml | 28 | ||||
-rw-r--r-- | cc/src/com.rs | 14 | ||||
-rw-r--r-- | cc/src/lib.rs | 302 | ||||
-rw-r--r-- | cc/src/setup_config.rs | 24 | ||||
-rw-r--r-- | cc/src/windows_registry.rs | 125 | ||||
-rw-r--r-- | cc/tests/cc_env.rs | 5 | ||||
-rw-r--r-- | cc/tests/cflags.rs | 5 | ||||
-rw-r--r-- | cc/tests/cxxflags.rs | 5 | ||||
-rw-r--r-- | cc/tests/support/mod.rs | 4 | ||||
-rw-r--r-- | cc/tests/test.rs | 37 |
18 files changed, 514 insertions, 325 deletions
diff --git a/cc/.github/workflows/main.yml b/cc/.github/workflows/main.yml new file mode 100644 index 0000000..9c35a41 --- /dev/null +++ b/cc/.github/workflows/main.yml @@ -0,0 +1,124 @@ +name: CI +on: [push, pull_request] + +jobs: + test: + name: Test + runs-on: ${{ matrix.os }} + strategy: + matrix: + build: [stable, beta, nightly, linux32, macos, aarch64-ios, win32, win64, mingw32, mingw64, windows-2016] + include: + - build: stable + os: ubuntu-latest + rust: stable + target: x86_64-unknown-linux-gnu + - build: beta + os: ubuntu-latest + rust: beta + target: x86_64-unknown-linux-gnu + - build: nightly + os: ubuntu-latest + rust: nightly + target: x86_64-unknown-linux-gnu + - build: linux32 + os: ubuntu-latest + rust: stable + target: i686-unknown-linux-gnu + - build: macos + os: macos-latest + rust: stable + target: x86_64-apple-darwin + - build: aarch64-ios + os: macos-latest + rust: stable + target: aarch64-apple-ios + no_run: --no-run + - build: win32 + os: windows-2016 + rust: stable-i686-msvc + target: i686-pc-windows-msvc + - build: win64 + os: windows-latest + rust: stable + target: x86_64-pc-windows-msvc + - build: mingw32 + os: windows-latest + rust: stable-i686-gnu + target: i686-pc-windows-gnu + - build: mingw64 + os: windows-latest + rust: stable-x86_64-gnu + target: x86_64-pc-windows-gnu + - build: windows-2016 + os: windows-2016 + rust: stable-x86_64 + target: x86_64-pc-windows-msvc + steps: + - uses: actions/checkout@master + - name: Install Rust (rustup) + run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} + if: matrix.os != 'macos-latest' + shell: bash + - name: Install Rust (macos) + run: | + curl https://sh.rustup.rs | sh -s -- -y + echo "##[add-path]$HOME/.cargo/bin" + if: matrix.os == 'macos-latest' + - run: rustup target add ${{ matrix.target }} + - name: Install g++-multilib + run: | + set -e + # Remove the ubuntu-toolchain-r/test PPA, which is added by default. + # Some packages were removed, and this is causing the g++multilib + # install to fail. Similar issue: + # https://github.com/scikit-learn/scikit-learn/issues/13928. + sudo add-apt-repository --remove ppa:ubuntu-toolchain-r/test + sudo apt-get install g++-multilib + if: matrix.build == 'linux32' + - run: cargo build + - run: cargo test ${{ matrix.no_run }} + - run: cargo test ${{ matrix.no_run }} --features parallel + - run: cargo test ${{ matrix.no_run }} --manifest-path cc-test/Cargo.toml --target ${{ matrix.target }} + - run: cargo test ${{ matrix.no_run }} --manifest-path cc-test/Cargo.toml --target ${{ matrix.target }} --features parallel + - run: cargo test ${{ matrix.no_run }} --manifest-path cc-test/Cargo.toml --target ${{ matrix.target }} --release + + msrv: + name: MSRV + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + steps: + - uses: actions/checkout@master + - name: Install Rust + run: rustup update 1.31.0 --no-self-update && rustup default 1.31.0 + shell: bash + - run: cargo build + + rustfmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Install Rust + run: rustup update stable && rustup default stable && rustup component add rustfmt + - run: cargo fmt -- --check + + publish_docs: + name: Publish Documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Install Rust + run: rustup update stable && rustup default stable + - name: Build documentation + run: cargo doc --no-deps --all-features + - name: Publish documentation + run: | + cd target/doc + git init + git add . + git -c user.name='ci' -c user.email='ci' commit -m init + git push -f -q https://git:${{ secrets.github_token }}@github.com/${{ github.repository }} HEAD:gh-pages + if: github.event_name == 'push' && github.event.ref == 'refs/heads/master' diff --git a/cc/Cargo.toml b/cc/Cargo.toml index 52e18ce..88f44eb 100644 --- a/cc/Cargo.toml +++ b/cc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cc" -version = "1.0.40" +version = "1.0.48" authors = ["Alex Crichton <alex@alexcrichton.com>"] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/cc-rs" @@ -15,12 +15,14 @@ keywords = ["build-dependencies"] readme = "README.md" categories = ["development-tools::build-utils"] exclude = ["/.travis.yml", "/appveyor.yml"] +edition = "2018" [dependencies] -rayon = { version = "1.0", optional = true } +num_cpus = { version = "1.10", optional = true } +jobserver = { version = "0.1.16", optional = true } [features] -parallel = ["rayon"] +parallel = ["num_cpus", "jobserver"] [dev-dependencies] -tempdir = "0.3" +tempfile = "3" diff --git a/cc/README.md b/cc/README.md index e3c6ad0..68448ac 100644 --- a/cc/README.md +++ b/cc/README.md @@ -2,8 +2,6 @@ A library to compile C/C++/assembly into a Rust library/application. -[![Build Status](https://dev.azure.com/alexcrichton/cc-rs/_apis/build/status/alexcrichton.cc-rs?branchName=master)](https://dev.azure.com/alexcrichton/cc-rs/_build/latest?definitionId=5&branchName=master) - [Documentation](https://docs.rs/cc) A simple library meant to be used as a build dependency with Cargo packages in @@ -28,8 +26,6 @@ Next up, you'll want to write a build script like so: ```rust,no_run // build.rs -extern crate cc; - fn main() { cc::Build::new() .file("foo.c") @@ -143,8 +139,6 @@ required varies per platform, but there are three broad categories: `Build`: ```rust,no_run -extern crate cc; - fn main() { cc::Build::new() .cpp(true) // Switch to C++ library compilation. @@ -163,8 +157,6 @@ linked to the crate target. on `Build` (currently for GNU/Clang toolchains only): ```rust,no_run -extern crate cc; - fn main() { cc::Build::new() // Switch to CUDA C++ library compilation using NVCC. diff --git a/cc/azure-pipelines.yml b/cc/azure-pipelines.yml deleted file mode 100644 index e925da1..0000000 --- a/cc/azure-pipelines.yml +++ /dev/null @@ -1,105 +0,0 @@ -# Note for forks: Azure Pipelines is triggered only by commits to the branches -# matching the patterns below. -# See https://docs.microsoft.com/en-us/azure/devops/pipelines/build/triggers -trigger: - - master - - ci-* - -jobs: - - job: min_linux - pool: - vmImage: ubuntu-16.04 - displayName: Minimum Rust (Linux) - variables: - TOOLCHAIN: 1.16.0 - steps: - - template: ci/azure-install-rust.yml - - script: cargo build - - - job: min_Windows - pool: - vmImage: vs2017-win2016 - displayName: Minimum Rust (Windows) - variables: - TOOLCHAIN: 1.16.0 - steps: - - template: ci/azure-install-rust.yml - - script: cargo build - - - job: Linux - pool: - vmImage: ubuntu-16.04 - steps: - - template: ci/azure-steps.yml - strategy: - matrix: - x86_64: - TARGET: x86_64-unknown-linux-gnu - i686: - TARGET: i686-unknown-linux-gnu - x86_64-beta: - TARGET: x86_64-unknown-linux-gnu - TOOLCHAIN: beta - x86_64-nightly: - TARGET: x86_64-unknown-linux-gnu - TOOLCHAIN: nightly - - - job: macOS - pool: - vmImage: macos-10.13 - steps: - - template: ci/azure-steps.yml - strategy: - matrix: - x86_64: - TARGET: x86_64-apple-darwin - aarch64-ios: - TARGET: aarch64-apple-ios - NO_RUN: --no-run - - - job: Windows_vs2019 - pool: - vmImage: windows-2019 - steps: - - template: ci/azure-steps.yml - strategy: - matrix: - x86_64-msvc: - TARGET: x86_64-pc-windows-msvc - - - job: Windows_vs2017 - pool: - vmImage: vs2017-win2016 - steps: - - template: ci/azure-steps.yml - strategy: - matrix: - x86_64-msvc: - TARGET: x86_64-pc-windows-msvc - i686-msvc: - TARGET: i686-pc-windows-msvc - x86_64-gnu: - TARGET: x86_64-pc-windows-gnu - i686-gnu: - TARGET: i686-pc-windows-gnu - - - job: Windows_vs2015 - pool: - vmImage: vs2015-win2012r2 - steps: - - template: ci/azure-steps.yml - strategy: - matrix: - x86_64-msvc: - TARGET: x86_64-pc-windows-msvc - i686-msvc: - TARGET: i686-pc-windows-msvc - - - job: docs - steps: - - template: ci/azure-install-rust.yml - - script: cargo doc --no-deps --all-features - - script: curl -LsSf https://git.io/fhJ8n | rustc - && (cd target/doc && ../../rust_out) - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) - env: - GITHUB_DEPLOY_KEY: $(GITHUB_DEPLOY_KEY) diff --git a/cc/cc-test/Cargo.toml b/cc/cc-test/Cargo.toml index e83416a..c25854c 100644 --- a/cc/cc-test/Cargo.toml +++ b/cc/cc-test/Cargo.toml @@ -2,6 +2,7 @@ name = "cc-test" version = "0.1.0" authors = ["Alex Crichton <alex@alexcrichton.com>"] +edition = "2018" [lib] name = "cc_test" diff --git a/cc/cc-test/build.rs b/cc/cc-test/build.rs index 677161e..3065861 100644 --- a/cc/cc-test/build.rs +++ b/cc/cc-test/build.rs @@ -1,5 +1,3 @@ -extern crate cc; - use std::env; use std::fs; use std::path::PathBuf; diff --git a/cc/cc-test/tests/all.rs b/cc/cc-test/tests/all.rs index 59cb8e4..a457c72 100644 --- a/cc/cc-test/tests/all.rs +++ b/cc/cc-test/tests/all.rs @@ -1,5 +1,3 @@ -extern crate cc_test; - use cc_test::*; #[link(name = "OptLinkage", kind = "static")] diff --git a/cc/ci/azure-install-rust.yml b/cc/ci/azure-install-rust.yml deleted file mode 100644 index 9c1bae8..0000000 --- a/cc/ci/azure-install-rust.yml +++ /dev/null @@ -1,38 +0,0 @@ -steps: - - bash: | - set -e - toolchain=$TOOLCHAIN - if [ "$toolchain" = "" ]; then - toolchain=stable - fi - if command -v rustup; then - rustup update $toolchain - rustup default $toolchain - else - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $toolchain - echo "##vso[task.prependpath]$HOME/.cargo/bin" - fi - displayName: Install rust (unix) - condition: ne( variables['Agent.OS'], 'Windows_NT' ) - - - bash: | - set -e - toolchain=$TOOLCHAIN - if [ "$toolchain" = "" ]; then - toolchain=stable-$TARGET - fi - if command -v rustup; then - rustup update --no-self-update $toolchain - rustup default $toolchain - else - curl.exe -sSf -o rustup-init.exe https://win.rustup.rs - ./rustup-init.exe -y --default-toolchain $toolchain - echo "##vso[task.prependpath]$USERPROFILE/.cargo/bin" - fi - displayName: Install rust (windows) - condition: eq( variables['Agent.OS'], 'Windows_NT' ) - - - script: | - rustc -Vv - cargo -V - displayName: Query rust and cargo versions diff --git a/cc/ci/azure-steps.yml b/cc/ci/azure-steps.yml deleted file mode 100644 index bbf8ec6..0000000 --- a/cc/ci/azure-steps.yml +++ /dev/null @@ -1,28 +0,0 @@ -steps: - - template: azure-install-rust.yml - - bash: rustup target add $TARGET - displayName: Install Rust target - - # Remove the ubuntu-toolchain-r/test PPA, which is added by default. Some - # packages were removed, and this is causing the g++multilib install to fail. - # Similar issue: https://github.com/scikit-learn/scikit-learn/issues/13928 - - bash: sudo add-apt-repository --remove ppa:ubuntu-toolchain-r/test - condition: eq( variables['Agent.OS'], 'Linux' ) - displayName: Remove ppa:ubuntu-toolchain-r/test - - - bash: sudo apt-get install g++-multilib - condition: eq( variables['Agent.OS'], 'Linux' ) - displayName: Install g++-multilib - - - script: cargo build - displayName: "Normal build" - - bash: cargo test $NO_RUN - displayName: "Crate tests" - - bash: cargo test $NO_RUN --features parallel - displayName: "Crate tests (parallel)" - - bash: cargo test $NO_RUN --manifest-path cc-test/Cargo.toml --target $TARGET - displayName: "cc-test tests" - - bash: cargo test $NO_RUN --manifest-path cc-test/Cargo.toml --target $TARGET --features parallel - displayName: "cc-test tests (parallel)" - - bash: cargo test $NO_RUN --manifest-path cc-test/Cargo.toml --target $TARGET --release - displayName: "cc-test tests (release)" diff --git a/cc/src/com.rs b/cc/src/com.rs index 9b75d47..a5f2afe 100644 --- a/cc/src/com.rs +++ b/cc/src/com.rs @@ -7,19 +7,19 @@ #![allow(unused)] +use crate::winapi::CoInitializeEx; +use crate::winapi::IUnknown; +use crate::winapi::Interface; +use crate::winapi::BSTR; +use crate::winapi::COINIT_MULTITHREADED; +use crate::winapi::{SysFreeString, SysStringLen}; +use crate::winapi::{HRESULT, S_FALSE, S_OK}; use std::ffi::{OsStr, OsString}; use std::mem::forget; use std::ops::Deref; use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::ptr::null_mut; use std::slice::from_raw_parts; -use winapi::CoInitializeEx; -use winapi::IUnknown; -use winapi::Interface; -use winapi::BSTR; -use winapi::COINIT_MULTITHREADED; -use winapi::{SysFreeString, SysStringLen}; -use winapi::{HRESULT, S_FALSE, S_OK}; pub fn initialize() -> Result<(), HRESULT> { let err = unsafe { CoInitializeEx(null_mut(), COINIT_MULTITHREADED) }; diff --git a/cc/src/lib.rs b/cc/src/lib.rs index 2d918b8..d795e5a 100644 --- a/cc/src/lib.rs +++ b/cc/src/lib.rs @@ -35,15 +35,13 @@ //! Cargo will also set this environment variable when executed with the `-jN` flag. //! //! If `NUM_JOBS` is not set, the `RAYON_NUM_THREADS` environment variable can -//! also specify the build paralellism. +//! also specify the build parallelism. //! //! # Examples //! //! Use the `Build` struct to compile `src/foo.c`: //! //! ```no_run -//! extern crate cc; -//! //! fn main() { //! cc::Build::new() //! .file("src/foo.c") @@ -58,9 +56,6 @@ #![allow(deprecated)] #![deny(missing_docs)] -#[cfg(feature = "parallel")] -extern crate rayon; - use std::collections::HashMap; use std::env; use std::ffi::{OsStr, OsString}; @@ -99,6 +94,8 @@ pub struct Build { flags: Vec<String>, flags_supported: Vec<String>, known_flag_support_status: Arc<Mutex<HashMap<String, bool>>>, + ar_flags: Vec<String>, + no_default_flags: bool, files: Vec<PathBuf>, cpp: bool, cpp_link_stdlib: Option<Option<String>>, @@ -109,6 +106,7 @@ pub struct Build { out_dir: Option<PathBuf>, opt_level: Option<String>, debug: Option<bool>, + force_frame_pointer: Option<bool>, env: Vec<(OsString, OsString)>, compiler: Option<PathBuf>, archiver: Option<PathBuf>, @@ -213,8 +211,17 @@ impl ToolFamily { } ToolFamily::Gnu | ToolFamily::Clang => { cmd.push_cc_arg("-g".into()); + } + } + } + + /// What the flag to force frame pointers. + fn add_force_frame_pointer(&self, cmd: &mut Tool) { + match *self { + ToolFamily::Gnu | ToolFamily::Clang => { cmd.push_cc_arg("-fno-omit-frame-pointer".into()); } + _ => (), } } @@ -277,6 +284,8 @@ impl Build { flags: Vec::new(), flags_supported: Vec::new(), known_flag_support_status: Arc::new(Mutex::new(HashMap::new())), + ar_flags: Vec::new(), + no_default_flags: false, files: Vec::new(), shared_flag: None, static_flag: None, @@ -289,6 +298,7 @@ impl Build { out_dir: None, opt_level: None, debug: None, + force_frame_pointer: None, env: Vec::new(), compiler: None, archiver: None, @@ -361,6 +371,23 @@ impl Build { self } + /// Add an arbitrary flag to the invocation of the compiler + /// + /// # Example + /// + /// ```no_run + /// cc::Build::new() + /// .file("src/foo.c") + /// .file("src/bar.c") + /// .ar_flag("/NODEFAULTLIB:libc.dll") + /// .compile("foo"); + /// ``` + + pub fn ar_flag(&mut self, flag: &str) -> &mut Build { + self.ar_flags.push(flag.to_string()); + self + } + fn ensure_check_file(&self) -> Result<PathBuf, Error> { let out_dir = self.get_out_dir()?; let src = if self.cuda { @@ -497,6 +524,17 @@ impl Build { self } + /// Disables the generation of default compiler flags. The default compiler + /// flags may cause conflicts in some cross compiling scenarios. + /// + /// Setting the `CRATE_CC_NO_DEFAULTS` environment variable has the same + /// effect as setting this to `true`. The presence of the environment + /// variable and the value of `no_default_flags` will be OR'd together. + pub fn no_default_flags(&mut self, no_default_flags: bool) -> &mut Build { + self.no_default_flags = no_default_flags; + self + } + /// Add a file which will be compiled pub fn file<P: AsRef<Path>>(&mut self, p: P) -> &mut Build { self.files.push(p.as_ref().to_path_buf()); @@ -750,6 +788,17 @@ impl Build { self } + /// Configures whether the compiler will emit instructions to store + /// frame pointers during codegen. + /// + /// This option is automatically enabled when debug information is emitted. + /// Otherwise the target platform compiler's default will be used. + /// You can use this option to force a specific setting. + pub fn force_frame_pointer(&mut self, force: bool) -> &mut Build { + self.force_frame_pointer = Some(force); + self + } + /// Configures the output directory where all object files and static /// libraries will be located. /// @@ -931,22 +980,150 @@ impl Build { } #[cfg(feature = "parallel")] - fn compile_objects(&self, objs: &[Object]) -> Result<(), Error> { - use self::rayon::prelude::*; + fn compile_objects<'me>(&'me self, objs: &[Object]) -> Result<(), Error> { + use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; + use std::sync::Once; + + // Limit our parallelism globally with a jobserver. Start off by + // releasing our own token for this process so we can have a bit of an + // easier to write loop below. If this fails, though, then we're likely + // on Windows with the main implicit token, so we just have a bit extra + // parallelism for a bit and don't reacquire later. + let server = jobserver(); + let reacquire = server.release_raw().is_ok(); + + // When compiling objects in parallel we do a few dirty tricks to speed + // things up: + // + // * First is that we use the `jobserver` crate to limit the parallelism + // of this build script. The `jobserver` crate will use a jobserver + // configured by Cargo for build scripts to ensure that parallelism is + // coordinated across C compilations and Rust compilations. Before we + // compile anything we make sure to wait until we acquire a token. + // + // Note that this jobserver is cached globally so we only used one per + // process and only worry about creating it once. + // + // * Next we use a raw `thread::spawn` per thread to actually compile + // objects in parallel. We only actually spawn a thread after we've + // acquired a token to perform some work + // + // * Finally though we want to keep the dependencies of this crate + // pretty light, so we avoid using a safe abstraction like `rayon` and + // instead rely on some bits of `unsafe` code. We know that this stack + // frame persists while everything is compiling so we use all the + // stack-allocated objects without cloning/reallocating. We use a + // transmute to `State` with a `'static` lifetime to persist + // everything we need across the boundary, and the join-on-drop + // semantics of `JoinOnDrop` should ensure that our stack frame is + // alive while threads are alive. + // + // With all that in mind we compile all objects in a loop here, after we + // acquire the appropriate tokens, Once all objects have been compiled + // we join on all the threads and propagate the results of compilation. + // + // Note that as a slight optimization we try to break out as soon as + // possible as soon as any compilation fails to ensure that errors get + // out to the user as fast as possible. + let error = AtomicBool::new(false); + let mut threads = Vec::new(); + for obj in objs { + if error.load(SeqCst) { + break; + } + let token = server.acquire()?; + let state = State { + build: self, + obj, + error: &error, + }; + let state = unsafe { std::mem::transmute::<State, State<'static>>(state) }; + let thread = thread::spawn(|| { + let state: State<'me> = state; // erase the `'static` lifetime + let result = state.build.compile_object(state.obj); + if result.is_err() { + state.error.store(true, SeqCst); + } + drop(token); // make sure our jobserver token is released after the compile + return result; + }); + threads.push(JoinOnDrop(Some(thread))); + } - if let Some(amt) = self.getenv("NUM_JOBS") { - if let Ok(amt) = amt.parse() { - let _ = rayon::ThreadPoolBuilder::new() - .num_threads(amt) - .build_global(); + for mut thread in threads { + if let Some(thread) = thread.0.take() { + thread.join().expect("thread should not panic")?; } } - // Check for any errors and return the first one found. - objs.par_iter() - .with_max_len(1) - .map(|obj| self.compile_object(obj)) - .collect() + // Reacquire our process's token before we proceed, which we released + // before entering the loop above. + if reacquire { + server.acquire_raw()?; + } + + return Ok(()); + + /// Shared state from the parent thread to the child thread. This + /// package of pointers is temporarily transmuted to a `'static` + /// lifetime to cross the thread boundary and then once the thread is + /// running we erase the `'static` to go back to an anonymous lifetime. + struct State<'a> { + build: &'a Build, + obj: &'a Object, + error: &'a AtomicBool, + } + + /// Returns a suitable `jobserver::Client` used to coordinate + /// parallelism between build scripts. + fn jobserver() -> &'static jobserver::Client { + static INIT: Once = Once::new(); + static mut JOBSERVER: Option<jobserver::Client> = None; + + fn _assert_sync<T: Sync>() {} + _assert_sync::<jobserver::Client>(); + + unsafe { + INIT.call_once(|| { + let server = default_jobserver(); + JOBSERVER = Some(server); + }); + JOBSERVER.as_ref().unwrap() + } + } + + unsafe fn default_jobserver() -> jobserver::Client { + // Try to use the environmental jobserver which Cargo typically + // initializes for us... + if let Some(client) = jobserver::Client::from_env() { + return client; + } + + // ... but if that fails for whatever reason fall back to the number + // of cpus on the system or the `NUM_JOBS` env var. + let mut parallelism = num_cpus::get(); + if let Ok(amt) = env::var("NUM_JOBS") { + if let Ok(amt) = amt.parse() { + parallelism = amt; + } + } + + // If we create our own jobserver then be sure to reserve one token + // for ourselves. + let client = jobserver::Client::new(parallelism).expect("failed to create jobserver"); + client.acquire_raw().expect("failed to acquire initial"); + return client; + } + + struct JoinOnDrop(Option<thread::JoinHandle<Result<(), Error>>>); + + impl Drop for JoinOnDrop { + fn drop(&mut self) { + if let Some(thread) = self.0.take() { + drop(thread.join()); + } + } + } } #[cfg(not(feature = "parallel"))] @@ -1073,11 +1250,10 @@ impl Build { let mut cmd = self.get_base_compiler()?; let envflags = self.envflags(if self.cpp { "CXXFLAGS" } else { "CFLAGS" }); - // Disable default flag generation via environment variable or when - // certain cross compiling arguments are set - let use_defaults = self.getenv("CRATE_CC_NO_DEFAULTS").is_none(); + // Disable default flag generation via `no_default_flags` or environment variable + let no_defaults = self.no_default_flags || self.getenv("CRATE_CC_NO_DEFAULTS").is_some(); - if use_defaults { + if !no_defaults { self.add_default_flags(&mut cmd, &target, &opt_level)?; } else { println!("Info: default compiler flags are disabled"); @@ -1213,12 +1389,22 @@ impl Build { family.add_debug_flags(cmd); } + if self.get_force_frame_pointer() { + let family = cmd.family; + family.add_force_frame_pointer(cmd); + } + // Target flags match cmd.family { ToolFamily::Clang => { cmd.args.push(format!("--target={}", target).into()); } ToolFamily::Msvc { clang_cl } => { + // This is an undocumented flag from MSVC but helps with making + // builds more reproducible by avoiding putting timestamps into + // files. + cmd.args.push("-Brepro".into()); + if clang_cl { if target.contains("x86_64") { cmd.args.push("-m64".into()); @@ -1402,9 +1588,11 @@ impl Build { if let Some(arch) = parts.next() { let arch = &arch[5..]; cmd.args.push(("-march=rv".to_owned() + arch).into()); - // ABI is always soft-float right now, update this when this is no longer the - // case: - if arch.starts_with("64") { + if target.contains("linux") && arch.starts_with("64") { + cmd.args.push("-mabi=lp64d".into()); + } else if target.contains("linux") && arch.starts_with("32") { + cmd.args.push("-mabi=ilp32d".into()); + } else if arch.starts_with("64") { cmd.args.push("-mabi=lp64".into()); } else { cmd.args.push("-mabi=ilp32".into()); @@ -1501,6 +1689,9 @@ impl Build { let mut out = OsString::from("-out:"); out.push(dst); cmd.arg(out).arg("-nologo"); + for flag in self.ar_flags.iter() { + cmd.arg(flag); + } // Similar to https://github.com/rust-lang/rust/pull/47507 // and https://github.com/rust-lang/rust/pull/48548 @@ -1563,6 +1754,33 @@ impl Build { }; } else { let (mut ar, cmd) = self.get_ar()?; + + // Set an environment variable to tell the OSX archiver to ensure + // that all dates listed in the archive are zero, improving + // determinism of builds. AFAIK there's not really official + // documentation of this but there's a lot of references to it if + // you search google. + // + // You can reproduce this locally on a mac with: + // + // $ touch foo.c + // $ cc -c foo.c -o foo.o + // + // # Notice that these two checksums are different + // $ ar crus libfoo1.a foo.o && sleep 2 && ar crus libfoo2.a foo.o + // $ md5sum libfoo*.a + // + // # Notice that these two checksums are the same + // $ export ZERO_AR_DATE=1 + // $ ar crus libfoo1.a foo.o && sleep 2 && touch foo.o && ar crus libfoo2.a foo.o + // $ md5sum libfoo*.a + // + // In any case if this doesn't end up getting read, it shouldn't + // cause that many issues! + ar.env("ZERO_AR_DATE", "1"); + for flag in self.ar_flags.iter() { + ar.arg(flag); + } run( ar.arg("crs").arg(dst).args(&objects).args(&self.objects), &cmd, @@ -1687,13 +1905,19 @@ impl Build { let tool_opt: Option<Tool> = self .env_tool(env) - .map(|(tool, cc, args)| { + .map(|(tool, wrapper, args)| { + // find the driver mode, if any + const DRIVER_MODE: &str = "--driver-mode="; + let driver_mode = args + .iter() + .find(|a| a.starts_with(DRIVER_MODE)) + .map(|a| &a[DRIVER_MODE.len()..]); // chop off leading/trailing whitespace to work around // semi-buggy build scripts which are shared in // makefiles/configure scripts (where spaces are far more // lenient) - let mut t = Tool::new(PathBuf::from(tool.trim())); - if let Some(cc) = cc { + let mut t = Tool::with_clang_driver(PathBuf::from(tool.trim()), driver_mode); + if let Some(cc) = wrapper { t.cc_wrapper_path = Some(PathBuf::from(cc)); } for arg in args { @@ -1846,7 +2070,7 @@ impl Build { Err(_) => "nvcc".into(), Ok(nvcc) => nvcc, }; - let mut nvcc_tool = Tool::with_features(PathBuf::from(nvcc), self.cuda); + let mut nvcc_tool = Tool::with_features(PathBuf::from(nvcc), None, self.cuda); nvcc_tool .args .push(format!("-ccbin={}", tool.path.display()).into()); @@ -2062,6 +2286,10 @@ impl Build { }) } + fn get_force_frame_pointer(&self) -> bool { + self.force_frame_pointer.unwrap_or_else(|| self.get_debug()) + } + fn get_out_dir(&self) -> Result<PathBuf, Error> { match self.out_dir.clone() { Some(p) => Ok(p), @@ -2109,11 +2337,15 @@ impl Default for Build { } impl Tool { - fn new(path: PathBuf) -> Tool { - Tool::with_features(path, false) + fn new(path: PathBuf) -> Self { + Tool::with_features(path, None, false) } - fn with_features(path: PathBuf, cuda: bool) -> Tool { + fn with_clang_driver(path: PathBuf, clang_driver: Option<&str>) -> Self { + Self::with_features(path, clang_driver, false) + } + + fn with_features(path: PathBuf, clang_driver: Option<&str>, cuda: bool) -> Self { // Try to detect family of the tool from its name, falling back to Gnu. let family = if let Some(fname) = path.file_name().and_then(|p| p.to_str()) { if fname.contains("clang-cl") { @@ -2125,13 +2357,17 @@ impl Tool { { ToolFamily::Msvc { clang_cl: false } } else if fname.contains("clang") { - ToolFamily::Clang + match clang_driver { + Some("cl") => ToolFamily::Msvc { clang_cl: true }, + _ => ToolFamily::Clang, + } } else { ToolFamily::Gnu } } else { ToolFamily::Gnu }; + Tool { path: path, cc_wrapper_path: None, diff --git a/cc/src/setup_config.rs b/cc/src/setup_config.rs index 56fe114..bc2b1c2 100644 --- a/cc/src/setup_config.rs +++ b/cc/src/setup_config.rs @@ -8,19 +8,19 @@ #![allow(bad_style)] #![allow(unused)] +use crate::winapi::Interface; +use crate::winapi::BSTR; +use crate::winapi::LPCOLESTR; +use crate::winapi::LPSAFEARRAY; +use crate::winapi::S_FALSE; +use crate::winapi::{CoCreateInstance, CLSCTX_ALL}; +use crate::winapi::{IUnknown, IUnknownVtbl}; +use crate::winapi::{HRESULT, LCID, LPCWSTR, PULONGLONG}; +use crate::winapi::{LPFILETIME, ULONG}; use std::ffi::OsString; use std::ptr::null_mut; -use winapi::Interface; -use winapi::BSTR; -use winapi::LPCOLESTR; -use winapi::LPSAFEARRAY; -use winapi::S_FALSE; -use winapi::{CoCreateInstance, CLSCTX_ALL}; -use winapi::{IUnknown, IUnknownVtbl}; -use winapi::{HRESULT, LCID, LPCWSTR, PULONGLONG}; -use winapi::{LPFILETIME, ULONG}; -use com::{BStr, ComPtr}; +use crate::com::{BStr, ComPtr}; // Bindings to the Setup.Configuration stuff pub type InstanceState = u32; @@ -196,7 +196,7 @@ impl SetupConfiguration { } pub fn enum_all_instances(&self) -> Result<EnumSetupInstances, i32> { let mut obj = null_mut(); - let this = try!(self.0.cast::<ISetupConfiguration2>()); + let this = self.0.cast::<ISetupConfiguration2>()?; let err = unsafe { this.EnumAllInstances(&mut obj) }; if err < 0 { return Err(err); @@ -249,7 +249,7 @@ impl SetupInstance { } pub fn product_path(&self) -> Result<OsString, i32> { let mut s = null_mut(); - let this = try!(self.0.cast::<ISetupInstance2>()); + let this = self.0.cast::<ISetupInstance2>()?; let err = unsafe { this.GetProductPath(&mut s) }; let bstr = unsafe { BStr::from_raw(s) }; if err < 0 { diff --git a/cc/src/windows_registry.rs b/cc/src/windows_registry.rs index ee39339..5af3ff7 100644 --- a/cc/src/windows_registry.rs +++ b/cc/src/windows_registry.rs @@ -13,17 +13,7 @@ use std::process::Command; -use Tool; - -#[cfg(windows)] -macro_rules! otry { - ($expr:expr) => { - match $expr { - Some(val) => val, - None => return None, - } - }; -} +use crate::Tool; /// Attempts to find a tool within an MSVC installation using the Windows /// registry as a point to search from. @@ -173,9 +163,9 @@ pub fn find_vs_version() -> Result<VsVers, String> { #[cfg(windows)] mod impl_ { - use com; - use registry::{RegistryKey, LOCAL_MACHINE}; - use setup_config::{EnumSetupInstances, SetupConfiguration, SetupInstance}; + use crate::com; + use crate::registry::{RegistryKey, LOCAL_MACHINE}; + use crate::setup_config::{EnumSetupInstances, SetupConfiguration, SetupInstance}; use std::env; use std::ffi::OsString; use std::fs::File; @@ -184,7 +174,7 @@ mod impl_ { use std::mem; use std::path::{Path, PathBuf}; - use Tool; + use crate::Tool; struct MsvcTool { tool: PathBuf, @@ -218,6 +208,7 @@ mod impl_ { } } + #[allow(bare_trait_objects)] fn vs16_instances() -> Box<Iterator<Item = PathBuf>> { let instances = if let Some(instances) = vs15_instances() { instances @@ -225,10 +216,10 @@ mod impl_ { return Box::new(iter::empty()); }; Box::new(instances.filter_map(|instance| { - let instance = otry!(instance.ok()); - let installation_name = otry!(instance.installation_name().ok()); - if otry!(installation_name.to_str()).starts_with("VisualStudio/16.") { - Some(PathBuf::from(otry!(instance.installation_path().ok()))) + let instance = instance.ok()?; + let installation_name = instance.installation_name().ok()?; + if installation_name.to_str()?.starts_with("VisualStudio/16.") { + Some(PathBuf::from(instance.installation_path().ok()?)) } else { None } @@ -263,16 +254,16 @@ mod impl_ { // // [online]: https://blogs.msdn.microsoft.com/vcblog/2017/03/06/finding-the-visual-c-compiler-tools-in-visual-studio-2017/ fn vs15_instances() -> Option<EnumSetupInstances> { - otry!(com::initialize().ok()); + com::initialize().ok()?; - let config = otry!(SetupConfiguration::new().ok()); + let config = SetupConfiguration::new().ok()?; config.enum_all_instances().ok() } pub fn find_msvc_15(tool: &str, target: &str) -> Option<Tool> { - let iter = otry!(vs15_instances()); + let iter = vs15_instances()?; for instance in iter { - let instance = otry!(instance.ok()); + let instance = instance.ok()?; let tool = tool_from_vs15_instance(tool, target, &instance); if tool.is_some() { return tool; @@ -322,8 +313,7 @@ mod impl_ { } fn tool_from_vs15_instance(tool: &str, target: &str, instance: &SetupInstance) -> Option<Tool> { - let (bin_path, host_dylib_path, lib_path, include_path) = - otry!(vs15_vc_paths(target, instance)); + let (bin_path, host_dylib_path, lib_path, include_path) = vs15_vc_paths(target, instance)?; let tool_path = bin_path.join(tool); if !tool_path.exists() { return None; @@ -339,7 +329,7 @@ mod impl_ { tool.include.push(atl_include_path); } - otry!(add_sdks(&mut tool, target)); + add_sdks(&mut tool, target)?; Some(tool.into_tool()) } @@ -348,19 +338,19 @@ mod impl_ { target: &str, instance: &SetupInstance, ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf)> { - let instance_path: PathBuf = otry!(instance.installation_path().ok()).into(); + let instance_path: PathBuf = instance.installation_path().ok()?.into(); let version_path = instance_path.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"); - let mut version_file = otry!(File::open(version_path).ok()); + let mut version_file = File::open(version_path).ok()?; let mut version = String::new(); - otry!(version_file.read_to_string(&mut version).ok()); + version_file.read_to_string(&mut version).ok()?; let version = version.trim(); let host = match host_arch() { X86 => "X86", X86_64 => "X64", _ => return None, }; - let target = otry!(lib_subdir(target)); + let target = lib_subdir(target)?; // The directory layout here is MSVC/bin/Host$host/$target/ let path = instance_path.join(r"VC\Tools\MSVC").join(version); // This is the path to the toolchain for a particular target, running @@ -383,7 +373,7 @@ mod impl_ { fn atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)> { let atl_path = path.join("atlfmc"); - let sub = otry!(lib_subdir(target)); + let sub = lib_subdir(target)?; if atl_path.exists() { Some((atl_path.join("lib").join(sub), atl_path.join("include"))) } else { @@ -394,15 +384,15 @@ mod impl_ { // For MSVC 14 we need to find the Universal CRT as well as either // the Windows 10 SDK or Windows 8.1 SDK. pub fn find_msvc_14(tool: &str, target: &str) -> Option<Tool> { - let vcdir = otry!(get_vc_dir("14.0")); - let mut tool = otry!(get_tool(tool, &vcdir, target)); - otry!(add_sdks(&mut tool, target)); + let vcdir = get_vc_dir("14.0")?; + let mut tool = get_tool(tool, &vcdir, target)?; + add_sdks(&mut tool, target)?; Some(tool.into_tool()) } fn add_sdks(tool: &mut MsvcTool, target: &str) -> Option<()> { - let sub = otry!(lib_subdir(target)); - let (ucrt, ucrt_version) = otry!(get_ucrt_dir()); + let sub = lib_subdir(target)?; + let (ucrt, ucrt_version) = get_ucrt_dir()?; tool.path .push(ucrt.join("bin").join(&ucrt_version).join(sub)); @@ -437,10 +427,10 @@ mod impl_ { // For MSVC 12 we need to find the Windows 8.1 SDK. pub fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> { - let vcdir = otry!(get_vc_dir("12.0")); - let mut tool = otry!(get_tool(tool, &vcdir, target)); - let sub = otry!(lib_subdir(target)); - let sdk81 = otry!(get_sdk81_dir()); + let vcdir = get_vc_dir("12.0")?; + let mut tool = get_tool(tool, &vcdir, target)?; + let sub = lib_subdir(target)?; + let sdk81 = get_sdk81_dir()?; tool.path.push(sdk81.join("bin").join(sub)); let sdk_lib = sdk81.join("lib").join("winv6.3"); tool.libs.push(sdk_lib.join("um").join(sub)); @@ -453,10 +443,10 @@ mod impl_ { // For MSVC 11 we need to find the Windows 8 SDK. pub fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> { - let vcdir = otry!(get_vc_dir("11.0")); - let mut tool = otry!(get_tool(tool, &vcdir, target)); - let sub = otry!(lib_subdir(target)); - let sdk8 = otry!(get_sdk8_dir()); + let vcdir = get_vc_dir("11.0")?; + let mut tool = get_tool(tool, &vcdir, target)?; + let sub = lib_subdir(target)?; + let sdk8 = get_sdk8_dir()?; tool.path.push(sdk8.join("bin").join(sub)); let sdk_lib = sdk8.join("lib").join("win8"); tool.libs.push(sdk_lib.join("um").join(sub)); @@ -493,7 +483,7 @@ mod impl_ { tool }) .filter_map(|mut tool| { - let sub = otry!(vc_lib_subdir(target)); + let sub = vc_lib_subdir(target)?; tool.libs.push(path.join("lib").join(sub)); tool.include.push(path.join("include")); let atlmfc_path = path.join("atlmfc"); @@ -510,8 +500,8 @@ mod impl_ { // trying to find. fn get_vc_dir(ver: &str) -> Option<PathBuf> { let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7"; - let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok()); - let path = otry!(key.query_str(ver).ok()); + let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; + let path = key.query_str(ver).ok()?; Some(path.into()) } @@ -523,19 +513,20 @@ mod impl_ { // Returns a pair of (root, version) for the ucrt dir if found fn get_ucrt_dir() -> Option<(PathBuf, String)> { let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots"; - let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok()); - let root = otry!(key.query_str("KitsRoot10").ok()); - let readdir = otry!(Path::new(&root).join("lib").read_dir().ok()); - let max_libdir = otry!(readdir + let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; + let root = key.query_str("KitsRoot10").ok()?; + let readdir = Path::new(&root).join("lib").read_dir().ok()?; + let max_libdir = readdir .filter_map(|dir| dir.ok()) .map(|dir| dir.path()) - .filter(|dir| dir - .components() - .last() - .and_then(|c| c.as_os_str().to_str()) - .map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir()) - .unwrap_or(false)) - .max()); + .filter(|dir| { + dir.components() + .last() + .and_then(|c| c.as_os_str().to_str()) + .map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir()) + .unwrap_or(false) + }) + .max()?; let version = max_libdir.components().last().unwrap(); let version = version.as_os_str().to_str().unwrap().to_string(); Some((root.into(), version)) @@ -551,19 +542,19 @@ mod impl_ { // asciibetically to find the newest one as that is what vcvars does. fn get_sdk10_dir() -> Option<(PathBuf, String)> { let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0"; - let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok()); - let root = otry!(key.query_str("InstallationFolder").ok()); - let readdir = otry!(Path::new(&root).join("lib").read_dir().ok()); + let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; + let root = key.query_str("InstallationFolder").ok()?; + let readdir = Path::new(&root).join("lib").read_dir().ok()?; let mut dirs = readdir .filter_map(|dir| dir.ok()) .map(|dir| dir.path()) .collect::<Vec<_>>(); dirs.sort(); - let dir = otry!(dirs + let dir = dirs .into_iter() .rev() .filter(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file()) - .next()); + .next()?; let version = dir.components().last().unwrap(); let version = version.as_os_str().to_str().unwrap().to_string(); Some((root.into(), version)) @@ -575,15 +566,15 @@ mod impl_ { // instead of user mode applications, we would care. fn get_sdk81_dir() -> Option<PathBuf> { let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1"; - let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok()); - let root = otry!(key.query_str("InstallationFolder").ok()); + let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; + let root = key.query_str("InstallationFolder").ok()?; Some(root.into()) } fn get_sdk8_dir() -> Option<PathBuf> { let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0"; - let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok()); - let root = otry!(key.query_str("InstallationFolder").ok()); + let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; + let root = key.query_str("InstallationFolder").ok()?; Some(root.into()) } diff --git a/cc/tests/cc_env.rs b/cc/tests/cc_env.rs index e862fea..43eb689 100644 --- a/cc/tests/cc_env.rs +++ b/cc/tests/cc_env.rs @@ -1,12 +1,9 @@ -extern crate cc; -extern crate tempdir; - use std::env; use std::ffi::OsString; use std::path::Path; mod support; -use support::Test; +use crate::support::Test; #[test] fn main() { diff --git a/cc/tests/cflags.rs b/cc/tests/cflags.rs index df6b0a7..caec6ea 100644 --- a/cc/tests/cflags.rs +++ b/cc/tests/cflags.rs @@ -1,10 +1,7 @@ -extern crate cc; -extern crate tempdir; - mod support; +use crate::support::Test; use std::env; -use support::Test; /// This test is in its own module because it modifies the environment and would affect other tests /// when run in parallel with them. diff --git a/cc/tests/cxxflags.rs b/cc/tests/cxxflags.rs index 26426af..c524c7d 100644 --- a/cc/tests/cxxflags.rs +++ b/cc/tests/cxxflags.rs @@ -1,10 +1,7 @@ -extern crate cc; -extern crate tempdir; - mod support; +use crate::support::Test; use std::env; -use support::Test; /// This test is in its own module because it modifies the environment and would affect other tests /// when run in parallel with them. diff --git a/cc/tests/support/mod.rs b/cc/tests/support/mod.rs index 7d74719..fe8acde 100644 --- a/cc/tests/support/mod.rs +++ b/cc/tests/support/mod.rs @@ -8,7 +8,7 @@ use std::io::prelude::*; use std::path::{Path, PathBuf}; use cc; -use tempdir::TempDir; +use tempfile::{Builder, TempDir}; pub struct Test { pub td: TempDir, @@ -27,7 +27,7 @@ impl Test { if gcc.ends_with("deps") { gcc.pop(); } - let td = TempDir::new_in(&gcc, "gcc-test").unwrap(); + let td = Builder::new().prefix("gcc-test").tempdir_in(&gcc).unwrap(); gcc.push(format!("gcc-shim{}", env::consts::EXE_SUFFIX)); Test { td: td, diff --git a/cc/tests/test.rs b/cc/tests/test.rs index 74eca1e..def11f0 100644 --- a/cc/tests/test.rs +++ b/cc/tests/test.rs @@ -1,7 +1,4 @@ -extern crate cc; -extern crate tempdir; - -use support::Test; +use crate::support::Test; mod support; @@ -42,10 +39,40 @@ fn gnu_opt_level_s() { } #[test] -fn gnu_debug() { +fn gnu_debug_fp_auto() { let test = Test::gnu(); test.gcc().debug(true).file("foo.c").compile("foo"); test.cmd(0).must_have("-g"); + test.cmd(0).must_have("-fno-omit-frame-pointer"); +} + +#[test] +fn gnu_debug_fp() { + let test = Test::gnu(); + test.gcc().debug(true).file("foo.c").compile("foo"); + test.cmd(0).must_have("-g"); + test.cmd(0).must_have("-fno-omit-frame-pointer"); +} + +#[test] +fn gnu_debug_nofp() { + let test = Test::gnu(); + test.gcc() + .debug(true) + .force_frame_pointer(false) + .file("foo.c") + .compile("foo"); + test.cmd(0).must_have("-g"); + test.cmd(0).must_not_have("-fno-omit-frame-pointer"); + + let test = Test::gnu(); + test.gcc() + .force_frame_pointer(false) + .debug(true) + .file("foo.c") + .compile("foo"); + test.cmd(0).must_have("-g"); + test.cmd(0).must_not_have("-fno-omit-frame-pointer"); } #[test] |