Skip to main content

cargo/core/compiler/
compilation.rs

1use std::collections::{BTreeSet, HashMap, HashSet};
2use std::env;
3use std::ffi::{OsStr, OsString};
4use std::path::PathBuf;
5
6use cargo_platform::CfgExpr;
7use semver::Version;
8
9use super::BuildContext;
10use crate::core::compiler::CompileKind;
11use crate::core::{Edition, Package, PackageId, Target};
12use crate::util::{self, config, join_paths, process, CargoResult, Config, ProcessBuilder};
13
14/// Structure with enough information to run `rustdoc --test`.
15pub struct Doctest {
16    /// The package being doc-tested.
17    pub package: Package,
18    /// The target being tested (currently always the package's lib).
19    pub target: Target,
20    /// Arguments needed to pass to rustdoc to run this test.
21    pub args: Vec<OsString>,
22    /// Whether or not -Zunstable-options is needed.
23    pub unstable_opts: bool,
24}
25
26/// A structure returning the result of a compilation.
27pub struct Compilation<'cfg> {
28    /// An array of all tests created during this compilation.
29    /// `(package, target, path_to_test_exe)`
30    pub tests: Vec<(Package, Target, PathBuf)>,
31
32    /// An array of all binaries created.
33    pub binaries: Vec<PathBuf>,
34
35    /// All directories for the output of native build commands.
36    ///
37    /// This is currently used to drive some entries which are added to the
38    /// LD_LIBRARY_PATH as appropriate.
39    ///
40    /// The order should be deterministic.
41    pub native_dirs: BTreeSet<PathBuf>,
42
43    /// Root output directory (for the local package's artifacts)
44    pub root_output: PathBuf,
45
46    /// Output directory for rust dependencies.
47    /// May be for the host or for a specific target.
48    pub deps_output: PathBuf,
49
50    /// Output directory for the rust host dependencies.
51    pub host_deps_output: PathBuf,
52
53    /// The path to rustc's own libstd
54    pub host_dylib_path: PathBuf,
55
56    /// The path to libstd for the target
57    pub target_dylib_path: PathBuf,
58
59    /// Extra environment variables that were passed to compilations and should
60    /// be passed to future invocations of programs.
61    pub extra_env: HashMap<PackageId, Vec<(String, String)>>,
62
63    /// Libraries to test with rustdoc.
64    pub to_doc_test: Vec<Doctest>,
65
66    /// Features per package enabled during this compilation.
67    pub cfgs: HashMap<PackageId, HashSet<String>>,
68
69    /// Flags to pass to rustdoc when invoked from cargo test, per package.
70    pub rustdocflags: HashMap<PackageId, Vec<String>>,
71
72    pub host: String,
73    pub target: String,
74
75    config: &'cfg Config,
76
77    /// Rustc process to be used by default
78    rustc_process: ProcessBuilder,
79    /// Rustc process to be used for workspace crates instead of rustc_process
80    rustc_workspace_wrapper_process: ProcessBuilder,
81    /// Optional rustc process to be used for primary crates instead of either rustc_process or
82    /// rustc_workspace_wrapper_process
83    primary_rustc_process: Option<ProcessBuilder>,
84
85    target_runner: Option<(PathBuf, Vec<String>)>,
86}
87
88impl<'cfg> Compilation<'cfg> {
89    pub fn new<'a>(
90        bcx: &BuildContext<'a, 'cfg>,
91        default_kind: CompileKind,
92    ) -> CargoResult<Compilation<'cfg>> {
93        let mut rustc = bcx.rustc().process();
94        let mut primary_rustc_process = bcx.build_config.primary_unit_rustc.clone();
95        let mut rustc_workspace_wrapper_process = bcx.rustc().workspace_process();
96
97        if bcx.config.extra_verbose() {
98            rustc.display_env_vars();
99            rustc_workspace_wrapper_process.display_env_vars();
100
101            if let Some(rustc) = primary_rustc_process.as_mut() {
102                rustc.display_env_vars();
103            }
104        }
105
106        Ok(Compilation {
107            // TODO: deprecated; remove.
108            native_dirs: BTreeSet::new(),
109            root_output: PathBuf::from("/"),
110            deps_output: PathBuf::from("/"),
111            host_deps_output: PathBuf::from("/"),
112            host_dylib_path: bcx
113                .target_data
114                .info(CompileKind::Host)
115                .sysroot_host_libdir
116                .clone(),
117            target_dylib_path: bcx
118                .target_data
119                .info(default_kind)
120                .sysroot_target_libdir
121                .clone(),
122            tests: Vec::new(),
123            binaries: Vec::new(),
124            extra_env: HashMap::new(),
125            to_doc_test: Vec::new(),
126            cfgs: HashMap::new(),
127            rustdocflags: HashMap::new(),
128            config: bcx.config,
129            rustc_process: rustc,
130            rustc_workspace_wrapper_process,
131            primary_rustc_process,
132            host: bcx.host_triple().to_string(),
133            target: bcx.target_data.short_name(&default_kind).to_string(),
134            target_runner: target_runner(bcx, default_kind)?,
135        })
136    }
137
138    /// See `process`.
139    pub fn rustc_process(
140        &self,
141        pkg: &Package,
142        is_primary: bool,
143        is_workspace: bool,
144    ) -> CargoResult<ProcessBuilder> {
145        let rustc = if is_primary && self.primary_rustc_process.is_some() {
146            self.primary_rustc_process.clone().unwrap()
147        } else if is_workspace {
148            self.rustc_workspace_wrapper_process.clone()
149        } else {
150            self.rustc_process.clone()
151        };
152
153        self.fill_env(rustc, pkg, true)
154    }
155
156    /// See `process`.
157    pub fn rustdoc_process(&self, pkg: &Package, target: &Target) -> CargoResult<ProcessBuilder> {
158        let mut p = self.fill_env(process(&*self.config.rustdoc()?), pkg, false)?;
159        if target.edition() != Edition::Edition2015 {
160            p.arg(format!("--edition={}", target.edition()));
161        }
162
163        for crate_type in target.rustc_crate_types() {
164            p.arg("--crate-type").arg(crate_type);
165        }
166
167        Ok(p)
168    }
169
170    /// See `process`.
171    pub fn host_process<T: AsRef<OsStr>>(
172        &self,
173        cmd: T,
174        pkg: &Package,
175    ) -> CargoResult<ProcessBuilder> {
176        self.fill_env(process(cmd), pkg, true)
177    }
178
179    pub fn target_runner(&self) -> &Option<(PathBuf, Vec<String>)> {
180        &self.target_runner
181    }
182
183    /// See `process`.
184    pub fn target_process<T: AsRef<OsStr>>(
185        &self,
186        cmd: T,
187        pkg: &Package,
188    ) -> CargoResult<ProcessBuilder> {
189        let builder = if let Some((ref runner, ref args)) = *self.target_runner() {
190            let mut builder = process(runner);
191            builder.args(args);
192            builder.arg(cmd);
193            builder
194        } else {
195            process(cmd)
196        };
197        self.fill_env(builder, pkg, false)
198    }
199
200    /// Prepares a new process with an appropriate environment to run against
201    /// the artifacts produced by the build process.
202    ///
203    /// The package argument is also used to configure environment variables as
204    /// well as the working directory of the child process.
205    fn fill_env(
206        &self,
207        mut cmd: ProcessBuilder,
208        pkg: &Package,
209        is_host: bool,
210    ) -> CargoResult<ProcessBuilder> {
211        let mut search_path = if is_host {
212            let mut search_path = vec![self.host_deps_output.clone()];
213            search_path.push(self.host_dylib_path.clone());
214            search_path
215        } else {
216            let mut search_path =
217                super::filter_dynamic_search_path(self.native_dirs.iter(), &self.root_output);
218            search_path.push(self.deps_output.clone());
219            search_path.push(self.root_output.clone());
220            // For build-std, we don't want to accidentally pull in any shared
221            // libs from the sysroot that ships with rustc. This may not be
222            // required (at least I cannot craft a situation where it
223            // matters), but is here to be safe.
224            if self.config.cli_unstable().build_std.is_none() {
225                search_path.push(self.target_dylib_path.clone());
226            }
227            search_path
228        };
229
230        let dylib_path = util::dylib_path();
231        let dylib_path_is_empty = dylib_path.is_empty();
232        search_path.extend(dylib_path.into_iter());
233        if cfg!(target_os = "macos") && dylib_path_is_empty {
234            // These are the defaults when DYLD_FALLBACK_LIBRARY_PATH isn't
235            // set or set to an empty string. Since Cargo is explicitly setting
236            // the value, make sure the defaults still work.
237            if let Some(home) = env::var_os("HOME") {
238                search_path.push(PathBuf::from(home).join("lib"));
239            }
240            search_path.push(PathBuf::from("/usr/local/lib"));
241            search_path.push(PathBuf::from("/usr/lib"));
242        }
243        let search_path = join_paths(&search_path, util::dylib_path_envvar())?;
244
245        cmd.env(util::dylib_path_envvar(), &search_path);
246        if let Some(env) = self.extra_env.get(&pkg.package_id()) {
247            for &(ref k, ref v) in env {
248                cmd.env(k, v);
249            }
250        }
251
252        let metadata = pkg.manifest().metadata();
253
254        let cargo_exe = self.config.cargo_exe()?;
255        cmd.env(crate::CARGO_ENV, cargo_exe);
256
257        // When adding new environment variables depending on
258        // crate properties which might require rebuild upon change
259        // consider adding the corresponding properties to the hash
260        // in BuildContext::target_metadata()
261        cmd.env("CARGO_MANIFEST_DIR", pkg.root())
262            .env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string())
263            .env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string())
264            .env("CARGO_PKG_VERSION_PATCH", &pkg.version().patch.to_string())
265            .env(
266                "CARGO_PKG_VERSION_PRE",
267                &pre_version_component(pkg.version()),
268            )
269            .env("CARGO_PKG_VERSION", &pkg.version().to_string())
270            .env("CARGO_PKG_NAME", &*pkg.name())
271            .env(
272                "CARGO_PKG_DESCRIPTION",
273                metadata.description.as_ref().unwrap_or(&String::new()),
274            )
275            .env(
276                "CARGO_PKG_HOMEPAGE",
277                metadata.homepage.as_ref().unwrap_or(&String::new()),
278            )
279            .env(
280                "CARGO_PKG_REPOSITORY",
281                metadata.repository.as_ref().unwrap_or(&String::new()),
282            )
283            .env("CARGO_PKG_AUTHORS", &pkg.authors().join(":"))
284            .cwd(pkg.root());
285        Ok(cmd)
286    }
287}
288
289fn pre_version_component(v: &Version) -> String {
290    if v.pre.is_empty() {
291        return String::new();
292    }
293
294    let mut ret = String::new();
295
296    for (i, x) in v.pre.iter().enumerate() {
297        if i != 0 {
298            ret.push('.')
299        };
300        ret.push_str(&x.to_string());
301    }
302
303    ret
304}
305
306fn target_runner(
307    bcx: &BuildContext<'_, '_>,
308    kind: CompileKind,
309) -> CargoResult<Option<(PathBuf, Vec<String>)>> {
310    let target = bcx.target_data.short_name(&kind);
311
312    // try target.{}.runner
313    let key = format!("target.{}.runner", target);
314    if let Some(v) = bcx.config.get::<Option<config::PathAndArgs>>(&key)? {
315        let path = v.path.resolve_program(bcx.config);
316        return Ok(Some((path, v.args)));
317    }
318
319    // try target.'cfg(...)'.runner
320    let target_cfg = bcx.target_data.info(kind).cfg();
321    let mut cfgs = bcx
322        .config
323        .target_cfgs()?
324        .iter()
325        .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner)))
326        .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg));
327    let matching_runner = cfgs.next();
328    if let Some((key, runner)) = cfgs.next() {
329        anyhow::bail!(
330            "several matching instances of `target.'cfg(..)'.runner` in `.cargo/config`\n\
331             first match `{}` located in {}\n\
332             second match `{}` located in {}",
333            matching_runner.unwrap().0,
334            matching_runner.unwrap().1.definition,
335            key,
336            runner.definition
337        );
338    }
339    Ok(matching_runner.map(|(_k, runner)| {
340        (
341            runner.val.path.clone().resolve_program(bcx.config),
342            runner.val.args.clone(),
343        )
344    }))
345}