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
14pub struct Doctest {
16 pub package: Package,
18 pub target: Target,
20 pub args: Vec<OsString>,
22 pub unstable_opts: bool,
24}
25
26pub struct Compilation<'cfg> {
28 pub tests: Vec<(Package, Target, PathBuf)>,
31
32 pub binaries: Vec<PathBuf>,
34
35 pub native_dirs: BTreeSet<PathBuf>,
42
43 pub root_output: PathBuf,
45
46 pub deps_output: PathBuf,
49
50 pub host_deps_output: PathBuf,
52
53 pub host_dylib_path: PathBuf,
55
56 pub target_dylib_path: PathBuf,
58
59 pub extra_env: HashMap<PackageId, Vec<(String, String)>>,
62
63 pub to_doc_test: Vec<Doctest>,
65
66 pub cfgs: HashMap<PackageId, HashSet<String>>,
68
69 pub rustdocflags: HashMap<PackageId, Vec<String>>,
71
72 pub host: String,
73 pub target: String,
74
75 config: &'cfg Config,
76
77 rustc_process: ProcessBuilder,
79 rustc_workspace_wrapper_process: ProcessBuilder,
81 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 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 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 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 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 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 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 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 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 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 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 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}