diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs index 4d3244b0d94..fd40f4f3e05 100644 --- a/src/cargo/core/compiler/build_context/target_info.rs +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -137,6 +137,7 @@ impl TargetInfo { kind, "RUSTFLAGS", )?; + let extra_fingerprint = kind.fingerprint_hash(); let mut process = rustc.process(); process .arg("-") @@ -163,14 +164,17 @@ impl TargetInfo { process.arg("--crate-type").arg(crate_type.as_str()); } let supports_split_debuginfo = rustc - .cached_output(process.clone().arg("-Csplit-debuginfo=packed")) + .cached_output( + process.clone().arg("-Csplit-debuginfo=packed"), + extra_fingerprint, + ) .is_ok(); process.arg("--print=sysroot"); process.arg("--print=cfg"); let (output, error) = rustc - .cached_output(&process) + .cached_output(&process, extra_fingerprint) .chain_err(|| "failed to run `rustc` to learn about target-specific information")?; let mut lines = output.lines(); diff --git a/src/cargo/core/compiler/compile_kind.rs b/src/cargo/core/compiler/compile_kind.rs index 4c123601f8e..41d5f64aac1 100644 --- a/src/cargo/core/compiler/compile_kind.rs +++ b/src/cargo/core/compiler/compile_kind.rs @@ -1,10 +1,12 @@ use crate::core::Target; use crate::util::errors::{CargoResult, CargoResultExt}; use crate::util::interning::InternedString; -use crate::util::Config; +use crate::util::{Config, StableHasher}; use anyhow::bail; use serde::Serialize; use std::collections::BTreeSet; +use std::fs; +use std::hash::{Hash, Hasher}; use std::path::Path; /// Indicator for how a unit is being compiled. @@ -78,6 +80,18 @@ impl CompileKind { }; Ok(vec![kind]) } + + /// Hash used for fingerprinting. + /// + /// Metadata hashing uses the normal Hash trait, which does not + /// differentiate on `.json` file contents. The fingerprint hash does + /// check the contents. + pub fn fingerprint_hash(&self) -> u64 { + match self { + CompileKind::Host => 0, + CompileKind::Target(target) => target.fingerprint_hash(), + } + } } impl serde::ser::Serialize for CompileKind { @@ -166,4 +180,19 @@ impl CompileTarget { &self.name } } + + /// See [`CompileKind::fingerprint_hash`]. + pub fn fingerprint_hash(&self) -> u64 { + let mut hasher = StableHasher::new(); + self.name.hash(&mut hasher); + if self.name.ends_with(".json") { + // This may have some performance concerns, since it is called + // fairly often. If that ever seems worth fixing, consider + // embedding this in `CompileTarget`. + if let Ok(contents) = fs::read_to_string(self.name) { + contents.hash(&mut hasher); + } + } + hasher.finish() + } } diff --git a/src/cargo/core/compiler/fingerprint.rs b/src/cargo/core/compiler/fingerprint.rs index 9c776d09fc7..a5811dd4a08 100644 --- a/src/cargo/core/compiler/fingerprint.rs +++ b/src/cargo/core/compiler/fingerprint.rs @@ -60,10 +60,10 @@ //! `cargo rustc` extra args | ✓ | ✓ //! CompileMode | ✓ | ✓ //! Target Name | ✓ | ✓ -//! Target CompileKind (bin/lib/etc.) | ✓ | ✓ +//! TargetKind (bin/lib/etc.) | ✓ | ✓ //! Enabled Features | ✓ | ✓ //! Immediate dependency’s hashes | ✓[^1] | ✓ -//! Target or Host mode | | ✓ +//! CompileKind (host/target) | ✓ | ✓ //! __CARGO_DEFAULT_LIB_METADATA[^4] | | ✓ //! package_id | | ✓ //! authors, description, homepage, repo | ✓ | @@ -542,6 +542,9 @@ pub struct Fingerprint { metadata: u64, /// Hash of various config settings that change how things are compiled. config: u64, + /// The rustc target. This is only relevant for `.json` files, otherwise + /// the metadata hash segregates the units. + compile_kind: u64, /// Description of whether the filesystem status for this unit is up to date /// or should be considered stale. #[serde(skip)] @@ -780,6 +783,7 @@ impl Fingerprint { rustflags: Vec::new(), metadata: 0, config: 0, + compile_kind: 0, fs_status: FsStatus::Stale, outputs: Vec::new(), } @@ -843,6 +847,9 @@ impl Fingerprint { if self.config != old.config { bail!("configuration settings have changed") } + if self.compile_kind != old.compile_kind { + bail!("compile kind (rustc target) changed") + } let my_local = self.local.lock().unwrap(); let old_local = old.local.lock().unwrap(); if my_local.len() != old_local.len() { @@ -1090,12 +1097,22 @@ impl hash::Hash for Fingerprint { ref local, metadata, config, + compile_kind, ref rustflags, .. } = *self; let local = local.lock().unwrap(); ( - rustc, features, target, path, profile, &*local, metadata, config, rustflags, + rustc, + features, + target, + path, + profile, + &*local, + metadata, + config, + compile_kind, + rustflags, ) .hash(h); @@ -1318,6 +1335,7 @@ fn calculate_normal(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult, unit: &Unit) -> CargoResult CargoResult<&str> { verbose_version @@ -100,8 +100,25 @@ impl Rustc { util::process(&self.path) } - pub fn cached_output(&self, cmd: &ProcessBuilder) -> CargoResult<(String, String)> { - self.cache.lock().unwrap().cached_output(cmd) + /// Gets the output for the given command. + /// + /// This will return the cached value if available, otherwise it will run + /// the command and cache the output. + /// + /// `extra_fingerprint` is extra data to include in the cache fingerprint. + /// Use this if there is other information about the environment that may + /// affect the output that is not part of `cmd`. + /// + /// Returns a tuple of strings `(stdout, stderr)`. + pub fn cached_output( + &self, + cmd: &ProcessBuilder, + extra_fingerprint: u64, + ) -> CargoResult<(String, String)> { + self.cache + .lock() + .unwrap() + .cached_output(cmd, extra_fingerprint) } } @@ -187,8 +204,12 @@ impl Cache { } } - fn cached_output(&mut self, cmd: &ProcessBuilder) -> CargoResult<(String, String)> { - let key = process_fingerprint(cmd); + fn cached_output( + &mut self, + cmd: &ProcessBuilder, + extra_fingerprint: u64, + ) -> CargoResult<(String, String)> { + let key = process_fingerprint(cmd, extra_fingerprint); if self.data.outputs.contains_key(&key) { debug!("rustc info cache hit"); } else { @@ -295,8 +316,9 @@ fn rustc_fingerprint(path: &Path, rustup_rustc: &Path) -> CargoResult { Ok(hasher.finish()) } -fn process_fingerprint(cmd: &ProcessBuilder) -> u64 { +fn process_fingerprint(cmd: &ProcessBuilder, extra_fingerprint: u64) -> u64 { let mut hasher = StableHasher::new(); + extra_fingerprint.hash(&mut hasher); cmd.get_args().hash(&mut hasher); let mut env = cmd.get_envs().iter().collect::>(); env.sort_unstable(); diff --git a/tests/testsuite/custom_target.rs b/tests/testsuite/custom_target.rs index ef425730d99..8b4c9ac3d43 100644 --- a/tests/testsuite/custom_target.rs +++ b/tests/testsuite/custom_target.rs @@ -2,6 +2,37 @@ use cargo_test_support::is_nightly; use cargo_test_support::{basic_manifest, project}; +use std::fs; + +const MINIMAL_LIB: &str = r#" +#![feature(no_core)] +#![feature(lang_items)] +#![no_core] + +#[lang = "sized"] +pub trait Sized { + // Empty. +} +#[lang = "copy"] +pub trait Copy { + // Empty. +} +"#; + +const SIMPLE_SPEC: &str = r#" +{ + "llvm-target": "x86_64-unknown-none-gnu", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "none", + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "executables": true +} +"#; #[cargo_test] fn custom_target_minimal() { @@ -12,40 +43,16 @@ fn custom_target_minimal() { let p = project() .file( "src/lib.rs", - r#" - #![feature(no_core)] - #![feature(lang_items)] - #![no_core] + &" + __MINIMAL_LIB__ pub fn foo() -> u32 { 42 } - - #[lang = "sized"] - pub trait Sized { - // Empty. - } - #[lang = "copy"] - pub trait Copy { - // Empty. - } - "#, - ) - .file( - "custom-target.json", - r#" - { - "llvm-target": "x86_64-unknown-none-gnu", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "arch": "x86_64", - "target-endian": "little", - "target-pointer-width": "64", - "target-c-int-width": "32", - "os": "none", - "linker-flavor": "ld.lld" - } - "#, + " + .replace("__MINIMAL_LIB__", MINIMAL_LIB), ) + .file("custom-target.json", SIMPLE_SPEC) .build(); p.cargo("build --lib --target custom-target.json -v").run(); @@ -100,40 +107,16 @@ fn custom_target_dependency() { .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) .file( "bar/src/lib.rs", - r#" - #![feature(no_core)] - #![feature(lang_items)] - #![no_core] + &" + __MINIMAL_LIB__ pub fn bar() -> u32 { 42 } - - #[lang = "sized"] - pub trait Sized { - // Empty. - } - #[lang = "copy"] - pub trait Copy { - // Empty. - } - "#, - ) - .file( - "custom-target.json", - r#" - { - "llvm-target": "x86_64-unknown-none-gnu", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "arch": "x86_64", - "target-endian": "little", - "target-pointer-width": "64", - "target-c-int-width": "32", - "os": "none", - "linker-flavor": "ld.lld" - } - "#, + " + .replace("__MINIMAL_LIB__", MINIMAL_LIB), ) + .file("custom-target.json", SIMPLE_SPEC) .build(); p.cargo("build --lib --target custom-target.json -v").run(); @@ -148,40 +131,106 @@ fn custom_bin_target() { let p = project() .file( "src/main.rs", - r#" - #![feature(no_core)] - #![feature(lang_items)] - #![no_core] + &" #![no_main] + __MINIMAL_LIB__ + " + .replace("__MINIMAL_LIB__", MINIMAL_LIB), + ) + .file("custom-bin-target.json", SIMPLE_SPEC) + .build(); - #[lang = "sized"] - pub trait Sized { - // Empty. - } - #[lang = "copy"] - pub trait Copy { - // Empty. + p.cargo("build --target custom-bin-target.json -v").run(); +} + +#[cargo_test] +fn changing_spec_rebuilds() { + // Changing the .json file will trigger a rebuild. + if !is_nightly() { + // Requires features no_core, lang_items + return; + } + let p = project() + .file( + "src/lib.rs", + &" + __MINIMAL_LIB__ + + pub fn foo() -> u32 { + 42 } - "#, + " + .replace("__MINIMAL_LIB__", MINIMAL_LIB), + ) + .file("custom-target.json", SIMPLE_SPEC) + .build(); + + p.cargo("build --lib --target custom-target.json -v").run(); + p.cargo("build --lib --target custom-target.json -v") + .with_stderr( + "\ +[FRESH] foo [..] +[FINISHED] [..] +", + ) + .run(); + let spec_path = p.root().join("custom-target.json"); + let spec = fs::read_to_string(&spec_path).unwrap(); + // Some arbitrary change that I hope is safe. + let spec = spec.replace('{', "{\n\"vendor\": \"unknown\",\n"); + fs::write(&spec_path, spec).unwrap(); + p.cargo("build --lib --target custom-target.json -v") + .with_stderr( + "\ +[COMPILING] foo v0.0.1 [..] +[RUNNING] `rustc [..] +[FINISHED] [..] +", ) + .run(); +} + +#[cargo_test] +fn changing_spec_relearns_crate_types() { + // Changing the .json file will invalidate the cache of crate types. + if !is_nightly() { + // Requires features no_core, lang_items + return; + } + let p = project() .file( - "custom-bin-target.json", + "Cargo.toml", r#" - { - "llvm-target": "x86_64-unknown-none-gnu", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "arch": "x86_64", - "target-endian": "little", - "target-pointer-width": "64", - "target-c-int-width": "32", - "os": "none", - "linker-flavor": "ld.lld", - "linker": "rust-lld", - "executables": true - } + [package] + name = "foo" + version = "0.1.0" + + [lib] + crate-type = ["cdylib"] "#, ) + .file("src/lib.rs", MINIMAL_LIB) + .file("custom-target.json", SIMPLE_SPEC) .build(); - p.cargo("build --target custom-bin-target.json -v").run(); + p.cargo("build --lib --target custom-target.json -v") + .with_status(101) + .with_stderr("error: cannot produce cdylib for `foo [..]") + .run(); + + // Enable dynamic linking. + let spec_path = p.root().join("custom-target.json"); + let spec = fs::read_to_string(&spec_path).unwrap(); + let spec = spec.replace('{', "{\n\"dynamic-linking\": true,\n"); + fs::write(&spec_path, spec).unwrap(); + + p.cargo("build --lib --target custom-target.json -v") + .with_stderr( + "\ +[COMPILING] foo [..] +[RUNNING] `rustc [..] +[FINISHED] [..] +", + ) + .run(); }