Skip to content

Commit 921c4a5

Browse files
committed
Auto merge of #4469 - nipunn1313:workspace_features, r=alexcrichton
Hashed dependencies of metadata into the metadata of a lib This fixes one part of #3620. To my understanding, the more fundamental fix is more challenging
2 parents db2e07f + f90d21f commit 921c4a5

File tree

5 files changed

+164
-34
lines changed

5 files changed

+164
-34
lines changed

src/cargo/ops/cargo_rustc/context.rs

+46-13
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub struct Context<'a, 'cfg: 'a> {
4040
pub compilation: Compilation<'cfg>,
4141
pub packages: &'a PackageSet<'cfg>,
4242
pub build_state: Arc<BuildState>,
43+
pub build_script_overridden: HashSet<(PackageId, Kind)>,
4344
pub build_explicit_deps: HashMap<Unit<'a>, BuildDeps>,
4445
pub fingerprints: HashMap<Unit<'a>, Arc<Fingerprint>>,
4546
pub compiled: HashSet<Unit<'a>>,
@@ -55,7 +56,9 @@ pub struct Context<'a, 'cfg: 'a> {
5556
host_info: TargetInfo,
5657
profiles: &'a Profiles,
5758
incremental_enabled: bool,
59+
5860
target_filenames: HashMap<Unit<'a>, Arc<Vec<(PathBuf, Option<PathBuf>, bool)>>>,
61+
target_metadatas: HashMap<Unit<'a>, Option<Metadata>>,
5962
}
6063

6164
#[derive(Clone, Default)]
@@ -82,7 +85,7 @@ impl TargetInfo {
8285
}
8386
}
8487

85-
#[derive(Clone)]
88+
#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
8689
pub struct Metadata(u64);
8790

8891
impl<'a, 'cfg> Context<'a, 'cfg> {
@@ -154,7 +157,11 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
154157
used_in_plugin: HashSet::new(),
155158
incremental_enabled: incremental_enabled,
156159
jobserver: jobserver,
160+
build_script_overridden: HashSet::new(),
161+
162+
// TODO: Pre-Calculate these with a topo-sort, rather than lazy-calculating
157163
target_filenames: HashMap::new(),
164+
target_metadatas: HashMap::new(),
158165
})
159166
}
160167

@@ -362,21 +369,21 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
362369

363370
/// Returns the directory for the specified unit where fingerprint
364371
/// information is stored.
365-
pub fn fingerprint_dir(&mut self, unit: &Unit) -> PathBuf {
372+
pub fn fingerprint_dir(&mut self, unit: &Unit<'a>) -> PathBuf {
366373
let dir = self.pkg_dir(unit);
367374
self.layout(unit.kind).fingerprint().join(dir)
368375
}
369376

370377
/// Returns the appropriate directory layout for either a plugin or not.
371-
pub fn build_script_dir(&mut self, unit: &Unit) -> PathBuf {
378+
pub fn build_script_dir(&mut self, unit: &Unit<'a>) -> PathBuf {
372379
assert!(unit.target.is_custom_build());
373380
assert!(!unit.profile.run_custom_build);
374381
let dir = self.pkg_dir(unit);
375382
self.layout(Kind::Host).build().join(dir)
376383
}
377384

378385
/// Returns the appropriate directory layout for either a plugin or not.
379-
pub fn build_script_out_dir(&mut self, unit: &Unit) -> PathBuf {
386+
pub fn build_script_out_dir(&mut self, unit: &Unit<'a>) -> PathBuf {
380387
assert!(unit.target.is_custom_build());
381388
assert!(unit.profile.run_custom_build);
382389
let dir = self.pkg_dir(unit);
@@ -394,7 +401,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
394401

395402
/// Returns the appropriate output directory for the specified package and
396403
/// target.
397-
pub fn out_dir(&mut self, unit: &Unit) -> PathBuf {
404+
pub fn out_dir(&mut self, unit: &Unit<'a>) -> PathBuf {
398405
if unit.profile.doc {
399406
self.layout(unit.kind).root().parent().unwrap().join("doc")
400407
} else if unit.target.is_custom_build() {
@@ -406,7 +413,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
406413
}
407414
}
408415

409-
fn pkg_dir(&mut self, unit: &Unit) -> String {
416+
fn pkg_dir(&mut self, unit: &Unit<'a>) -> String {
410417
let name = unit.pkg.package_id().name();
411418
match self.target_metadata(unit) {
412419
Some(meta) => format!("{}-{}", name, meta),
@@ -440,7 +447,17 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
440447
/// We build to the path: "{filename}-{target_metadata}"
441448
/// We use a linking step to link/copy to a predictable filename
442449
/// like `target/debug/libfoo.{a,so,rlib}` and such.
443-
pub fn target_metadata(&mut self, unit: &Unit) -> Option<Metadata> {
450+
pub fn target_metadata(&mut self, unit: &Unit<'a>) -> Option<Metadata> {
451+
if let Some(cache) = self.target_metadatas.get(unit) {
452+
return cache.clone()
453+
}
454+
455+
let metadata = self.calc_target_metadata(unit);
456+
self.target_metadatas.insert(*unit, metadata.clone());
457+
metadata
458+
}
459+
460+
fn calc_target_metadata(&mut self, unit: &Unit<'a>) -> Option<Metadata> {
444461
// No metadata for dylibs because of a couple issues
445462
// - OSX encodes the dylib name in the executable
446463
// - Windows rustc multiple files of which we can't easily link all of them
@@ -483,6 +500,15 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
483500
// when changing feature sets each lib is separately cached.
484501
self.resolve.features_sorted(unit.pkg.package_id()).hash(&mut hasher);
485502

503+
// Mix in the target-metadata of all the dependencies of this target
504+
if let Ok(deps) = self.dep_targets(unit) {
505+
let mut deps_metadata = deps.into_iter().map(|dep_unit| {
506+
self.target_metadata(&dep_unit)
507+
}).collect::<Vec<_>>();
508+
deps_metadata.sort();
509+
deps_metadata.hash(&mut hasher);
510+
}
511+
486512
// Throw in the profile we're compiling with. This helps caching
487513
// panic=abort and panic=unwind artifacts, additionally with various
488514
// settings like debuginfo and whatnot.
@@ -512,7 +538,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
512538
}
513539

514540
/// Returns the file stem for a given target/profile combo (with metadata)
515-
pub fn file_stem(&mut self, unit: &Unit) -> String {
541+
pub fn file_stem(&mut self, unit: &Unit<'a>) -> String {
516542
match self.target_metadata(unit) {
517543
Some(ref metadata) => format!("{}-{}", unit.target.crate_name(),
518544
metadata),
@@ -537,7 +563,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
537563
538564
/// Returns an Option because in some cases we don't want to link
539565
/// (eg a dependent lib)
540-
pub fn link_stem(&mut self, unit: &Unit) -> Option<(PathBuf, String)> {
566+
pub fn link_stem(&mut self, unit: &Unit<'a>) -> Option<(PathBuf, String)> {
541567
let src_dir = self.out_dir(unit);
542568
let bin_stem = self.bin_stem(unit);
543569
let file_stem = self.file_stem(unit);
@@ -578,6 +604,15 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
578604
return Ok(cache.clone())
579605
}
580606

607+
let result = self.calc_target_filenames(unit);
608+
if let Ok(ref ret) = result {
609+
self.target_filenames.insert(*unit, ret.clone());
610+
}
611+
result
612+
}
613+
614+
fn calc_target_filenames(&mut self, unit: &Unit<'a>)
615+
-> CargoResult<Arc<Vec<(PathBuf, Option<PathBuf>, bool)>>> {
581616
let out_dir = self.out_dir(unit);
582617
let stem = self.file_stem(unit);
583618
let link_stem = self.link_stem(unit);
@@ -659,9 +694,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
659694
}
660695
info!("Target filenames: {:?}", ret);
661696

662-
let ret = Arc::new(ret);
663-
self.target_filenames.insert(*unit, ret.clone());
664-
Ok(ret)
697+
Ok(Arc::new(ret))
665698
}
666699

667700
/// For a package, return all targets which are registered as dependencies
@@ -776,7 +809,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
776809
// actually depend on anything, we've reached the end of the dependency
777810
// chain as we've got all the info we're gonna get.
778811
let key = (unit.pkg.package_id().clone(), unit.kind);
779-
if self.build_state.outputs.lock().unwrap().contains_key(&key) {
812+
if self.build_script_overridden.contains(&key) {
780813
return Ok(Vec::new())
781814
}
782815

src/cargo/ops/cargo_rustc/custom_build.rs

+18-14
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ pub fn prepare<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>)
7777
-> CargoResult<(Work, Work, Freshness)> {
7878
let _p = profile::start(format!("build script prepare: {}/{}",
7979
unit.pkg, unit.target.name()));
80-
let overridden = cx.build_state.has_override(unit);
80+
81+
let key = (unit.pkg.package_id().clone(), unit.kind);
82+
let overridden = cx.build_script_overridden.contains(&key);
8183
let (work_dirty, work_fresh) = if overridden {
8284
(Work::noop(), Work::noop())
8385
} else {
@@ -314,18 +316,6 @@ impl BuildState {
314316
fn insert(&self, id: PackageId, kind: Kind, output: BuildOutput) {
315317
self.outputs.lock().unwrap().insert((id, kind), output);
316318
}
317-
318-
fn has_override(&self, unit: &Unit) -> bool {
319-
let key = unit.pkg.manifest().links().map(|l| (l.to_string(), unit.kind));
320-
match key.and_then(|k| self.overrides.get(&k)) {
321-
Some(output) => {
322-
self.insert(unit.pkg.package_id().clone(), unit.kind,
323-
output.clone());
324-
true
325-
}
326-
None => false,
327-
}
328-
}
329319
}
330320

331321
impl BuildOutput {
@@ -483,7 +473,7 @@ pub fn build_map<'b, 'cfg>(cx: &mut Context<'b, 'cfg>,
483473
// Recursive function to build up the map we're constructing. This function
484474
// memoizes all of its return values as it goes along.
485475
fn build<'a, 'b, 'cfg>(out: &'a mut HashMap<Unit<'b>, BuildScripts>,
486-
cx: &Context<'b, 'cfg>,
476+
cx: &mut Context<'b, 'cfg>,
487477
unit: &Unit<'b>)
488478
-> CargoResult<&'a BuildScripts> {
489479
// Do a quick pre-flight check to see if we've already calculated the
@@ -492,6 +482,20 @@ pub fn build_map<'b, 'cfg>(cx: &mut Context<'b, 'cfg>,
492482
return Ok(&out[unit])
493483
}
494484

485+
{
486+
let key = unit.pkg.manifest().links().map(|l| (l.to_string(), unit.kind));
487+
let build_state = &cx.build_state;
488+
if let Some(output) = key.and_then(|k| build_state.overrides.get(&k)) {
489+
let key = (unit.pkg.package_id().clone(), unit.kind);
490+
cx.build_script_overridden.insert(key.clone());
491+
build_state
492+
.outputs
493+
.lock()
494+
.unwrap()
495+
.insert(key, output.clone());
496+
}
497+
}
498+
495499
let mut ret = BuildScripts::default();
496500

497501
if !unit.target.is_custom_build() && unit.pkg.has_custom_build() {

src/cargo/ops/cargo_rustc/fingerprint.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ fn write_fingerprint(loc: &Path, fingerprint: &Fingerprint) -> CargoResult<()> {
538538
}
539539

540540
/// Prepare for work when a package starts to build
541-
pub fn prepare_init(cx: &mut Context, unit: &Unit) -> CargoResult<()> {
541+
pub fn prepare_init<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult<()> {
542542
let new1 = cx.fingerprint_dir(unit);
543543

544544
if fs::metadata(&new1).is_err() {
@@ -548,7 +548,7 @@ pub fn prepare_init(cx: &mut Context, unit: &Unit) -> CargoResult<()> {
548548
Ok(())
549549
}
550550

551-
pub fn dep_info_loc(cx: &mut Context, unit: &Unit) -> PathBuf {
551+
pub fn dep_info_loc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> PathBuf {
552552
cx.fingerprint_dir(unit).join(&format!("dep-{}", filename(cx, unit)))
553553
}
554554

@@ -670,7 +670,7 @@ fn mtime_if_fresh<I>(output: &Path, paths: I) -> Option<FileTime>
670670
}
671671
}
672672

673-
fn filename(cx: &mut Context, unit: &Unit) -> String {
673+
fn filename<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> String {
674674
// file_stem includes metadata hash. Thus we have a different
675675
// fingerprint for every metadata hash version. This works because
676676
// even if the package is fresh, we'll still link the fresh target

src/cargo/ops/cargo_rustc/mod.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -684,10 +684,10 @@ fn root_path(cx: &Context, unit: &Unit) -> PathBuf {
684684
}
685685
}
686686

687-
fn build_base_args(cx: &mut Context,
688-
cmd: &mut ProcessBuilder,
689-
unit: &Unit,
690-
crate_types: &[&str]) {
687+
fn build_base_args<'a, 'cfg>(cx: &mut Context<'a, 'cfg>,
688+
cmd: &mut ProcessBuilder,
689+
unit: &Unit<'a>,
690+
crate_types: &[&str]) {
691691
let Profile {
692692
ref opt_level, lto, codegen_units, ref rustc_args, debuginfo,
693693
debug_assertions, overflow_checks, rpath, test, doc: _doc,

tests/workspaces.rs

+93
Original file line numberDiff line numberDiff line change
@@ -1465,3 +1465,96 @@ Caused by:
14651465
"));
14661466
}
14671467

1468+
/// This is a freshness test for feature use with workspaces
1469+
///
1470+
/// feat_lib is used by caller1 and caller2, but with different features enabled.
1471+
/// This test ensures that alternating building caller1, caller2 doesn't force
1472+
/// recompile of feat_lib.
1473+
///
1474+
/// Ideally once we solve https://github.com/rust-lang/cargo/issues/3620, then
1475+
/// a single cargo build at the top level will be enough.
1476+
#[test]
1477+
fn dep_used_with_separate_features() {
1478+
let p = project("foo")
1479+
.file("Cargo.toml", r#"
1480+
[workspace]
1481+
members = ["feat_lib", "caller1", "caller2"]
1482+
"#)
1483+
.file("feat_lib/Cargo.toml", r#"
1484+
[project]
1485+
name = "feat_lib"
1486+
version = "0.1.0"
1487+
authors = []
1488+
1489+
[features]
1490+
myfeature = []
1491+
"#)
1492+
.file("feat_lib/src/lib.rs", "")
1493+
.file("caller1/Cargo.toml", r#"
1494+
[project]
1495+
name = "caller1"
1496+
version = "0.1.0"
1497+
authors = []
1498+
1499+
[dependencies]
1500+
feat_lib = { path = "../feat_lib" }
1501+
"#)
1502+
.file("caller1/src/main.rs", "fn main() {}")
1503+
.file("caller1/src/lib.rs", "")
1504+
.file("caller2/Cargo.toml", r#"
1505+
[project]
1506+
name = "caller2"
1507+
version = "0.1.0"
1508+
authors = []
1509+
1510+
[dependencies]
1511+
feat_lib = { path = "../feat_lib", features = ["myfeature"] }
1512+
caller1 = { path = "../caller1" }
1513+
"#)
1514+
.file("caller2/src/main.rs", "fn main() {}")
1515+
.file("caller2/src/lib.rs", "");
1516+
p.build();
1517+
1518+
// Build the entire workspace
1519+
assert_that(p.cargo("build").arg("--all"),
1520+
execs().with_status(0)
1521+
.with_stderr("\
1522+
[..]Compiling feat_lib v0.1.0 ([..])
1523+
[..]Compiling caller1 v0.1.0 ([..])
1524+
[..]Compiling caller2 v0.1.0 ([..])
1525+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
1526+
"));
1527+
assert_that(&p.bin("caller1"), existing_file());
1528+
assert_that(&p.bin("caller2"), existing_file());
1529+
1530+
1531+
// Build caller1. should build the dep library. Because the features
1532+
// are different than the full workspace, it rebuilds.
1533+
// Ideally once we solve https://github.com/rust-lang/cargo/issues/3620, then
1534+
// a single cargo build at the top level will be enough.
1535+
assert_that(p.cargo("build").cwd(p.root().join("caller1")),
1536+
execs().with_status(0)
1537+
.with_stderr("\
1538+
[..]Compiling feat_lib v0.1.0 ([..])
1539+
[..]Compiling caller1 v0.1.0 ([..])
1540+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
1541+
"));
1542+
1543+
// Alternate building caller2/caller1 a few times, just to make sure
1544+
// features are being built separately. Should not rebuild anything
1545+
assert_that(p.cargo("build").cwd(p.root().join("caller2")),
1546+
execs().with_status(0)
1547+
.with_stderr("\
1548+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
1549+
"));
1550+
assert_that(p.cargo("build").cwd(p.root().join("caller1")),
1551+
execs().with_status(0)
1552+
.with_stderr("\
1553+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
1554+
"));
1555+
assert_that(p.cargo("build").cwd(p.root().join("caller2")),
1556+
execs().with_status(0)
1557+
.with_stderr("\
1558+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
1559+
"));
1560+
}

0 commit comments

Comments
 (0)