Detect changes for JSON spec targets.

This commit is contained in:
Eric Huss 2021-03-01 12:19:14 -08:00
parent ccf781ab90
commit 83487e41a9
5 changed files with 224 additions and 101 deletions

View file

@ -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();

View file

@ -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()
}
}

View file

@ -60,10 +60,10 @@
//! `cargo rustc` extra args | ✓ | ✓
//! CompileMode | ✓ | ✓
//! Target Name | ✓ | ✓
//! Target CompileKind (bin/lib/etc.) | ✓ | ✓
//! TargetKind (bin/lib/etc.) | ✓ | ✓
//! Enabled Features | ✓ | ✓
//! Immediate dependencys 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<Finger
} else {
0
};
let compile_kind = unit.kind.fingerprint_hash();
Ok(Fingerprint {
rustc: util::hash_u64(&cx.bcx.rustc().verbose_version),
target: util::hash_u64(&unit.target),
@ -1331,6 +1349,7 @@ fn calculate_normal(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Finger
memoized_hash: Mutex::new(None),
metadata,
config,
compile_kind,
rustflags: extra_flags,
fs_status: FsStatus::Stale,
outputs,

View file

@ -49,7 +49,7 @@ impl Rustc {
let mut cmd = util::process(&path);
cmd.arg("-vV");
let verbose_version = cache.cached_output(&cmd)?.0;
let verbose_version = cache.cached_output(&cmd, 0)?.0;
let extract = |field: &str| -> 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<u64> {
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::<Vec<_>>();
env.sort_unstable();

View file

@ -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]
#[lang = "sized"]
pub trait Sized {
// Empty.
}
#[lang = "copy"]
pub trait Copy {
// Empty.
}
"#,
)
.file(
"custom-bin-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",
"linker": "rust-lld",
"executables": true
}
"#,
__MINIMAL_LIB__
"
.replace("__MINIMAL_LIB__", MINIMAL_LIB),
)
.file("custom-bin-target.json", SIMPLE_SPEC)
.build();
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(
"Cargo.toml",
r#"
[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 --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();
}