cargo/core/compiler/
mod.rs

1//! # Interact with the compiler
2//!
3//! If you consider [`ops::cargo_compile::compile`] as a `rustc` driver but on
4//! Cargo side, this module is kinda the `rustc_interface` for that merits.
5//! It contains all the interaction between Cargo and the rustc compiler,
6//! from preparing the context for the entire build process, to scheduling
7//! and executing each unit of work (e.g. running `rustc`), to managing and
8//! caching the output artifact of a build.
9//!
10//! However, it hasn't yet exposed a clear definition of each phase or session,
11//! like what rustc has done[^1]. Also, no one knows if Cargo really needs that.
12//! To be pragmatic, here we list a handful of items you may want to learn:
13//!
14//! * [`BuildContext`] is a static context containing all information you need
15//!   before a build gets started.
16//! * [`BuildRunner`] is the center of the world, coordinating a running build and
17//!   collecting information from it.
18//! * [`custom_build`] is the home of build script executions and output parsing.
19//! * [`fingerprint`] not only defines but also executes a set of rules to
20//!   determine if a re-compile is needed.
21//! * [`job_queue`] is where the parallelism, job scheduling, and communication
22//!   machinery happen between Cargo and the compiler.
23//! * [`layout`] defines and manages output artifacts of a build in the filesystem.
24//! * [`unit_dependencies`] is for building a dependency graph for compilation
25//!   from a result of dependency resolution.
26//! * [`Unit`] contains sufficient information to build something, usually
27//!   turning into a compiler invocation in a later phase.
28//!
29//! [^1]: Maybe [`-Zbuild-plan`](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-plan)
30//!   was designed to serve that purpose but still [in flux](https://github.com/rust-lang/cargo/issues/7614).
31//!
32//! [`ops::cargo_compile::compile`]: crate::ops::compile
33
34pub mod artifact;
35mod build_config;
36pub(crate) mod build_context;
37mod build_plan;
38pub(crate) mod build_runner;
39mod compilation;
40mod compile_kind;
41mod crate_type;
42mod custom_build;
43pub(crate) mod fingerprint;
44pub mod future_incompat;
45pub(crate) mod job_queue;
46pub(crate) mod layout;
47mod links;
48mod lto;
49mod output_depinfo;
50mod output_sbom;
51pub mod rustdoc;
52pub mod standard_lib;
53mod timings;
54mod unit;
55pub mod unit_dependencies;
56pub mod unit_graph;
57
58use std::borrow::Cow;
59use std::collections::{HashMap, HashSet};
60use std::env;
61use std::ffi::{OsStr, OsString};
62use std::fmt::Display;
63use std::fs::{self, File};
64use std::io::{BufRead, BufWriter, Write};
65use std::path::{Path, PathBuf};
66use std::sync::Arc;
67
68use anyhow::{Context as _, Error};
69use lazycell::LazyCell;
70use tracing::{debug, trace};
71
72pub use self::build_config::{BuildConfig, CompileMode, MessageFormat, TimingOutput};
73pub use self::build_context::{
74    BuildContext, FileFlavor, FileType, RustDocFingerprint, RustcTargetData, TargetInfo,
75};
76use self::build_plan::BuildPlan;
77pub use self::build_runner::{BuildRunner, Metadata, UnitHash};
78pub use self::compilation::{Compilation, Doctest, UnitOutput};
79pub use self::compile_kind::{CompileKind, CompileKindFallback, CompileTarget};
80pub use self::crate_type::CrateType;
81pub use self::custom_build::LinkArgTarget;
82pub use self::custom_build::{BuildOutput, BuildScriptOutputs, BuildScripts};
83pub(crate) use self::fingerprint::DirtyReason;
84pub use self::job_queue::Freshness;
85use self::job_queue::{Job, JobQueue, JobState, Work};
86pub(crate) use self::layout::Layout;
87pub use self::lto::Lto;
88use self::output_depinfo::output_depinfo;
89use self::output_sbom::build_sbom;
90use self::unit_graph::UnitDep;
91use crate::core::compiler::future_incompat::FutureIncompatReport;
92pub use crate::core::compiler::unit::{Unit, UnitInterner};
93use crate::core::manifest::TargetSourcePath;
94use crate::core::profiles::{PanicStrategy, Profile, StripInner};
95use crate::core::{Feature, PackageId, Target, Verbosity};
96use crate::util::context::WarningHandling;
97use crate::util::errors::{CargoResult, VerboseError};
98use crate::util::interning::InternedString;
99use crate::util::machine_message::{self, Message};
100use crate::util::{add_path_args, internal};
101use cargo_util::{paths, ProcessBuilder, ProcessError};
102use cargo_util_schemas::manifest::TomlDebugInfo;
103use cargo_util_schemas::manifest::TomlTrimPaths;
104use cargo_util_schemas::manifest::TomlTrimPathsValue;
105use rustfix::diagnostics::Applicability;
106
107const RUSTDOC_CRATE_VERSION_FLAG: &str = "--crate-version";
108
109/// A glorified callback for executing calls to rustc. Rather than calling rustc
110/// directly, we'll use an `Executor`, giving clients an opportunity to intercept
111/// the build calls.
112pub trait Executor: Send + Sync + 'static {
113    /// Called after a rustc process invocation is prepared up-front for a given
114    /// unit of work (may still be modified for runtime-known dependencies, when
115    /// the work is actually executed).
116    fn init(&self, _build_runner: &BuildRunner<'_, '_>, _unit: &Unit) {}
117
118    /// In case of an `Err`, Cargo will not continue with the build process for
119    /// this package.
120    fn exec(
121        &self,
122        cmd: &ProcessBuilder,
123        id: PackageId,
124        target: &Target,
125        mode: CompileMode,
126        on_stdout_line: &mut dyn FnMut(&str) -> CargoResult<()>,
127        on_stderr_line: &mut dyn FnMut(&str) -> CargoResult<()>,
128    ) -> CargoResult<()>;
129
130    /// Queried when queuing each unit of work. If it returns true, then the
131    /// unit will always be rebuilt, independent of whether it needs to be.
132    fn force_rebuild(&self, _unit: &Unit) -> bool {
133        false
134    }
135}
136
137/// A `DefaultExecutor` calls rustc without doing anything else. It is Cargo's
138/// default behaviour.
139#[derive(Copy, Clone)]
140pub struct DefaultExecutor;
141
142impl Executor for DefaultExecutor {
143    fn exec(
144        &self,
145        cmd: &ProcessBuilder,
146        _id: PackageId,
147        _target: &Target,
148        _mode: CompileMode,
149        on_stdout_line: &mut dyn FnMut(&str) -> CargoResult<()>,
150        on_stderr_line: &mut dyn FnMut(&str) -> CargoResult<()>,
151    ) -> CargoResult<()> {
152        cmd.exec_with_streaming(on_stdout_line, on_stderr_line, false)
153            .map(drop)
154    }
155}
156
157/// Builds up and enqueue a list of pending jobs onto the `job` queue.
158///
159/// Starting from the `unit`, this function recursively calls itself to build
160/// all jobs for dependencies of the `unit`. Each of these jobs represents
161/// compiling a particular package.
162///
163/// Note that **no actual work is executed as part of this**, that's all done
164/// next as part of [`JobQueue::execute`] function which will run everything
165/// in order with proper parallelism.
166#[tracing::instrument(skip(build_runner, jobs, plan, exec))]
167fn compile<'gctx>(
168    build_runner: &mut BuildRunner<'_, 'gctx>,
169    jobs: &mut JobQueue<'gctx>,
170    plan: &mut BuildPlan,
171    unit: &Unit,
172    exec: &Arc<dyn Executor>,
173    force_rebuild: bool,
174) -> CargoResult<()> {
175    let bcx = build_runner.bcx;
176    let build_plan = bcx.build_config.build_plan;
177    if !build_runner.compiled.insert(unit.clone()) {
178        return Ok(());
179    }
180
181    // Build up the work to be done to compile this unit, enqueuing it once
182    // we've got everything constructed.
183    fingerprint::prepare_init(build_runner, unit)?;
184
185    let job = if unit.mode.is_run_custom_build() {
186        custom_build::prepare(build_runner, unit)?
187    } else if unit.mode.is_doc_test() {
188        // We run these targets later, so this is just a no-op for now.
189        Job::new_fresh()
190    } else if build_plan {
191        Job::new_dirty(
192            rustc(build_runner, unit, &exec.clone())?,
193            DirtyReason::FreshBuild,
194        )
195    } else {
196        let force = exec.force_rebuild(unit) || force_rebuild;
197        let mut job = fingerprint::prepare_target(build_runner, unit, force)?;
198        job.before(if job.freshness().is_dirty() {
199            let work = if unit.mode.is_doc() || unit.mode.is_doc_scrape() {
200                rustdoc(build_runner, unit)?
201            } else {
202                rustc(build_runner, unit, exec)?
203            };
204            work.then(link_targets(build_runner, unit, false)?)
205        } else {
206            // We always replay the output cache,
207            // since it might contain future-incompat-report messages
208            let show_diagnostics = unit.show_warnings(bcx.gctx)
209                && build_runner.bcx.gctx.warning_handling()? != WarningHandling::Allow;
210            let work = replay_output_cache(
211                unit.pkg.package_id(),
212                PathBuf::from(unit.pkg.manifest_path()),
213                &unit.target,
214                build_runner.files().message_cache_path(unit),
215                build_runner.bcx.build_config.message_format,
216                show_diagnostics,
217            );
218            // Need to link targets on both the dirty and fresh.
219            work.then(link_targets(build_runner, unit, true)?)
220        });
221
222        job
223    };
224    jobs.enqueue(build_runner, unit, job)?;
225
226    // Be sure to compile all dependencies of this target as well.
227    let deps = Vec::from(build_runner.unit_deps(unit)); // Create vec due to mutable borrow.
228    for dep in deps {
229        compile(build_runner, jobs, plan, &dep.unit, exec, false)?;
230    }
231    if build_plan {
232        plan.add(build_runner, unit)?;
233    }
234
235    Ok(())
236}
237
238/// Generates the warning message used when fallible doc-scrape units fail,
239/// either for rustdoc or rustc.
240fn make_failed_scrape_diagnostic(
241    build_runner: &BuildRunner<'_, '_>,
242    unit: &Unit,
243    top_line: impl Display,
244) -> String {
245    let manifest_path = unit.pkg.manifest_path();
246    let relative_manifest_path = manifest_path
247        .strip_prefix(build_runner.bcx.ws.root())
248        .unwrap_or(&manifest_path);
249
250    format!(
251        "\
252{top_line}
253    Try running with `--verbose` to see the error message.
254    If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in {}",
255        relative_manifest_path.display()
256    )
257}
258
259/// Creates a unit of work invoking `rustc` for building the `unit`.
260fn rustc(
261    build_runner: &mut BuildRunner<'_, '_>,
262    unit: &Unit,
263    exec: &Arc<dyn Executor>,
264) -> CargoResult<Work> {
265    let mut rustc = prepare_rustc(build_runner, unit)?;
266    let build_plan = build_runner.bcx.build_config.build_plan;
267
268    let name = unit.pkg.name();
269    let buildkey = unit.buildkey();
270
271    let outputs = build_runner.outputs(unit)?;
272    let root = build_runner.files().out_dir(unit);
273
274    // Prepare the native lib state (extra `-L` and `-l` flags).
275    let build_script_outputs = Arc::clone(&build_runner.build_script_outputs);
276    let current_id = unit.pkg.package_id();
277    let manifest_path = PathBuf::from(unit.pkg.manifest_path());
278    let build_scripts = build_runner.build_scripts.get(unit).cloned();
279
280    // If we are a binary and the package also contains a library, then we
281    // don't pass the `-l` flags.
282    let pass_l_flag = unit.target.is_lib() || !unit.pkg.targets().iter().any(|t| t.is_lib());
283
284    let dep_info_name =
285        if let Some(c_extra_filename) = build_runner.files().metadata(unit).c_extra_filename() {
286            format!("{}-{}.d", unit.target.crate_name(), c_extra_filename)
287        } else {
288            format!("{}.d", unit.target.crate_name())
289        };
290    let rustc_dep_info_loc = root.join(dep_info_name);
291    let dep_info_loc = fingerprint::dep_info_loc(build_runner, unit);
292
293    let mut output_options = OutputOptions::new(build_runner, unit);
294    let package_id = unit.pkg.package_id();
295    let target = Target::clone(&unit.target);
296    let mode = unit.mode;
297
298    exec.init(build_runner, unit);
299    let exec = exec.clone();
300
301    let root_output = build_runner.files().host_dest().to_path_buf();
302    let build_dir = build_runner.bcx.ws.build_dir().into_path_unlocked();
303    let pkg_root = unit.pkg.root().to_path_buf();
304    let cwd = rustc
305        .get_cwd()
306        .unwrap_or_else(|| build_runner.bcx.gctx.cwd())
307        .to_path_buf();
308    let fingerprint_dir = build_runner.files().fingerprint_dir(unit);
309    let script_metadata = build_runner.find_build_script_metadata(unit);
310    let is_local = unit.is_local();
311    let artifact = unit.artifact;
312    let sbom_files = build_runner.sbom_output_files(unit)?;
313    let sbom = build_sbom(build_runner, unit)?;
314
315    let hide_diagnostics_for_scrape_unit = build_runner.bcx.unit_can_fail_for_docscraping(unit)
316        && !matches!(
317            build_runner.bcx.gctx.shell().verbosity(),
318            Verbosity::Verbose
319        );
320    let failed_scrape_diagnostic = hide_diagnostics_for_scrape_unit.then(|| {
321        // If this unit is needed for doc-scraping, then we generate a diagnostic that
322        // describes the set of reverse-dependencies that cause the unit to be needed.
323        let target_desc = unit.target.description_named();
324        let mut for_scrape_units = build_runner
325            .bcx
326            .scrape_units_have_dep_on(unit)
327            .into_iter()
328            .map(|unit| unit.target.description_named())
329            .collect::<Vec<_>>();
330        for_scrape_units.sort();
331        let for_scrape_units = for_scrape_units.join(", ");
332        make_failed_scrape_diagnostic(build_runner, unit, format_args!("failed to check {target_desc} in package `{name}` as a prerequisite for scraping examples from: {for_scrape_units}"))
333    });
334    if hide_diagnostics_for_scrape_unit {
335        output_options.show_diagnostics = false;
336    }
337    let env_config = Arc::clone(build_runner.bcx.gctx.env_config()?);
338    return Ok(Work::new(move |state| {
339        // Artifacts are in a different location than typical units,
340        // hence we must assure the crate- and target-dependent
341        // directory is present.
342        if artifact.is_true() {
343            paths::create_dir_all(&root)?;
344        }
345
346        // Only at runtime have we discovered what the extra -L and -l
347        // arguments are for native libraries, so we process those here. We
348        // also need to be sure to add any -L paths for our plugins to the
349        // dynamic library load path as a plugin's dynamic library may be
350        // located somewhere in there.
351        // Finally, if custom environment variables have been produced by
352        // previous build scripts, we include them in the rustc invocation.
353        if let Some(build_scripts) = build_scripts {
354            let script_outputs = build_script_outputs.lock().unwrap();
355            if !build_plan {
356                add_native_deps(
357                    &mut rustc,
358                    &script_outputs,
359                    &build_scripts,
360                    pass_l_flag,
361                    &target,
362                    current_id,
363                )?;
364                add_plugin_deps(&mut rustc, &script_outputs, &build_scripts, &root_output)?;
365            }
366            add_custom_flags(&mut rustc, &script_outputs, script_metadata)?;
367        }
368
369        for output in outputs.iter() {
370            // If there is both an rmeta and rlib, rustc will prefer to use the
371            // rlib, even if it is older. Therefore, we must delete the rlib to
372            // force using the new rmeta.
373            if output.path.extension() == Some(OsStr::new("rmeta")) {
374                let dst = root.join(&output.path).with_extension("rlib");
375                if dst.exists() {
376                    paths::remove_file(&dst)?;
377                }
378            }
379
380            // Some linkers do not remove the executable, but truncate and modify it.
381            // That results in the old hard-link being modified even after renamed.
382            // We delete the old artifact here to prevent this behavior from confusing users.
383            // See rust-lang/cargo#8348.
384            if output.hardlink.is_some() && output.path.exists() {
385                _ = paths::remove_file(&output.path).map_err(|e| {
386                    tracing::debug!(
387                        "failed to delete previous output file `{:?}`: {e:?}",
388                        output.path
389                    );
390                });
391            }
392        }
393
394        state.running(&rustc);
395        let timestamp = paths::set_invocation_time(&fingerprint_dir)?;
396        if build_plan {
397            state.build_plan(buildkey, rustc.clone(), outputs.clone());
398        } else {
399            for file in sbom_files {
400                tracing::debug!("writing sbom to {}", file.display());
401                let outfile = BufWriter::new(paths::create(&file)?);
402                serde_json::to_writer(outfile, &sbom)?;
403            }
404
405            let result = exec
406                .exec(
407                    &rustc,
408                    package_id,
409                    &target,
410                    mode,
411                    &mut |line| on_stdout_line(state, line, package_id, &target),
412                    &mut |line| {
413                        on_stderr_line(
414                            state,
415                            line,
416                            package_id,
417                            &manifest_path,
418                            &target,
419                            &mut output_options,
420                        )
421                    },
422                )
423                .map_err(|e| {
424                    if output_options.errors_seen == 0 {
425                        // If we didn't expect an error, do not require --verbose to fail.
426                        // This is intended to debug
427                        // https://github.com/rust-lang/crater/issues/733, where we are seeing
428                        // Cargo exit unsuccessfully while seeming to not show any errors.
429                        e
430                    } else {
431                        verbose_if_simple_exit_code(e)
432                    }
433                })
434                .with_context(|| {
435                    // adapted from rustc_errors/src/lib.rs
436                    let warnings = match output_options.warnings_seen {
437                        0 => String::new(),
438                        1 => "; 1 warning emitted".to_string(),
439                        count => format!("; {} warnings emitted", count),
440                    };
441                    let errors = match output_options.errors_seen {
442                        0 => String::new(),
443                        1 => " due to 1 previous error".to_string(),
444                        count => format!(" due to {} previous errors", count),
445                    };
446                    let name = descriptive_pkg_name(&name, &target, &mode);
447                    format!("could not compile {name}{errors}{warnings}")
448                });
449
450            if let Err(e) = result {
451                if let Some(diagnostic) = failed_scrape_diagnostic {
452                    state.warning(diagnostic);
453                }
454
455                return Err(e);
456            }
457
458            // Exec should never return with success *and* generate an error.
459            debug_assert_eq!(output_options.errors_seen, 0);
460        }
461
462        if rustc_dep_info_loc.exists() {
463            fingerprint::translate_dep_info(
464                &rustc_dep_info_loc,
465                &dep_info_loc,
466                &cwd,
467                &pkg_root,
468                &build_dir,
469                &rustc,
470                // Do not track source files in the fingerprint for registry dependencies.
471                is_local,
472                &env_config,
473            )
474            .with_context(|| {
475                internal(format!(
476                    "could not parse/generate dep info at: {}",
477                    rustc_dep_info_loc.display()
478                ))
479            })?;
480            // This mtime shift allows Cargo to detect if a source file was
481            // modified in the middle of the build.
482            paths::set_file_time_no_err(dep_info_loc, timestamp);
483        }
484
485        Ok(())
486    }));
487
488    // Add all relevant `-L` and `-l` flags from dependencies (now calculated and
489    // present in `state`) to the command provided.
490    fn add_native_deps(
491        rustc: &mut ProcessBuilder,
492        build_script_outputs: &BuildScriptOutputs,
493        build_scripts: &BuildScripts,
494        pass_l_flag: bool,
495        target: &Target,
496        current_id: PackageId,
497    ) -> CargoResult<()> {
498        for key in build_scripts.to_link.iter() {
499            let output = build_script_outputs.get(key.1).ok_or_else(|| {
500                internal(format!(
501                    "couldn't find build script output for {}/{}",
502                    key.0, key.1
503                ))
504            })?;
505            for path in output.library_paths.iter() {
506                rustc.arg("-L").arg(path);
507            }
508
509            if key.0 == current_id {
510                if pass_l_flag {
511                    for name in output.library_links.iter() {
512                        rustc.arg("-l").arg(name);
513                    }
514                }
515            }
516
517            for (lt, arg) in &output.linker_args {
518                // There was an unintentional change where cdylibs were
519                // allowed to be passed via transitive dependencies. This
520                // clause should have been kept in the `if` block above. For
521                // now, continue allowing it for cdylib only.
522                // See https://github.com/rust-lang/cargo/issues/9562
523                if lt.applies_to(target) && (key.0 == current_id || *lt == LinkArgTarget::Cdylib) {
524                    rustc.arg("-C").arg(format!("link-arg={}", arg));
525                }
526            }
527        }
528        Ok(())
529    }
530}
531
532fn verbose_if_simple_exit_code(err: Error) -> Error {
533    // If a signal on unix (`code == None`) or an abnormal termination
534    // on Windows (codes like `0xC0000409`), don't hide the error details.
535    match err
536        .downcast_ref::<ProcessError>()
537        .as_ref()
538        .and_then(|perr| perr.code)
539    {
540        Some(n) if cargo_util::is_simple_exit_code(n) => VerboseError::new(err).into(),
541        _ => err,
542    }
543}
544
545/// Link the compiled target (often of form `foo-{metadata_hash}`) to the
546/// final target. This must happen during both "Fresh" and "Compile".
547fn link_targets(
548    build_runner: &mut BuildRunner<'_, '_>,
549    unit: &Unit,
550    fresh: bool,
551) -> CargoResult<Work> {
552    let bcx = build_runner.bcx;
553    let outputs = build_runner.outputs(unit)?;
554    let export_dir = build_runner.files().export_dir();
555    let package_id = unit.pkg.package_id();
556    let manifest_path = PathBuf::from(unit.pkg.manifest_path());
557    let profile = unit.profile.clone();
558    let unit_mode = unit.mode;
559    let features = unit.features.iter().map(|s| s.to_string()).collect();
560    let json_messages = bcx.build_config.emit_json();
561    let executable = build_runner.get_executable(unit)?;
562    let mut target = Target::clone(&unit.target);
563    if let TargetSourcePath::Metabuild = target.src_path() {
564        // Give it something to serialize.
565        let path = unit
566            .pkg
567            .manifest()
568            .metabuild_path(build_runner.bcx.ws.build_dir());
569        target.set_src_path(TargetSourcePath::Path(path));
570    }
571
572    Ok(Work::new(move |state| {
573        // If we're a "root crate", e.g., the target of this compilation, then we
574        // hard link our outputs out of the `deps` directory into the directory
575        // above. This means that `cargo build` will produce binaries in
576        // `target/debug` which one probably expects.
577        let mut destinations = vec![];
578        for output in outputs.iter() {
579            let src = &output.path;
580            // This may have been a `cargo rustc` command which changes the
581            // output, so the source may not actually exist.
582            if !src.exists() {
583                continue;
584            }
585            let Some(dst) = output.hardlink.as_ref() else {
586                destinations.push(src.clone());
587                continue;
588            };
589            destinations.push(dst.clone());
590            paths::link_or_copy(src, dst)?;
591            if let Some(ref path) = output.export_path {
592                let export_dir = export_dir.as_ref().unwrap();
593                paths::create_dir_all(export_dir)?;
594
595                paths::link_or_copy(src, path)?;
596            }
597        }
598
599        if json_messages {
600            let debuginfo = match profile.debuginfo.into_inner() {
601                TomlDebugInfo::None => machine_message::ArtifactDebuginfo::Int(0),
602                TomlDebugInfo::Limited => machine_message::ArtifactDebuginfo::Int(1),
603                TomlDebugInfo::Full => machine_message::ArtifactDebuginfo::Int(2),
604                TomlDebugInfo::LineDirectivesOnly => {
605                    machine_message::ArtifactDebuginfo::Named("line-directives-only")
606                }
607                TomlDebugInfo::LineTablesOnly => {
608                    machine_message::ArtifactDebuginfo::Named("line-tables-only")
609                }
610            };
611            let art_profile = machine_message::ArtifactProfile {
612                opt_level: profile.opt_level.as_str(),
613                debuginfo: Some(debuginfo),
614                debug_assertions: profile.debug_assertions,
615                overflow_checks: profile.overflow_checks,
616                test: unit_mode.is_any_test(),
617            };
618
619            let msg = machine_message::Artifact {
620                package_id: package_id.to_spec(),
621                manifest_path,
622                target: &target,
623                profile: art_profile,
624                features,
625                filenames: destinations,
626                executable,
627                fresh,
628            }
629            .to_json_string();
630            state.stdout(msg)?;
631        }
632        Ok(())
633    }))
634}
635
636// For all plugin dependencies, add their -L paths (now calculated and present
637// in `build_script_outputs`) to the dynamic library load path for the command
638// to execute.
639fn add_plugin_deps(
640    rustc: &mut ProcessBuilder,
641    build_script_outputs: &BuildScriptOutputs,
642    build_scripts: &BuildScripts,
643    root_output: &Path,
644) -> CargoResult<()> {
645    let var = paths::dylib_path_envvar();
646    let search_path = rustc.get_env(var).unwrap_or_default();
647    let mut search_path = env::split_paths(&search_path).collect::<Vec<_>>();
648    for (pkg_id, metadata) in &build_scripts.plugins {
649        let output = build_script_outputs
650            .get(*metadata)
651            .ok_or_else(|| internal(format!("couldn't find libs for plugin dep {}", pkg_id)))?;
652        search_path.append(&mut filter_dynamic_search_path(
653            output.library_paths.iter(),
654            root_output,
655        ));
656    }
657    let search_path = paths::join_paths(&search_path, var)?;
658    rustc.env(var, &search_path);
659    Ok(())
660}
661
662// Determine paths to add to the dynamic search path from -L entries
663//
664// Strip off prefixes like "native=" or "framework=" and filter out directories
665// **not** inside our output directory since they are likely spurious and can cause
666// clashes with system shared libraries (issue #3366).
667fn filter_dynamic_search_path<'a, I>(paths: I, root_output: &Path) -> Vec<PathBuf>
668where
669    I: Iterator<Item = &'a PathBuf>,
670{
671    let mut search_path = vec![];
672    for dir in paths {
673        let dir = match dir.to_str().and_then(|s| s.split_once("=")) {
674            Some(("native" | "crate" | "dependency" | "framework" | "all", path)) => path.into(),
675            _ => dir.clone(),
676        };
677        if dir.starts_with(&root_output) {
678            search_path.push(dir);
679        } else {
680            debug!(
681                "Not including path {} in runtime library search path because it is \
682                 outside target root {}",
683                dir.display(),
684                root_output.display()
685            );
686        }
687    }
688    search_path
689}
690
691/// Prepares flags and environments we can compute for a `rustc` invocation
692/// before the job queue starts compiling any unit.
693///
694/// This builds a static view of the invocation. Flags depending on the
695/// completion of other units will be added later in runtime, such as flags
696/// from build scripts.
697fn prepare_rustc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<ProcessBuilder> {
698    let gctx = build_runner.bcx.gctx;
699    let is_primary = build_runner.is_primary_package(unit);
700    let is_workspace = build_runner.bcx.ws.is_member(&unit.pkg);
701
702    let mut base = build_runner
703        .compilation
704        .rustc_process(unit, is_primary, is_workspace)?;
705    build_base_args(build_runner, &mut base, unit)?;
706
707    base.inherit_jobserver(&build_runner.jobserver);
708    build_deps_args(&mut base, build_runner, unit)?;
709    add_cap_lints(build_runner.bcx, unit, &mut base);
710    if let Some(args) = build_runner.bcx.extra_args_for(unit) {
711        base.args(args);
712    }
713    base.args(&unit.rustflags);
714    if gctx.cli_unstable().binary_dep_depinfo {
715        base.arg("-Z").arg("binary-dep-depinfo");
716    }
717    if build_runner.bcx.gctx.cli_unstable().checksum_freshness {
718        base.arg("-Z").arg("checksum-hash-algorithm=blake3");
719    }
720
721    if is_primary {
722        base.env("CARGO_PRIMARY_PACKAGE", "1");
723        let file_list = std::env::join_paths(build_runner.sbom_output_files(unit)?)?;
724        base.env("CARGO_SBOM_PATH", file_list);
725    }
726
727    if unit.target.is_test() || unit.target.is_bench() {
728        let tmp = build_runner.files().layout(unit.kind).prepare_tmp()?;
729        base.env("CARGO_TARGET_TMPDIR", tmp.display().to_string());
730    }
731
732    Ok(base)
733}
734
735/// Prepares flags and environments we can compute for a `rustdoc` invocation
736/// before the job queue starts compiling any unit.
737///
738/// This builds a static view of the invocation. Flags depending on the
739/// completion of other units will be added later in runtime, such as flags
740/// from build scripts.
741fn prepare_rustdoc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<ProcessBuilder> {
742    let bcx = build_runner.bcx;
743    // script_metadata is not needed here, it is only for tests.
744    let mut rustdoc = build_runner.compilation.rustdoc_process(unit, None)?;
745    rustdoc.inherit_jobserver(&build_runner.jobserver);
746    let crate_name = unit.target.crate_name();
747    rustdoc.arg("--crate-name").arg(&crate_name);
748    add_path_args(bcx.ws, unit, &mut rustdoc);
749    add_cap_lints(bcx, unit, &mut rustdoc);
750
751    if let CompileKind::Target(target) = unit.kind {
752        rustdoc.arg("--target").arg(target.rustc_target());
753    }
754    let doc_dir = build_runner.files().out_dir(unit);
755    rustdoc.arg("-o").arg(&doc_dir);
756    rustdoc.args(&features_args(unit));
757    rustdoc.args(&check_cfg_args(unit));
758
759    add_error_format_and_color(build_runner, &mut rustdoc);
760    add_allow_features(build_runner, &mut rustdoc);
761
762    if let Some(trim_paths) = unit.profile.trim_paths.as_ref() {
763        trim_paths_args_rustdoc(&mut rustdoc, build_runner, unit, trim_paths)?;
764    }
765
766    rustdoc.args(unit.pkg.manifest().lint_rustflags());
767
768    let metadata = build_runner.metadata_for_doc_units[unit];
769    rustdoc
770        .arg("-C")
771        .arg(format!("metadata={}", metadata.c_metadata()));
772
773    if unit.mode.is_doc_scrape() {
774        debug_assert!(build_runner.bcx.scrape_units.contains(unit));
775
776        if unit.target.is_test() {
777            rustdoc.arg("--scrape-tests");
778        }
779
780        rustdoc.arg("-Zunstable-options");
781
782        rustdoc
783            .arg("--scrape-examples-output-path")
784            .arg(scrape_output_path(build_runner, unit)?);
785
786        // Only scrape example for items from crates in the workspace, to reduce generated file size
787        for pkg in build_runner.bcx.packages.packages() {
788            let names = pkg
789                .targets()
790                .iter()
791                .map(|target| target.crate_name())
792                .collect::<HashSet<_>>();
793            for name in names {
794                rustdoc.arg("--scrape-examples-target-crate").arg(name);
795            }
796        }
797    }
798
799    if should_include_scrape_units(build_runner.bcx, unit) {
800        rustdoc.arg("-Zunstable-options");
801    }
802
803    build_deps_args(&mut rustdoc, build_runner, unit)?;
804    rustdoc::add_root_urls(build_runner, unit, &mut rustdoc)?;
805
806    rustdoc::add_output_format(build_runner, unit, &mut rustdoc)?;
807
808    if let Some(args) = build_runner.bcx.extra_args_for(unit) {
809        rustdoc.args(args);
810    }
811    rustdoc.args(&unit.rustdocflags);
812
813    if !crate_version_flag_already_present(&rustdoc) {
814        append_crate_version_flag(unit, &mut rustdoc);
815    }
816
817    Ok(rustdoc)
818}
819
820/// Creates a unit of work invoking `rustdoc` for documenting the `unit`.
821fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<Work> {
822    let mut rustdoc = prepare_rustdoc(build_runner, unit)?;
823
824    let crate_name = unit.target.crate_name();
825    let doc_dir = build_runner.files().out_dir(unit);
826    // Create the documentation directory ahead of time as rustdoc currently has
827    // a bug where concurrent invocations will race to create this directory if
828    // it doesn't already exist.
829    paths::create_dir_all(&doc_dir)?;
830
831    let target_desc = unit.target.description_named();
832    let name = unit.pkg.name();
833    let build_script_outputs = Arc::clone(&build_runner.build_script_outputs);
834    let package_id = unit.pkg.package_id();
835    let manifest_path = PathBuf::from(unit.pkg.manifest_path());
836    let target = Target::clone(&unit.target);
837    let mut output_options = OutputOptions::new(build_runner, unit);
838    let script_metadata = build_runner.find_build_script_metadata(unit);
839    let scrape_outputs = if should_include_scrape_units(build_runner.bcx, unit) {
840        Some(
841            build_runner
842                .bcx
843                .scrape_units
844                .iter()
845                .map(|unit| {
846                    Ok((
847                        build_runner.files().metadata(unit).unit_id(),
848                        scrape_output_path(build_runner, unit)?,
849                    ))
850                })
851                .collect::<CargoResult<HashMap<_, _>>>()?,
852        )
853    } else {
854        None
855    };
856
857    let failed_scrape_units = Arc::clone(&build_runner.failed_scrape_units);
858    let hide_diagnostics_for_scrape_unit = build_runner.bcx.unit_can_fail_for_docscraping(unit)
859        && !matches!(
860            build_runner.bcx.gctx.shell().verbosity(),
861            Verbosity::Verbose
862        );
863    let failed_scrape_diagnostic = hide_diagnostics_for_scrape_unit.then(|| {
864        make_failed_scrape_diagnostic(
865            build_runner,
866            unit,
867            format_args!("failed to scan {target_desc} in package `{name}` for example code usage"),
868        )
869    });
870    if hide_diagnostics_for_scrape_unit {
871        output_options.show_diagnostics = false;
872    }
873
874    Ok(Work::new(move |state| {
875        add_custom_flags(
876            &mut rustdoc,
877            &build_script_outputs.lock().unwrap(),
878            script_metadata,
879        )?;
880
881        // Add the output of scraped examples to the rustdoc command.
882        // This action must happen after the unit's dependencies have finished,
883        // because some of those deps may be Docscrape units which have failed.
884        // So we dynamically determine which `--with-examples` flags to pass here.
885        if let Some(scrape_outputs) = scrape_outputs {
886            let failed_scrape_units = failed_scrape_units.lock().unwrap();
887            for (metadata, output_path) in &scrape_outputs {
888                if !failed_scrape_units.contains(metadata) {
889                    rustdoc.arg("--with-examples").arg(output_path);
890                }
891            }
892        }
893
894        let crate_dir = doc_dir.join(&crate_name);
895        if crate_dir.exists() {
896            // Remove output from a previous build. This ensures that stale
897            // files for removed items are removed.
898            debug!("removing pre-existing doc directory {:?}", crate_dir);
899            paths::remove_dir_all(crate_dir)?;
900        }
901        state.running(&rustdoc);
902
903        let result = rustdoc
904            .exec_with_streaming(
905                &mut |line| on_stdout_line(state, line, package_id, &target),
906                &mut |line| {
907                    on_stderr_line(
908                        state,
909                        line,
910                        package_id,
911                        &manifest_path,
912                        &target,
913                        &mut output_options,
914                    )
915                },
916                false,
917            )
918            .map_err(verbose_if_simple_exit_code)
919            .with_context(|| format!("could not document `{}`", name));
920
921        if let Err(e) = result {
922            if let Some(diagnostic) = failed_scrape_diagnostic {
923                state.warning(diagnostic);
924            }
925
926            return Err(e);
927        }
928
929        Ok(())
930    }))
931}
932
933// The --crate-version flag could have already been passed in RUSTDOCFLAGS
934// or as an extra compiler argument for rustdoc
935fn crate_version_flag_already_present(rustdoc: &ProcessBuilder) -> bool {
936    rustdoc.get_args().any(|flag| {
937        flag.to_str()
938            .map_or(false, |flag| flag.starts_with(RUSTDOC_CRATE_VERSION_FLAG))
939    })
940}
941
942fn append_crate_version_flag(unit: &Unit, rustdoc: &mut ProcessBuilder) {
943    rustdoc
944        .arg(RUSTDOC_CRATE_VERSION_FLAG)
945        .arg(unit.pkg.version().to_string());
946}
947
948/// Adds [`--cap-lints`] to the command to execute.
949///
950/// [`--cap-lints`]: https://doc.rust-lang.org/nightly/rustc/lints/levels.html#capping-lints
951fn add_cap_lints(bcx: &BuildContext<'_, '_>, unit: &Unit, cmd: &mut ProcessBuilder) {
952    // If this is an upstream dep we don't want warnings from, turn off all
953    // lints.
954    if !unit.show_warnings(bcx.gctx) {
955        cmd.arg("--cap-lints").arg("allow");
956
957    // If this is an upstream dep but we *do* want warnings, make sure that they
958    // don't fail compilation.
959    } else if !unit.is_local() {
960        cmd.arg("--cap-lints").arg("warn");
961    }
962}
963
964/// Forwards [`-Zallow-features`] if it is set for cargo.
965///
966/// [`-Zallow-features`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#allow-features
967fn add_allow_features(build_runner: &BuildRunner<'_, '_>, cmd: &mut ProcessBuilder) {
968    if let Some(allow) = &build_runner.bcx.gctx.cli_unstable().allow_features {
969        use std::fmt::Write;
970        let mut arg = String::from("-Zallow-features=");
971        for f in allow {
972            let _ = write!(&mut arg, "{f},");
973        }
974        cmd.arg(arg.trim_end_matches(','));
975    }
976}
977
978/// Adds [`--error-format`] to the command to execute.
979///
980/// Cargo always uses JSON output. This has several benefits, such as being
981/// easier to parse, handles changing formats (for replaying cached messages),
982/// ensures atomic output (so messages aren't interleaved), allows for
983/// intercepting messages like rmeta artifacts, etc. rustc includes a
984/// "rendered" field in the JSON message with the message properly formatted,
985/// which Cargo will extract and display to the user.
986///
987/// [`--error-format`]: https://doc.rust-lang.org/nightly/rustc/command-line-arguments.html#--error-format-control-how-errors-are-produced
988fn add_error_format_and_color(build_runner: &BuildRunner<'_, '_>, cmd: &mut ProcessBuilder) {
989    cmd.arg("--error-format=json");
990    let mut json = String::from("--json=diagnostic-rendered-ansi,artifacts,future-incompat");
991
992    match build_runner.bcx.build_config.message_format {
993        MessageFormat::Short | MessageFormat::Json { short: true, .. } => {
994            json.push_str(",diagnostic-short");
995        }
996        _ => {}
997    }
998    cmd.arg(json);
999
1000    let gctx = build_runner.bcx.gctx;
1001    if let Some(width) = gctx.shell().err_width().diagnostic_terminal_width() {
1002        cmd.arg(format!("--diagnostic-width={width}"));
1003    }
1004}
1005
1006/// Adds essential rustc flags and environment variables to the command to execute.
1007fn build_base_args(
1008    build_runner: &BuildRunner<'_, '_>,
1009    cmd: &mut ProcessBuilder,
1010    unit: &Unit,
1011) -> CargoResult<()> {
1012    assert!(!unit.mode.is_run_custom_build());
1013
1014    let bcx = build_runner.bcx;
1015    let Profile {
1016        ref opt_level,
1017        codegen_backend,
1018        codegen_units,
1019        debuginfo,
1020        debug_assertions,
1021        split_debuginfo,
1022        overflow_checks,
1023        rpath,
1024        ref panic,
1025        incremental,
1026        strip,
1027        rustflags: profile_rustflags,
1028        trim_paths,
1029        ..
1030    } = unit.profile.clone();
1031    let test = unit.mode.is_any_test();
1032
1033    cmd.arg("--crate-name").arg(&unit.target.crate_name());
1034
1035    let edition = unit.target.edition();
1036    edition.cmd_edition_arg(cmd);
1037
1038    add_path_args(bcx.ws, unit, cmd);
1039    add_error_format_and_color(build_runner, cmd);
1040    add_allow_features(build_runner, cmd);
1041
1042    let mut contains_dy_lib = false;
1043    if !test {
1044        for crate_type in &unit.target.rustc_crate_types() {
1045            cmd.arg("--crate-type").arg(crate_type.as_str());
1046            contains_dy_lib |= crate_type == &CrateType::Dylib;
1047        }
1048    }
1049
1050    if unit.mode.is_check() {
1051        cmd.arg("--emit=dep-info,metadata");
1052    } else if !unit.requires_upstream_objects() {
1053        // Always produce metadata files for rlib outputs. Metadata may be used
1054        // in this session for a pipelined compilation, or it may be used in a
1055        // future Cargo session as part of a pipelined compile.
1056        cmd.arg("--emit=dep-info,metadata,link");
1057    } else {
1058        cmd.arg("--emit=dep-info,link");
1059    }
1060
1061    let prefer_dynamic = (unit.target.for_host() && !unit.target.is_custom_build())
1062        || (contains_dy_lib && !build_runner.is_primary_package(unit));
1063    if prefer_dynamic {
1064        cmd.arg("-C").arg("prefer-dynamic");
1065    }
1066
1067    if opt_level.as_str() != "0" {
1068        cmd.arg("-C").arg(&format!("opt-level={}", opt_level));
1069    }
1070
1071    if *panic != PanicStrategy::Unwind {
1072        cmd.arg("-C").arg(format!("panic={}", panic));
1073    }
1074
1075    cmd.args(&lto_args(build_runner, unit));
1076
1077    if let Some(backend) = codegen_backend {
1078        cmd.arg("-Z").arg(&format!("codegen-backend={}", backend));
1079    }
1080
1081    if let Some(n) = codegen_units {
1082        cmd.arg("-C").arg(&format!("codegen-units={}", n));
1083    }
1084
1085    let debuginfo = debuginfo.into_inner();
1086    // Shorten the number of arguments if possible.
1087    if debuginfo != TomlDebugInfo::None {
1088        cmd.arg("-C").arg(format!("debuginfo={debuginfo}"));
1089        // This is generally just an optimization on build time so if we don't
1090        // pass it then it's ok. The values for the flag (off, packed, unpacked)
1091        // may be supported or not depending on the platform, so availability is
1092        // checked per-value. For example, at the time of writing this code, on
1093        // Windows the only stable valid value for split-debuginfo is "packed",
1094        // while on Linux "unpacked" is also stable.
1095        if let Some(split) = split_debuginfo {
1096            if build_runner
1097                .bcx
1098                .target_data
1099                .info(unit.kind)
1100                .supports_debuginfo_split(split)
1101            {
1102                cmd.arg("-C").arg(format!("split-debuginfo={split}"));
1103            }
1104        }
1105    }
1106
1107    if let Some(trim_paths) = trim_paths {
1108        trim_paths_args(cmd, build_runner, unit, &trim_paths)?;
1109    }
1110
1111    cmd.args(unit.pkg.manifest().lint_rustflags());
1112    cmd.args(&profile_rustflags);
1113
1114    // `-C overflow-checks` is implied by the setting of `-C debug-assertions`,
1115    // so we only need to provide `-C overflow-checks` if it differs from
1116    // the value of `-C debug-assertions` we would provide.
1117    if opt_level.as_str() != "0" {
1118        if debug_assertions {
1119            cmd.args(&["-C", "debug-assertions=on"]);
1120            if !overflow_checks {
1121                cmd.args(&["-C", "overflow-checks=off"]);
1122            }
1123        } else if overflow_checks {
1124            cmd.args(&["-C", "overflow-checks=on"]);
1125        }
1126    } else if !debug_assertions {
1127        cmd.args(&["-C", "debug-assertions=off"]);
1128        if overflow_checks {
1129            cmd.args(&["-C", "overflow-checks=on"]);
1130        }
1131    } else if !overflow_checks {
1132        cmd.args(&["-C", "overflow-checks=off"]);
1133    }
1134
1135    if test && unit.target.harness() {
1136        cmd.arg("--test");
1137
1138        // Cargo has historically never compiled `--test` binaries with
1139        // `panic=abort` because the `test` crate itself didn't support it.
1140        // Support is now upstream, however, but requires an unstable flag to be
1141        // passed when compiling the test. We require, in Cargo, an unstable
1142        // flag to pass to rustc, so register that here. Eventually this flag
1143        // will simply not be needed when the behavior is stabilized in the Rust
1144        // compiler itself.
1145        if *panic == PanicStrategy::Abort {
1146            cmd.arg("-Z").arg("panic-abort-tests");
1147        }
1148    } else if test {
1149        cmd.arg("--cfg").arg("test");
1150    }
1151
1152    cmd.args(&features_args(unit));
1153    cmd.args(&check_cfg_args(unit));
1154
1155    let meta = build_runner.files().metadata(unit);
1156    cmd.arg("-C")
1157        .arg(&format!("metadata={}", meta.c_metadata()));
1158    if let Some(c_extra_filename) = meta.c_extra_filename() {
1159        cmd.arg("-C")
1160            .arg(&format!("extra-filename=-{c_extra_filename}"));
1161    }
1162
1163    if rpath {
1164        cmd.arg("-C").arg("rpath");
1165    }
1166
1167    cmd.arg("--out-dir")
1168        .arg(&build_runner.files().out_dir(unit));
1169
1170    fn opt(cmd: &mut ProcessBuilder, key: &str, prefix: &str, val: Option<&OsStr>) {
1171        if let Some(val) = val {
1172            let mut joined = OsString::from(prefix);
1173            joined.push(val);
1174            cmd.arg(key).arg(joined);
1175        }
1176    }
1177
1178    if let CompileKind::Target(n) = unit.kind {
1179        cmd.arg("--target").arg(n.rustc_target());
1180    }
1181
1182    opt(
1183        cmd,
1184        "-C",
1185        "linker=",
1186        build_runner
1187            .compilation
1188            .target_linker(unit.kind)
1189            .as_ref()
1190            .map(|s| s.as_ref()),
1191    );
1192    if incremental {
1193        let dir = build_runner
1194            .files()
1195            .layout(unit.kind)
1196            .incremental()
1197            .as_os_str();
1198        opt(cmd, "-C", "incremental=", Some(dir));
1199    }
1200
1201    let strip = strip.into_inner();
1202    if strip != StripInner::None {
1203        cmd.arg("-C").arg(format!("strip={}", strip));
1204    }
1205
1206    if unit.is_std {
1207        // -Zforce-unstable-if-unmarked prevents the accidental use of
1208        // unstable crates within the sysroot (such as "extern crate libc" or
1209        // any non-public crate in the sysroot).
1210        //
1211        // RUSTC_BOOTSTRAP allows unstable features on stable.
1212        cmd.arg("-Z")
1213            .arg("force-unstable-if-unmarked")
1214            .env("RUSTC_BOOTSTRAP", "1");
1215    }
1216
1217    // Add `CARGO_BIN_EXE_` environment variables for building tests.
1218    if unit.target.is_test() || unit.target.is_bench() {
1219        for bin_target in unit
1220            .pkg
1221            .manifest()
1222            .targets()
1223            .iter()
1224            .filter(|target| target.is_bin())
1225        {
1226            let exe_path = build_runner.files().bin_link_for_target(
1227                bin_target,
1228                unit.kind,
1229                build_runner.bcx,
1230            )?;
1231            let name = bin_target
1232                .binary_filename()
1233                .unwrap_or(bin_target.name().to_string());
1234            let key = format!("CARGO_BIN_EXE_{}", name);
1235            cmd.env(&key, exe_path);
1236        }
1237    }
1238    Ok(())
1239}
1240
1241/// All active features for the unit passed as `--cfg features=<feature-name>`.
1242fn features_args(unit: &Unit) -> Vec<OsString> {
1243    let mut args = Vec::with_capacity(unit.features.len() * 2);
1244
1245    for feat in &unit.features {
1246        args.push(OsString::from("--cfg"));
1247        args.push(OsString::from(format!("feature=\"{}\"", feat)));
1248    }
1249
1250    args
1251}
1252
1253/// Like [`trim_paths_args`] but for rustdoc invocations.
1254fn trim_paths_args_rustdoc(
1255    cmd: &mut ProcessBuilder,
1256    build_runner: &BuildRunner<'_, '_>,
1257    unit: &Unit,
1258    trim_paths: &TomlTrimPaths,
1259) -> CargoResult<()> {
1260    match trim_paths {
1261        // rustdoc supports diagnostics trimming only.
1262        TomlTrimPaths::Values(values) if !values.contains(&TomlTrimPathsValue::Diagnostics) => {
1263            return Ok(())
1264        }
1265        _ => {}
1266    }
1267
1268    // feature gate was checked during manifest/config parsing.
1269    cmd.arg("-Zunstable-options");
1270
1271    // Order of `--remap-path-prefix` flags is important for `-Zbuild-std`.
1272    // We want to show `/rustc/<hash>/library/std` instead of `std-0.0.0`.
1273    cmd.arg(package_remap(build_runner, unit));
1274    cmd.arg(sysroot_remap(build_runner, unit));
1275
1276    Ok(())
1277}
1278
1279/// Generates the `--remap-path-scope` and `--remap-path-prefix` for [RFC 3127].
1280/// See also unstable feature [`-Ztrim-paths`].
1281///
1282/// [RFC 3127]: https://rust-lang.github.io/rfcs/3127-trim-paths.html
1283/// [`-Ztrim-paths`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#profile-trim-paths-option
1284fn trim_paths_args(
1285    cmd: &mut ProcessBuilder,
1286    build_runner: &BuildRunner<'_, '_>,
1287    unit: &Unit,
1288    trim_paths: &TomlTrimPaths,
1289) -> CargoResult<()> {
1290    if trim_paths.is_none() {
1291        return Ok(());
1292    }
1293
1294    // feature gate was checked during manifest/config parsing.
1295    cmd.arg("-Zunstable-options");
1296    cmd.arg(format!("-Zremap-path-scope={trim_paths}"));
1297
1298    // Order of `--remap-path-prefix` flags is important for `-Zbuild-std`.
1299    // We want to show `/rustc/<hash>/library/std` instead of `std-0.0.0`.
1300    cmd.arg(package_remap(build_runner, unit));
1301    cmd.arg(sysroot_remap(build_runner, unit));
1302
1303    Ok(())
1304}
1305
1306/// Path prefix remap rules for sysroot.
1307///
1308/// This remap logic aligns with rustc:
1309/// <https://github.com/rust-lang/rust/blob/c2ef3516/src/bootstrap/src/lib.rs#L1113-L1116>
1310fn sysroot_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> OsString {
1311    let mut remap = OsString::from("--remap-path-prefix=");
1312    remap.push({
1313        // See also `detect_sysroot_src_path()`.
1314        let mut sysroot = build_runner.bcx.target_data.info(unit.kind).sysroot.clone();
1315        sysroot.push("lib");
1316        sysroot.push("rustlib");
1317        sysroot.push("src");
1318        sysroot.push("rust");
1319        sysroot
1320    });
1321    remap.push("=");
1322    remap.push("/rustc/");
1323    if let Some(commit_hash) = build_runner.bcx.rustc().commit_hash.as_ref() {
1324        remap.push(commit_hash);
1325    } else {
1326        remap.push(build_runner.bcx.rustc().version.to_string());
1327    }
1328    remap
1329}
1330
1331/// Path prefix remap rules for dependencies.
1332///
1333/// * Git dependencies: remove `~/.cargo/git/checkouts` prefix.
1334/// * Registry dependencies: remove `~/.cargo/registry/src` prefix.
1335/// * Others (e.g. path dependencies):
1336///     * relative paths to workspace root if inside the workspace directory.
1337///     * otherwise remapped to `<pkg>-<version>`.
1338fn package_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> OsString {
1339    let pkg_root = unit.pkg.root();
1340    let ws_root = build_runner.bcx.ws.root();
1341    let mut remap = OsString::from("--remap-path-prefix=");
1342    let source_id = unit.pkg.package_id().source_id();
1343    if source_id.is_git() {
1344        remap.push(
1345            build_runner
1346                .bcx
1347                .gctx
1348                .git_checkouts_path()
1349                .as_path_unlocked(),
1350        );
1351        remap.push("=");
1352    } else if source_id.is_registry() {
1353        remap.push(
1354            build_runner
1355                .bcx
1356                .gctx
1357                .registry_source_path()
1358                .as_path_unlocked(),
1359        );
1360        remap.push("=");
1361    } else if pkg_root.strip_prefix(ws_root).is_ok() {
1362        remap.push(ws_root);
1363        remap.push("=."); // remap to relative rustc work dir explicitly
1364    } else {
1365        remap.push(pkg_root);
1366        remap.push("=");
1367        remap.push(unit.pkg.name());
1368        remap.push("-");
1369        remap.push(unit.pkg.version().to_string());
1370    }
1371    remap
1372}
1373
1374/// Generates the `--check-cfg` arguments for the `unit`.
1375fn check_cfg_args(unit: &Unit) -> Vec<OsString> {
1376    // The routine below generates the --check-cfg arguments. Our goals here are to
1377    // enable the checking of conditionals and pass the list of declared features.
1378    //
1379    // In the simplified case, it would resemble something like this:
1380    //
1381    //   --check-cfg=cfg() --check-cfg=cfg(feature, values(...))
1382    //
1383    // but having `cfg()` is redundant with the second argument (as well-known names
1384    // and values are implicitly enabled when one or more `--check-cfg` argument is
1385    // passed) so we don't emit it and just pass:
1386    //
1387    //   --check-cfg=cfg(feature, values(...))
1388    //
1389    // This way, even if there are no declared features, the config `feature` will
1390    // still be expected, meaning users would get "unexpected value" instead of name.
1391    // This wasn't always the case, see rust-lang#119930 for some details.
1392
1393    let gross_cap_estimation = unit.pkg.summary().features().len() * 7 + 25;
1394    let mut arg_feature = OsString::with_capacity(gross_cap_estimation);
1395
1396    arg_feature.push("cfg(feature, values(");
1397    for (i, feature) in unit.pkg.summary().features().keys().enumerate() {
1398        if i != 0 {
1399            arg_feature.push(", ");
1400        }
1401        arg_feature.push("\"");
1402        arg_feature.push(feature);
1403        arg_feature.push("\"");
1404    }
1405    arg_feature.push("))");
1406
1407    // In addition to the package features, we also include the `test` cfg (since
1408    // compiler-team#785, as to be able to someday apply yt conditionaly), as well
1409    // the `docsrs` cfg from the docs.rs service.
1410    //
1411    // We include `docsrs` here (in Cargo) instead of rustc, since there is a much closer
1412    // relationship between Cargo and docs.rs than rustc and docs.rs. In particular, all
1413    // users of docs.rs use Cargo, but not all users of rustc (like Rust-for-Linux) use docs.rs.
1414
1415    vec![
1416        OsString::from("--check-cfg"),
1417        OsString::from("cfg(docsrs,test)"),
1418        OsString::from("--check-cfg"),
1419        arg_feature,
1420    ]
1421}
1422
1423/// Adds LTO related codegen flags.
1424fn lto_args(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec<OsString> {
1425    let mut result = Vec::new();
1426    let mut push = |arg: &str| {
1427        result.push(OsString::from("-C"));
1428        result.push(OsString::from(arg));
1429    };
1430    match build_runner.lto[unit] {
1431        lto::Lto::Run(None) => push("lto"),
1432        lto::Lto::Run(Some(s)) => push(&format!("lto={}", s)),
1433        lto::Lto::Off => {
1434            push("lto=off");
1435            push("embed-bitcode=no");
1436        }
1437        lto::Lto::ObjectAndBitcode => {} // this is rustc's default
1438        lto::Lto::OnlyBitcode => push("linker-plugin-lto"),
1439        lto::Lto::OnlyObject => push("embed-bitcode=no"),
1440    }
1441    result
1442}
1443
1444/// Adds dependency-relevant rustc flags and environment variables
1445/// to the command to execute, such as [`-L`] and [`--extern`].
1446///
1447/// [`-L`]: https://doc.rust-lang.org/nightly/rustc/command-line-arguments.html#-l-add-a-directory-to-the-library-search-path
1448/// [`--extern`]: https://doc.rust-lang.org/nightly/rustc/command-line-arguments.html#--extern-specify-where-an-external-library-is-located
1449fn build_deps_args(
1450    cmd: &mut ProcessBuilder,
1451    build_runner: &BuildRunner<'_, '_>,
1452    unit: &Unit,
1453) -> CargoResult<()> {
1454    let bcx = build_runner.bcx;
1455    cmd.arg("-L").arg(&{
1456        let mut deps = OsString::from("dependency=");
1457        deps.push(build_runner.files().deps_dir(unit));
1458        deps
1459    });
1460
1461    // Be sure that the host path is also listed. This'll ensure that proc macro
1462    // dependencies are correctly found (for reexported macros).
1463    if !unit.kind.is_host() {
1464        cmd.arg("-L").arg(&{
1465            let mut deps = OsString::from("dependency=");
1466            deps.push(build_runner.files().host_deps());
1467            deps
1468        });
1469    }
1470
1471    let deps = build_runner.unit_deps(unit);
1472
1473    // If there is not one linkable target but should, rustc fails later
1474    // on if there is an `extern crate` for it. This may turn into a hard
1475    // error in the future (see PR #4797).
1476    if !deps
1477        .iter()
1478        .any(|dep| !dep.unit.mode.is_doc() && dep.unit.target.is_linkable())
1479    {
1480        if let Some(dep) = deps.iter().find(|dep| {
1481            !dep.unit.mode.is_doc() && dep.unit.target.is_lib() && !dep.unit.artifact.is_true()
1482        }) {
1483            bcx.gctx.shell().warn(format!(
1484                "The package `{}` \
1485                 provides no linkable target. The compiler might raise an error while compiling \
1486                 `{}`. Consider adding 'dylib' or 'rlib' to key `crate-type` in `{}`'s \
1487                 Cargo.toml. This warning might turn into a hard error in the future.",
1488                dep.unit.target.crate_name(),
1489                unit.target.crate_name(),
1490                dep.unit.target.crate_name()
1491            ))?;
1492        }
1493    }
1494
1495    let mut unstable_opts = false;
1496
1497    for dep in deps {
1498        if dep.unit.mode.is_run_custom_build() {
1499            cmd.env(
1500                "OUT_DIR",
1501                &build_runner.files().build_script_out_dir(&dep.unit),
1502            );
1503        }
1504    }
1505
1506    for arg in extern_args(build_runner, unit, &mut unstable_opts)? {
1507        cmd.arg(arg);
1508    }
1509
1510    for (var, env) in artifact::get_env(build_runner, deps)? {
1511        cmd.env(&var, env);
1512    }
1513
1514    // This will only be set if we're already using a feature
1515    // requiring nightly rust
1516    if unstable_opts {
1517        cmd.arg("-Z").arg("unstable-options");
1518    }
1519
1520    Ok(())
1521}
1522
1523/// Adds extra rustc flags and environment variables collected from the output
1524/// of a build-script to the command to execute, include custom environment
1525/// variables and `cfg`.
1526fn add_custom_flags(
1527    cmd: &mut ProcessBuilder,
1528    build_script_outputs: &BuildScriptOutputs,
1529    metadata: Option<UnitHash>,
1530) -> CargoResult<()> {
1531    if let Some(metadata) = metadata {
1532        if let Some(output) = build_script_outputs.get(metadata) {
1533            for cfg in output.cfgs.iter() {
1534                cmd.arg("--cfg").arg(cfg);
1535            }
1536            for check_cfg in &output.check_cfgs {
1537                cmd.arg("--check-cfg").arg(check_cfg);
1538            }
1539            for (name, value) in output.env.iter() {
1540                cmd.env(name, value);
1541            }
1542        }
1543    }
1544
1545    Ok(())
1546}
1547
1548/// Generates a list of `--extern` arguments.
1549pub fn extern_args(
1550    build_runner: &BuildRunner<'_, '_>,
1551    unit: &Unit,
1552    unstable_opts: &mut bool,
1553) -> CargoResult<Vec<OsString>> {
1554    let mut result = Vec::new();
1555    let deps = build_runner.unit_deps(unit);
1556
1557    // Closure to add one dependency to `result`.
1558    let mut link_to =
1559        |dep: &UnitDep, extern_crate_name: InternedString, noprelude: bool| -> CargoResult<()> {
1560            let mut value = OsString::new();
1561            let mut opts = Vec::new();
1562            let is_public_dependency_enabled = unit
1563                .pkg
1564                .manifest()
1565                .unstable_features()
1566                .require(Feature::public_dependency())
1567                .is_ok()
1568                || build_runner.bcx.gctx.cli_unstable().public_dependency;
1569            if !dep.public && unit.target.is_lib() && is_public_dependency_enabled {
1570                opts.push("priv");
1571                *unstable_opts = true;
1572            }
1573            if noprelude {
1574                opts.push("noprelude");
1575                *unstable_opts = true;
1576            }
1577            if !opts.is_empty() {
1578                value.push(opts.join(","));
1579                value.push(":");
1580            }
1581            value.push(extern_crate_name.as_str());
1582            value.push("=");
1583
1584            let mut pass = |file| {
1585                let mut value = value.clone();
1586                value.push(file);
1587                result.push(OsString::from("--extern"));
1588                result.push(value);
1589            };
1590
1591            let outputs = build_runner.outputs(&dep.unit)?;
1592
1593            if build_runner.only_requires_rmeta(unit, &dep.unit) || dep.unit.mode.is_check() {
1594                // Example: rlib dependency for an rlib, rmeta is all that is required.
1595                let output = outputs
1596                    .iter()
1597                    .find(|output| output.flavor == FileFlavor::Rmeta)
1598                    .expect("failed to find rmeta dep for pipelined dep");
1599                pass(&output.path);
1600            } else {
1601                // Example: a bin needs `rlib` for dependencies, it cannot use rmeta.
1602                for output in outputs.iter() {
1603                    if output.flavor == FileFlavor::Linkable {
1604                        pass(&output.path);
1605                    }
1606                }
1607            }
1608            Ok(())
1609        };
1610
1611    for dep in deps {
1612        if dep.unit.target.is_linkable() && !dep.unit.mode.is_doc() {
1613            link_to(dep, dep.extern_crate_name, dep.noprelude)?;
1614        }
1615    }
1616    if unit.target.proc_macro() {
1617        // Automatically import `proc_macro`.
1618        result.push(OsString::from("--extern"));
1619        result.push(OsString::from("proc_macro"));
1620    }
1621
1622    Ok(result)
1623}
1624
1625fn envify(s: &str) -> String {
1626    s.chars()
1627        .flat_map(|c| c.to_uppercase())
1628        .map(|c| if c == '-' { '_' } else { c })
1629        .collect()
1630}
1631
1632/// Configuration of the display of messages emitted by the compiler,
1633/// e.g. diagnostics, warnings, errors, and message caching.
1634struct OutputOptions {
1635    /// What format we're emitting from Cargo itself.
1636    format: MessageFormat,
1637    /// Where to write the JSON messages to support playback later if the unit
1638    /// is fresh. The file is created lazily so that in the normal case, lots
1639    /// of empty files are not created. If this is None, the output will not
1640    /// be cached (such as when replaying cached messages).
1641    cache_cell: Option<(PathBuf, LazyCell<File>)>,
1642    /// If `true`, display any diagnostics.
1643    /// Other types of JSON messages are processed regardless
1644    /// of the value of this flag.
1645    ///
1646    /// This is used primarily for cache replay. If you build with `-vv`, the
1647    /// cache will be filled with diagnostics from dependencies. When the
1648    /// cache is replayed without `-vv`, we don't want to show them.
1649    show_diagnostics: bool,
1650    /// Tracks the number of warnings we've seen so far.
1651    warnings_seen: usize,
1652    /// Tracks the number of errors we've seen so far.
1653    errors_seen: usize,
1654}
1655
1656impl OutputOptions {
1657    fn new(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> OutputOptions {
1658        let path = build_runner.files().message_cache_path(unit);
1659        // Remove old cache, ignore ENOENT, which is the common case.
1660        drop(fs::remove_file(&path));
1661        let cache_cell = Some((path, LazyCell::new()));
1662        let show_diagnostics =
1663            build_runner.bcx.gctx.warning_handling().unwrap_or_default() != WarningHandling::Allow;
1664        OutputOptions {
1665            format: build_runner.bcx.build_config.message_format,
1666            cache_cell,
1667            show_diagnostics,
1668            warnings_seen: 0,
1669            errors_seen: 0,
1670        }
1671    }
1672}
1673
1674fn on_stdout_line(
1675    state: &JobState<'_, '_>,
1676    line: &str,
1677    _package_id: PackageId,
1678    _target: &Target,
1679) -> CargoResult<()> {
1680    state.stdout(line.to_string())?;
1681    Ok(())
1682}
1683
1684fn on_stderr_line(
1685    state: &JobState<'_, '_>,
1686    line: &str,
1687    package_id: PackageId,
1688    manifest_path: &std::path::Path,
1689    target: &Target,
1690    options: &mut OutputOptions,
1691) -> CargoResult<()> {
1692    if on_stderr_line_inner(state, line, package_id, manifest_path, target, options)? {
1693        // Check if caching is enabled.
1694        if let Some((path, cell)) = &mut options.cache_cell {
1695            // Cache the output, which will be replayed later when Fresh.
1696            let f = cell.try_borrow_mut_with(|| paths::create(path))?;
1697            debug_assert!(!line.contains('\n'));
1698            f.write_all(line.as_bytes())?;
1699            f.write_all(&[b'\n'])?;
1700        }
1701    }
1702    Ok(())
1703}
1704
1705/// Returns true if the line should be cached.
1706fn on_stderr_line_inner(
1707    state: &JobState<'_, '_>,
1708    line: &str,
1709    package_id: PackageId,
1710    manifest_path: &std::path::Path,
1711    target: &Target,
1712    options: &mut OutputOptions,
1713) -> CargoResult<bool> {
1714    // We primarily want to use this function to process JSON messages from
1715    // rustc. The compiler should always print one JSON message per line, and
1716    // otherwise it may have other output intermingled (think RUST_LOG or
1717    // something like that), so skip over everything that doesn't look like a
1718    // JSON message.
1719    if !line.starts_with('{') {
1720        state.stderr(line.to_string())?;
1721        return Ok(true);
1722    }
1723
1724    let mut compiler_message: Box<serde_json::value::RawValue> = match serde_json::from_str(line) {
1725        Ok(msg) => msg,
1726
1727        // If the compiler produced a line that started with `{` but it wasn't
1728        // valid JSON, maybe it wasn't JSON in the first place! Forward it along
1729        // to stderr.
1730        Err(e) => {
1731            debug!("failed to parse json: {:?}", e);
1732            state.stderr(line.to_string())?;
1733            return Ok(true);
1734        }
1735    };
1736
1737    let count_diagnostic = |level, options: &mut OutputOptions| {
1738        if level == "warning" {
1739            options.warnings_seen += 1;
1740        } else if level == "error" {
1741            options.errors_seen += 1;
1742        }
1743    };
1744
1745    if let Ok(report) = serde_json::from_str::<FutureIncompatReport>(compiler_message.get()) {
1746        for item in &report.future_incompat_report {
1747            count_diagnostic(&*item.diagnostic.level, options);
1748        }
1749        state.future_incompat_report(report.future_incompat_report);
1750        return Ok(true);
1751    }
1752
1753    // Depending on what we're emitting from Cargo itself, we figure out what to
1754    // do with this JSON message.
1755    match options.format {
1756        // In the "human" output formats (human/short) or if diagnostic messages
1757        // from rustc aren't being included in the output of Cargo's JSON
1758        // messages then we extract the diagnostic (if present) here and handle
1759        // it ourselves.
1760        MessageFormat::Human
1761        | MessageFormat::Short
1762        | MessageFormat::Json {
1763            render_diagnostics: true,
1764            ..
1765        } => {
1766            #[derive(serde::Deserialize)]
1767            struct CompilerMessage<'a> {
1768                // `rendered` contains escape sequences, which can't be
1769                // zero-copy deserialized by serde_json.
1770                // See https://github.com/serde-rs/json/issues/742
1771                rendered: String,
1772                #[serde(borrow)]
1773                message: Cow<'a, str>,
1774                #[serde(borrow)]
1775                level: Cow<'a, str>,
1776                children: Vec<PartialDiagnostic>,
1777            }
1778
1779            // A partial rustfix::diagnostics::Diagnostic. We deserialize only a
1780            // subset of the fields because rustc's output can be extremely
1781            // deeply nested JSON in pathological cases involving macro
1782            // expansion. Rustfix's Diagnostic struct is recursive containing a
1783            // field `children: Vec<Self>`, and it can cause deserialization to
1784            // hit serde_json's default recursion limit, or overflow the stack
1785            // if we turn that off. Cargo only cares about the 1 field listed
1786            // here.
1787            #[derive(serde::Deserialize)]
1788            struct PartialDiagnostic {
1789                spans: Vec<PartialDiagnosticSpan>,
1790            }
1791
1792            // A partial rustfix::diagnostics::DiagnosticSpan.
1793            #[derive(serde::Deserialize)]
1794            struct PartialDiagnosticSpan {
1795                suggestion_applicability: Option<Applicability>,
1796            }
1797
1798            if let Ok(mut msg) = serde_json::from_str::<CompilerMessage<'_>>(compiler_message.get())
1799            {
1800                if msg.message.starts_with("aborting due to")
1801                    || msg.message.ends_with("warning emitted")
1802                    || msg.message.ends_with("warnings emitted")
1803                {
1804                    // Skip this line; we'll print our own summary at the end.
1805                    return Ok(true);
1806                }
1807                // state.stderr will add a newline
1808                if msg.rendered.ends_with('\n') {
1809                    msg.rendered.pop();
1810                }
1811                let rendered = msg.rendered;
1812                if options.show_diagnostics {
1813                    let machine_applicable: bool = msg
1814                        .children
1815                        .iter()
1816                        .map(|child| {
1817                            child
1818                                .spans
1819                                .iter()
1820                                .filter_map(|span| span.suggestion_applicability)
1821                                .any(|app| app == Applicability::MachineApplicable)
1822                        })
1823                        .any(|b| b);
1824                    count_diagnostic(&msg.level, options);
1825                    state.emit_diag(&msg.level, rendered, machine_applicable)?;
1826                }
1827                return Ok(true);
1828            }
1829        }
1830
1831        // Remove color information from the rendered string if color is not
1832        // enabled. Cargo always asks for ANSI colors from rustc. This allows
1833        // cached replay to enable/disable colors without re-invoking rustc.
1834        MessageFormat::Json { ansi: false, .. } => {
1835            #[derive(serde::Deserialize, serde::Serialize)]
1836            struct CompilerMessage<'a> {
1837                rendered: String,
1838                #[serde(flatten, borrow)]
1839                other: std::collections::BTreeMap<Cow<'a, str>, serde_json::Value>,
1840            }
1841            if let Ok(mut error) =
1842                serde_json::from_str::<CompilerMessage<'_>>(compiler_message.get())
1843            {
1844                error.rendered = anstream::adapter::strip_str(&error.rendered).to_string();
1845                let new_line = serde_json::to_string(&error)?;
1846                compiler_message = serde_json::value::RawValue::from_string(new_line)?;
1847            }
1848        }
1849
1850        // If ansi colors are desired then we should be good to go! We can just
1851        // pass through this message as-is.
1852        MessageFormat::Json { ansi: true, .. } => {}
1853    }
1854
1855    // We always tell rustc to emit messages about artifacts being produced.
1856    // These messages feed into pipelined compilation, as well as timing
1857    // information.
1858    //
1859    // Look for a matching directive and inform Cargo internally that a
1860    // metadata file has been produced.
1861    #[derive(serde::Deserialize)]
1862    struct ArtifactNotification<'a> {
1863        #[serde(borrow)]
1864        artifact: Cow<'a, str>,
1865    }
1866
1867    if let Ok(artifact) = serde_json::from_str::<ArtifactNotification<'_>>(compiler_message.get()) {
1868        trace!("found directive from rustc: `{}`", artifact.artifact);
1869        if artifact.artifact.ends_with(".rmeta") {
1870            debug!("looks like metadata finished early!");
1871            state.rmeta_produced();
1872        }
1873        return Ok(false);
1874    }
1875
1876    // And failing all that above we should have a legitimate JSON diagnostic
1877    // from the compiler, so wrap it in an external Cargo JSON message
1878    // indicating which package it came from and then emit it.
1879
1880    if !options.show_diagnostics {
1881        return Ok(true);
1882    }
1883
1884    #[derive(serde::Deserialize)]
1885    struct CompilerMessage<'a> {
1886        #[serde(borrow)]
1887        message: Cow<'a, str>,
1888        #[serde(borrow)]
1889        level: Cow<'a, str>,
1890    }
1891
1892    if let Ok(msg) = serde_json::from_str::<CompilerMessage<'_>>(compiler_message.get()) {
1893        if msg.message.starts_with("aborting due to")
1894            || msg.message.ends_with("warning emitted")
1895            || msg.message.ends_with("warnings emitted")
1896        {
1897            // Skip this line; we'll print our own summary at the end.
1898            return Ok(true);
1899        }
1900        count_diagnostic(&msg.level, options);
1901    }
1902
1903    let msg = machine_message::FromCompiler {
1904        package_id: package_id.to_spec(),
1905        manifest_path,
1906        target,
1907        message: compiler_message,
1908    }
1909    .to_json_string();
1910
1911    // Switch json lines from rustc/rustdoc that appear on stderr to stdout
1912    // instead. We want the stdout of Cargo to always be machine parseable as
1913    // stderr has our colorized human-readable messages.
1914    state.stdout(msg)?;
1915    Ok(true)
1916}
1917
1918/// Creates a unit of work that replays the cached compiler message.
1919///
1920/// Usually used when a job is fresh and doesn't need to recompile.
1921fn replay_output_cache(
1922    package_id: PackageId,
1923    manifest_path: PathBuf,
1924    target: &Target,
1925    path: PathBuf,
1926    format: MessageFormat,
1927    show_diagnostics: bool,
1928) -> Work {
1929    let target = target.clone();
1930    let mut options = OutputOptions {
1931        format,
1932        cache_cell: None,
1933        show_diagnostics,
1934        warnings_seen: 0,
1935        errors_seen: 0,
1936    };
1937    Work::new(move |state| {
1938        if !path.exists() {
1939            // No cached output, probably didn't emit anything.
1940            return Ok(());
1941        }
1942        // We sometimes have gigabytes of output from the compiler, so avoid
1943        // loading it all into memory at once, as that can cause OOM where
1944        // otherwise there would be none.
1945        let file = paths::open(&path)?;
1946        let mut reader = std::io::BufReader::new(file);
1947        let mut line = String::new();
1948        loop {
1949            let length = reader.read_line(&mut line)?;
1950            if length == 0 {
1951                break;
1952            }
1953            let trimmed = line.trim_end_matches(&['\n', '\r'][..]);
1954            on_stderr_line(
1955                state,
1956                trimmed,
1957                package_id,
1958                &manifest_path,
1959                &target,
1960                &mut options,
1961            )?;
1962            line.clear();
1963        }
1964        Ok(())
1965    })
1966}
1967
1968/// Provides a package name with descriptive target information,
1969/// e.g., '`foo` (bin "bar" test)', '`foo` (lib doctest)'.
1970fn descriptive_pkg_name(name: &str, target: &Target, mode: &CompileMode) -> String {
1971    let desc_name = target.description_named();
1972    let mode = if mode.is_rustc_test() && !(target.is_test() || target.is_bench()) {
1973        " test"
1974    } else if mode.is_doc_test() {
1975        " doctest"
1976    } else if mode.is_doc() {
1977        " doc"
1978    } else {
1979        ""
1980    };
1981    format!("`{name}` ({desc_name}{mode})")
1982}
1983
1984/// Applies environment variables from config `[env]` to [`ProcessBuilder`].
1985pub(crate) fn apply_env_config(
1986    gctx: &crate::GlobalContext,
1987    cmd: &mut ProcessBuilder,
1988) -> CargoResult<()> {
1989    for (key, value) in gctx.env_config()?.iter() {
1990        // never override a value that has already been set by cargo
1991        if cmd.get_envs().contains_key(key) {
1992            continue;
1993        }
1994        cmd.env(key, value);
1995    }
1996    Ok(())
1997}
1998
1999/// Checks if there are some scrape units waiting to be processed.
2000fn should_include_scrape_units(bcx: &BuildContext<'_, '_>, unit: &Unit) -> bool {
2001    unit.mode.is_doc() && bcx.scrape_units.len() > 0 && bcx.ws.unit_needs_doc_scrape(unit)
2002}
2003
2004/// Gets the file path of function call information output from `rustdoc`.
2005fn scrape_output_path(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<PathBuf> {
2006    assert!(unit.mode.is_doc() || unit.mode.is_doc_scrape());
2007    build_runner
2008        .outputs(unit)
2009        .map(|outputs| outputs[0].path.clone())
2010}