Skip to content

Commit 18e7686

Browse files
committed
rustbuild: Fail the build if we build Cargo twice
This commit updates the `ToolBuild` step to stream Cargo's JSON messages, parse them, and record all libraries built. If we build anything twice (aka Cargo) it'll most likely happen due to dependencies being recompiled which is caught by this check.
1 parent 8aa27ee commit 18e7686

File tree

7 files changed

+158
-50
lines changed

7 files changed

+158
-50
lines changed

src/Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bootstrap/compile.rs

+71-47
Original file line numberDiff line numberDiff line change
@@ -996,24 +996,6 @@ fn stderr_isatty() -> bool {
996996
pub fn run_cargo(build: &Build, cargo: &mut Command, stamp: &Path, is_check: bool)
997997
-> Vec<PathBuf>
998998
{
999-
// Instruct Cargo to give us json messages on stdout, critically leaving
1000-
// stderr as piped so we can get those pretty colors.
1001-
cargo.arg("--message-format").arg("json")
1002-
.stdout(Stdio::piped());
1003-
1004-
if stderr_isatty() && build.ci_env == CiEnv::None {
1005-
// since we pass message-format=json to cargo, we need to tell the rustc
1006-
// wrapper to give us colored output if necessary. This is because we
1007-
// only want Cargo's JSON output, not rustcs.
1008-
cargo.env("RUSTC_COLOR", "1");
1009-
}
1010-
1011-
build.verbose(&format!("running: {:?}", cargo));
1012-
let mut child = match cargo.spawn() {
1013-
Ok(child) => child,
1014-
Err(e) => panic!("failed to execute command: {:?}\nerror: {}", cargo, e),
1015-
};
1016-
1017999
// `target_root_dir` looks like $dir/$target/release
10181000
let target_root_dir = stamp.parent().unwrap();
10191001
// `target_deps_dir` looks like $dir/$target/release/deps
@@ -1028,46 +1010,33 @@ pub fn run_cargo(build: &Build, cargo: &mut Command, stamp: &Path, is_check: boo
10281010
// files we need to probe for later.
10291011
let mut deps = Vec::new();
10301012
let mut toplevel = Vec::new();
1031-
let stdout = BufReader::new(child.stdout.take().unwrap());
1032-
for line in stdout.lines() {
1033-
let line = t!(line);
1034-
let json: serde_json::Value = if line.starts_with("{") {
1035-
t!(serde_json::from_str(&line))
1036-
} else {
1037-
// If this was informational, just print it out and continue
1038-
println!("{}", line);
1039-
continue
1013+
let ok = stream_cargo(build, cargo, &mut |msg| {
1014+
let filenames = match msg {
1015+
CargoMessage::CompilerArtifact { filenames, .. } => filenames,
1016+
_ => return,
10401017
};
1041-
if json["reason"].as_str() != Some("compiler-artifact") {
1042-
if build.config.rustc_error_format.as_ref().map_or(false, |e| e == "json") {
1043-
// most likely not a cargo message, so let's send it out as well
1044-
println!("{}", line);
1045-
}
1046-
continue
1047-
}
1048-
for filename in json["filenames"].as_array().unwrap() {
1049-
let filename = filename.as_str().unwrap();
1018+
for filename in filenames {
10501019
// Skip files like executables
10511020
if !filename.ends_with(".rlib") &&
10521021
!filename.ends_with(".lib") &&
10531022
!is_dylib(&filename) &&
10541023
!(is_check && filename.ends_with(".rmeta")) {
1055-
continue
1024+
return;
10561025
}
10571026

10581027
let filename = Path::new(filename);
10591028

10601029
// If this was an output file in the "host dir" we don't actually
10611030
// worry about it, it's not relevant for us.
10621031
if filename.starts_with(&host_root_dir) {
1063-
continue;
1032+
return;
10641033
}
10651034

10661035
// If this was output in the `deps` dir then this is a precise file
10671036
// name (hash included) so we start tracking it.
10681037
if filename.starts_with(&target_deps_dir) {
10691038
deps.push(filename.to_path_buf());
1070-
continue;
1039+
return;
10711040
}
10721041

10731042
// Otherwise this was a "top level artifact" which right now doesn't
@@ -1088,15 +1057,10 @@ pub fn run_cargo(build: &Build, cargo: &mut Command, stamp: &Path, is_check: boo
10881057

10891058
toplevel.push((file_stem, extension, expected_len));
10901059
}
1091-
}
1060+
});
10921061

1093-
// Make sure Cargo actually succeeded after we read all of its stdout.
1094-
let status = t!(child.wait());
1095-
if !status.success() {
1096-
panic!("command did not execute successfully: {:?}\n\
1097-
expected success, got: {}",
1098-
cargo,
1099-
status);
1062+
if !ok {
1063+
panic!("cargo must succeed");
11001064
}
11011065

11021066
// Ok now we need to actually find all the files listed in `toplevel`. We've
@@ -1167,3 +1131,63 @@ pub fn run_cargo(build: &Build, cargo: &mut Command, stamp: &Path, is_check: boo
11671131
t!(t!(File::create(stamp)).write_all(&new_contents));
11681132
deps
11691133
}
1134+
1135+
pub fn stream_cargo(
1136+
build: &Build,
1137+
cargo: &mut Command,
1138+
cb: &mut FnMut(CargoMessage),
1139+
) -> bool {
1140+
// Instruct Cargo to give us json messages on stdout, critically leaving
1141+
// stderr as piped so we can get those pretty colors.
1142+
cargo.arg("--message-format").arg("json")
1143+
.stdout(Stdio::piped());
1144+
1145+
if stderr_isatty() && build.ci_env == CiEnv::None {
1146+
// since we pass message-format=json to cargo, we need to tell the rustc
1147+
// wrapper to give us colored output if necessary. This is because we
1148+
// only want Cargo's JSON output, not rustcs.
1149+
cargo.env("RUSTC_COLOR", "1");
1150+
}
1151+
1152+
build.verbose(&format!("running: {:?}", cargo));
1153+
let mut child = match cargo.spawn() {
1154+
Ok(child) => child,
1155+
Err(e) => panic!("failed to execute command: {:?}\nerror: {}", cargo, e),
1156+
};
1157+
1158+
// Spawn Cargo slurping up its JSON output. We'll start building up the
1159+
// `deps` array of all files it generated along with a `toplevel` array of
1160+
// files we need to probe for later.
1161+
let stdout = BufReader::new(child.stdout.take().unwrap());
1162+
for line in stdout.lines() {
1163+
let line = t!(line);
1164+
match serde_json::from_str::<CargoMessage>(&line) {
1165+
Ok(msg) => cb(msg),
1166+
// If this was informational, just print it out and continue
1167+
Err(_) => println!("{}", line)
1168+
}
1169+
}
1170+
1171+
// Make sure Cargo actually succeeded after we read all of its stdout.
1172+
let status = t!(child.wait());
1173+
if !status.success() {
1174+
println!("command did not execute successfully: {:?}\n\
1175+
expected success, got: {}",
1176+
cargo,
1177+
status);
1178+
}
1179+
status.success()
1180+
}
1181+
1182+
#[derive(Deserialize)]
1183+
#[serde(tag = "reason", rename_all = "kebab-case")]
1184+
pub enum CargoMessage<'a> {
1185+
CompilerArtifact {
1186+
package_id: &'a str,
1187+
features: Vec<&'a str>,
1188+
filenames: Vec<&'a str>,
1189+
},
1190+
BuildScriptExecuted {
1191+
package_id: &'a str,
1192+
}
1193+
}

src/bootstrap/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,10 @@ pub struct Build {
254254
ci_env: CiEnv,
255255
delayed_failures: RefCell<Vec<String>>,
256256
prerelease_version: Cell<Option<u32>>,
257+
tool_artifacts: RefCell<HashMap<
258+
Interned<String>,
259+
HashMap<String, (&'static str, PathBuf, Vec<String>)>
260+
>>,
257261
}
258262

259263
#[derive(Debug)]
@@ -353,6 +357,7 @@ impl Build {
353357
ci_env: CiEnv::current(),
354358
delayed_failures: RefCell::new(Vec::new()),
355359
prerelease_version: Cell::new(None),
360+
tool_artifacts: Default::default(),
356361
}
357362
}
358363

src/bootstrap/tool.rs

+75-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,81 @@ impl Step for ToolBuild {
117117

118118
let mut cargo = prepare_tool_cargo(builder, compiler, target, "build", path);
119119
cargo.arg("--features").arg(self.extra_features.join(" "));
120-
let is_expected = build.try_run(&mut cargo);
120+
121+
let mut duplicates = Vec::new();
122+
let is_expected = compile::stream_cargo(build, &mut cargo, &mut |msg| {
123+
// Only care about big things like the RLS/Cargo for now
124+
if tool != "rls" && tool != "cargo" {
125+
return
126+
}
127+
let (id, features, filenames) = match msg {
128+
compile::CargoMessage::CompilerArtifact {
129+
package_id,
130+
features,
131+
filenames
132+
} => {
133+
(package_id, features, filenames)
134+
}
135+
_ => return,
136+
};
137+
let features = features.iter().map(|s| s.to_string()).collect::<Vec<_>>();
138+
139+
for path in filenames {
140+
let val = (tool, PathBuf::from(path), features.clone());
141+
// we're only interested in deduplicating rlibs for now
142+
if val.1.extension().and_then(|s| s.to_str()) != Some("rlib") {
143+
continue
144+
}
145+
146+
// Don't worry about libs that turn out to be host dependencies
147+
// or build scripts, we only care about target dependencies that
148+
// are in `deps`.
149+
if let Some(maybe_target) = val.1
150+
.parent() // chop off file name
151+
.and_then(|p| p.parent()) // chop off `deps`
152+
.and_then(|p| p.parent()) // chop off `release`
153+
.and_then(|p| p.file_name())
154+
.and_then(|p| p.to_str())
155+
{
156+
if maybe_target != &*target {
157+
continue
158+
}
159+
}
160+
161+
let mut artifacts = build.tool_artifacts.borrow_mut();
162+
let prev_artifacts = artifacts
163+
.entry(target)
164+
.or_insert_with(Default::default);
165+
if let Some(prev) = prev_artifacts.get(id) {
166+
if prev.1 != val.1 {
167+
duplicates.push((
168+
id.to_string(),
169+
val,
170+
prev.clone(),
171+
));
172+
}
173+
return
174+
}
175+
prev_artifacts.insert(id.to_string(), val);
176+
}
177+
});
178+
179+
if is_expected && duplicates.len() != 0 {
180+
println!("duplicate artfacts found when compiling a tool, this \
181+
typically means that something was recompiled because \
182+
a transitive dependency has different features activated \
183+
than in a previous build:\n");
184+
for (id, cur, prev) in duplicates {
185+
println!(" {}", id);
186+
println!(" `{}` enabled features {:?} at {:?}",
187+
cur.0, cur.2, cur.1);
188+
println!(" `{}` enabled features {:?} at {:?}",
189+
prev.0, prev.2, prev.1);
190+
}
191+
println!("");
192+
panic!("tools should not compile multiple copies of the same crate");
193+
}
194+
121195
build.save_toolstate(tool, if is_expected {
122196
ToolState::TestFail
123197
} else {

src/tools/rls

Submodule rls updated from fc4db0a to 567fb6d

src/tools/unstable-book-gen/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ license = "MIT/Apache-2.0"
77

88
[dependencies]
99
tidy = { path = "../tidy" }
10+
11+
# not actually needed but required for now to unify the feature selection of
12+
# `num-traits` between this and `rustbook`
13+
num-traits = "0.2"

0 commit comments

Comments
 (0)