1use super::job::{Freshness, Job, Work};
2use super::{fingerprint, Context, Unit};
3use crate::core::compiler::context::Metadata;
4use crate::core::compiler::job_queue::JobState;
5use crate::core::{profiles::ProfileRoot, PackageId};
6use crate::util::errors::{CargoResult, CargoResultExt};
7use crate::util::machine_message::{self, Message};
8use crate::util::{self, internal, paths, profile};
9use cargo_platform::Cfg;
10use std::collections::hash_map::{Entry, HashMap};
11use std::collections::{BTreeSet, HashSet};
12use std::path::{Path, PathBuf};
13use std::str;
14use std::sync::{Arc, Mutex};
15
16const CARGO_WARNING: &str = "cargo:warning=";
17
18#[derive(Clone, Debug, Hash, Default)]
20pub struct BuildOutput {
21 pub library_paths: Vec<PathBuf>,
23 pub library_links: Vec<String>,
25 pub linker_args: Vec<String>,
27 pub cfgs: Vec<String>,
29 pub env: Vec<(String, String)>,
31 pub metadata: Vec<(String, String)>,
33 pub rerun_if_changed: Vec<PathBuf>,
36 pub rerun_if_env_changed: Vec<String>,
38 pub warnings: Vec<String>,
40}
41
42#[derive(Default)]
53pub struct BuildScriptOutputs {
54 outputs: HashMap<(PackageId, Metadata), BuildOutput>,
55}
56
57#[derive(Default)]
61pub struct BuildScripts {
62 pub to_link: Vec<(PackageId, Metadata)>,
79 seen_to_link: HashSet<(PackageId, Metadata)>,
81 pub plugins: BTreeSet<(PackageId, Metadata)>,
90}
91
92#[derive(Debug)]
94pub struct BuildDeps {
95 pub build_script_output: PathBuf,
98 pub rerun_if_changed: Vec<PathBuf>,
100 pub rerun_if_env_changed: Vec<String>,
102}
103
104pub fn prepare<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult<Job> {
106 let _p = profile::start(format!(
107 "build script prepare: {}/{}",
108 unit.pkg,
109 unit.target.name()
110 ));
111
112 let metadata = cx.get_run_build_script_metadata(unit);
113 if cx
114 .build_script_outputs
115 .lock()
116 .unwrap()
117 .contains_key(unit.pkg.package_id(), metadata)
118 {
119 fingerprint::prepare_target(cx, unit, false)
121 } else {
122 build_work(cx, unit)
123 }
124}
125
126fn emit_build_output(
127 state: &JobState<'_>,
128 output: &BuildOutput,
129 out_dir: &Path,
130 package_id: PackageId,
131) {
132 let library_paths = output
133 .library_paths
134 .iter()
135 .map(|l| l.display().to_string())
136 .collect::<Vec<_>>();
137
138 let msg = machine_message::BuildScript {
139 package_id,
140 linked_libs: &output.library_links,
141 linked_paths: &library_paths,
142 cfgs: &output.cfgs,
143 env: &output.env,
144 out_dir,
145 }
146 .to_json_string();
147 state.stdout(msg);
148}
149
150fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult<Job> {
151 assert!(unit.mode.is_run_custom_build());
152 let bcx = &cx.bcx;
153 let dependencies = cx.unit_deps(unit);
154 let build_script_unit = dependencies
155 .iter()
156 .find(|d| !d.unit.mode.is_run_custom_build() && d.unit.target.is_custom_build())
157 .map(|d| &d.unit)
158 .expect("running a script not depending on an actual script");
159 let script_dir = cx.files().build_script_dir(build_script_unit);
160 let script_out_dir = cx.files().build_script_out_dir(unit);
161 let script_run_dir = cx.files().build_script_run_dir(unit);
162 let build_plan = bcx.build_config.build_plan;
163 let invocation_name = unit.buildkey();
164
165 if let Some(deps) = unit.pkg.manifest().metabuild() {
166 prepare_metabuild(cx, build_script_unit, deps)?;
167 }
168
169 let to_exec = script_dir.join(unit.target.name());
171
172 let to_exec = to_exec.into_os_string();
180 let mut cmd = cx.compilation.host_process(to_exec, unit.pkg)?;
181 let debug = unit.profile.debuginfo.unwrap_or(0) != 0;
182 cmd.env("OUT_DIR", &script_out_dir)
183 .env("CARGO_MANIFEST_DIR", unit.pkg.root())
184 .env("NUM_JOBS", &bcx.jobs().to_string())
185 .env("TARGET", bcx.target_data.short_name(&unit.kind))
186 .env("DEBUG", debug.to_string())
187 .env("OPT_LEVEL", &unit.profile.opt_level.to_string())
188 .env(
189 "PROFILE",
190 match unit.profile.root {
191 ProfileRoot::Release => "release",
192 ProfileRoot::Debug => "debug",
193 },
194 )
195 .env("HOST", &bcx.host_triple())
196 .env("RUSTC", &bcx.rustc().path)
197 .env("RUSTDOC", &*bcx.config.rustdoc()?)
198 .inherit_jobserver(&cx.jobserver);
199
200 if let Some(linker) = &bcx.target_data.target_config(unit.kind).linker {
201 cmd.env(
202 "RUSTC_LINKER",
203 linker.val.clone().resolve_program(bcx.config),
204 );
205 }
206
207 if let Some(links) = unit.pkg.manifest().links() {
208 cmd.env("CARGO_MANIFEST_LINKS", links);
209 }
210
211 for feat in &unit.features {
214 cmd.env(&format!("CARGO_FEATURE_{}", super::envify(feat)), "1");
215 }
216
217 let mut cfg_map = HashMap::new();
218 for cfg in bcx.target_data.cfg(unit.kind) {
219 match *cfg {
220 Cfg::Name(ref n) => {
221 cfg_map.insert(n.clone(), None);
222 }
223 Cfg::KeyPair(ref k, ref v) => {
224 if let Some(ref mut values) =
225 *cfg_map.entry(k.clone()).or_insert_with(|| Some(Vec::new()))
226 {
227 values.push(v.clone())
228 }
229 }
230 }
231 }
232 for (k, v) in cfg_map {
233 if k == "debug_assertions" {
234 continue;
237 }
238 let k = format!("CARGO_CFG_{}", super::envify(&k));
239 match v {
240 Some(list) => {
241 cmd.env(&k, list.join(","));
242 }
243 None => {
244 cmd.env(&k, "");
245 }
246 }
247 }
248
249 let lib_deps = dependencies
255 .iter()
256 .filter_map(|dep| {
257 if dep.unit.mode.is_run_custom_build() {
258 let dep_metadata = cx.get_run_build_script_metadata(&dep.unit);
259 Some((
260 dep.unit.pkg.manifest().links().unwrap().to_string(),
261 dep.unit.pkg.package_id(),
262 dep_metadata,
263 ))
264 } else {
265 None
266 }
267 })
268 .collect::<Vec<_>>();
269 let pkg_name = unit.pkg.to_string();
270 let build_script_outputs = Arc::clone(&cx.build_script_outputs);
271 let id = unit.pkg.package_id();
272 let output_file = script_run_dir.join("output");
273 let err_file = script_run_dir.join("stderr");
274 let root_output_file = script_run_dir.join("root-output");
275 let host_target_root = cx.files().host_root().to_path_buf();
276 let all = (
277 id,
278 pkg_name.clone(),
279 Arc::clone(&build_script_outputs),
280 output_file.clone(),
281 script_out_dir.clone(),
282 );
283 let build_scripts = cx.build_scripts.get(unit).cloned();
284 let json_messages = bcx.build_config.emit_json();
285 let extra_verbose = bcx.config.extra_verbose();
286 let (prev_output, prev_script_out_dir) = prev_build_output(cx, unit);
287 let metadata_hash = cx.get_run_build_script_metadata(unit);
288
289 paths::create_dir_all(&script_dir)?;
290 paths::create_dir_all(&script_out_dir)?;
291
292 let dirty = Work::new(move |state| {
298 paths::create_dir_all(&script_out_dir)
303 .chain_err(|| "failed to create script output directory for build command")?;
304
305 if !build_plan {
310 let build_script_outputs = build_script_outputs.lock().unwrap();
311 for (name, dep_id, dep_metadata) in lib_deps {
312 let script_output =
313 build_script_outputs
314 .get(dep_id, dep_metadata)
315 .ok_or_else(|| {
316 internal(format!(
317 "failed to locate build state for env vars: {}/{}",
318 dep_id, dep_metadata
319 ))
320 })?;
321 let data = &script_output.metadata;
322 for &(ref key, ref value) in data.iter() {
323 cmd.env(
324 &format!("DEP_{}_{}", super::envify(&name), super::envify(key)),
325 value,
326 );
327 }
328 }
329 if let Some(build_scripts) = build_scripts {
330 super::add_plugin_deps(
331 &mut cmd,
332 &build_script_outputs,
333 &build_scripts,
334 &host_target_root,
335 )?;
336 }
337 }
338
339 if build_plan {
340 state.build_plan(invocation_name, cmd.clone(), Arc::new(Vec::new()));
341 return Ok(());
342 }
343
344 state.running(&cmd);
346 let timestamp = paths::set_invocation_time(&script_run_dir)?;
347 let prefix = format!("[{} {}] ", id.name(), id.version());
348 let mut warnings_in_case_of_panic = Vec::new();
349 let output = cmd
350 .exec_with_streaming(
351 &mut |stdout| {
352 if stdout.starts_with(CARGO_WARNING) {
353 warnings_in_case_of_panic.push(stdout[CARGO_WARNING.len()..].to_owned());
354 }
355 if extra_verbose {
356 state.stdout(format!("{}{}", prefix, stdout));
357 }
358 Ok(())
359 },
360 &mut |stderr| {
361 if extra_verbose {
362 state.stderr(format!("{}{}", prefix, stderr));
363 }
364 Ok(())
365 },
366 true,
367 )
368 .chain_err(|| format!("failed to run custom build command for `{}`", pkg_name));
369
370 if let Err(error) = output {
371 insert_warnings_in_build_outputs(
372 build_script_outputs,
373 id,
374 metadata_hash,
375 warnings_in_case_of_panic,
376 );
377 return Err(error);
378 }
379
380 let output = output.unwrap();
381
382 paths::write(&output_file, &output.stdout)?;
390 log::debug!(
391 "rewinding custom script output mtime {:?} to {}",
392 output_file,
393 timestamp
394 );
395 filetime::set_file_times(output_file, timestamp, timestamp)?;
396 paths::write(&err_file, &output.stderr)?;
397 paths::write(&root_output_file, util::path2bytes(&script_out_dir)?)?;
398 let parsed_output =
399 BuildOutput::parse(&output.stdout, &pkg_name, &script_out_dir, &script_out_dir)?;
400
401 if json_messages {
402 emit_build_output(state, &parsed_output, script_out_dir.as_path(), id);
403 }
404 build_script_outputs
405 .lock()
406 .unwrap()
407 .insert(id, metadata_hash, parsed_output);
408 Ok(())
409 });
410
411 let fresh = Work::new(move |state| {
415 let (id, pkg_name, build_script_outputs, output_file, script_out_dir) = all;
416 let output = match prev_output {
417 Some(output) => output,
418 None => BuildOutput::parse_file(
419 &output_file,
420 &pkg_name,
421 &prev_script_out_dir,
422 &script_out_dir,
423 )?,
424 };
425
426 if json_messages {
427 emit_build_output(state, &output, script_out_dir.as_path(), id);
428 }
429
430 build_script_outputs
431 .lock()
432 .unwrap()
433 .insert(id, metadata_hash, output);
434 Ok(())
435 });
436
437 let mut job = if cx.bcx.build_config.build_plan {
438 Job::new(Work::noop(), Freshness::Dirty)
439 } else {
440 fingerprint::prepare_target(cx, unit, false)?
441 };
442 if job.freshness() == Freshness::Dirty {
443 job.before(dirty);
444 } else {
445 job.before(fresh);
446 }
447 Ok(job)
448}
449
450fn insert_warnings_in_build_outputs(
451 build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
452 id: PackageId,
453 metadata_hash: Metadata,
454 warnings: Vec<String>,
455) {
456 let build_output_with_only_warnings = BuildOutput {
457 warnings,
458 ..BuildOutput::default()
459 };
460 build_script_outputs
461 .lock()
462 .unwrap()
463 .insert(id, metadata_hash, build_output_with_only_warnings);
464}
465
466impl BuildOutput {
467 pub fn parse_file(
468 path: &Path,
469 pkg_name: &str,
470 script_out_dir_when_generated: &Path,
471 script_out_dir: &Path,
472 ) -> CargoResult<BuildOutput> {
473 let contents = paths::read_bytes(path)?;
474 BuildOutput::parse(
475 &contents,
476 pkg_name,
477 script_out_dir_when_generated,
478 script_out_dir,
479 )
480 }
481
482 pub fn parse(
485 input: &[u8],
486 pkg_name: &str,
487 script_out_dir_when_generated: &Path,
488 script_out_dir: &Path,
489 ) -> CargoResult<BuildOutput> {
490 let mut library_paths = Vec::new();
491 let mut library_links = Vec::new();
492 let mut linker_args = Vec::new();
493 let mut cfgs = Vec::new();
494 let mut env = Vec::new();
495 let mut metadata = Vec::new();
496 let mut rerun_if_changed = Vec::new();
497 let mut rerun_if_env_changed = Vec::new();
498 let mut warnings = Vec::new();
499 let whence = format!("build script of `{}`", pkg_name);
500
501 for line in input.split(|b| *b == b'\n') {
502 let line = match str::from_utf8(line) {
503 Ok(line) => line.trim(),
504 Err(..) => continue,
505 };
506 let mut iter = line.splitn(2, ':');
507 if iter.next() != Some("cargo") {
508 continue;
510 }
511 let data = match iter.next() {
512 Some(val) => val,
513 None => continue,
514 };
515
516 let mut iter = data.splitn(2, '=');
518 let key = iter.next();
519 let value = iter.next();
520 let (key, value) = match (key, value) {
521 (Some(a), Some(b)) => (a, b.trim_end()),
522 _ => anyhow::bail!("Wrong output in {}: `{}`", whence, line),
524 };
525
526 let value = value.replace(
528 script_out_dir_when_generated.to_str().unwrap(),
529 script_out_dir.to_str().unwrap(),
530 );
531
532 match key {
534 "rustc-flags" => {
535 let (paths, links) = BuildOutput::parse_rustc_flags(&value, &whence)?;
536 library_links.extend(links.into_iter());
537 library_paths.extend(paths.into_iter());
538 }
539 "rustc-link-lib" => library_links.push(value.to_string()),
540 "rustc-link-search" => library_paths.push(PathBuf::from(value)),
541 "rustc-cdylib-link-arg" => linker_args.push(value.to_string()),
542 "rustc-cfg" => cfgs.push(value.to_string()),
543 "rustc-env" => env.push(BuildOutput::parse_rustc_env(&value, &whence)?),
544 "warning" => warnings.push(value.to_string()),
545 "rerun-if-changed" => rerun_if_changed.push(PathBuf::from(value)),
546 "rerun-if-env-changed" => rerun_if_env_changed.push(value.to_string()),
547 _ => metadata.push((key.to_string(), value.to_string())),
548 }
549 }
550
551 Ok(BuildOutput {
552 library_paths,
553 library_links,
554 linker_args,
555 cfgs,
556 env,
557 metadata,
558 rerun_if_changed,
559 rerun_if_env_changed,
560 warnings,
561 })
562 }
563
564 pub fn parse_rustc_flags(
565 value: &str,
566 whence: &str,
567 ) -> CargoResult<(Vec<PathBuf>, Vec<String>)> {
568 let value = value.trim();
569 let mut flags_iter = value
570 .split(|c: char| c.is_whitespace())
571 .filter(|w| w.chars().any(|c| !c.is_whitespace()));
572 let (mut library_paths, mut library_links) = (Vec::new(), Vec::new());
573
574 while let Some(flag) = flags_iter.next() {
575 if flag.starts_with("-l") || flag.starts_with("-L") {
576 let (flag, mut value) = flag.split_at(2);
580 if value.is_empty() {
581 value = match flags_iter.next() {
582 Some(v) => v,
583 None => anyhow::bail! {
584 "Flag in rustc-flags has no value in {}: {}",
585 whence,
586 value
587 },
588 }
589 }
590
591 match flag {
592 "-l" => library_links.push(value.to_string()),
593 "-L" => library_paths.push(PathBuf::from(value)),
594
595 _ => unreachable!(),
597 };
598 } else {
599 anyhow::bail!(
600 "Only `-l` and `-L` flags are allowed in {}: `{}`",
601 whence,
602 value
603 )
604 }
605 }
606 Ok((library_paths, library_links))
607 }
608
609 pub fn parse_rustc_env(value: &str, whence: &str) -> CargoResult<(String, String)> {
610 let mut iter = value.splitn(2, '=');
611 let name = iter.next();
612 let val = iter.next();
613 match (name, val) {
614 (Some(n), Some(v)) => Ok((n.to_owned(), v.to_owned())),
615 _ => anyhow::bail!("Variable rustc-env has no value in {}: {}", whence, value),
616 }
617 }
618}
619
620fn prepare_metabuild<'a, 'cfg>(
621 cx: &Context<'a, 'cfg>,
622 unit: &Unit<'a>,
623 deps: &[String],
624) -> CargoResult<()> {
625 let mut output = Vec::new();
626 let available_deps = cx.unit_deps(unit);
627 let meta_deps: Vec<_> = deps
629 .iter()
630 .filter_map(|name| {
631 available_deps
632 .iter()
633 .find(|d| d.unit.pkg.name().as_str() == name.as_str())
634 .map(|d| d.unit.target.crate_name())
635 })
636 .collect();
637 for dep in &meta_deps {
638 output.push(format!("use {};\n", dep));
639 }
640 output.push("fn main() {\n".to_string());
641 for dep in &meta_deps {
642 output.push(format!(" {}::metabuild();\n", dep));
643 }
644 output.push("}\n".to_string());
645 let output = output.join("");
646 let path = unit.pkg.manifest().metabuild_path(cx.bcx.ws.target_dir());
647 paths::create_dir_all(path.parent().unwrap())?;
648 paths::write_if_changed(path, &output)?;
649 Ok(())
650}
651
652impl BuildDeps {
653 pub fn new(output_file: &Path, output: Option<&BuildOutput>) -> BuildDeps {
654 BuildDeps {
655 build_script_output: output_file.to_path_buf(),
656 rerun_if_changed: output
657 .map(|p| &p.rerun_if_changed)
658 .cloned()
659 .unwrap_or_default(),
660 rerun_if_env_changed: output
661 .map(|p| &p.rerun_if_env_changed)
662 .cloned()
663 .unwrap_or_default(),
664 }
665 }
666}
667
668pub fn build_map<'b, 'cfg>(cx: &mut Context<'b, 'cfg>, units: &[Unit<'b>]) -> CargoResult<()> {
685 let mut ret = HashMap::new();
686 for unit in units {
687 build(&mut ret, cx, unit)?;
688 }
689 cx.build_scripts
690 .extend(ret.into_iter().map(|(k, v)| (k, Arc::new(v))));
691 return Ok(());
692
693 fn build<'a, 'b, 'cfg>(
696 out: &'a mut HashMap<Unit<'b>, BuildScripts>,
697 cx: &mut Context<'b, 'cfg>,
698 unit: &Unit<'b>,
699 ) -> CargoResult<&'a BuildScripts> {
700 if out.contains_key(unit) {
703 return Ok(&out[unit]);
704 }
705
706 if unit.mode.is_run_custom_build() {
708 if let Some(links) = unit.pkg.manifest().links() {
709 if let Some(output) = cx.bcx.script_override(links, unit.kind) {
710 let metadata = cx.get_run_build_script_metadata(unit);
711 cx.build_script_outputs.lock().unwrap().insert(
712 unit.pkg.package_id(),
713 metadata,
714 output.clone(),
715 );
716 }
717 }
718 }
719
720 let mut ret = BuildScripts::default();
721
722 if !unit.target.is_custom_build() && unit.pkg.has_custom_build() {
724 let script_meta = cx
725 .find_build_script_metadata(*unit)
726 .expect("has_custom_build should have RunCustomBuild");
727 add_to_link(&mut ret, unit.pkg.package_id(), script_meta);
728 }
729
730 if unit.mode.is_run_custom_build() {
732 parse_previous_explicit_deps(cx, unit)?;
733 }
734
735 let mut dependencies: Vec<Unit<'_>> = cx.unit_deps(unit).iter().map(|d| d.unit).collect();
740 dependencies.sort_by_key(|u| u.pkg.package_id());
741
742 for dep_unit in dependencies.iter() {
743 let dep_scripts = build(out, cx, dep_unit)?;
744
745 if dep_unit.target.for_host() {
746 ret.plugins.extend(dep_scripts.to_link.iter().cloned());
747 } else if dep_unit.target.linkable() {
748 for &(pkg, metadata) in dep_scripts.to_link.iter() {
749 add_to_link(&mut ret, pkg, metadata);
750 }
751 }
752 }
753
754 match out.entry(*unit) {
755 Entry::Vacant(entry) => Ok(entry.insert(ret)),
756 Entry::Occupied(_) => panic!("cyclic dependencies in `build_map`"),
757 }
758 }
759
760 fn add_to_link(scripts: &mut BuildScripts, pkg: PackageId, metadata: Metadata) {
763 if scripts.seen_to_link.insert((pkg, metadata)) {
764 scripts.to_link.push((pkg, metadata));
765 }
766 }
767
768 fn parse_previous_explicit_deps<'a, 'cfg>(
769 cx: &mut Context<'a, 'cfg>,
770 unit: &Unit<'a>,
771 ) -> CargoResult<()> {
772 let script_run_dir = cx.files().build_script_run_dir(unit);
773 let output_file = script_run_dir.join("output");
774 let (prev_output, _) = prev_build_output(cx, unit);
775 let deps = BuildDeps::new(&output_file, prev_output.as_ref());
776 cx.build_explicit_deps.insert(*unit, deps);
777 Ok(())
778 }
779}
780
781fn prev_build_output<'a, 'cfg>(
787 cx: &mut Context<'a, 'cfg>,
788 unit: &Unit<'a>,
789) -> (Option<BuildOutput>, PathBuf) {
790 let script_out_dir = cx.files().build_script_out_dir(unit);
791 let script_run_dir = cx.files().build_script_run_dir(unit);
792 let root_output_file = script_run_dir.join("root-output");
793 let output_file = script_run_dir.join("output");
794
795 let prev_script_out_dir = paths::read_bytes(&root_output_file)
796 .and_then(|bytes| util::bytes2path(&bytes))
797 .unwrap_or_else(|_| script_out_dir.clone());
798
799 (
800 BuildOutput::parse_file(
801 &output_file,
802 &unit.pkg.to_string(),
803 &prev_script_out_dir,
804 &script_out_dir,
805 )
806 .ok(),
807 prev_script_out_dir,
808 )
809}
810
811impl BuildScriptOutputs {
812 fn insert(&mut self, pkg_id: PackageId, metadata: Metadata, parsed_output: BuildOutput) {
814 match self.outputs.entry((pkg_id, metadata)) {
815 Entry::Vacant(entry) => {
816 entry.insert(parsed_output);
817 }
818 Entry::Occupied(entry) => panic!(
819 "build script output collision for {}/{}\n\
820 old={:?}\nnew={:?}",
821 pkg_id,
822 metadata,
823 entry.get(),
824 parsed_output
825 ),
826 }
827 }
828
829 fn contains_key(&self, pkg_id: PackageId, metadata: Metadata) -> bool {
831 self.outputs.contains_key(&(pkg_id, metadata))
832 }
833
834 pub fn get(&self, pkg_id: PackageId, meta: Metadata) -> Option<&BuildOutput> {
836 self.outputs.get(&(pkg_id, meta))
837 }
838
839 pub fn iter(&self) -> impl Iterator<Item = (PackageId, &BuildOutput)> {
841 self.outputs.iter().map(|(key, value)| (key.0, value))
842 }
843}