Skip to content

Commit 9d57564

Browse files
committed
Auto merge of #5432 - djc:split-context, r=alexcrichton
Split Context into two types @matklad here's the crazy attempt to make the `Context` situation slightly more sane. It basically splits all of the stuff that is immutable over the lifetime of the compilation from the stuff that's initialized empty. The latter is now stored in a type which I have provisionally called `BuildProgress` -- feel free to bikeshed about the name (I came up with `BuildData` as another alternative). It stores a reference to the `Context` to make things easier for now, we might want to change that down the line. IMO this is a pretty good improvement, which clarifies the design quite a bit. @alexcrichton you'll probably want to review this, too.
2 parents acea5e2 + d02c726 commit 9d57564

14 files changed

+582
-542
lines changed
Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
use std::env;
2+
use std::path::Path;
3+
use std::str::{self, FromStr};
4+
5+
use core::profiles::Profiles;
6+
use core::{Dependency, Workspace};
7+
use core::{Package, PackageId, PackageSet, Resolve};
8+
use util::errors::CargoResult;
9+
use util::{profile, Cfg, CfgExpr, Config};
10+
11+
use super::{BuildConfig, Kind, TargetConfig, Unit};
12+
13+
mod target_info;
14+
pub use self::target_info::{FileFlavor, TargetInfo};
15+
16+
/// The build context, containing all information about a build task
17+
pub struct BuildContext<'a, 'cfg: 'a> {
18+
/// The workspace the build is for
19+
pub ws: &'a Workspace<'cfg>,
20+
/// The cargo configuration
21+
pub config: &'cfg Config,
22+
/// The dependency graph for our build
23+
pub resolve: &'a Resolve,
24+
pub profiles: &'a Profiles,
25+
pub build_config: &'a BuildConfig,
26+
/// This is a workaround to carry the extra compiler args for either
27+
/// `rustc` or `rustdoc` given on the command-line for the commands `cargo
28+
/// rustc` and `cargo rustdoc`. These commands only support one target,
29+
/// but we don't want the args passed to any dependencies, so we include
30+
/// the `Unit` corresponding to the top-level target.
31+
pub extra_compiler_args: Option<(Unit<'a>, Vec<String>)>,
32+
pub packages: &'a PackageSet<'cfg>,
33+
34+
pub target_info: TargetInfo,
35+
pub host_info: TargetInfo,
36+
pub incremental_env: Option<bool>,
37+
}
38+
39+
impl<'a, 'cfg> BuildContext<'a, 'cfg> {
40+
pub fn new(
41+
ws: &'a Workspace<'cfg>,
42+
resolve: &'a Resolve,
43+
packages: &'a PackageSet<'cfg>,
44+
config: &'cfg Config,
45+
build_config: &'a BuildConfig,
46+
profiles: &'a Profiles,
47+
extra_compiler_args: Option<(Unit<'a>, Vec<String>)>,
48+
) -> CargoResult<BuildContext<'a, 'cfg>> {
49+
let incremental_env = match env::var("CARGO_INCREMENTAL") {
50+
Ok(v) => Some(v == "1"),
51+
Err(_) => None,
52+
};
53+
54+
let (host_info, target_info) = {
55+
let _p = profile::start("BuildContext::probe_target_info");
56+
debug!("probe_target_info");
57+
let host_info = TargetInfo::new(config, &build_config, Kind::Host)?;
58+
let target_info = TargetInfo::new(config, &build_config, Kind::Target)?;
59+
(host_info, target_info)
60+
};
61+
62+
Ok(BuildContext {
63+
ws,
64+
resolve,
65+
packages,
66+
config,
67+
target_info,
68+
host_info,
69+
build_config,
70+
profiles,
71+
incremental_env,
72+
extra_compiler_args,
73+
})
74+
}
75+
76+
pub fn extern_crate_name(&self, unit: &Unit<'a>, dep: &Unit<'a>) -> CargoResult<String> {
77+
let deps = {
78+
let a = unit.pkg.package_id();
79+
let b = dep.pkg.package_id();
80+
if a == b {
81+
&[]
82+
} else {
83+
self.resolve.dependencies_listed(a, b)
84+
}
85+
};
86+
87+
let crate_name = dep.target.crate_name();
88+
let mut names = deps.iter()
89+
.map(|d| d.rename().unwrap_or(&crate_name));
90+
let name = names.next().unwrap_or(&crate_name);
91+
for n in names {
92+
if n == name {
93+
continue
94+
}
95+
bail!("multiple dependencies listed for the same crate must \
96+
all have the same name, but the dependency on `{}` \
97+
is listed as having different names", dep.pkg.package_id());
98+
}
99+
Ok(name.to_string())
100+
}
101+
102+
/// Whether a dependency should be compiled for the host or target platform,
103+
/// specified by `Kind`.
104+
pub fn dep_platform_activated(&self, dep: &Dependency, kind: Kind) -> bool {
105+
// If this dependency is only available for certain platforms,
106+
// make sure we're only enabling it for that platform.
107+
let platform = match dep.platform() {
108+
Some(p) => p,
109+
None => return true,
110+
};
111+
let (name, info) = match kind {
112+
Kind::Host => (self.build_config.host_triple(), &self.host_info),
113+
Kind::Target => (self.build_config.target_triple(), &self.target_info),
114+
};
115+
platform.matches(name, info.cfg())
116+
}
117+
118+
/// Gets a package for the given package id.
119+
pub fn get_package(&self, id: &PackageId) -> CargoResult<&'a Package> {
120+
self.packages.get(id)
121+
}
122+
123+
/// Get the user-specified linker for a particular host or target
124+
pub fn linker(&self, kind: Kind) -> Option<&Path> {
125+
self.target_config(kind).linker.as_ref().map(|s| s.as_ref())
126+
}
127+
128+
/// Get the user-specified `ar` program for a particular host or target
129+
pub fn ar(&self, kind: Kind) -> Option<&Path> {
130+
self.target_config(kind).ar.as_ref().map(|s| s.as_ref())
131+
}
132+
133+
/// Get the list of cfg printed out from the compiler for the specified kind
134+
pub fn cfg(&self, kind: Kind) -> &[Cfg] {
135+
let info = match kind {
136+
Kind::Host => &self.host_info,
137+
Kind::Target => &self.target_info,
138+
};
139+
info.cfg().unwrap_or(&[])
140+
}
141+
142+
/// Get the target configuration for a particular host or target
143+
fn target_config(&self, kind: Kind) -> &TargetConfig {
144+
match kind {
145+
Kind::Host => &self.build_config.host,
146+
Kind::Target => &self.build_config.target,
147+
}
148+
}
149+
150+
/// Number of jobs specified for this build
151+
pub fn jobs(&self) -> u32 {
152+
self.build_config.jobs
153+
}
154+
155+
pub fn rustflags_args(&self, unit: &Unit) -> CargoResult<Vec<String>> {
156+
env_args(
157+
self.config,
158+
&self.build_config,
159+
self.info(&unit.kind).cfg(),
160+
unit.kind,
161+
"RUSTFLAGS",
162+
)
163+
}
164+
165+
pub fn rustdocflags_args(&self, unit: &Unit) -> CargoResult<Vec<String>> {
166+
env_args(
167+
self.config,
168+
&self.build_config,
169+
self.info(&unit.kind).cfg(),
170+
unit.kind,
171+
"RUSTDOCFLAGS",
172+
)
173+
}
174+
175+
pub fn show_warnings(&self, pkg: &PackageId) -> bool {
176+
pkg.source_id().is_path() || self.config.extra_verbose()
177+
}
178+
179+
fn info(&self, kind: &Kind) -> &TargetInfo {
180+
match *kind {
181+
Kind::Host => &self.host_info,
182+
Kind::Target => &self.target_info,
183+
}
184+
}
185+
186+
pub fn extra_args_for(&self, unit: &Unit<'a>) -> Option<&Vec<String>> {
187+
if let Some((ref args_unit, ref args)) = self.extra_compiler_args {
188+
if args_unit == unit {
189+
return Some(args);
190+
}
191+
}
192+
None
193+
}
194+
}
195+
196+
/// Acquire extra flags to pass to the compiler from various locations.
197+
///
198+
/// The locations are:
199+
///
200+
/// - the `RUSTFLAGS` environment variable
201+
///
202+
/// then if this was not found
203+
///
204+
/// - `target.*.rustflags` from the manifest (Cargo.toml)
205+
/// - `target.cfg(..).rustflags` from the manifest
206+
///
207+
/// then if neither of these were found
208+
///
209+
/// - `build.rustflags` from the manifest
210+
///
211+
/// Note that if a `target` is specified, no args will be passed to host code (plugins, build
212+
/// scripts, ...), even if it is the same as the target.
213+
fn env_args(
214+
config: &Config,
215+
build_config: &BuildConfig,
216+
target_cfg: Option<&[Cfg]>,
217+
kind: Kind,
218+
name: &str,
219+
) -> CargoResult<Vec<String>> {
220+
// We *want* to apply RUSTFLAGS only to builds for the
221+
// requested target architecture, and not to things like build
222+
// scripts and plugins, which may be for an entirely different
223+
// architecture. Cargo's present architecture makes it quite
224+
// hard to only apply flags to things that are not build
225+
// scripts and plugins though, so we do something more hacky
226+
// instead to avoid applying the same RUSTFLAGS to multiple targets
227+
// arches:
228+
//
229+
// 1) If --target is not specified we just apply RUSTFLAGS to
230+
// all builds; they are all going to have the same target.
231+
//
232+
// 2) If --target *is* specified then we only apply RUSTFLAGS
233+
// to compilation units with the Target kind, which indicates
234+
// it was chosen by the --target flag.
235+
//
236+
// This means that, e.g. even if the specified --target is the
237+
// same as the host, build scripts in plugins won't get
238+
// RUSTFLAGS.
239+
let compiling_with_target = build_config.requested_target.is_some();
240+
let is_target_kind = kind == Kind::Target;
241+
242+
if compiling_with_target && !is_target_kind {
243+
// This is probably a build script or plugin and we're
244+
// compiling with --target. In this scenario there are
245+
// no rustflags we can apply.
246+
return Ok(Vec::new());
247+
}
248+
249+
// First try RUSTFLAGS from the environment
250+
if let Ok(a) = env::var(name) {
251+
let args = a.split(' ')
252+
.map(str::trim)
253+
.filter(|s| !s.is_empty())
254+
.map(str::to_string);
255+
return Ok(args.collect());
256+
}
257+
258+
let mut rustflags = Vec::new();
259+
260+
let name = name.chars()
261+
.flat_map(|c| c.to_lowercase())
262+
.collect::<String>();
263+
// Then the target.*.rustflags value...
264+
let target = build_config
265+
.requested_target
266+
.as_ref()
267+
.map(|s| s.as_str())
268+
.unwrap_or(build_config.host_triple());
269+
let key = format!("target.{}.{}", target, name);
270+
if let Some(args) = config.get_list_or_split_string(&key)? {
271+
let args = args.val.into_iter();
272+
rustflags.extend(args);
273+
}
274+
// ...including target.'cfg(...)'.rustflags
275+
if let Some(target_cfg) = target_cfg {
276+
if let Some(table) = config.get_table("target")? {
277+
let cfgs = table.val.keys().filter_map(|t| {
278+
if t.starts_with("cfg(") && t.ends_with(')') {
279+
let cfg = &t[4..t.len() - 1];
280+
CfgExpr::from_str(cfg).ok().and_then(|c| {
281+
if c.matches(target_cfg) {
282+
Some(t)
283+
} else {
284+
None
285+
}
286+
})
287+
} else {
288+
None
289+
}
290+
});
291+
292+
// Note that we may have multiple matching `[target]` sections and
293+
// because we're passing flags to the compiler this can affect
294+
// cargo's caching and whether it rebuilds. Ensure a deterministic
295+
// ordering through sorting for now. We may perhaps one day wish to
296+
// ensure a deterministic ordering via the order keys were defined
297+
// in files perhaps.
298+
let mut cfgs = cfgs.collect::<Vec<_>>();
299+
cfgs.sort();
300+
301+
for n in cfgs {
302+
let key = format!("target.{}.{}", n, name);
303+
if let Some(args) = config.get_list_or_split_string(&key)? {
304+
let args = args.val.into_iter();
305+
rustflags.extend(args);
306+
}
307+
}
308+
}
309+
}
310+
311+
if !rustflags.is_empty() {
312+
return Ok(rustflags);
313+
}
314+
315+
// Then the build.rustflags value
316+
let key = format!("build.{}", name);
317+
if let Some(args) = config.get_list_or_split_string(&key)? {
318+
let args = args.val.into_iter();
319+
return Ok(args.collect());
320+
}
321+
322+
Ok(Vec::new())
323+
}

src/cargo/core/compiler/compilation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ impl<'cfg> Compilation<'cfg> {
175175
// When adding new environment variables depending on
176176
// crate properties which might require rebuild upon change
177177
// consider adding the corresponding properties to the hash
178-
// in Context::target_metadata()
178+
// in BuildContext::target_metadata()
179179
cmd.env("CARGO_MANIFEST_DIR", pkg.root())
180180
.env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string())
181181
.env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string())

0 commit comments

Comments
 (0)