| //! Type definitions for the result of a compilation. | |
| use crate::util::data_structures::HashMap; | |
| use std::collections::BTreeSet; | |
| use std::ffi::{OsStr, OsString}; | |
| use std::path::Path; | |
| use std::path::PathBuf; | |
| use std::rc::Rc; | |
| use cargo_platform::CfgExpr; | |
| use cargo_util::{ProcessBuilder, paths}; | |
| use crate::compiler::BuildContext; | |
| use crate::compiler::CompileTarget; | |
| use crate::compiler::RustdocFingerprint; | |
| use crate::compiler::apply_env_config; | |
| use crate::compiler::{CompileKind, Unit, UnitHash}; | |
| use crate::util::{CargoResult, GlobalContext}; | |
| use crate::workspace::Package; | |
| /// Represents the kind of process we are creating. | |
| #[derive(Debug)] | |
| enum ToolKind { | |
| /// See [`Compilation::rustc_process`]. | |
| Rustc, | |
| /// See [`Compilation::rustdoc_process`]. | |
| Rustdoc, | |
| /// See [`Compilation::host_process`]. | |
| HostProcess, | |
| /// See [`Compilation::target_process`]. | |
| TargetProcess, | |
| } | |
| impl ToolKind { | |
| fn is_rustc_tool(&self) -> bool { | |
| matches!(self, ToolKind::Rustc | ToolKind::Rustdoc) | |
| } | |
| } | |
| /// Structure with enough information to run `rustdoc --test`. | |
| pub struct Doctest { | |
| /// What's being doctested | |
| pub unit: Unit, | |
| /// Arguments needed to pass to rustdoc to run this test. | |
| pub args: Vec<OsString>, | |
| /// Whether or not -Zunstable-options is needed. | |
| pub unstable_opts: bool, | |
| /// The -Clinker value to use. | |
| pub linker: Option<PathBuf>, | |
| /// The script metadata, if this unit's package has a build script. | |
| /// | |
| /// This is used for indexing [`Compilation::extra_env`]. | |
| pub script_metas: Option<Vec<UnitHash>>, | |
| /// Environment variables to set in the rustdoc process. | |
| pub env: HashMap<String, OsString>, | |
| } | |
| /// Information about the output of a unit. | |
| pub struct UnitOutput { | |
| /// The unit that generated this output. | |
| pub unit: Unit, | |
| /// Path to the unit's primary output (an executable or cdylib). | |
| pub path: PathBuf, | |
| /// The script metadata, if this unit's package has a build script. | |
| /// | |
| /// This is used for indexing [`Compilation::extra_env`]. | |
| pub script_metas: Option<Vec<UnitHash>>, | |
| /// Environment variables to set in the unit's process. | |
| pub env: HashMap<String, OsString>, | |
| } | |
| /// A structure returning the result of a compilation. | |
| pub struct Compilation<'gctx> { | |
| /// An array of all tests created during this compilation. | |
| pub tests: Vec<UnitOutput>, | |
| /// An array of all binaries created. | |
| pub binaries: Vec<UnitOutput>, | |
| /// An array of all cdylibs created. | |
| pub cdylibs: Vec<UnitOutput>, | |
| /// The crate names of the root units specified on the command-line. | |
| pub root_crate_names: Vec<String>, | |
| /// All directories for the output of native build commands. | |
| /// | |
| /// This is currently used to drive some entries which are added to the | |
| /// `LD_LIBRARY_PATH` as appropriate. | |
| /// | |
| /// The order should be deterministic. | |
| pub native_dirs: BTreeSet<PathBuf>, | |
| /// Root output directory (for the local package's artifacts) | |
| pub root_output: HashMap<CompileKind, PathBuf>, | |
| /// Output directories for rust dependencies. | |
| /// May be for the host or for a specific target. | |
| pub deps_output: HashMap<CompileKind, BTreeSet<PathBuf>>, | |
| /// The path to libstd for each target | |
| sysroot_target_libdir: HashMap<CompileKind, PathBuf>, | |
| /// Extra environment variables that were passed to compilations and should | |
| /// be passed to future invocations of programs. | |
| /// | |
| /// The key is the build script metadata for uniquely identifying the | |
| /// `RunCustomBuild` unit that generated these env vars. | |
| pub extra_env: HashMap<UnitHash, Vec<(String, String)>>, | |
| /// Libraries to test with rustdoc. | |
| pub to_doc_test: Vec<Doctest>, | |
| /// Rustdoc fingerprint files to determine whether we need to run `rustdoc --read-doc-meta-dir`. | |
| /// | |
| /// See `-Zrustdoc-mergeable-info` for more. | |
| pub rustdoc_fingerprints: Option<HashMap<CompileKind, RustdocFingerprint>>, | |
| /// Extra flags to pass to rustdoc for each host or target. | |
| pub rustdocflags: HashMap<CompileKind, Rc<[String]>>, | |
| /// The target host tuple. | |
| pub host: String, | |
| gctx: &'gctx GlobalContext, | |
| /// Rustc process to be used by default | |
| rustc_process: ProcessBuilder, | |
| /// Rustc process to be used for workspace crates instead of `rustc_process` | |
| rustc_workspace_wrapper_process: ProcessBuilder, | |
| /// Optional rustc process to be used for primary crates instead of either `rustc_process` or | |
| /// `rustc_workspace_wrapper_process` | |
| primary_rustc_process: Option<ProcessBuilder>, | |
| /// The runner to use for each host or target process. | |
| runners: HashMap<CompileKind, Option<(PathBuf, Vec<String>)>>, | |
| /// The linker to use for each host or target. | |
| linkers: HashMap<CompileKind, Option<PathBuf>>, | |
| /// The total number of lint warnings emitted by the compilation. | |
| pub lint_warning_count: usize, | |
| } | |
| impl<'gctx> Compilation<'gctx> { | |
| pub fn new<'a>(bcx: &BuildContext<'a, 'gctx>) -> CargoResult<Compilation<'gctx>> { | |
| let rustc_process = bcx.rustc().process(); | |
| let primary_rustc_process = bcx.build_config.primary_unit_rustc.clone(); | |
| let rustc_workspace_wrapper_process = bcx.rustc().workspace_process(); | |
| let host = bcx.host_triple().to_string(); | |
| let sysroot_target_libdir = get_sysroot_target_libdir(bcx)?; | |
| let rustdocflags = bcx | |
| .all_kinds | |
| .iter() | |
| .map(|&kind| (kind, bcx.target_data.info(kind).rustdocflags.clone())) | |
| .collect(); | |
| // When `target-applies-to-host=false`, and without `--target`, | |
| // there will be only `CompileKind::Host` in requested_kinds. | |
| // Need to insert target config explicitly for target-applies-to-host=false | |
| // to find the correct configs. | |
| let insert_explicit_host_runner = !bcx.gctx.target_applies_to_host()? | |
| && bcx | |
| .build_config | |
| .requested_kinds | |
| .iter() | |
| .any(CompileKind::is_host); | |
| let mut runners = bcx | |
| .build_config | |
| .requested_kinds | |
| .iter() | |
| .chain(Some(&CompileKind::Host)) | |
| .map(|kind| Ok((*kind, target_runner(bcx, *kind)?))) | |
| .collect::<CargoResult<HashMap<_, _>>>()?; | |
| if insert_explicit_host_runner { | |
| let kind = explicit_host_kind(&host); | |
| runners.insert(kind, target_runner(bcx, kind)?); | |
| } | |
| let mut linkers = bcx | |
| .build_config | |
| .requested_kinds | |
| .iter() | |
| .chain(Some(&CompileKind::Host)) | |
| .map(|kind| Ok((*kind, target_linker(bcx, *kind)?))) | |
| .collect::<CargoResult<HashMap<_, _>>>()?; | |
| if insert_explicit_host_runner { | |
| let kind = explicit_host_kind(&host); | |
| linkers.insert(kind, target_linker(bcx, kind)?); | |
| } | |
| Ok(Compilation { | |
| native_dirs: BTreeSet::new(), | |
| root_output: HashMap::default(), | |
| deps_output: HashMap::default(), | |
| sysroot_target_libdir, | |
| tests: Vec::new(), | |
| binaries: Vec::new(), | |
| cdylibs: Vec::new(), | |
| root_crate_names: Vec::new(), | |
| extra_env: HashMap::default(), | |
| to_doc_test: Vec::new(), | |
| rustdoc_fingerprints: None, | |
| rustdocflags, | |
| gctx: bcx.gctx, | |
| host, | |
| rustc_process, | |
| rustc_workspace_wrapper_process, | |
| primary_rustc_process, | |
| runners, | |
| linkers, | |
| lint_warning_count: 0, | |
| }) | |
| } | |
| /// Returns a [`ProcessBuilder`] for running `rustc`. | |
| /// | |
| /// `is_primary` is true if this is a "primary package", which means it | |
| /// was selected by the user on the command-line (such as with a `-p` | |
| /// flag), see [`crate::compiler::BuildRunner::primary_packages`]. | |
| /// | |
| /// `is_workspace` is true if this is a workspace member. | |
| pub fn rustc_process( | |
| &self, | |
| unit: &Unit, | |
| is_primary: bool, | |
| is_workspace: bool, | |
| ) -> CargoResult<ProcessBuilder> { | |
| let mut rustc = if is_primary && self.primary_rustc_process.is_some() { | |
| self.primary_rustc_process.clone().unwrap() | |
| } else if is_workspace { | |
| self.rustc_workspace_wrapper_process.clone() | |
| } else { | |
| self.rustc_process.clone() | |
| }; | |
| if self.gctx.extra_verbose() { | |
| rustc.display_env_vars(); | |
| } | |
| let cmd = fill_rustc_tool_env(rustc, unit); | |
| self.fill_env(cmd, &unit.pkg, None, unit.kind, ToolKind::Rustc) | |
| } | |
| /// Returns a [`ProcessBuilder`] for running `rustdoc`. | |
| pub fn rustdoc_process( | |
| &self, | |
| unit: &Unit, | |
| script_metas: Option<&Vec<UnitHash>>, | |
| ) -> CargoResult<ProcessBuilder> { | |
| let mut rustdoc = ProcessBuilder::new(&*self.gctx.rustdoc()?); | |
| if self.gctx.extra_verbose() { | |
| rustdoc.display_env_vars(); | |
| } | |
| let cmd = fill_rustc_tool_env(rustdoc, unit); | |
| let mut cmd = self.fill_env(cmd, &unit.pkg, script_metas, unit.kind, ToolKind::Rustdoc)?; | |
| cmd.retry_with_argfile(true); | |
| unit.target.edition().cmd_edition_arg(&mut cmd); | |
| for crate_type in unit.target.rustc_crate_types() { | |
| cmd.arg("--crate-type").arg(crate_type.as_str()); | |
| } | |
| Ok(cmd) | |
| } | |
| /// Returns a [`ProcessBuilder`] appropriate for running a process for the | |
| /// host platform. | |
| /// | |
| /// This is currently only used for running build scripts. If you use this | |
| /// for anything else, please be extra careful on how environment | |
| /// variables are set! | |
| pub fn host_process<T: AsRef<OsStr>>( | |
| &self, | |
| cmd: T, | |
| pkg: &Package, | |
| ) -> CargoResult<ProcessBuilder> { | |
| // Only use host runner when -Zhost-config is enabled | |
| // to ensure `target.<host>.runner` does not wrap build scripts. | |
| let builder = if !self.gctx.target_applies_to_host()? | |
| && let Some((runner, args)) = self | |
| .runners | |
| .get(&CompileKind::Host) | |
| .and_then(|x| x.as_ref()) | |
| { | |
| let mut builder = ProcessBuilder::new(runner); | |
| builder.args(args); | |
| builder.arg(cmd); | |
| builder | |
| } else { | |
| ProcessBuilder::new(cmd) | |
| }; | |
| self.fill_env(builder, pkg, None, CompileKind::Host, ToolKind::HostProcess) | |
| } | |
| pub fn target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec<String>)> { | |
| let target_applies_to_host = self.gctx.target_applies_to_host().unwrap_or(true); | |
| let kind = if !target_applies_to_host && kind.is_host() { | |
| // Use explicit host target tuple when `target-applies-to-host=false` | |
| // This ensures `host.runner` won't be accidentally applied to `cargo run` / `cargo test`. | |
| explicit_host_kind(&self.host) | |
| } else { | |
| kind | |
| }; | |
| self.runners.get(&kind).and_then(|x| x.as_ref()) | |
| } | |
| /// Gets the `[host.linker]` for host build target (build scripts and proc macros). | |
| pub fn host_linker(&self) -> Option<&Path> { | |
| self.linkers | |
| .get(&CompileKind::Host) | |
| .and_then(|x| x.as_ref()) | |
| .map(|x| x.as_path()) | |
| } | |
| /// Gets the user-specified linker for a particular host or target. | |
| pub fn target_linker(&self, kind: CompileKind) -> Option<&Path> { | |
| let target_applies_to_host = self.gctx.target_applies_to_host().unwrap_or(true); | |
| let kind = if !target_applies_to_host && kind.is_host() { | |
| // Use explicit host target tuple when `target-applies-to-host=false` | |
| // This ensures `host.linker` won't be accidentally applied to normal builds | |
| explicit_host_kind(&self.host) | |
| } else { | |
| kind | |
| }; | |
| self.linkers | |
| .get(&kind) | |
| .and_then(|x| x.as_ref()) | |
| .map(|x| x.as_path()) | |
| } | |
| /// Returns a [`ProcessBuilder`] appropriate for running a process for the | |
| /// target platform. This is typically used for `cargo run` and `cargo | |
| /// test`. | |
| /// | |
| /// `script_metas` is the metadata for the `RunCustomBuild` unit that this | |
| /// unit used for its build script. Use `None` if the package did not have | |
| /// a build script. | |
| pub fn target_process<T: AsRef<OsStr>>( | |
| &self, | |
| cmd: T, | |
| kind: CompileKind, | |
| pkg: &Package, | |
| script_metas: Option<&Vec<UnitHash>>, | |
| ) -> CargoResult<ProcessBuilder> { | |
| let builder = if let Some((runner, args)) = self.target_runner(kind) { | |
| let mut builder = ProcessBuilder::new(runner); | |
| builder.args(args); | |
| builder.arg(cmd); | |
| builder | |
| } else { | |
| ProcessBuilder::new(cmd) | |
| }; | |
| let tool_kind = ToolKind::TargetProcess; | |
| let mut builder = self.fill_env(builder, pkg, script_metas, kind, tool_kind)?; | |
| if let Some(client) = self.gctx.jobserver_from_env() { | |
| builder.inherit_jobserver(client); | |
| } | |
| Ok(builder) | |
| } | |
| /// Prepares a new process with an appropriate environment to run against | |
| /// the artifacts produced by the build process. | |
| /// | |
| /// The package argument is also used to configure environment variables as | |
| /// well as the working directory of the child process. | |
| fn fill_env( | |
| &self, | |
| mut cmd: ProcessBuilder, | |
| pkg: &Package, | |
| script_metas: Option<&Vec<UnitHash>>, | |
| kind: CompileKind, | |
| tool_kind: ToolKind, | |
| ) -> CargoResult<ProcessBuilder> { | |
| let mut search_path = Vec::new(); | |
| if tool_kind.is_rustc_tool() { | |
| if matches!(tool_kind, ToolKind::Rustdoc) { | |
| // HACK: `rustdoc --test` not only compiles but executes doctests. | |
| // Ideally only execution phase should have search paths appended, | |
| // so the executions can find native libs just like other tests. | |
| // However, there is no way to separate these two phase, so this | |
| // hack is added for both phases. | |
| // TODO: handle doctest-xcompile | |
| search_path.extend(super::filter_dynamic_search_path( | |
| self.native_dirs.iter(), | |
| &self.root_output[&CompileKind::Host], | |
| )); | |
| } | |
| if let Some(paths) = self.deps_output.get(&CompileKind::Host) { | |
| search_path.extend(paths.clone()); | |
| } | |
| } else { | |
| if let Some(path) = self.root_output.get(&kind) { | |
| search_path.extend(super::filter_dynamic_search_path( | |
| self.native_dirs.iter(), | |
| path, | |
| )); | |
| search_path.push(path.clone()); | |
| } | |
| if let Some(paths) = self.deps_output.get(&kind) { | |
| search_path.extend(paths.clone()); | |
| } | |
| // For build-std, we don't want to accidentally pull in any shared | |
| // libs from the sysroot that ships with rustc. This may not be | |
| // required (at least I cannot craft a situation where it | |
| // matters), but is here to be safe. | |
| if self.gctx.cli_unstable().build_std.is_none() || | |
| // Proc macros dynamically link to std, so set it anyway. | |
| pkg.proc_macro() | |
| { | |
| search_path.push(self.sysroot_target_libdir[&kind].clone()); | |
| } | |
| } | |
| let dylib_path = paths::dylib_path(); | |
| let dylib_path_is_empty = dylib_path.is_empty(); | |
| if dylib_path.starts_with(&search_path) { | |
| search_path = dylib_path; | |
| } else { | |
| search_path.extend(dylib_path.into_iter()); | |
| } | |
| if cfg!(target_os = "macos") && dylib_path_is_empty { | |
| // These are the defaults when DYLD_FALLBACK_LIBRARY_PATH isn't | |
| // set or set to an empty string. Since Cargo is explicitly setting | |
| // the value, make sure the defaults still work. | |
| if let Some(home) = self.gctx.get_env_os("HOME") { | |
| search_path.push(PathBuf::from(home).join("lib")); | |
| } | |
| search_path.push(PathBuf::from("/usr/local/lib")); | |
| search_path.push(PathBuf::from("/usr/lib")); | |
| } | |
| let search_path = paths::join_paths(&search_path, paths::dylib_path_envvar())?; | |
| cmd.env(paths::dylib_path_envvar(), &search_path); | |
| if let Some(meta_vec) = script_metas { | |
| for meta in meta_vec { | |
| if let Some(env) = self.extra_env.get(meta) { | |
| for (k, v) in env { | |
| if k == "OUT_DIR" && !tool_kind.is_rustc_tool() { | |
| continue; | |
| } | |
| cmd.env(k, v); | |
| } | |
| } | |
| } | |
| } | |
| let cargo_exe = self.gctx.cargo_exe()?; | |
| cmd.env(crate::CARGO_ENV, cargo_exe); | |
| // When adding new environment variables depending on | |
| // crate properties which might require rebuild upon change | |
| // consider adding the corresponding properties to the hash | |
| // in BuildContext::target_metadata() | |
| cmd.env("CARGO_MANIFEST_DIR", pkg.root()) | |
| .env("CARGO_MANIFEST_PATH", pkg.manifest_path()) | |
| .env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string()) | |
| .env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string()) | |
| .env("CARGO_PKG_VERSION_PATCH", &pkg.version().patch.to_string()) | |
| .env("CARGO_PKG_VERSION_PRE", pkg.version().pre.as_str()) | |
| .env("CARGO_PKG_VERSION", &pkg.version().to_string()) | |
| .env("CARGO_PKG_NAME", &*pkg.name()); | |
| for (key, value) in pkg.manifest().metadata().env_vars() { | |
| cmd.env(key, value.as_ref()); | |
| } | |
| cmd.cwd(pkg.root()); | |
| apply_env_config(self.gctx, &mut cmd)?; | |
| Ok(cmd) | |
| } | |
| } | |
| /// Prepares a `rustc_tool` process with additional environment variables | |
| /// that are only relevant in a context that has a unit | |
| fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder { | |
| if unit.target.is_executable() { | |
| let name = unit | |
| .target | |
| .binary_filename() | |
| .unwrap_or(unit.target.name().to_string()); | |
| cmd.env("CARGO_BIN_NAME", name); | |
| } | |
| cmd.env("CARGO_CRATE_NAME", unit.target.crate_name()); | |
| cmd | |
| } | |
| fn get_sysroot_target_libdir( | |
| bcx: &BuildContext<'_, '_>, | |
| ) -> CargoResult<HashMap<CompileKind, PathBuf>> { | |
| bcx.all_kinds | |
| .iter() | |
| .map(|&kind| { | |
| let Some(info) = bcx.target_data.get_info(kind) else { | |
| let target = match kind { | |
| CompileKind::Host => "host".to_owned(), | |
| CompileKind::Target(s) => s.short_name().to_owned(), | |
| }; | |
| let dependency = bcx | |
| .unit_graph | |
| .iter() | |
| .find_map(|(u, _)| (u.kind == kind).then_some(u.pkg.summary().package_id())) | |
| .unwrap(); | |
| anyhow::bail!( | |
| "could not find specification for target `{target}`.\n \ | |
| Dependency `{dependency}` requires to build for target `{target}`." | |
| ) | |
| }; | |
| Ok((kind, info.sysroot_target_libdir.clone())) | |
| }) | |
| .collect() | |
| } | |
| fn target_runner( | |
| bcx: &BuildContext<'_, '_>, | |
| kind: CompileKind, | |
| ) -> CargoResult<Option<(PathBuf, Vec<String>)>> { | |
| if let Some(runner) = bcx.target_data.target_config(kind).runner.as_ref() { | |
| let path = runner.val.path.clone().resolve_program(bcx.gctx); | |
| return Ok(Some((path, runner.val.args.clone()))); | |
| } | |
| // With `target-applies-to-host = true`, | |
| // host artifacts must fall through to pick up from [target] | |
| // since this is the stable behavior | |
| if kind.is_host() && !bcx.gctx.target_applies_to_host()? { | |
| return Ok(None); | |
| } | |
| // try target.'cfg(...)'.runner | |
| let target_cfg = bcx.target_data.info(kind).cfg(); | |
| let mut cfgs = bcx | |
| .gctx | |
| .target_cfgs()? | |
| .iter() | |
| .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner))) | |
| .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg)); | |
| let matching_runner = cfgs.next(); | |
| if let Some((key, runner)) = cfgs.next() { | |
| anyhow::bail!( | |
| "several matching instances of `target.'cfg(..)'.runner` in configurations\n\ | |
| first match `{}` located in {}\n\ | |
| second match `{}` located in {}", | |
| matching_runner.unwrap().0, | |
| matching_runner.unwrap().1.definition, | |
| key, | |
| runner.definition | |
| ); | |
| } | |
| Ok(matching_runner.map(|(_k, runner)| { | |
| ( | |
| runner.val.path.clone().resolve_program(bcx.gctx), | |
| runner.val.args.clone(), | |
| ) | |
| })) | |
| } | |
| /// Gets the user-specified linker for a particular host or target from the configuration. | |
| fn target_linker(bcx: &BuildContext<'_, '_>, kind: CompileKind) -> CargoResult<Option<PathBuf>> { | |
| // Try host.linker and target.{}.linker. | |
| if let Some(path) = bcx | |
| .target_data | |
| .target_config(kind) | |
| .linker | |
| .as_ref() | |
| .map(|l| l.val.clone().resolve_program(bcx.gctx)) | |
| { | |
| return Ok(Some(path)); | |
| } | |
| // With `target-applies-to-host = true`, | |
| // host artifacts must fall through to pick up from [target] | |
| // since this is the stable behavior | |
| if kind.is_host() && !bcx.gctx.target_applies_to_host()? { | |
| return Ok(None); | |
| } | |
| // Try target.'cfg(...)'.linker. | |
| let target_cfg = bcx.target_data.info(kind).cfg(); | |
| let mut cfgs = bcx | |
| .gctx | |
| .target_cfgs()? | |
| .iter() | |
| .filter_map(|(key, cfg)| cfg.linker.as_ref().map(|linker| (key, linker))) | |
| .filter(|(key, _linker)| CfgExpr::matches_key(key, target_cfg)); | |
| let matching_linker = cfgs.next(); | |
| if let Some((key, linker)) = cfgs.next() { | |
| anyhow::bail!( | |
| "several matching instances of `target.'cfg(..)'.linker` in configurations\n\ | |
| first match `{}` located in {}\n\ | |
| second match `{}` located in {}", | |
| matching_linker.unwrap().0, | |
| matching_linker.unwrap().1.definition, | |
| key, | |
| linker.definition | |
| ); | |
| } | |
| Ok(matching_linker.map(|(_k, linker)| linker.val.clone().resolve_program(bcx.gctx))) | |
| } | |
| fn explicit_host_kind(host: &str) -> CompileKind { | |
| let target = CompileTarget::new(host, false).expect("must be a host tuple"); | |
| CompileKind::Target(target) | |
| } |