Skip to content

Commit 6b28a0c

Browse files
committed
Fix fingerprint handling in pipelining mode
This commit fixes an issue when pipelining mode is used in handling recompilations. Previously a sequence of compilations could look like: * Crate A starts to build * Crate A produces metadata * Crate B, which depends on A, starts * Crate B finishes * Crate A finishes In this case the mtime for B is before that of A, which fooled Cargo into thinking that B needed to be recompiled. In this case, however, B doesn't actually need to be recompiled because it only depends on the metadata of A, not the final artifacts. This unfortunately resulted in some duplication in a few places, but not really much moreso than already exists between fingerprinting and compilation.
1 parent c2152f0 commit 6b28a0c

File tree

10 files changed

+134
-140
lines changed

10 files changed

+134
-140
lines changed

src/cargo/core/compiler/build_context/target_info.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub enum FileFlavor {
2525
/// Not a special file type.
2626
Normal,
2727
/// Something you can link against (e.g., a library).
28-
Linkable { rmeta: PathBuf },
28+
Linkable { rmeta: bool },
2929
/// Piece of external debug information (e.g., `.dSYM`/`.pdb` file).
3030
DebugInfo,
3131
}

src/cargo/core/compiler/context/compilation_files.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,10 +305,10 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
305305
// for both libraries and binaries.
306306
let path = out_dir.join(format!("lib{}.rmeta", file_stem));
307307
ret.push(OutputFile {
308-
path: path.clone(),
308+
path,
309309
hardlink: None,
310310
export_path: None,
311-
flavor: FileFlavor::Linkable { rmeta: path },
311+
flavor: FileFlavor::Linkable { rmeta: false },
312312
});
313313
} else {
314314
let mut add = |crate_type: &str, flavor: FileFlavor| -> CargoResult<()> {
@@ -372,13 +372,21 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
372372
add(
373373
kind.crate_type(),
374374
if kind.linkable() {
375-
let rmeta = out_dir.join(format!("lib{}.rmeta", file_stem));
376-
FileFlavor::Linkable { rmeta }
375+
FileFlavor::Linkable { rmeta: false }
377376
} else {
378377
FileFlavor::Normal
379378
},
380379
)?;
381380
}
381+
let path = out_dir.join(format!("lib{}.rmeta", file_stem));
382+
if !unit.target.requires_upstream_objects() {
383+
ret.push(OutputFile {
384+
path,
385+
hardlink: None,
386+
export_path: None,
387+
flavor: FileFlavor::Linkable { rmeta: true },
388+
});
389+
}
382390
}
383391
}
384392
}

src/cargo/core/compiler/fingerprint.rs

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@
187187
//! See the `A-rebuild-detection` flag on the issue tracker for more:
188188
//! <https://github.com/rust-lang/cargo/issues?q=is%3Aissue+is%3Aopen+label%3AA-rebuild-detection>
189189
190+
use std::collections::HashMap;
190191
use std::env;
191192
use std::fs;
192193
use std::hash::{self, Hasher};
@@ -322,6 +323,7 @@ struct DepFingerprint {
322323
pkg_id: u64,
323324
name: String,
324325
public: bool,
326+
only_requires_rmeta: bool,
325327
fingerprint: Arc<Fingerprint>,
326328
}
327329

@@ -395,17 +397,15 @@ enum FsStatus {
395397
/// unit needs to subsequently be recompiled.
396398
Stale,
397399

398-
/// This unit is up-to-date, it does not need to be recompiled. If there are
399-
/// any outputs then the `FileTime` listed here is the minimum of all their
400-
/// mtimes. This is then later used to see if a unit is newer than one of
401-
/// its dependants, causing the dependant to be recompiled.
402-
UpToDate(Option<FileTime>),
400+
/// This unit is up-to-date. All outputs and their corresponding mtime are
401+
/// listed in the payload here for other dependencies to compare against.
402+
UpToDate { mtimes: HashMap<PathBuf, FileTime> },
403403
}
404404

405405
impl FsStatus {
406406
fn up_to_date(&self) -> bool {
407407
match self {
408-
FsStatus::UpToDate(_) => true,
408+
FsStatus::UpToDate { .. } => true,
409409
FsStatus::Stale => false,
410410
}
411411
}
@@ -442,6 +442,7 @@ impl<'de> Deserialize<'de> for DepFingerprint {
442442
pkg_id,
443443
name,
444444
public,
445+
only_requires_rmeta: false,
445446
fingerprint: Arc::new(Fingerprint {
446447
memoized_hash: Mutex::new(Some(hash)),
447448
..Fingerprint::new()
@@ -753,51 +754,71 @@ impl Fingerprint {
753754
) -> CargoResult<()> {
754755
assert!(!self.fs_status.up_to_date());
755756

757+
let mut mtimes = HashMap::new();
758+
756759
// Get the `mtime` of all outputs. Optionally update their mtime
757760
// afterwards based on the `mtime_on_use` flag. Afterwards we want the
758761
// minimum mtime as it's the one we'll be comparing to inputs and
759762
// dependencies.
760-
let status = self
761-
.outputs
762-
.iter()
763-
.map(|f| {
764-
let mtime = paths::mtime(f).ok();
765-
if mtime_on_use {
766-
let t = FileTime::from_system_time(SystemTime::now());
767-
drop(filetime::set_file_times(f, t, t));
763+
for output in self.outputs.iter() {
764+
let mtime = match paths::mtime(output) {
765+
Ok(mtime) => mtime,
766+
767+
// This path failed to report its `mtime`. It probably doesn't
768+
// exists, so leave ourselves as stale and bail out.
769+
Err(e) => {
770+
log::debug!("failed to get mtime of {:?}: {}", output, e);
771+
return Ok(());
768772
}
769-
mtime
770-
})
771-
.min();
773+
};
774+
if mtime_on_use {
775+
let t = FileTime::from_system_time(SystemTime::now());
776+
filetime::set_file_times(output, t, t)?;
777+
}
778+
assert!(mtimes.insert(output.clone(), mtime).is_none());
779+
}
780+
781+
let max_mtime = match mtimes.values().max() {
782+
Some(mtime) => mtime,
772783

773-
let mtime = match status {
774784
// We had no output files. This means we're an overridden build
775785
// script and we're just always up to date because we aren't
776786
// watching the filesystem.
777787
None => {
778-
self.fs_status = FsStatus::UpToDate(None);
788+
self.fs_status = FsStatus::UpToDate { mtimes };
779789
return Ok(());
780790
}
781-
782-
// At least one path failed to report its `mtime`. It probably
783-
// doesn't exists, so leave ourselves as stale and bail out.
784-
Some(None) => return Ok(()),
785-
786-
// All files successfully reported an `mtime`, and we've got the
787-
// minimum one, so let's keep going with that.
788-
Some(Some(mtime)) => mtime,
789791
};
790792

791793
for dep in self.deps.iter() {
792-
let dep_mtime = match dep.fingerprint.fs_status {
794+
let dep_mtimes = match &dep.fingerprint.fs_status {
795+
FsStatus::UpToDate { mtimes } => mtimes,
793796
// If our dependency is stale, so are we, so bail out.
794797
FsStatus::Stale => return Ok(()),
798+
};
795799

796-
// If our dependencies is up to date and has no filesystem
797-
// interactions, then we can move on to the next dependency.
798-
FsStatus::UpToDate(None) => continue,
799-
800-
FsStatus::UpToDate(Some(mtime)) => mtime,
800+
// If our dependency edge only requires the rmeta fiel to be present
801+
// then we only need to look at that one output file, otherwise we
802+
// need to consider all output files to see if we're out of date.
803+
let dep_mtime = if dep.only_requires_rmeta {
804+
dep_mtimes
805+
.iter()
806+
.filter_map(|(path, mtime)| {
807+
if path.extension().and_then(|s| s.to_str()) == Some("rmeta") {
808+
Some(mtime)
809+
} else {
810+
None
811+
}
812+
})
813+
.next()
814+
.expect("failed to find rmeta")
815+
} else {
816+
match dep_mtimes.values().max() {
817+
Some(mtime) => mtime,
818+
// If our dependencies is up to date and has no filesystem
819+
// interactions, then we can move on to the next dependency.
820+
None => continue,
821+
}
801822
};
802823

803824
// If the dependency is newer than our own output then it was
@@ -807,7 +828,8 @@ impl Fingerprint {
807828
// Note that this comparison should probably be `>=`, not `>`, but
808829
// for a discussion of why it's `>` see the discussion about #5918
809830
// below in `find_stale`.
810-
if dep_mtime > mtime {
831+
if dep_mtime > max_mtime {
832+
log::info!("dependency on `{}` is newer than we are", dep.name);
811833
return Ok(());
812834
}
813835
}
@@ -824,7 +846,7 @@ impl Fingerprint {
824846
}
825847

826848
// Everything was up to date! Record such.
827-
self.fs_status = FsStatus::UpToDate(Some(mtime));
849+
self.fs_status = FsStatus::UpToDate { mtimes };
828850

829851
Ok(())
830852
}
@@ -856,6 +878,7 @@ impl hash::Hash for Fingerprint {
856878
name,
857879
public,
858880
fingerprint,
881+
only_requires_rmeta: _,
859882
} in deps
860883
{
861884
pkg_id.hash(h);
@@ -929,6 +952,7 @@ impl DepFingerprint {
929952
name,
930953
public,
931954
fingerprint,
955+
only_requires_rmeta: cx.only_requires_rmeta(parent, dep),
932956
})
933957
}
934958
}

src/cargo/core/compiler/mod.rs

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ use crate::core::profiles::{Lto, PanicStrategy, Profile};
3939
use crate::core::Feature;
4040
use crate::core::{PackageId, Target};
4141
use crate::util::errors::{CargoResult, CargoResultExt, Internal, ProcessError};
42+
use crate::util::machine_message::Message;
4243
use crate::util::paths;
4344
use crate::util::{self, machine_message, process, ProcessBuilder};
4445
use crate::util::{internal, join_paths, profile};
45-
use crate::util::machine_message::Message;
4646

4747
/// Indicates whether an object is for the host architcture or the target architecture.
4848
///
@@ -498,7 +498,8 @@ fn link_targets<'a, 'cfg>(
498498
filenames: destinations,
499499
executable,
500500
fresh,
501-
}.to_json_string();
501+
}
502+
.to_json_string();
502503
state.stdout(&msg);
503504
}
504505
Ok(())
@@ -1016,20 +1017,14 @@ fn build_deps_args<'a, 'cfg>(
10161017
need_unstable_opts: &mut bool,
10171018
) -> CargoResult<()> {
10181019
let bcx = cx.bcx;
1019-
for output in cx.outputs(dep)?.iter() {
1020-
let rmeta = match &output.flavor {
1021-
FileFlavor::Linkable { rmeta } => rmeta,
1022-
_ => continue,
1023-
};
1024-
let mut v = OsString::new();
1025-
let name = bcx.extern_crate_name(current, dep)?;
1026-
v.push(name);
1027-
v.push("=");
1028-
if cx.only_requires_rmeta(current, dep) {
1029-
v.push(&rmeta);
1030-
} else {
1031-
v.push(&output.path);
1032-
}
1020+
1021+
let mut value = OsString::new();
1022+
value.push(bcx.extern_crate_name(current, dep)?);
1023+
value.push("=");
1024+
1025+
let mut pass = |file| {
1026+
let mut value = value.clone();
1027+
value.push(file);
10331028

10341029
if current
10351030
.pkg
@@ -1045,7 +1040,26 @@ fn build_deps_args<'a, 'cfg>(
10451040
cmd.arg("--extern");
10461041
}
10471042

1048-
cmd.arg(&v);
1043+
cmd.arg(&value);
1044+
};
1045+
1046+
let outputs = cx.outputs(dep)?;
1047+
let mut outputs = outputs.iter().filter_map(|output| match output.flavor {
1048+
FileFlavor::Linkable { rmeta } => Some((output, rmeta)),
1049+
_ => None,
1050+
});
1051+
1052+
if cx.only_requires_rmeta(current, dep) {
1053+
let (output, _rmeta) = outputs
1054+
.find(|(_output, rmeta)| *rmeta)
1055+
.expect("failed to find rlib dep for pipelined dep");
1056+
pass(&output.path);
1057+
} else {
1058+
for (output, rmeta) in outputs {
1059+
if !rmeta {
1060+
pass(&output.path);
1061+
}
1062+
}
10491063
}
10501064
Ok(())
10511065
}
@@ -1146,7 +1160,7 @@ fn on_stderr_line(
11461160
log::debug!("looks like metadata finished early!");
11471161
state.rmeta_produced();
11481162
}
1149-
return Ok(())
1163+
return Ok(());
11501164
}
11511165
}
11521166

@@ -1157,7 +1171,8 @@ fn on_stderr_line(
11571171
package_id,
11581172
target,
11591173
message: compiler_message,
1160-
}.to_json_string();
1174+
}
1175+
.to_json_string();
11611176

11621177
// Switch json lines from rustc/rustdoc that appear on stderr to stdout
11631178
// instead. We want the stdout of Cargo to always be machine parseable as

src/cargo/ops/cargo_clean.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::HashMap;
22
use std::fs;
33
use std::path::Path;
44

5-
use crate::core::compiler::{UnitInterner, FileFlavor};
5+
use crate::core::compiler::UnitInterner;
66
use crate::core::compiler::{BuildConfig, BuildContext, CompileMode, Context, Kind};
77
use crate::core::profiles::UnitFor;
88
use crate::core::Workspace;
@@ -119,9 +119,6 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
119119
if let Some(ref dst) = output.hardlink {
120120
rm_rf(dst, config)?;
121121
}
122-
if let FileFlavor::Linkable { rmeta } = &output.flavor {
123-
rm_rf(rmeta, config)?;
124-
}
125122
}
126123
}
127124

0 commit comments

Comments
 (0)