Skip to content

Commit 1877f59

Browse files
authored
Auto merge of #3102 - nipunn1313:attempt, r=alexcrichton
Mix feature flags into fingerprint/metadata shorthash Since building dependencies results in different libraries depending on the feature flags, I added the feature flags into the short_hash. This solves an issue when multiple crates share a target directory or multiple targets share a common library with divergent feature flag choice. I'm not sure if this architecturally the best way to solve this problem, but I did confirm that this fixes the issue I was seeing. I can also add a test for this case if this code is taking the right approach (or if it would help illustrate the issue).
2 parents a9c23dd + 8e22eca commit 1877f59

19 files changed

+592
-152
lines changed

src/cargo/core/manifest.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,13 @@ impl Target {
395395
}
396396
}
397397

398+
pub fn is_dylib(&self) -> bool {
399+
match self.kind {
400+
TargetKind::Lib(ref libs) => libs.iter().any(|l| *l == LibKind::Dylib),
401+
_ => false
402+
}
403+
}
404+
398405
pub fn linkable(&self) -> bool {
399406
match self.kind {
400407
TargetKind::Lib(ref kinds) => {

src/cargo/ops/cargo_clean.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> {
7777
rm_rf(&layout.proxy().fingerprint(&unit.pkg))?;
7878
rm_rf(&layout.build(&unit.pkg))?;
7979

80-
let root = cx.out_dir(&unit);
81-
for (filename, _) in cx.target_filenames(&unit)? {
82-
rm_rf(&root.join(&filename))?;
80+
for (src, link_dst, _) in cx.target_filenames(&unit)? {
81+
rm_rf(&src)?;
82+
if let Some(dst) = link_dst {
83+
rm_rf(&dst)?;
84+
}
8385
}
8486
}
8587

src/cargo/ops/cargo_rustc/context.rs

Lines changed: 116 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -308,71 +308,144 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
308308
}
309309

310310
/// Get the metadata for a target in a specific profile
311+
/// We build to the path: "{filename}-{target_metadata}"
312+
/// We use a linking step to link/copy to a predictable filename
313+
/// like `target/debug/libfoo.{a,so,rlib}` and such.
311314
pub fn target_metadata(&self, unit: &Unit) -> Option<Metadata> {
312-
let metadata = unit.target.metadata();
315+
// No metadata for dylibs because of a couple issues
316+
// - OSX encodes the dylib name in the executable
317+
// - Windows rustc multiple files of which we can't easily link all of them
318+
//
319+
// Two expeptions
320+
// 1) Upstream dependencies (we aren't exporting + need to resolve name conflict)
321+
// 2) __CARGO_DEFAULT_LIB_METADATA env var
322+
//
323+
// Note, though, that the compiler's build system at least wants
324+
// path dependencies (eg libstd) to have hashes in filenames. To account for
325+
// that we have an extra hack here which reads the
326+
// `__CARGO_DEFAULT_METADATA` environment variable and creates a
327+
// hash in the filename if that's present.
328+
//
329+
// This environment variable should not be relied on! It's
330+
// just here for rustbuild. We need a more principled method
331+
// doing this eventually.
332+
if !unit.profile.test &&
333+
unit.target.is_dylib() &&
334+
unit.pkg.package_id().source_id().is_path() &&
335+
!env::var("__CARGO_DEFAULT_LIB_METADATA").is_ok() {
336+
return None;
337+
}
338+
339+
let metadata = unit.target.metadata().cloned().map(|mut m| {
340+
if let Some(features) = self.resolve.features(unit.pkg.package_id()) {
341+
let mut feat_vec: Vec<&String> = features.iter().collect();
342+
feat_vec.sort();
343+
for feat in feat_vec {
344+
m.mix(feat);
345+
}
346+
}
347+
m.mix(unit.profile);
348+
m
349+
});
350+
let mut pkg_metadata = {
351+
let mut m = unit.pkg.generate_metadata();
352+
if let Some(features) = self.resolve.features(unit.pkg.package_id()) {
353+
let mut feat_vec: Vec<&String> = features.iter().collect();
354+
feat_vec.sort();
355+
for feat in feat_vec {
356+
m.mix(feat);
357+
}
358+
}
359+
m.mix(unit.profile);
360+
m
361+
};
362+
313363
if unit.target.is_lib() && unit.profile.test {
314364
// Libs and their tests are built in parallel, so we need to make
315365
// sure that their metadata is different.
316-
metadata.cloned().map(|mut m| {
366+
metadata.map(|mut m| {
317367
m.mix(&"test");
318368
m
319369
})
320370
} else if unit.target.is_bin() && unit.profile.test {
321371
// Make sure that the name of this test executable doesn't
322372
// conflict with a library that has the same name and is
323373
// being tested
324-
let mut metadata = unit.pkg.generate_metadata();
325-
metadata.mix(&format!("bin-{}", unit.target.name()));
326-
Some(metadata)
374+
pkg_metadata.mix(&format!("bin-{}", unit.target.name()));
375+
Some(pkg_metadata)
327376
} else if unit.pkg.package_id().source_id().is_path() &&
328377
!unit.profile.test {
329-
// If we're not building a unit test but we're building a path
330-
// dependency, then we're likely compiling the "current package" or
331-
// some package in a workspace. In this situation we pass no
332-
// metadata by default so we'll have predictable
333-
// file names like `target/debug/libfoo.{a,so,rlib}` and such.
334-
//
335-
// Note, though, that the compiler's build system at least wants
336-
// path dependencies to have hashes in filenames. To account for
337-
// that we have an extra hack here which reads the
338-
// `__CARGO_DEFAULT_METADATA` environment variable and creates a
339-
// hash in the filename if that's present.
340-
//
341-
// This environment variable should not be relied on! It's basically
342-
// just here for rustbuild. We need a more principled method of
343-
// doing this eventually.
344-
if unit.target.is_lib() {
345-
env::var("__CARGO_DEFAULT_LIB_METADATA").ok().map(|meta| {
346-
let mut metadata = unit.pkg.generate_metadata();
347-
metadata.mix(&meta);
348-
metadata
349-
})
350-
} else {
351-
None
352-
}
378+
Some(pkg_metadata)
353379
} else {
354-
metadata.cloned()
380+
metadata
355381
}
356382
}
357383

358-
/// Returns the file stem for a given target/profile combo
384+
/// Returns the file stem for a given target/profile combo (with metadata)
359385
pub fn file_stem(&self, unit: &Unit) -> String {
360386
match self.target_metadata(unit) {
361387
Some(ref metadata) => format!("{}{}", unit.target.crate_name(),
362388
metadata.extra_filename),
363-
None if unit.target.allows_underscores() => {
364-
unit.target.name().to_string()
389+
None => self.bin_stem(unit),
390+
}
391+
}
392+
393+
/// Returns the bin stem for a given target (without metadata)
394+
fn bin_stem(&self, unit: &Unit) -> String {
395+
if unit.target.allows_underscores() {
396+
unit.target.name().to_string()
397+
} else {
398+
unit.target.crate_name()
399+
}
400+
}
401+
402+
/// Returns a tuple with the directory and name of the hard link we expect
403+
/// our target to be copied to. Eg, file_stem may be out_dir/deps/foo-abcdef
404+
/// and link_stem would be out_dir/foo
405+
/// This function returns it in two parts so the caller can add prefix/suffis
406+
/// to filename separately
407+
408+
/// Returns an Option because in some cases we don't want to link
409+
/// (eg a dependent lib)
410+
pub fn link_stem(&self, unit: &Unit) -> Option<(PathBuf, String)> {
411+
let src_dir = self.out_dir(unit);
412+
let bin_stem = self.bin_stem(unit);
413+
let file_stem = self.file_stem(unit);
414+
415+
// We currently only lift files up from the `deps` directory. If
416+
// it was compiled into something like `example/` or `doc/` then
417+
// we don't want to link it up.
418+
if src_dir.ends_with("deps") {
419+
// Don't lift up library dependencies
420+
if unit.pkg.package_id() != &self.current_package && !unit.target.is_bin() {
421+
None
422+
} else {
423+
Some((
424+
src_dir.parent().unwrap().to_owned(),
425+
if unit.profile.test {file_stem} else {bin_stem},
426+
))
365427
}
366-
None => unit.target.crate_name(),
428+
} else if bin_stem == file_stem {
429+
None
430+
} else if src_dir.ends_with("examples") {
431+
Some((src_dir, bin_stem))
432+
} else if src_dir.parent().unwrap().ends_with("build") {
433+
Some((src_dir, bin_stem))
434+
} else {
435+
None
367436
}
368437
}
369438

370439
/// Return the filenames that the given target for the given profile will
371-
/// generate, along with whether you can link against that file (e.g. it's a
372-
/// library).
440+
/// generate as a list of 3-tuples (filename, link_dst, linkable)
441+
/// filename: filename rustc compiles to. (Often has metadata suffix).
442+
/// link_dst: Optional file to link/copy the result to (without metadata suffix)
443+
/// linkable: Whether possible to link against file (eg it's a library)
373444
pub fn target_filenames(&self, unit: &Unit)
374-
-> CargoResult<Vec<(String, bool)>> {
445+
-> CargoResult<Vec<(PathBuf, Option<PathBuf>, bool)>> {
446+
let out_dir = self.out_dir(unit);
375447
let stem = self.file_stem(unit);
448+
let link_stem = self.link_stem(unit);
376449
let info = if unit.target.for_host() {
377450
&self.host_info
378451
} else {
@@ -386,8 +459,11 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
386459
let crate_type = if crate_type == "lib" {"rlib"} else {crate_type};
387460
match info.crate_types.get(crate_type) {
388461
Some(&Some((ref prefix, ref suffix))) => {
389-
ret.push((format!("{}{}{}", prefix, stem, suffix),
390-
linkable));
462+
let filename = out_dir.join(format!("{}{}{}", prefix, stem, suffix));
463+
let link_dst = link_stem.clone().map(|(ld, ls)| {
464+
ld.join(format!("{}{}{}", prefix, ls, suffix))
465+
});
466+
ret.push((filename, link_dst, linkable));
391467
Ok(())
392468
}
393469
// not supported, don't worry about it
@@ -429,6 +505,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
429505
support any of the output crate types",
430506
unit.pkg, self.target_triple());
431507
}
508+
info!("Target filenames: {:?}", ret);
432509
Ok(ret)
433510
}
434511

src/cargo/ops/cargo_rustc/fingerprint.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub fn prepare_target<'a, 'cfg>(cx: &mut Context<'a, 'cfg>,
4949
let _p = profile::start(format!("fingerprint: {} / {}",
5050
unit.pkg.package_id(), unit.target.name()));
5151
let new = dir(cx, unit);
52-
let loc = new.join(&filename(unit));
52+
let loc = new.join(&filename(cx, unit));
5353

5454
debug!("fingerprint at: {}", loc.display());
5555

@@ -82,8 +82,11 @@ pub fn prepare_target<'a, 'cfg>(cx: &mut Context<'a, 'cfg>,
8282
missing_outputs = !root.join(unit.target.crate_name())
8383
.join("index.html").exists();
8484
} else {
85-
for (filename, _) in cx.target_filenames(unit)? {
86-
missing_outputs |= fs::metadata(root.join(filename)).is_err();
85+
for (src, link_dst, _) in cx.target_filenames(unit)? {
86+
missing_outputs |= !src.exists();
87+
if let Some(link_dst) = link_dst {
88+
missing_outputs |= !link_dst.exists();
89+
}
8790
}
8891
}
8992

@@ -529,7 +532,7 @@ pub fn dir(cx: &Context, unit: &Unit) -> PathBuf {
529532

530533
/// Returns the (old, new) location for the dep info file of a target.
531534
pub fn dep_info_loc(cx: &Context, unit: &Unit) -> PathBuf {
532-
dir(cx, unit).join(&format!("dep-{}", filename(unit)))
535+
dir(cx, unit).join(&format!("dep-{}", filename(cx, unit)))
533536
}
534537

535538
fn compare_old_fingerprint(loc: &Path, new_fingerprint: &Fingerprint)
@@ -650,7 +653,11 @@ fn mtime_if_fresh<I>(output: &Path, paths: I) -> Option<FileTime>
650653
}
651654
}
652655

653-
fn filename(unit: &Unit) -> String {
656+
fn filename(cx: &Context, unit: &Unit) -> String {
657+
// file_stem includes metadata hash. Thus we have a different
658+
// fingerprint for every metadata hash version. This works because
659+
// even if the package is fresh, we'll still link the fresh target
660+
let file_stem = cx.file_stem(unit);
654661
let kind = match *unit.target.kind() {
655662
TargetKind::Lib(..) => "lib",
656663
TargetKind::Bin => "bin",
@@ -666,7 +673,7 @@ fn filename(unit: &Unit) -> String {
666673
} else {
667674
""
668675
};
669-
format!("{}{}-{}", flavor, kind, unit.target.name())
676+
format!("{}{}-{}", flavor, kind, file_stem)
670677
}
671678

672679
// The dep-info files emitted by the compiler all have their listed paths

src/cargo/ops/cargo_rustc/layout.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,8 @@ impl<'a> LayoutProxy<'a> {
171171
self.build(unit.pkg)
172172
} else if unit.target.is_example() {
173173
self.examples().to_path_buf()
174-
} else if unit.target.is_lib() {
175-
self.deps().to_path_buf()
176174
} else {
177-
self.root().to_path_buf()
175+
self.deps().to_path_buf()
178176
}
179177
}
180178

0 commit comments

Comments
 (0)