Skip to content

Commit 3875bbb

Browse files
committed
Auto merge of #9122 - ehuss:fix-multiple-run-custom-build, r=alexcrichton
Fix env/cfg set for `cargo test` and `cargo run`. There are some situations where a build script may need to run multiple times for the same package during the same `cargo` session. There was a bug in that some of the values in the `Compilation` struct didn't handle this case. The solution here is to be more careful about how this extra data is associated with `Unit`s, instead of assuming a package's build script only runs once. The things that were not being handled properly: * `Compilation::extra_env`, which is the output of `cargo:rustc-env` in build scripts. The solution here is to use the `Metadata` hash which is used elsewhere for distinguishing build script outputs. * `Compilation::cfgs`, which is the output of `cargo:rustc-cfg` in build scripts and the features to be set, and this is only used for doctests. The solution here is to just add those `--cfg` flags directly in the `Doctest` struct. The situations that cause a build script to be run multiple times: * A package needed for both the host and target. In the test here, this was accomplished with a proc-macro (which has to be `host`) and a cyclical dev dependency, but there are many other ways to trigger this. * Something built with different features (with the new feature resolver), usually due to host/target differences. * Something built with different profile settings, usually due to host/target differences. Fixes #9100
2 parents e099df2 + 1607a68 commit 3875bbb

File tree

13 files changed

+265
-148
lines changed

13 files changed

+265
-148
lines changed

src/cargo/core/compiler/compilation.rs

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::{BTreeSet, HashMap, HashSet};
1+
use std::collections::{BTreeSet, HashMap};
22
use std::env;
33
use std::ffi::{OsStr, OsString};
44
use std::path::PathBuf;
@@ -7,9 +7,8 @@ use cargo_platform::CfgExpr;
77
use semver::Version;
88

99
use super::BuildContext;
10-
use crate::core::compiler::CompileKind;
11-
use crate::core::compiler::Unit;
12-
use crate::core::{Edition, Package, PackageId};
10+
use crate::core::compiler::{CompileKind, Metadata, Unit};
11+
use crate::core::{Edition, Package};
1312
use crate::util::{self, config, join_paths, process, CargoResult, Config, ProcessBuilder};
1413

1514
/// Structure with enough information to run `rustdoc --test`.
@@ -22,20 +21,35 @@ pub struct Doctest {
2221
pub unstable_opts: bool,
2322
/// The -Clinker value to use.
2423
pub linker: Option<PathBuf>,
24+
/// The script metadata, if this unit's package has a build script.
25+
///
26+
/// This is used for indexing [`Compilation::extra_env`].
27+
pub script_meta: Option<Metadata>,
28+
}
29+
30+
/// Information about the output of a unit.
31+
#[derive(Ord, PartialOrd, Eq, PartialEq)]
32+
pub struct UnitOutput {
33+
/// The unit that generated this output.
34+
pub unit: Unit,
35+
/// Path to the unit's primary output (an executable or cdylib).
36+
pub path: PathBuf,
37+
/// The script metadata, if this unit's package has a build script.
38+
///
39+
/// This is used for indexing [`Compilation::extra_env`].
40+
pub script_meta: Option<Metadata>,
2541
}
2642

2743
/// A structure returning the result of a compilation.
2844
pub struct Compilation<'cfg> {
2945
/// An array of all tests created during this compilation.
30-
/// `(unit, path_to_test_exe)` where `unit` contains information such as the
31-
/// package, compile target, etc.
32-
pub tests: Vec<(Unit, PathBuf)>,
46+
pub tests: Vec<UnitOutput>,
3347

3448
/// An array of all binaries created.
35-
pub binaries: Vec<(Unit, PathBuf)>,
49+
pub binaries: Vec<UnitOutput>,
3650

3751
/// An array of all cdylibs created.
38-
pub cdylibs: Vec<(Unit, PathBuf)>,
52+
pub cdylibs: Vec<UnitOutput>,
3953

4054
/// All directories for the output of native build commands.
4155
///
@@ -60,17 +74,14 @@ pub struct Compilation<'cfg> {
6074

6175
/// Extra environment variables that were passed to compilations and should
6276
/// be passed to future invocations of programs.
63-
pub extra_env: HashMap<PackageId, Vec<(String, String)>>,
77+
///
78+
/// The key is the build script metadata for uniquely identifying the
79+
/// `RunCustomBuild` unit that generated these env vars.
80+
pub extra_env: HashMap<Metadata, Vec<(String, String)>>,
6481

6582
/// Libraries to test with rustdoc.
6683
pub to_doc_test: Vec<Doctest>,
6784

68-
/// Features per package enabled during this compilation.
69-
pub cfgs: HashMap<PackageId, HashSet<String>>,
70-
71-
/// Flags to pass to rustdoc when invoked from cargo test, per package.
72-
pub rustdocflags: HashMap<PackageId, Vec<String>>,
73-
7485
/// The target host triple.
7586
pub host: String,
7687

@@ -127,8 +138,6 @@ impl<'cfg> Compilation<'cfg> {
127138
cdylibs: Vec::new(),
128139
extra_env: HashMap::new(),
129140
to_doc_test: Vec::new(),
130-
cfgs: HashMap::new(),
131-
rustdocflags: HashMap::new(),
132141
config: bcx.config,
133142
host: bcx.host_triple().to_string(),
134143
rustc_process: rustc,
@@ -144,7 +153,13 @@ impl<'cfg> Compilation<'cfg> {
144153
})
145154
}
146155

147-
/// See `process`.
156+
/// Returns a [`ProcessBuilder`] for running `rustc`.
157+
///
158+
/// `is_primary` is true if this is a "primary package", which means it
159+
/// was selected by the user on the command-line (such as with a `-p`
160+
/// flag), see [`crate::core::compiler::Context::primary_packages`].
161+
///
162+
/// `is_workspace` is true if this is a workspace member.
148163
pub fn rustc_process(
149164
&self,
150165
unit: &Unit,
@@ -160,14 +175,18 @@ impl<'cfg> Compilation<'cfg> {
160175
};
161176

162177
let cmd = fill_rustc_tool_env(rustc, unit);
163-
self.fill_env(cmd, &unit.pkg, unit.kind, true)
178+
self.fill_env(cmd, &unit.pkg, None, unit.kind, true)
164179
}
165180

166-
/// See `process`.
167-
pub fn rustdoc_process(&self, unit: &Unit) -> CargoResult<ProcessBuilder> {
181+
/// Returns a [`ProcessBuilder`] for running `rustdoc`.
182+
pub fn rustdoc_process(
183+
&self,
184+
unit: &Unit,
185+
script_meta: Option<Metadata>,
186+
) -> CargoResult<ProcessBuilder> {
168187
let rustdoc = process(&*self.config.rustdoc()?);
169188
let cmd = fill_rustc_tool_env(rustdoc, unit);
170-
let mut p = self.fill_env(cmd, &unit.pkg, unit.kind, true)?;
189+
let mut p = self.fill_env(cmd, &unit.pkg, script_meta, unit.kind, true)?;
171190
if unit.target.edition() != Edition::Edition2015 {
172191
p.arg(format!("--edition={}", unit.target.edition()));
173192
}
@@ -179,25 +198,37 @@ impl<'cfg> Compilation<'cfg> {
179198
Ok(p)
180199
}
181200

182-
/// See `process`.
201+
/// Returns a [`ProcessBuilder`] appropriate for running a process for the
202+
/// host platform.
203+
///
204+
/// This is currently only used for running build scripts. If you use this
205+
/// for anything else, please be extra careful on how environment
206+
/// variables are set!
183207
pub fn host_process<T: AsRef<OsStr>>(
184208
&self,
185209
cmd: T,
186210
pkg: &Package,
187211
) -> CargoResult<ProcessBuilder> {
188-
self.fill_env(process(cmd), pkg, CompileKind::Host, false)
212+
self.fill_env(process(cmd), pkg, None, CompileKind::Host, false)
189213
}
190214

191215
pub fn target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec<String>)> {
192216
self.target_runners.get(&kind).and_then(|x| x.as_ref())
193217
}
194218

195-
/// See `process`.
219+
/// Returns a [`ProcessBuilder`] appropriate for running a process for the
220+
/// target platform. This is typically used for `cargo run` and `cargo
221+
/// test`.
222+
///
223+
/// `script_meta` is the metadata for the `RunCustomBuild` unit that this
224+
/// unit used for its build script. Use `None` if the package did not have
225+
/// a build script.
196226
pub fn target_process<T: AsRef<OsStr>>(
197227
&self,
198228
cmd: T,
199229
kind: CompileKind,
200230
pkg: &Package,
231+
script_meta: Option<Metadata>,
201232
) -> CargoResult<ProcessBuilder> {
202233
let builder = if let Some((runner, args)) = self.target_runner(kind) {
203234
let mut builder = process(runner);
@@ -207,7 +238,7 @@ impl<'cfg> Compilation<'cfg> {
207238
} else {
208239
process(cmd)
209240
};
210-
self.fill_env(builder, pkg, kind, false)
241+
self.fill_env(builder, pkg, script_meta, kind, false)
211242
}
212243

213244
/// Prepares a new process with an appropriate environment to run against
@@ -219,6 +250,7 @@ impl<'cfg> Compilation<'cfg> {
219250
&self,
220251
mut cmd: ProcessBuilder,
221252
pkg: &Package,
253+
script_meta: Option<Metadata>,
222254
kind: CompileKind,
223255
is_rustc_tool: bool,
224256
) -> CargoResult<ProcessBuilder> {
@@ -258,9 +290,11 @@ impl<'cfg> Compilation<'cfg> {
258290
let search_path = join_paths(&search_path, util::dylib_path_envvar())?;
259291

260292
cmd.env(util::dylib_path_envvar(), &search_path);
261-
if let Some(env) = self.extra_env.get(&pkg.package_id()) {
262-
for &(ref k, ref v) in env {
263-
cmd.env(k, v);
293+
if let Some(meta) = script_meta {
294+
if let Some(env) = self.extra_env.get(&meta) {
295+
for (k, v) in env {
296+
cmd.env(k, v);
297+
}
264298
}
265299
}
266300

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

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use std::collections::{BTreeSet, HashMap, HashSet};
2-
use std::path::PathBuf;
2+
use std::path::{Path, PathBuf};
33
use std::sync::{Arc, Mutex};
44

55
use filetime::FileTime;
66
use jobserver::Client;
77

8-
use crate::core::compiler::{self, compilation, Unit};
8+
use crate::core::compiler::compilation::{self, UnitOutput};
9+
use crate::core::compiler::{self, Unit};
910
use crate::core::PackageId;
1011
use crate::util::errors::{CargoResult, CargoResultExt};
1112
use crate::util::profile;
@@ -174,16 +175,16 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
174175
if unit.mode == CompileMode::Test {
175176
self.compilation
176177
.tests
177-
.push((unit.clone(), output.path.clone()));
178+
.push(self.unit_output(unit, &output.path));
178179
} else if unit.target.is_executable() {
179180
self.compilation
180181
.binaries
181-
.push((unit.clone(), bindst.clone()));
182+
.push(self.unit_output(unit, bindst));
182183
} else if unit.target.is_cdylib() {
183-
if !self.compilation.cdylibs.iter().any(|(u, _)| u == unit) {
184+
if !self.compilation.cdylibs.iter().any(|uo| uo.unit == *unit) {
184185
self.compilation
185186
.cdylibs
186-
.push((unit.clone(), bindst.clone()));
187+
.push(self.unit_output(unit, bindst));
187188
}
188189
}
189190
}
@@ -198,9 +199,10 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
198199
.build_script_out_dir(&dep.unit)
199200
.display()
200201
.to_string();
202+
let script_meta = self.get_run_build_script_metadata(&dep.unit);
201203
self.compilation
202204
.extra_env
203-
.entry(dep.unit.pkg.package_id())
205+
.entry(script_meta)
204206
.or_insert_with(Vec::new)
205207
.push(("OUT_DIR".to_string(), out_dir));
206208
}
@@ -212,50 +214,36 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
212214
let mut unstable_opts = false;
213215
let mut args = compiler::extern_args(&self, unit, &mut unstable_opts)?;
214216
args.extend(compiler::lto_args(&self, unit));
217+
for feature in &unit.features {
218+
args.push("--cfg".into());
219+
args.push(format!("feature=\"{}\"", feature).into());
220+
}
221+
let script_meta = self.find_build_script_metadata(unit);
222+
if let Some(meta) = script_meta {
223+
if let Some(output) = self.build_script_outputs.lock().unwrap().get(meta) {
224+
for cfg in &output.cfgs {
225+
args.push("--cfg".into());
226+
args.push(cfg.into());
227+
}
228+
}
229+
}
230+
args.extend(self.bcx.rustdocflags_args(unit).iter().map(Into::into));
215231
self.compilation.to_doc_test.push(compilation::Doctest {
216232
unit: unit.clone(),
217233
args,
218234
unstable_opts,
219235
linker: self.bcx.linker(unit.kind),
236+
script_meta,
220237
});
221238
}
222239

223-
// Collect the enabled features.
224-
let feats = &unit.features;
225-
if !feats.is_empty() {
226-
self.compilation
227-
.cfgs
228-
.entry(unit.pkg.package_id())
229-
.or_insert_with(|| {
230-
feats
231-
.iter()
232-
.map(|feat| format!("feature=\"{}\"", feat))
233-
.collect()
234-
});
235-
}
236-
237-
// Collect rustdocflags.
238-
let rustdocflags = self.bcx.rustdocflags_args(unit);
239-
if !rustdocflags.is_empty() {
240-
self.compilation
241-
.rustdocflags
242-
.entry(unit.pkg.package_id())
243-
.or_insert_with(|| rustdocflags.to_vec());
244-
}
245-
246240
super::output_depinfo(&mut self, unit)?;
247241
}
248242

249-
for (pkg_id, output) in self.build_script_outputs.lock().unwrap().iter() {
250-
self.compilation
251-
.cfgs
252-
.entry(pkg_id)
253-
.or_insert_with(HashSet::new)
254-
.extend(output.cfgs.iter().cloned());
255-
243+
for (script_meta, output) in self.build_script_outputs.lock().unwrap().iter() {
256244
self.compilation
257245
.extra_env
258-
.entry(pkg_id)
246+
.entry(*script_meta)
259247
.or_insert_with(Vec::new)
260248
.extend(output.env.iter().cloned());
261249

@@ -352,11 +340,11 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
352340
/// Returns the RunCustomBuild Unit associated with the given Unit.
353341
///
354342
/// If the package does not have a build script, this returns None.
355-
pub fn find_build_script_unit(&self, unit: Unit) -> Option<Unit> {
343+
pub fn find_build_script_unit(&self, unit: &Unit) -> Option<Unit> {
356344
if unit.mode.is_run_custom_build() {
357-
return Some(unit);
345+
return Some(unit.clone());
358346
}
359-
self.bcx.unit_graph[&unit]
347+
self.bcx.unit_graph[unit]
360348
.iter()
361349
.find(|unit_dep| {
362350
unit_dep.unit.mode.is_run_custom_build()
@@ -369,7 +357,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
369357
/// the given unit.
370358
///
371359
/// If the package does not have a build script, this returns None.
372-
pub fn find_build_script_metadata(&self, unit: Unit) -> Option<Metadata> {
360+
pub fn find_build_script_metadata(&self, unit: &Unit) -> Option<Metadata> {
373361
let script_unit = self.find_build_script_unit(unit)?;
374362
Some(self.get_run_build_script_metadata(&script_unit))
375363
}
@@ -398,6 +386,17 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
398386
Ok(inputs.into_iter().collect())
399387
}
400388

389+
/// Returns a [`UnitOutput`] which represents some information about the
390+
/// output of a unit.
391+
pub fn unit_output(&self, unit: &Unit, path: &Path) -> UnitOutput {
392+
let script_meta = self.find_build_script_metadata(unit);
393+
UnitOutput {
394+
unit: unit.clone(),
395+
path: path.to_path_buf(),
396+
script_meta,
397+
}
398+
}
399+
401400
fn check_collisions(&self) -> CargoResult<()> {
402401
let mut output_collisions = HashMap::new();
403402
let describe_collision = |unit: &Unit, other_unit: &Unit, path: &PathBuf| -> String {

0 commit comments

Comments
 (0)