Skip to main content

cargo/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. 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//! [`ops::cargo_compile::compile`]: crate::ops::compile
30
31pub mod artifact;
32mod build_config;
33pub(crate) mod build_context;
34pub(crate) mod build_runner;
35mod compilation;
36mod compile_kind;
37mod crate_type;
38mod custom_build;
39pub(crate) mod fingerprint;
40pub mod future_incompat;
41pub(crate) mod job_queue;
42pub(crate) mod layout;
43mod links;
44mod locking;
45mod lto;
46mod output_depinfo;
47mod output_sbom;
48pub mod rustdoc;
49pub mod standard_lib;
50pub mod timings;
51pub(crate) mod trim_paths;
52mod unit;
53pub mod unit_dependencies;
54pub mod unit_graph;
55pub mod unused_deps;
56
57use cargo_util::collections::{HashMap, HashSet};
58use std::borrow::Cow;
59use std::cell::OnceCell;
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::ops::{Deref, Range};
66use std::path::{Path, PathBuf};
67use std::sync::{Arc, LazyLock};
68
69use anyhow::{Context as _, Error};
70use cargo_platform::{Cfg, Platform};
71use cargo_util_schemas::core::CrateType;
72use cargo_util_terminal::report::{AnnotationKind, Group, Level, Renderer, Snippet};
73use regex::Regex;
74use tracing::{debug, instrument, trace};
75
76pub use self::build_config::UserIntent;
77pub use self::build_config::{BuildConfig, CompileMode, MessageFormat};
78pub use self::build_context::BuildContext;
79pub use self::build_context::DepKindSet;
80pub use self::build_context::FileFlavor;
81pub use self::build_context::FileType;
82pub use self::build_context::RustcTargetData;
83pub use self::build_context::TargetInfo;
84pub use self::build_runner::{BuildRunner, Metadata, UnitHash};
85pub use self::compilation::{Compilation, Doctest, UnitOutput};
86pub use self::compile_kind::{CompileKind, CompileKindFallback, CompileTarget};
87pub use self::crate_type::CrateTypeExt;
88pub use self::custom_build::LinkArgTarget;
89pub use self::custom_build::{BuildOutput, BuildScriptOutputs, BuildScripts, LibraryPath};
90pub(crate) use self::fingerprint::DirtyReason;
91pub use self::fingerprint::RustdocFingerprint;
92pub use self::job_queue::Freshness;
93use self::job_queue::{Job, JobQueue, JobState, Work};
94pub(crate) use self::layout::Layout;
95pub use self::lto::Lto;
96use self::output_depinfo::output_depinfo;
97use self::output_sbom::build_sbom;
98use self::trim_paths::trim_paths_args;
99use self::trim_paths::trim_paths_args_rustdoc;
100use self::unit_graph::UnitDep;
101
102use crate::compiler::future_incompat::FutureIncompatReport;
103use crate::compiler::locking::LockKey;
104use crate::compiler::timings::SectionTiming;
105pub use crate::compiler::unit::Unit;
106pub use crate::compiler::unit::UnitIndex;
107pub use crate::compiler::unit::UnitInterner;
108use crate::context::FingerprintMethod;
109use crate::diagnostics::get_key_value;
110use crate::util::OnceExt;
111use crate::util::errors::{CargoResult, VerboseError};
112use crate::util::interning::InternedString;
113use crate::util::machine_message::{self, Message};
114use crate::util::{add_path_args, internal, path_args};
115use crate::workspace::Feature;
116use crate::workspace::PackageId;
117use crate::workspace::Target;
118use crate::workspace::TargetKind;
119use crate::workspace::manifest::TargetSourcePath;
120use crate::workspace::profiles::{PanicStrategy, Profile, StripInner};
121
122use cargo_util::{ExecStats, ProcessBuilder, ProcessError, paths};
123use cargo_util_schemas::manifest::TomlDebugInfo;
124use cargo_util_terminal::Verbosity;
125use rustfix::diagnostics::Applicability;
126
127const RUSTDOC_CRATE_VERSION_FLAG: &str = "--crate-version";
128
129/// A glorified callback for executing calls to rustc. Rather than calling rustc
130/// directly, we'll use an `Executor`, giving clients an opportunity to intercept
131/// the build calls.
132pub trait Executor: Send + Sync + 'static {
133    /// Called after a rustc process invocation is prepared up-front for a given
134    /// unit of work (may still be modified for runtime-known dependencies, when
135    /// the work is actually executed).
136    fn init(&self, _build_runner: &BuildRunner<'_, '_>, _unit: &Unit) {}
137
138    /// In case of an `Err`, Cargo will not continue with the build process for
139    /// this package.
140    ///
141    /// When `capture_rusage` is set, the returned [`ExecStats`] hold the peak
142    /// memory of the invocation (for `--timings`), even when it failed.
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        capture_rusage: bool,
152    ) -> (ExecStats, CargoResult<()>);
153
154    /// Queried when queuing each unit of work. If it returns true, then the
155    /// unit will always be rebuilt, independent of whether it needs to be.
156    fn force_rebuild(&self, _unit: &Unit) -> bool {
157        false
158    }
159}
160
161/// A `DefaultExecutor` calls rustc without doing anything else. It is Cargo's
162/// default behaviour.
163#[derive(Copy, Clone)]
164pub struct DefaultExecutor;
165
166impl Executor for DefaultExecutor {
167    #[instrument(name = "rustc", skip_all, fields(package = id.name().as_str(), process = cmd.to_string()))]
168    fn exec(
169        &self,
170        cmd: &ProcessBuilder,
171        id: PackageId,
172        _target: &Target,
173        _mode: CompileMode,
174        on_stdout_line: &mut dyn FnMut(&str) -> CargoResult<()>,
175        on_stderr_line: &mut dyn FnMut(&str) -> CargoResult<()>,
176        capture_rusage: bool,
177    ) -> (ExecStats, CargoResult<()>) {
178        let (stats, result) =
179            cmd.exec_with_streaming(on_stdout_line, on_stderr_line, false, capture_rusage);
180        (stats, result.map(drop))
181    }
182}
183
184/// Builds up and enqueue a list of pending jobs onto the `job` queue.
185///
186/// Starting from the `unit`, this function recursively calls itself to build
187/// all jobs for dependencies of the `unit`. Each of these jobs represents
188/// compiling a particular package.
189///
190/// Note that **no actual work is executed as part of this**, that's all done
191/// next as part of [`JobQueue::execute`] function which will run everything
192/// in order with proper parallelism.
193#[tracing::instrument(skip(build_runner, jobs, exec))]
194fn compile<'gctx>(
195    build_runner: &mut BuildRunner<'_, 'gctx>,
196    jobs: &mut JobQueue<'gctx>,
197    unit: &Unit,
198    exec: &Arc<dyn Executor>,
199    force_rebuild: bool,
200) -> CargoResult<()> {
201    if !build_runner.compiled.insert(unit.clone()) {
202        return Ok(());
203    }
204
205    let lock = if build_runner.bcx.gctx.cli_unstable().fine_grain_locking {
206        Some(build_runner.lock_manager.lock_shared(build_runner, unit)?)
207    } else {
208        None
209    };
210
211    match build_runner
212        .bcx
213        .gctx
214        .build_config()?
215        .fingerprint
216        .unwrap_or_default()
217    {
218        FingerprintMethod::Content => {
219            if !build_runner.bcx.gctx.cli_unstable().checksum_freshness {
220                build_runner.bcx.gctx.shell().warn(
221                    r#"ignoring `build.fingerprint = "content"` without `-Zchecksum-freshness`"#,
222                )?;
223            }
224        }
225        FingerprintMethod::Mtime => {}
226    }
227
228    // If we are in `--compile-time-deps` and the given unit is not a compile time
229    // dependency, skip compiling the unit and jumps to dependencies, which still
230    // have chances to be compile time dependencies
231    if !unit.skip_non_compile_time_dep {
232        // Build up the work to be done to compile this unit, enqueuing it once
233        // we've got everything constructed.
234        fingerprint::prepare_init(build_runner, unit)?;
235
236        let job = if unit.mode.is_run_custom_build() {
237            custom_build::prepare(build_runner, unit)?
238        } else if unit.mode.is_doc_test() {
239            // We run these targets later, so this is just a no-op for now.
240            Job::new_fresh()
241        } else {
242            let force = exec.force_rebuild(unit) || force_rebuild;
243            let mut job = fingerprint::prepare_target(build_runner, unit, force)?;
244            job.before(if job.freshness().is_dirty() {
245                let work = if unit.mode.is_doc() || unit.mode.is_doc_scrape() {
246                    rustdoc(build_runner, unit)?
247                } else {
248                    rustc(build_runner, unit, exec)?
249                };
250                work.then(link_targets(build_runner, unit, false)?)
251            } else {
252                let output_options = OutputOptions::for_fresh(build_runner, unit);
253                let manifest = ManifestErrorContext::new(build_runner, unit);
254                let work = replay_output_cache(
255                    unit.pkg.package_id(),
256                    manifest,
257                    &unit.target,
258                    build_runner.files().message_cache_path(unit),
259                    output_options,
260                );
261                // Need to link targets on both the dirty and fresh.
262                work.then(link_targets(build_runner, unit, true)?)
263            });
264
265            // If -Zfine-grain-locking is enabled, we wrap the job with an upgrade to exclusive
266            // lock before starting, then downgrade to a shared lock after the job is finished.
267            if build_runner.bcx.gctx.cli_unstable().fine_grain_locking && job.freshness().is_dirty()
268            {
269                if let Some(lock) = lock {
270                    // Here we unlock the current shared lock to avoid deadlocking with other cargo
271                    // processes. Then we configure our compile job to take an exclusive lock
272                    // before starting. Once we are done compiling (including both rmeta and rlib)
273                    // we downgrade to a shared lock to allow other cargo's to read the build unit.
274                    // We will hold this shared lock for the remainder of compilation to prevent
275                    // other cargo from re-compiling while we are still using the unit.
276                    build_runner.lock_manager.unlock(&lock)?;
277                    job.before(prebuild_lock_exclusive(lock.clone()));
278                    job.after(downgrade_lock_to_shared(lock));
279                }
280            }
281
282            job
283        };
284        jobs.enqueue(build_runner, unit, job)?;
285    }
286
287    // Be sure to compile all dependencies of this target as well.
288    let deps = Vec::from(build_runner.unit_deps(unit)); // Create vec due to mutable borrow.
289    for dep in deps {
290        compile(build_runner, jobs, &dep.unit, exec, false)?;
291    }
292
293    Ok(())
294}
295
296/// Generates the warning message used when fallible doc-scrape units fail,
297/// either for rustdoc or rustc.
298fn make_failed_scrape_diagnostic(
299    build_runner: &BuildRunner<'_, '_>,
300    unit: &Unit,
301    top_line: impl Display,
302) -> String {
303    let manifest_path = unit.pkg.manifest_path();
304    let relative_manifest_path = manifest_path
305        .strip_prefix(build_runner.bcx.ws.root())
306        .unwrap_or(&manifest_path);
307
308    format!(
309        "\
310{top_line}
311    Try running with `--verbose` to see the error message.
312    If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in {}",
313        relative_manifest_path.display()
314    )
315}
316
317/// Creates a unit of work invoking `rustc` for building the `unit`.
318#[tracing::instrument(skip_all)]
319fn rustc(
320    build_runner: &mut BuildRunner<'_, '_>,
321    unit: &Unit,
322    exec: &Arc<dyn Executor>,
323) -> CargoResult<Work> {
324    let mut rustc = prepare_rustc(build_runner, unit)?;
325
326    let name = unit.pkg.name();
327
328    let outputs = build_runner.outputs(unit)?;
329    let root = build_runner.files().output_dir(unit);
330
331    // Prepare the native lib state (extra `-L` and `-l` flags).
332    let build_script_outputs = Arc::clone(&build_runner.build_script_outputs);
333    let current_id = unit.pkg.package_id();
334    let manifest = ManifestErrorContext::new(build_runner, unit);
335    let build_scripts = build_runner.build_scripts.get(unit).cloned();
336
337    // If we are a binary and the package also contains a library, then we
338    // don't pass the `-l` flags.
339    let pass_l_flag = unit.target.is_lib() || !unit.pkg.targets().iter().any(|t| t.is_lib());
340
341    let dep_info_name =
342        if let Some(c_extra_filename) = build_runner.files().metadata(unit).c_extra_filename() {
343            format!("{}-{}.d", unit.target.crate_name(), c_extra_filename)
344        } else {
345            format!("{}.d", unit.target.crate_name())
346        };
347    let rustc_dep_info_loc = root.join(dep_info_name);
348    let dep_info_loc = fingerprint::dep_info_loc(build_runner, unit);
349
350    let mut output_options = OutputOptions::for_dirty(build_runner, unit);
351    let package_id = unit.pkg.package_id();
352    let target = Target::clone(&unit.target);
353    let mode = unit.mode;
354    let capture_rusage = build_runner.bcx.capture_rusage();
355
356    exec.init(build_runner, unit);
357    let exec = exec.clone();
358
359    let root_output = build_runner.files().host_dest().map(|v| v.to_path_buf());
360    let build_dir = build_runner.bcx.ws.build_dir().into_path_unlocked();
361    let pkg_root = unit.pkg.root().to_path_buf();
362    let ws_root = build_runner.bcx.ws.root().to_path_buf();
363    let cwd = rustc
364        .get_cwd()
365        .unwrap_or_else(|| build_runner.bcx.gctx.cwd())
366        .to_path_buf();
367    let fingerprint_dir = build_runner.files().fingerprint_dir(unit);
368    let script_metadatas = build_runner.find_build_script_metadatas(unit);
369    let is_local = unit.is_local();
370    let artifact = unit.artifact;
371    let build_dir_new_layout = build_runner.bcx.gctx.cli_unstable().build_dir_new_layout;
372    let sbom_files = build_runner.sbom_output_files(unit)?;
373    let sbom = if !sbom_files.is_empty() {
374        Some(build_sbom(build_runner, unit)?)
375    } else {
376        None
377    };
378
379    let unremap_files = build_runner.unremap_output_files(unit)?;
380    let unremap_content = if unremap_files.is_empty() {
381        None
382    } else {
383        let mut buf = Vec::new();
384        trim_paths::write_unremap_file(&mut buf, build_runner, unit)?;
385        Some(buf)
386    };
387
388    let hide_diagnostics_for_scrape_unit = build_runner.bcx.unit_can_fail_for_docscraping(unit)
389        && !matches!(
390            build_runner.bcx.gctx.shell().verbosity(),
391            Verbosity::Verbose
392        );
393    let failed_scrape_diagnostic = hide_diagnostics_for_scrape_unit.then(|| {
394        // If this unit is needed for doc-scraping, then we generate a diagnostic that
395        // describes the set of reverse-dependencies that cause the unit to be needed.
396        let target_desc = unit.target.description_named();
397        let mut for_scrape_units = build_runner
398            .bcx
399            .scrape_units_have_dep_on(unit)
400            .into_iter()
401            .map(|unit| unit.target.description_named())
402            .collect::<Vec<_>>();
403        for_scrape_units.sort();
404        let for_scrape_units = for_scrape_units.join(", ");
405        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}"))
406    });
407    if hide_diagnostics_for_scrape_unit {
408        output_options.show_diagnostics = false;
409    }
410    let env_config = Arc::clone(build_runner.bcx.gctx.env_config()?);
411    return Ok(Work::new(move |state| {
412        if build_dir_new_layout {
413            // Remove all files from the out dir to avoid overwriting a file that is hardlinked
414            // outside of build-dir.
415            if root.exists() {
416                paths::remove_dir_all(&root)?;
417            }
418            paths::create_dir_all(&root)?;
419        } else if artifact.is_true() {
420            // Artifacts are in a different location than typical units,
421            // hence we must assure the crate- and target-dependent
422            // directory is present.
423            paths::create_dir_all(&root)?;
424        }
425
426        // Only at runtime have we discovered what the extra -L and -l
427        // arguments are for native libraries, so we process those here. We
428        // also need to be sure to add any -L paths for our plugins to the
429        // dynamic library load path as a plugin's dynamic library may be
430        // located somewhere in there.
431        // Finally, if custom environment variables have been produced by
432        // previous build scripts, we include them in the rustc invocation.
433        if let Some(build_scripts) = build_scripts {
434            let script_outputs = build_script_outputs.lock().unwrap();
435            add_native_deps(
436                &mut rustc,
437                &script_outputs,
438                &build_scripts,
439                pass_l_flag,
440                &target,
441                current_id,
442                mode,
443            )?;
444            if let Some(ref root_output) = root_output {
445                add_plugin_deps(&mut rustc, &script_outputs, &build_scripts, root_output)?;
446            }
447            add_custom_flags(&mut rustc, &script_outputs, script_metadatas)?;
448        }
449
450        if !build_dir_new_layout {
451            for output in outputs.iter() {
452                // If there is both an rmeta and rlib, rustc will prefer to use the
453                // rlib, even if it is older. Therefore, we must delete the rlib to
454                // force using the new rmeta.
455                if output.path.extension() == Some(OsStr::new("rmeta")) {
456                    let dst = root.join(&output.path).with_extension("rlib");
457                    if dst.exists() {
458                        paths::remove_file(&dst)?;
459                    }
460                }
461
462                // Some linkers do not remove the executable, but truncate and modify it.
463                // That results in the old hard-link being modified even after renamed.
464                // We delete the old artifact here to prevent this behavior from confusing users.
465                // See rust-lang/cargo#8348.
466                if output.hardlink.is_some() && output.path.exists() {
467                    _ = paths::remove_file(&output.path).map_err(|e| {
468                        tracing::debug!(
469                            "failed to delete previous output file `{:?}`: {e:?}",
470                            output.path
471                        );
472                    });
473                }
474            }
475        }
476
477        state.running(&rustc);
478        let timestamp = paths::set_invocation_time(&fingerprint_dir)?;
479        if let Some(sbom) = sbom {
480            for file in sbom_files {
481                tracing::debug!("writing sbom to {}", file.display());
482                let outfile = BufWriter::new(paths::create(&file)?);
483                serde_json::to_writer(outfile, &sbom)?;
484            }
485        }
486
487        if let Some(content) = &unremap_content {
488            for file in &unremap_files {
489                tracing::debug!("writing unremap file to {}", file.display());
490                paths::write_atomic(file, content)?;
491            }
492        }
493
494        let (stats, result) = exec.exec(
495            &rustc,
496            package_id,
497            &target,
498            mode,
499            &mut |line| on_stdout_line(state, line, package_id, &target),
500            &mut |line| {
501                on_stderr_line(
502                    state,
503                    line,
504                    package_id,
505                    &manifest,
506                    &target,
507                    &mut output_options,
508                )
509            },
510            capture_rusage,
511        );
512        let result = result
513            .map_err(|e| {
514                if output_options.errors_seen == 0 {
515                    // If we didn't expect an error, do not require --verbose to fail.
516                    // This is intended to debug
517                    // https://github.com/rust-lang/crater/issues/733, where we are seeing
518                    // Cargo exit unsuccessfully while seeming to not show any errors.
519                    e
520                } else {
521                    verbose_if_simple_exit_code(e)
522                }
523            })
524            .with_context(|| {
525                // adapted from rustc_errors/src/lib.rs
526                let warnings = match output_options.warnings_seen {
527                    0 => String::new(),
528                    1 => "; 1 warning emitted".to_string(),
529                    count => format!("; {} warnings emitted", count),
530                };
531                let errors = match output_options.errors_seen {
532                    0 => String::new(),
533                    1 => " due to 1 previous error".to_string(),
534                    count => format!(" due to {} previous errors", count),
535                };
536                let name = descriptive_pkg_name(&name, &target, &mode);
537                format!("could not compile {name}{errors}{warnings}")
538            });
539
540        if let Some(peak_memory) = stats.peak_rss {
541            state.peak_memory(peak_memory);
542        }
543
544        if let Err(e) = result {
545            if let Some(diagnostic) = failed_scrape_diagnostic {
546                state.warning(diagnostic);
547            }
548
549            return Err(e);
550        }
551
552        // Exec should never return with success *and* generate an error.
553        debug_assert_eq!(output_options.errors_seen, 0);
554
555        if rustc_dep_info_loc.exists() {
556            fingerprint::translate_dep_info(
557                &rustc_dep_info_loc,
558                &dep_info_loc,
559                &cwd,
560                &pkg_root,
561                &build_dir,
562                &ws_root,
563                &rustc,
564                // Do not track source files in the fingerprint for registry dependencies.
565                is_local,
566                &env_config,
567            )
568            .with_context(|| {
569                internal(format!(
570                    "could not parse/generate dep info at: {}",
571                    rustc_dep_info_loc.display()
572                ))
573            })?;
574            // This mtime shift allows Cargo to detect if a source file was
575            // modified in the middle of the build.
576            paths::set_file_time_no_err(dep_info_loc, timestamp);
577        }
578
579        // This mtime shift for .rmeta is a workaround as rustc incremental build
580        // since rust-lang/rust#114669 (1.90.0) skips unnecessary rmeta generation.
581        //
582        // The situation is like this:
583        //
584        // 1. When build script execution's external dependendies
585        //    (rerun-if-changed, rerun-if-env-changed) got updated,
586        //    the execution unit reran and got a newer mtime.
587        // 2. rustc type-checked the associated crate, though with incremental
588        //    compilation, no rmeta regeneration. Its `.rmeta` stays old.
589        // 3. Run `cargo check` again. Cargo found build script execution had
590        //    a new mtime than existing crate rmeta, so re-checking the crate.
591        //    However the check is a no-op (input has no change), so stuck.
592        if mode.is_check() {
593            for output in outputs.iter() {
594                paths::set_file_time_no_err(&output.path, timestamp);
595            }
596        }
597
598        Ok(())
599    }));
600
601    // Add all relevant `-L` and `-l` flags from dependencies (now calculated and
602    // present in `state`) to the command provided.
603    fn add_native_deps(
604        rustc: &mut ProcessBuilder,
605        build_script_outputs: &BuildScriptOutputs,
606        build_scripts: &BuildScripts,
607        pass_l_flag: bool,
608        target: &Target,
609        current_id: PackageId,
610        mode: CompileMode,
611    ) -> CargoResult<()> {
612        let mut library_paths = vec![];
613
614        for key in build_scripts.to_link.iter() {
615            let output = build_script_outputs.get(key.1).ok_or_else(|| {
616                internal(format!(
617                    "couldn't find build script output for {}/{}",
618                    key.0, key.1
619                ))
620            })?;
621            library_paths.extend(output.library_paths.iter());
622        }
623
624        // NOTE: This very intentionally does not use the derived ord from LibraryPath because we need to
625        // retain relative ordering within the same type (i.e. not lexicographic). The use of a stable sort
626        // is also important here because it ensures that paths of the same type retain the same relative
627        // ordering (for an unstable sort to work here, the list would need to retain the idx of each element
628        // and then sort by that idx when the type is equivalent.
629        library_paths.sort_by_key(|p| match p {
630            LibraryPath::CargoArtifact(_) => 0,
631            LibraryPath::External(_) => 1,
632        });
633
634        for path in library_paths.iter() {
635            rustc.arg("-L").arg(path.as_ref());
636        }
637
638        for key in build_scripts.to_link.iter() {
639            let output = build_script_outputs.get(key.1).ok_or_else(|| {
640                internal(format!(
641                    "couldn't find build script output for {}/{}",
642                    key.0, key.1
643                ))
644            })?;
645
646            if key.0 == current_id {
647                if pass_l_flag {
648                    for name in output.library_links.iter() {
649                        rustc.arg("-l").arg(name);
650                    }
651                }
652            }
653
654            for (lt, arg) in &output.linker_args {
655                // There was an unintentional change where cdylibs were
656                // allowed to be passed via transitive dependencies. This
657                // clause should have been kept in the `if` block above. For
658                // now, continue allowing it for cdylib only.
659                // See https://github.com/rust-lang/cargo/issues/9562
660                if lt.applies_to(target, mode)
661                    && (key.0 == current_id || *lt == LinkArgTarget::Cdylib)
662                {
663                    rustc.arg("-C").arg(format!("link-arg={}", arg));
664                }
665            }
666        }
667        Ok(())
668    }
669}
670
671fn verbose_if_simple_exit_code(err: Error) -> Error {
672    // If a signal on unix (`code == None`) or an abnormal termination
673    // on Windows (codes like `0xC0000409`), don't hide the error details.
674    match err
675        .downcast_ref::<ProcessError>()
676        .as_ref()
677        .and_then(|perr| perr.code)
678    {
679        Some(n) if cargo_util::is_simple_exit_code(n) => VerboseError::new(err).into(),
680        _ => err,
681    }
682}
683
684fn prebuild_lock_exclusive(lock: LockKey) -> Work {
685    Work::new(move |state| {
686        state.lock_exclusive(&lock)?;
687        Ok(())
688    })
689}
690
691fn downgrade_lock_to_shared(lock: LockKey) -> Work {
692    Work::new(move |state| {
693        state.downgrade_to_shared(&lock)?;
694        Ok(())
695    })
696}
697
698/// Link the compiled target (often of form `foo-{metadata_hash}`) to the
699/// final target. This must happen during both "Fresh" and "Compile".
700fn link_targets(
701    build_runner: &mut BuildRunner<'_, '_>,
702    unit: &Unit,
703    fresh: bool,
704) -> CargoResult<Work> {
705    let bcx = build_runner.bcx;
706    let outputs = build_runner.outputs(unit)?;
707    let export_dir = build_runner.files().export_dir();
708    let package_id = unit.pkg.package_id();
709    let manifest_path = PathBuf::from(unit.pkg.manifest_path());
710    let profile = unit.profile.clone();
711    let unit_mode = unit.mode;
712    let features = unit.features.iter().map(|s| s.to_string()).collect();
713    let json_messages = bcx.build_config.emit_json();
714    let executable = build_runner.get_executable(unit)?;
715    let mut target = Target::clone(&unit.target);
716    if let TargetSourcePath::Metabuild = target.src_path() {
717        // Give it something to serialize.
718        let path = unit
719            .pkg
720            .manifest()
721            .metabuild_path(build_runner.bcx.ws.build_dir());
722        target.set_src_path(TargetSourcePath::Path(path));
723    }
724
725    Ok(Work::new(move |state| {
726        // If we're a "root crate", e.g., the target of this compilation, then we
727        // hard link our outputs out of the `deps` directory into the directory
728        // above. This means that `cargo build` will produce binaries in
729        // `target/debug` which one probably expects.
730        let mut destinations = vec![];
731        for output in outputs.iter() {
732            let src = &output.path;
733            // This may have been a `cargo rustc` command which changes the
734            // output, so the source may not actually exist.
735            if !src.exists() {
736                continue;
737            }
738            let Some(dst) = output.hardlink.as_ref() else {
739                destinations.push(src.clone());
740                continue;
741            };
742            destinations.push(dst.clone());
743            paths::link_or_copy(src, dst)?;
744            if let Some(ref path) = output.export_path {
745                let export_dir = export_dir.as_ref().unwrap();
746                paths::create_dir_all(export_dir)?;
747
748                paths::link_or_copy(src, path)?;
749            }
750        }
751
752        if json_messages {
753            let debuginfo = match profile.debuginfo.into_inner() {
754                TomlDebugInfo::None => machine_message::ArtifactDebuginfo::Int(0),
755                TomlDebugInfo::Limited => machine_message::ArtifactDebuginfo::Int(1),
756                TomlDebugInfo::Full => machine_message::ArtifactDebuginfo::Int(2),
757                TomlDebugInfo::LineDirectivesOnly => {
758                    machine_message::ArtifactDebuginfo::Named("line-directives-only")
759                }
760                TomlDebugInfo::LineTablesOnly => {
761                    machine_message::ArtifactDebuginfo::Named("line-tables-only")
762                }
763            };
764            let art_profile = machine_message::ArtifactProfile {
765                opt_level: profile.opt_level.as_str(),
766                debuginfo: Some(debuginfo),
767                debug_assertions: profile.debug_assertions,
768                overflow_checks: profile.overflow_checks,
769                test: unit_mode.is_any_test(),
770            };
771
772            let msg = machine_message::Artifact {
773                package_id: package_id.to_spec(),
774                manifest_path,
775                target: &target,
776                profile: art_profile,
777                features,
778                filenames: destinations,
779                executable,
780                fresh,
781            }
782            .to_json_string();
783            state.stdout(msg)?;
784        }
785        Ok(())
786    }))
787}
788
789// For all plugin dependencies, add their -L paths (now calculated and present
790// in `build_script_outputs`) to the dynamic library load path for the command
791// to execute.
792fn add_plugin_deps(
793    rustc: &mut ProcessBuilder,
794    build_script_outputs: &BuildScriptOutputs,
795    build_scripts: &BuildScripts,
796    root_output: &Path,
797) -> CargoResult<()> {
798    let var = paths::dylib_path_envvar();
799    let search_path = rustc.get_env(var).unwrap_or_default();
800    let mut search_path = env::split_paths(&search_path).collect::<Vec<_>>();
801    for (pkg_id, metadata) in &build_scripts.plugins {
802        let output = build_script_outputs
803            .get(*metadata)
804            .ok_or_else(|| internal(format!("couldn't find libs for plugin dep {}", pkg_id)))?;
805        search_path.append(&mut filter_dynamic_search_path(
806            output.library_paths.iter().map(AsRef::as_ref),
807            root_output,
808        ));
809    }
810    let search_path = paths::join_paths(&search_path, var)?;
811    rustc.env(var, &search_path);
812    Ok(())
813}
814
815fn get_dynamic_search_path(path: &Path) -> &Path {
816    match path.to_str().and_then(|s| s.split_once("=")) {
817        Some(("native" | "crate" | "dependency" | "framework" | "all", path)) => Path::new(path),
818        _ => path,
819    }
820}
821
822// Determine paths to add to the dynamic search path from -L entries
823//
824// Strip off prefixes like "native=" or "framework=" and filter out directories
825// **not** inside our output directory since they are likely spurious and can cause
826// clashes with system shared libraries (issue #3366).
827fn filter_dynamic_search_path<'a, I>(paths: I, root_output: &Path) -> Vec<PathBuf>
828where
829    I: Iterator<Item = &'a PathBuf>,
830{
831    let mut search_path = vec![];
832    for dir in paths {
833        let dir = get_dynamic_search_path(dir);
834        if dir.starts_with(&root_output) {
835            search_path.push(dir.to_path_buf());
836        } else {
837            debug!(
838                "Not including path {} in runtime library search path because it is \
839                 outside target root {}",
840                dir.display(),
841                root_output.display()
842            );
843        }
844    }
845    search_path
846}
847
848/// Prepares flags and environments we can compute for a `rustc` invocation
849/// before the job queue starts compiling any unit.
850///
851/// This builds a static view of the invocation. Flags depending on the
852/// completion of other units will be added later in runtime, such as flags
853/// from build scripts.
854#[tracing::instrument(skip_all)]
855fn prepare_rustc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<ProcessBuilder> {
856    let gctx = build_runner.bcx.gctx;
857    let is_primary = build_runner.is_primary_package(unit);
858    let is_workspace = build_runner.bcx.ws.is_member(&unit.pkg);
859
860    let mut base = build_runner
861        .compilation
862        .rustc_process(unit, is_primary, is_workspace)?;
863    build_base_args(build_runner, &mut base, unit)?;
864    if unit.pkg.manifest().is_embedded() {
865        if !gctx.cli_unstable().script {
866            anyhow::bail!(
867                "parsing `{}` requires `-Zscript`",
868                unit.pkg.manifest_path().display()
869            );
870        }
871        base.arg("-Z").arg("crate-attr=feature(frontmatter)");
872        base.arg("-Z").arg("crate-attr=allow(unused_features)");
873    }
874
875    base.inherit_jobserver(&build_runner.jobserver);
876    build_deps_args(&mut base, build_runner, unit)?;
877    add_cap_lints(build_runner.bcx, unit, &mut base);
878    if let Some(args) = build_runner.bcx.extra_args_for(unit) {
879        base.args(args);
880    }
881    base.args(&unit.rustflags);
882
883    // Library unit tests should not warn about unused public items,
884    // including when the lint is enabled through RUSTFLAGS.
885    if unit.target.is_lib() && unit.mode.is_rustc_test() {
886        base.arg("-Adead_code_pub_in_binary");
887    }
888    if gctx.cli_unstable().binary_dep_depinfo {
889        base.arg("-Z").arg("binary-dep-depinfo");
890    }
891    if build_runner.bcx.gctx.cli_unstable().checksum_freshness {
892        match build_runner
893            .bcx
894            .gctx
895            .build_config()?
896            .fingerprint
897            .unwrap_or_default()
898        {
899            FingerprintMethod::Content => {
900                base.arg("-Z").arg("checksum-hash-algorithm=blake3");
901            }
902            FingerprintMethod::Mtime => {}
903        }
904    }
905
906    if is_primary {
907        base.env("CARGO_PRIMARY_PACKAGE", "1");
908        let file_list = build_runner.sbom_output_files(unit)?;
909        if !file_list.is_empty() {
910            let file_list = std::env::join_paths(file_list)?;
911            base.env("CARGO_SBOM_PATH", file_list);
912        }
913    }
914
915    if unit.target.is_test() || unit.target.is_bench() {
916        let tmp = build_runner
917            .files()
918            .layout(unit.kind)
919            .build_dir()
920            .prepare_tmp()?;
921        base.env("CARGO_TARGET_TMPDIR", tmp.display().to_string());
922    }
923
924    // Added last to reduce the risk of RUSTFLAGS or `[lints]` from interfering with
925    // `unused_dependencies` tracking
926    base.arg("--force-warn=unused_crate_dependencies");
927
928    Ok(base)
929}
930
931/// Prepares flags and environments we can compute for a `rustdoc` invocation
932/// before the job queue starts compiling any unit.
933///
934/// This builds a static view of the invocation. Flags depending on the
935/// completion of other units will be added later in runtime, such as flags
936/// from build scripts.
937#[tracing::instrument(skip_all)]
938fn prepare_rustdoc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<ProcessBuilder> {
939    let bcx = build_runner.bcx;
940    // script_metadata is not needed here, it is only for tests.
941    let mut rustdoc = build_runner.compilation.rustdoc_process(unit, None)?;
942    let wants_json_output = build_runner.bcx.build_config.intent.wants_doc_json_output();
943    if unit.pkg.manifest().is_embedded() {
944        if !bcx.gctx.cli_unstable().script {
945            anyhow::bail!(
946                "parsing `{}` requires `-Zscript`",
947                unit.pkg.manifest_path().display()
948            );
949        }
950        rustdoc.arg("-Z").arg("crate-attr=feature(frontmatter)");
951        rustdoc.arg("-Z").arg("crate-attr=allow(unused_features)");
952    }
953    rustdoc.inherit_jobserver(&build_runner.jobserver);
954    let crate_name = unit.target.crate_name();
955    rustdoc.arg("--crate-name").arg(&crate_name);
956    add_path_args(bcx.ws, unit, &mut rustdoc);
957    add_cap_lints(bcx, unit, &mut rustdoc);
958
959    unit.kind.add_target_arg(&mut rustdoc);
960
961    let doc_dir = if wants_json_output {
962        // Always use new layout for '--output-format=json'.
963        // In fix for https://github.com/rust-lang/cargo/issues/16291
964
965        build_runner.files().out_dir_new_layout(unit)
966    } else {
967        build_runner.files().output_dir(unit)
968    };
969
970    rustdoc.arg("-o").arg(&doc_dir);
971    rustdoc.args(&features_args(unit));
972    rustdoc.args(&check_cfg_args(unit));
973
974    add_error_format_and_color(build_runner, &mut rustdoc);
975    add_allow_features(build_runner, &mut rustdoc);
976
977    if build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo {
978        // html-static-files is required for keeping the shared styling resources
979        // html-non-static-files is required for keeping the original rustdoc emission
980        let mut arg = if wants_json_output {
981            OsString::from("--emit=dep-info=")
982        } else if build_runner.bcx.gctx.cli_unstable().rustdoc_mergeable_info {
983            // toolchain resources are written at the end, at the same time as merging
984            OsString::from("--emit=html-non-static-files,dep-info=")
985        } else {
986            // if not using mergeable CCI, everything is written every time
987            OsString::from("--emit=html-static-files,html-non-static-files,dep-info=")
988        };
989        arg.push(rustdoc_dep_info_loc(build_runner, unit));
990        rustdoc.arg(arg);
991
992        if build_runner.bcx.gctx.cli_unstable().checksum_freshness {
993            match build_runner
994                .bcx
995                .gctx
996                .build_config()?
997                .fingerprint
998                .unwrap_or_default()
999            {
1000                FingerprintMethod::Content => {
1001                    rustdoc.arg("-Z").arg("checksum-hash-algorithm=blake3");
1002                }
1003                FingerprintMethod::Mtime => {}
1004            }
1005        }
1006    } else if build_runner.bcx.gctx.cli_unstable().rustdoc_mergeable_info && !wants_json_output {
1007        // toolchain resources are written at the end, at the same time as merging
1008        rustdoc.arg("--emit=html-non-static-files");
1009    }
1010
1011    if build_runner.bcx.gctx.cli_unstable().rustdoc_mergeable_info && !wants_json_output {
1012        // write out mergeable data to be imported
1013        rustdoc.arg("-Zunstable-options");
1014        let mut arg = OsString::from("--write-doc-meta-dir=");
1015        // `-Zrustdoc-mergeable-info` always uses the new layout.
1016        arg.push(build_runner.files().out_dir_new_layout(unit));
1017        rustdoc.arg(arg);
1018    }
1019
1020    trim_paths_args_rustdoc(&mut rustdoc, build_runner, unit, &unit.profile.trim_paths)?;
1021
1022    rustdoc.args(unit.pkg.manifest().lint_rustflags());
1023
1024    let metadata = build_runner.metadata_for_doc_units[unit];
1025    rustdoc
1026        .arg("-C")
1027        .arg(format!("metadata={}", metadata.c_metadata()));
1028
1029    if unit.mode.is_doc_scrape() {
1030        debug_assert!(build_runner.bcx.scrape_units.contains(unit));
1031
1032        if unit.target.is_test() {
1033            rustdoc.arg("--scrape-tests");
1034        }
1035
1036        rustdoc.arg("-Zunstable-options");
1037
1038        rustdoc
1039            .arg("--scrape-examples-output-path")
1040            .arg(scrape_output_path(build_runner, unit)?);
1041
1042        // Only scrape example for items from crates in the workspace, to reduce generated file size
1043        for pkg in build_runner.bcx.packages.packages() {
1044            let names = pkg
1045                .targets()
1046                .iter()
1047                .map(|target| target.crate_name())
1048                .collect::<HashSet<_>>();
1049            for name in names {
1050                rustdoc.arg("--scrape-examples-target-crate").arg(name);
1051            }
1052        }
1053    }
1054
1055    if should_include_scrape_units(build_runner.bcx, unit) {
1056        rustdoc.arg("-Zunstable-options");
1057    }
1058
1059    build_deps_args(&mut rustdoc, build_runner, unit)?;
1060    rustdoc::add_root_urls(build_runner, unit, &mut rustdoc)?;
1061
1062    rustdoc::add_output_format(build_runner, &mut rustdoc)?;
1063
1064    if let Some(args) = build_runner.bcx.extra_args_for(unit) {
1065        rustdoc.args(args);
1066    }
1067    rustdoc.args(&unit.rustdocflags);
1068
1069    if !crate_version_flag_already_present(&rustdoc) {
1070        append_crate_version_flag(unit, &mut rustdoc);
1071    }
1072
1073    Ok(rustdoc)
1074}
1075
1076/// Creates a unit of work invoking `rustdoc` for documenting the `unit`.
1077#[tracing::instrument(skip_all)]
1078fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<Work> {
1079    let mut rustdoc = prepare_rustdoc(build_runner, unit)?;
1080
1081    let crate_name = unit.target.crate_name();
1082    let is_json_output = build_runner.bcx.build_config.intent.wants_doc_json_output();
1083    let doc_dir = build_runner.files().output_dir(unit);
1084    // Create the documentation directory ahead of time as rustdoc currently has
1085    // a bug where concurrent invocations will race to create this directory if
1086    // it doesn't already exist.
1087    paths::create_dir_all(&doc_dir)?;
1088
1089    let target_desc = unit.target.description_named();
1090    let name = unit.pkg.name();
1091    let build_script_outputs = Arc::clone(&build_runner.build_script_outputs);
1092    let package_id = unit.pkg.package_id();
1093    let target = Target::clone(&unit.target);
1094    let manifest = ManifestErrorContext::new(build_runner, unit);
1095
1096    let rustdoc_dep_info_loc = rustdoc_dep_info_loc(build_runner, unit);
1097    let dep_info_loc = fingerprint::dep_info_loc(build_runner, unit);
1098    let build_dir = build_runner.bcx.ws.build_dir().into_path_unlocked();
1099    let pkg_root = unit.pkg.root().to_path_buf();
1100    let ws_root = build_runner.bcx.ws.root().to_path_buf();
1101    let cwd = rustdoc
1102        .get_cwd()
1103        .unwrap_or_else(|| build_runner.bcx.gctx.cwd())
1104        .to_path_buf();
1105    let fingerprint_dir = build_runner.files().fingerprint_dir(unit);
1106    let is_local = unit.is_local();
1107    let env_config = Arc::clone(build_runner.bcx.gctx.env_config()?);
1108    let capture_rusage = build_runner.bcx.capture_rusage();
1109    let rustdoc_depinfo_enabled = build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo;
1110
1111    let mut output_options = OutputOptions::for_dirty(build_runner, unit);
1112    let script_metadatas = build_runner.find_build_script_metadatas(unit);
1113    let scrape_outputs = if should_include_scrape_units(build_runner.bcx, unit) {
1114        Some(
1115            build_runner
1116                .bcx
1117                .scrape_units
1118                .iter()
1119                .map(|unit| {
1120                    Ok((
1121                        build_runner.files().metadata(unit).unit_id(),
1122                        scrape_output_path(build_runner, unit)?,
1123                    ))
1124                })
1125                .collect::<CargoResult<HashMap<_, _>>>()?,
1126        )
1127    } else {
1128        None
1129    };
1130
1131    let failed_scrape_units = Arc::clone(&build_runner.failed_scrape_units);
1132    let hide_diagnostics_for_scrape_unit = build_runner.bcx.unit_can_fail_for_docscraping(unit)
1133        && !matches!(
1134            build_runner.bcx.gctx.shell().verbosity(),
1135            Verbosity::Verbose
1136        );
1137    let failed_scrape_diagnostic = hide_diagnostics_for_scrape_unit.then(|| {
1138        make_failed_scrape_diagnostic(
1139            build_runner,
1140            unit,
1141            format_args!("failed to scan {target_desc} in package `{name}` for example code usage"),
1142        )
1143    });
1144    if hide_diagnostics_for_scrape_unit {
1145        output_options.show_diagnostics = false;
1146    }
1147
1148    Ok(Work::new(move |state| {
1149        add_custom_flags(
1150            &mut rustdoc,
1151            &build_script_outputs.lock().unwrap(),
1152            script_metadatas,
1153        )?;
1154
1155        // Add the output of scraped examples to the rustdoc command.
1156        // This action must happen after the unit's dependencies have finished,
1157        // because some of those deps may be Docscrape units which have failed.
1158        // So we dynamically determine which `--with-examples` flags to pass here.
1159        if let Some(scrape_outputs) = scrape_outputs {
1160            let failed_scrape_units = failed_scrape_units.lock().unwrap();
1161            for (metadata, output_path) in &scrape_outputs {
1162                if !failed_scrape_units.contains(metadata) {
1163                    rustdoc.arg("--with-examples").arg(output_path);
1164                }
1165            }
1166        }
1167
1168        if !is_json_output {
1169            let crate_dir = doc_dir.join(&crate_name);
1170            if crate_dir.exists() {
1171                // Remove output from a previous build. This ensures that stale
1172                // files for removed items are removed.
1173                debug!("removing pre-existing doc directory {:?}", crate_dir);
1174                paths::remove_dir_all(&crate_dir)?;
1175            }
1176        };
1177        state.running(&rustdoc);
1178        let timestamp = paths::set_invocation_time(&fingerprint_dir)?;
1179
1180        let (stats, result) = rustdoc.exec_with_streaming(
1181            &mut |line| on_stdout_line(state, line, package_id, &target),
1182            &mut |line| {
1183                on_stderr_line(
1184                    state,
1185                    line,
1186                    package_id,
1187                    &manifest,
1188                    &target,
1189                    &mut output_options,
1190                )
1191            },
1192            false,
1193            capture_rusage,
1194        );
1195        let result = result
1196            .map_err(verbose_if_simple_exit_code)
1197            .with_context(|| format!("could not document `{}`", name));
1198
1199        if let Some(peak_memory) = stats.peak_rss {
1200            state.peak_memory(peak_memory);
1201        }
1202
1203        if let Err(e) = result {
1204            if let Some(diagnostic) = failed_scrape_diagnostic {
1205                state.warning(diagnostic);
1206            }
1207
1208            return Err(e);
1209        }
1210        if rustdoc_depinfo_enabled && rustdoc_dep_info_loc.exists() {
1211            fingerprint::translate_dep_info(
1212                &rustdoc_dep_info_loc,
1213                &dep_info_loc,
1214                &cwd,
1215                &pkg_root,
1216                &build_dir,
1217                &ws_root,
1218                &rustdoc,
1219                // Should we track source file for doc gen?
1220                is_local,
1221                &env_config,
1222            )
1223            .with_context(|| {
1224                internal(format_args!(
1225                    "could not parse/generate dep info at: {}",
1226                    rustdoc_dep_info_loc.display()
1227                ))
1228            })?;
1229            // This mtime shift allows Cargo to detect if a source file was
1230            // modified in the middle of the build.
1231            paths::set_file_time_no_err(dep_info_loc, timestamp);
1232        }
1233
1234        Ok(())
1235    }))
1236}
1237
1238// The --crate-version flag could have already been passed in RUSTDOCFLAGS
1239// or as an extra compiler argument for rustdoc
1240fn crate_version_flag_already_present(rustdoc: &ProcessBuilder) -> bool {
1241    rustdoc.get_args().any(|flag| {
1242        flag.to_str()
1243            .map_or(false, |flag| flag.starts_with(RUSTDOC_CRATE_VERSION_FLAG))
1244    })
1245}
1246
1247fn append_crate_version_flag(unit: &Unit, rustdoc: &mut ProcessBuilder) {
1248    rustdoc
1249        .arg(RUSTDOC_CRATE_VERSION_FLAG)
1250        .arg(unit.pkg.version().to_string());
1251}
1252
1253enum CapLints {
1254    Allow,
1255    Warn,
1256}
1257
1258fn compute_cap_lints(bcx: &BuildContext<'_, '_>, unit: &Unit) -> Option<CapLints> {
1259    // If this is an upstream dep we don't want warnings from, turn off all
1260    // lints.
1261    if !unit.show_warnings(bcx.gctx) {
1262        Some(CapLints::Allow)
1263    // If this is an upstream dep but we *do* want warnings, make sure that they
1264    // don't fail compilation.
1265    } else if !unit.is_local() {
1266        Some(CapLints::Warn)
1267    } else {
1268        None
1269    }
1270}
1271
1272/// Adds [`--cap-lints`] to the command to execute.
1273///
1274/// [`--cap-lints`]: https://doc.rust-lang.org/nightly/rustc/lints/levels.html#capping-lints
1275fn add_cap_lints(bcx: &BuildContext<'_, '_>, unit: &Unit, cmd: &mut ProcessBuilder) {
1276    if let Some(cap_lints) = compute_cap_lints(bcx, unit) {
1277        match cap_lints {
1278            CapLints::Allow => {
1279                cmd.arg("--cap-lints").arg("allow");
1280            }
1281            CapLints::Warn => {
1282                cmd.arg("--cap-lints").arg("warn");
1283            }
1284        }
1285    }
1286}
1287
1288/// Forwards [`-Zallow-features`] if it is set for cargo.
1289///
1290/// [`-Zallow-features`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#allow-features
1291fn add_allow_features(build_runner: &BuildRunner<'_, '_>, cmd: &mut ProcessBuilder) {
1292    if let Some(allow) = &build_runner.bcx.gctx.cli_unstable().allow_features {
1293        use std::fmt::Write;
1294        let mut arg = String::from("-Zallow-features=");
1295        for f in allow {
1296            let _ = write!(&mut arg, "{f},");
1297        }
1298        cmd.arg(arg.trim_end_matches(','));
1299    }
1300}
1301
1302/// Adds [`--error-format`] to the command to execute.
1303///
1304/// Cargo always uses JSON output. This has several benefits, such as being
1305/// easier to parse, handles changing formats (for replaying cached messages),
1306/// ensures atomic output (so messages aren't interleaved), allows for
1307/// intercepting messages like rmeta artifacts, etc. rustc includes a
1308/// "rendered" field in the JSON message with the message properly formatted,
1309/// which Cargo will extract and display to the user.
1310///
1311/// [`--error-format`]: https://doc.rust-lang.org/nightly/rustc/command-line-arguments.html#--error-format-control-how-errors-are-produced
1312fn add_error_format_and_color(build_runner: &BuildRunner<'_, '_>, cmd: &mut ProcessBuilder) {
1313    let enable_timings =
1314        build_runner.bcx.gctx.cli_unstable().section_timings && build_runner.bcx.logger.is_some();
1315    if enable_timings {
1316        cmd.arg("-Zunstable-options");
1317    }
1318
1319    cmd.arg("--error-format=json");
1320
1321    let mut json = String::from(
1322        "--json=diagnostic-rendered-ansi,artifacts,future-incompat,unused-externs-silent",
1323    );
1324    if let MessageFormat::Short | MessageFormat::Json { short: true, .. } =
1325        build_runner.bcx.build_config.message_format
1326    {
1327        json.push_str(",diagnostic-short");
1328    } else if build_runner.bcx.gctx.shell().err_unicode()
1329        && build_runner.bcx.gctx.cli_unstable().rustc_unicode
1330    {
1331        json.push_str(",diagnostic-unicode");
1332    }
1333    if enable_timings {
1334        json.push_str(",timings");
1335    }
1336    cmd.arg(json);
1337
1338    let gctx = build_runner.bcx.gctx;
1339    if let Some(width) = gctx.shell().err_width().diagnostic_terminal_width() {
1340        cmd.arg(format!("--diagnostic-width={width}"));
1341    }
1342}
1343
1344/// Adds essential rustc flags and environment variables to the command to execute.
1345fn build_base_args(
1346    build_runner: &BuildRunner<'_, '_>,
1347    cmd: &mut ProcessBuilder,
1348    unit: &Unit,
1349) -> CargoResult<()> {
1350    assert!(!unit.mode.is_run_custom_build());
1351
1352    let bcx = build_runner.bcx;
1353    let Profile {
1354        ref opt_level,
1355        codegen_backend,
1356        codegen_units,
1357        debuginfo,
1358        debug_assertions,
1359        split_debuginfo,
1360        overflow_checks,
1361        rpath,
1362        ref panic,
1363        incremental,
1364        strip,
1365        rustflags: profile_rustflags,
1366        trim_paths,
1367        hint_mostly_unused: profile_hint_mostly_unused,
1368        ..
1369    } = unit.profile.clone();
1370    let hints = unit.pkg.hints().cloned().unwrap_or_default();
1371    let test = unit.mode.is_any_test();
1372
1373    cmd.arg("--crate-name").arg(&unit.target.crate_name());
1374
1375    let edition = unit.target.edition();
1376    edition.cmd_edition_arg(cmd);
1377
1378    add_path_args(bcx.ws, unit, cmd);
1379    add_error_format_and_color(build_runner, cmd);
1380    add_allow_features(build_runner, cmd);
1381
1382    let mut contains_dy_lib = false;
1383    if !test {
1384        for crate_type in &unit.target.rustc_crate_types() {
1385            cmd.arg("--crate-type").arg(crate_type.as_str());
1386            contains_dy_lib |= crate_type == &CrateType::Dylib;
1387        }
1388    }
1389
1390    if unit.mode.is_check() {
1391        cmd.arg("--emit=dep-info,metadata");
1392    } else if !build_runner
1393        .bcx
1394        .target_data
1395        .info(unit.kind)
1396        .should_embed_metadata()
1397    {
1398        // Nightly rustc supports the -Zembed-metadata=no flag, which tells it to avoid including
1399        // full metadata in rlib/dylib artifacts, to save space on disk. In this case, metadata
1400        // will only be stored in .rmeta files.
1401        // When we use this flag, we should also pass --emit=metadata to all artifacts that
1402        // contain useful metadata (rlib/dylib/proc macros), so that a .rmeta file is actually
1403        // generated. If we didn't do this, the full metadata would not get written anywhere.
1404        // However, we do not want to pass --emit=metadata to artifacts that never produce useful
1405        // metadata, such as binaries, because that would just unnecessarily create empty .rmeta
1406        // files on disk.
1407        if unit.benefits_from_no_embed_metadata() {
1408            cmd.arg("--emit=dep-info,metadata,link");
1409            cmd.args(&["-Z", "embed-metadata=no"]);
1410        } else {
1411            cmd.arg("--emit=dep-info,link");
1412        }
1413    } else {
1414        // If we don't use -Zembed-metadata=no, we emit .rmeta files only for rlib outputs.
1415        // This metadata may be used in this session for a pipelined compilation, or it may
1416        // be used in a future Cargo session as part of a pipelined compile.
1417        if !unit.requires_upstream_objects() {
1418            cmd.arg("--emit=dep-info,metadata,link");
1419        } else {
1420            cmd.arg("--emit=dep-info,link");
1421        }
1422    }
1423
1424    let prefer_dynamic = (unit.target.for_host() && !unit.target.is_custom_build())
1425        || (contains_dy_lib && !build_runner.is_primary_package(unit));
1426    if prefer_dynamic {
1427        cmd.arg("-C").arg("prefer-dynamic");
1428    }
1429
1430    if opt_level.as_str() != "0" {
1431        cmd.arg("-C").arg(&format!("opt-level={}", opt_level));
1432    }
1433
1434    if *panic != PanicStrategy::Unwind {
1435        cmd.arg("-C").arg(format!("panic={}", panic));
1436    }
1437    if *panic == PanicStrategy::ImmediateAbort {
1438        cmd.arg("-Z").arg("unstable-options");
1439    }
1440
1441    cmd.args(&lto_args(build_runner, unit));
1442
1443    if let Some(backend) = codegen_backend {
1444        cmd.arg("-Z").arg(&format!("codegen-backend={}", backend));
1445    }
1446
1447    if let Some(n) = codegen_units {
1448        cmd.arg("-C").arg(&format!("codegen-units={}", n));
1449    }
1450
1451    let debuginfo = debuginfo.into_inner();
1452    // Shorten the number of arguments if possible.
1453    if debuginfo != TomlDebugInfo::None {
1454        cmd.arg("-C").arg(format!("debuginfo={debuginfo}"));
1455        // This is generally just an optimization on build time so if we don't
1456        // pass it then it's ok. The values for the flag (off, packed, unpacked)
1457        // may be supported or not depending on the platform, so availability is
1458        // checked per-value. For example, at the time of writing this code, on
1459        // Windows the only stable valid value for split-debuginfo is "packed",
1460        // while on Linux "unpacked" is also stable.
1461        if let Some(split) = split_debuginfo {
1462            if build_runner
1463                .bcx
1464                .target_data
1465                .info(unit.kind)
1466                .supports_debuginfo_split(split)
1467            {
1468                cmd.arg("-C").arg(format!("split-debuginfo={split}"));
1469            }
1470        }
1471    }
1472
1473    trim_paths_args(cmd, build_runner, unit, &trim_paths)?;
1474
1475    match compute_cap_lints(bcx, unit) {
1476        None | Some(CapLints::Warn) => {
1477            cmd.args(unit.pkg.manifest().lint_rustflags());
1478        }
1479        // If we pass --cap-lints=allow, there is no point in making the CLI larger by including
1480        // potentially a lot of --warn lint flags.
1481        Some(CapLints::Allow) => {}
1482    }
1483    cmd.args(&profile_rustflags);
1484
1485    // `-C overflow-checks` is implied by the setting of `-C debug-assertions`,
1486    // so we only need to provide `-C overflow-checks` if it differs from
1487    // the value of `-C debug-assertions` we would provide.
1488    if opt_level.as_str() != "0" {
1489        if debug_assertions {
1490            cmd.args(&["-C", "debug-assertions=on"]);
1491            if !overflow_checks {
1492                cmd.args(&["-C", "overflow-checks=off"]);
1493            }
1494        } else if overflow_checks {
1495            cmd.args(&["-C", "overflow-checks=on"]);
1496        }
1497    } else if !debug_assertions {
1498        cmd.args(&["-C", "debug-assertions=off"]);
1499        if overflow_checks {
1500            cmd.args(&["-C", "overflow-checks=on"]);
1501        }
1502    } else if !overflow_checks {
1503        cmd.args(&["-C", "overflow-checks=off"]);
1504    }
1505
1506    if test && unit.target.harness() {
1507        cmd.arg("--test");
1508
1509        // Cargo has historically never compiled `--test` binaries with
1510        // `panic=abort` because the `test` crate itself didn't support it.
1511        // Support is now upstream, however, but requires an unstable flag to be
1512        // passed when compiling the test. We require, in Cargo, an unstable
1513        // flag to pass to rustc, so register that here. Eventually this flag
1514        // will simply not be needed when the behavior is stabilized in the Rust
1515        // compiler itself.
1516        if *panic == PanicStrategy::Abort || *panic == PanicStrategy::ImmediateAbort {
1517            cmd.arg("-Z").arg("panic-abort-tests");
1518        }
1519    } else if test {
1520        cmd.arg("--cfg").arg("test");
1521    }
1522
1523    cmd.args(&features_args(unit));
1524    cmd.args(&check_cfg_args(unit));
1525
1526    let meta = build_runner.files().metadata(unit);
1527    cmd.arg("-C")
1528        .arg(&format!("metadata={}", meta.c_metadata()));
1529    if let Some(c_extra_filename) = meta.c_extra_filename() {
1530        cmd.arg("-C")
1531            .arg(&format!("extra-filename=-{c_extra_filename}"));
1532    }
1533
1534    if rpath {
1535        cmd.arg("-C").arg("rpath");
1536    }
1537
1538    cmd.arg("--out-dir")
1539        .arg(&build_runner.files().output_dir(unit));
1540
1541    unit.kind.add_target_arg(cmd);
1542
1543    add_codegen_linker(cmd, build_runner, unit, bcx.gctx.target_applies_to_host()?);
1544
1545    if incremental {
1546        add_codegen_incremental(cmd, build_runner, unit)
1547    }
1548
1549    let pkg_hint_mostly_unused = match hints.mostly_unused {
1550        Some(toml::Value::Boolean(b)) => Some(b),
1551        // Unsupported values and ignored hints are reported before the build, see
1552        // `crate::diagnostics::rules::PARSE_PASS_RULES`.
1553        _ => None,
1554    };
1555    if profile_hint_mostly_unused
1556        .or(pkg_hint_mostly_unused)
1557        .unwrap_or(false)
1558        && bcx.gctx.cli_unstable().profile_hint_mostly_unused
1559    {
1560        cmd.arg("-Zhint-mostly-unused");
1561    }
1562
1563    let strip = strip.into_inner();
1564    if strip != StripInner::None {
1565        cmd.arg("-C").arg(format!("strip={}", strip));
1566    }
1567
1568    if unit.is_std {
1569        // -Zforce-unstable-if-unmarked prevents the accidental use of
1570        // unstable crates within the sysroot (such as "extern crate libc" or
1571        // any non-public crate in the sysroot).
1572        //
1573        // RUSTC_BOOTSTRAP allows unstable features on stable.
1574        cmd.arg("-Z")
1575            .arg("force-unstable-if-unmarked")
1576            .env("RUSTC_BOOTSTRAP", "1");
1577    }
1578
1579    if let Some(version) = unit.pkg.manifest().rust_version()
1580        && bcx.gctx.cli_unstable().hint_msrv
1581    {
1582        cmd.arg("-Z").arg(format!("hint-msrv={version}"));
1583    }
1584
1585    Ok(())
1586}
1587
1588/// All active features for the unit passed as `--cfg features=<feature-name>`.
1589fn features_args(unit: &Unit) -> Vec<OsString> {
1590    let mut args = Vec::with_capacity(unit.features.len() * 2);
1591
1592    for feat in &unit.features {
1593        args.push(OsString::from("--cfg"));
1594        args.push(OsString::from(format!("feature=\"{}\"", feat)));
1595    }
1596
1597    args
1598}
1599
1600/// Generates the `--check-cfg` arguments for the `unit`.
1601fn check_cfg_args(unit: &Unit) -> Vec<OsString> {
1602    // The routine below generates the --check-cfg arguments. Our goals here are to
1603    // enable the checking of conditionals and pass the list of declared features.
1604    //
1605    // In the simplified case, it would resemble something like this:
1606    //
1607    //   --check-cfg=cfg() --check-cfg=cfg(feature, values(...))
1608    //
1609    // but having `cfg()` is redundant with the second argument (as well-known names
1610    // and values are implicitly enabled when one or more `--check-cfg` argument is
1611    // passed) so we don't emit it and just pass:
1612    //
1613    //   --check-cfg=cfg(feature, values(...))
1614    //
1615    // This way, even if there are no declared features, the config `feature` will
1616    // still be expected, meaning users would get "unexpected value" instead of name.
1617    // This wasn't always the case, see rust-lang#119930 for some details.
1618
1619    let gross_cap_estimation = unit.pkg.summary().features().len() * 7 + 25;
1620    let mut arg_feature = OsString::with_capacity(gross_cap_estimation);
1621
1622    arg_feature.push("cfg(feature, values(");
1623    for (i, feature) in unit.pkg.summary().features().keys().enumerate() {
1624        if i != 0 {
1625            arg_feature.push(", ");
1626        }
1627        arg_feature.push("\"");
1628        arg_feature.push(feature);
1629        arg_feature.push("\"");
1630    }
1631    arg_feature.push("))");
1632
1633    // In addition to the package features, we also include the `test` cfg (since
1634    // compiler-team#785, as to be able to someday apply it conditionally), as well
1635    // the `docsrs` cfg from the docs.rs service.
1636    //
1637    // We include `docsrs` here (in Cargo) instead of rustc, since there is a much closer
1638    // relationship between Cargo and docs.rs than rustc and docs.rs. In particular, all
1639    // users of docs.rs use Cargo, but not all users of rustc (like Rust-for-Linux) use docs.rs.
1640
1641    vec![
1642        OsString::from("--check-cfg"),
1643        OsString::from("cfg(docsrs,test)"),
1644        OsString::from("--check-cfg"),
1645        arg_feature,
1646    ]
1647}
1648
1649/// Adds LTO related codegen flags.
1650fn lto_args(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec<OsString> {
1651    let mut result = Vec::new();
1652    let mut push = |arg: &str| {
1653        result.push(OsString::from("-C"));
1654        result.push(OsString::from(arg));
1655    };
1656    match build_runner.lto[unit] {
1657        lto::Lto::Run(None) => push("lto"),
1658        lto::Lto::Run(Some(s)) => push(&format!("lto={}", s)),
1659        lto::Lto::Off => {
1660            push("lto=off");
1661            push("embed-bitcode=no");
1662        }
1663        lto::Lto::ObjectAndBitcode => {} // this is rustc's default
1664        lto::Lto::OnlyBitcode => push("linker-plugin-lto"),
1665        lto::Lto::OnlyObject => push("embed-bitcode=no"),
1666    }
1667    result
1668}
1669
1670/// Adds dependency-relevant rustc flags and environment variables
1671/// to the command to execute, such as [`-L`] and [`--extern`].
1672///
1673/// [`-L`]: https://doc.rust-lang.org/nightly/rustc/command-line-arguments.html#-l-add-a-directory-to-the-library-search-path
1674/// [`--extern`]: https://doc.rust-lang.org/nightly/rustc/command-line-arguments.html#--extern-specify-where-an-external-library-is-located
1675fn build_deps_args(
1676    cmd: &mut ProcessBuilder,
1677    build_runner: &BuildRunner<'_, '_>,
1678    unit: &Unit,
1679) -> CargoResult<()> {
1680    let bcx = build_runner.bcx;
1681
1682    for arg in lib_search_paths(build_runner, unit)? {
1683        cmd.arg(arg);
1684    }
1685
1686    let deps = build_runner.unit_deps(unit);
1687
1688    // If there is not one linkable target but should, rustc fails later
1689    // on if there is an `extern crate` for it. This may turn into a hard
1690    // error in the future (see PR #4797).
1691    if !deps
1692        .iter()
1693        .any(|dep| !dep.unit.mode.is_doc() && dep.unit.target.is_linkable())
1694    {
1695        if let Some(dep) = deps.iter().find(|dep| {
1696            !dep.unit.mode.is_doc() && dep.unit.target.is_lib() && !dep.unit.artifact.is_true()
1697        }) {
1698            let dep_name = dep.unit.target.crate_name();
1699            let name = unit.target.crate_name();
1700            bcx.gctx.shell().print_report(&[
1701                Level::WARNING.secondary_title(format!("the package `{dep_name}` provides no linkable target"))
1702                    .elements([
1703                        Level::NOTE.message(format!("this might cause `{name}` to fail compilation")),
1704                        Level::NOTE.message("this warning might turn into a hard error in the future"),
1705                        Level::HELP.message(format!("consider adding 'dylib' or 'rlib' to key 'crate-type' in `{dep_name}`'s Cargo.toml"))
1706                    ])
1707            ], false)?;
1708        }
1709    }
1710
1711    let mut unstable_opts = false;
1712
1713    // Add `OUT_DIR` environment variables for build scripts
1714    let first_custom_build_dep = deps.iter().find(|dep| dep.unit.mode.is_run_custom_build());
1715    if let Some(dep) = first_custom_build_dep {
1716        let out_dir = if bcx.gctx.cli_unstable().build_dir_new_layout {
1717            build_runner.files().out_dir_new_layout(&dep.unit)
1718        } else {
1719            build_runner.files().build_script_out_dir(&dep.unit)
1720        };
1721        cmd.env("OUT_DIR", &out_dir);
1722    }
1723
1724    // Adding output directory for each build script
1725    let is_multiple_build_scripts_enabled = unit
1726        .pkg
1727        .manifest()
1728        .unstable_features()
1729        .require(Feature::multiple_build_scripts())
1730        .is_ok();
1731
1732    if is_multiple_build_scripts_enabled {
1733        for dep in deps {
1734            if dep.unit.mode.is_run_custom_build() {
1735                let out_dir = if bcx.gctx.cli_unstable().build_dir_new_layout {
1736                    build_runner.files().out_dir_new_layout(&dep.unit)
1737                } else {
1738                    build_runner.files().build_script_out_dir(&dep.unit)
1739                };
1740                let target_name = dep.unit.target.name();
1741                let out_dir_prefix = target_name
1742                    .strip_prefix("build-script-")
1743                    .unwrap_or(target_name);
1744                let out_dir_name = format!("{out_dir_prefix}_OUT_DIR");
1745                cmd.env(&out_dir_name, &out_dir);
1746            }
1747        }
1748    }
1749    for arg in extern_args(build_runner, unit, &mut unstable_opts)? {
1750        cmd.arg(arg);
1751    }
1752
1753    for (var, env) in artifact::get_env(build_runner, unit, deps)? {
1754        cmd.env(&var, env);
1755    }
1756
1757    // This will only be set if we're already using a feature
1758    // requiring nightly rust
1759    if unstable_opts {
1760        cmd.arg("-Z").arg("unstable-options");
1761    }
1762
1763    Ok(())
1764}
1765
1766/// Adds extra rustc flags and environment variables collected from the output
1767/// of a build-script to the command to execute, include custom environment
1768/// variables and `cfg`.
1769fn add_custom_flags(
1770    cmd: &mut ProcessBuilder,
1771    build_script_outputs: &BuildScriptOutputs,
1772    metadata_vec: Option<Vec<UnitHash>>,
1773) -> CargoResult<()> {
1774    if let Some(metadata_vec) = metadata_vec {
1775        for metadata in metadata_vec {
1776            if let Some(output) = build_script_outputs.get(metadata) {
1777                for cfg in output.cfgs.iter() {
1778                    cmd.arg("--cfg").arg(cfg);
1779                }
1780                for check_cfg in &output.check_cfgs {
1781                    cmd.arg("--check-cfg").arg(check_cfg);
1782                }
1783                for (name, value) in output.env.iter() {
1784                    cmd.env(name, value);
1785                }
1786            }
1787        }
1788    }
1789
1790    Ok(())
1791}
1792
1793/// Generate a list of `-L` arguments
1794#[tracing::instrument(skip_all)]
1795pub fn lib_search_paths(
1796    build_runner: &BuildRunner<'_, '_>,
1797    unit: &Unit,
1798) -> CargoResult<Vec<OsString>> {
1799    let mut lib_search_paths = Vec::new();
1800    if build_runner.bcx.gctx.cli_unstable().build_dir_new_layout {
1801        // Add all dependency args to rustc process
1802        let paths = dep_paths_for_args(unit, build_runner);
1803
1804        for path in paths {
1805            let mut deps = OsString::from("dependency=");
1806            deps.push(path);
1807            lib_search_paths.extend(["-L".into(), deps]);
1808        }
1809    } else {
1810        let mut deps = OsString::from("dependency=");
1811        deps.push(build_runner.files().deps_dir(unit));
1812        lib_search_paths.extend(["-L".into(), deps]);
1813    }
1814
1815    // Be sure that the host path is also listed. This'll ensure that proc macro
1816    // dependencies are correctly found (for reexported macros).
1817    if !unit.kind.is_host() {
1818        let mut deps = OsString::from("dependency=");
1819        deps.push(build_runner.files().host_deps(unit));
1820        lib_search_paths.extend(["-L".into(), deps]);
1821    }
1822
1823    Ok(lib_search_paths)
1824}
1825
1826/// Generate a list of paths for the rustc `-L` args.
1827///
1828/// To generate the list of paths we walk the dependency graph using DFS with a stack.
1829///
1830/// We try to avoid bloating the amount of args passed rustc as it can cause issues with OS limits
1831/// for large projects. (and generally just makes the rustc command ugly!).
1832/// Below are the rules for excluding build units from the args.
1833/// 1. Don't include build script out dir in the args to reduce rustc command bloat.
1834/// 2. Proc macros are statically linked, so when including a proc-macro dependency we can skip
1835///    adding it's dependencies. Note that we still do add them when we are compiling the
1836///    proc-macro itself.
1837/// 3. Don't include direct dependencies because they are passed with `--extern` and rustc does not
1838///    need `-L` if it was already passed as `--extern`
1839fn dep_paths_for_args(unit: &Unit, build_runner: &BuildRunner<'_, '_>) -> Vec<PathBuf> {
1840    let direct = build_runner.unit_deps(unit);
1841    if direct.is_empty() {
1842        return Vec::new();
1843    }
1844
1845    // Checks crate type rustc actually set rather than the `target.proc-macro` flag.
1846    // The latter is inconsistent.
1847    // See https://github.com/rust-lang/cargo/issues/17579
1848    let is_proc_macro = |u: &Unit| match u.target.kind() {
1849        TargetKind::Lib(kinds) => kinds.contains(&CrateType::ProcMacro),
1850        _ => false,
1851    };
1852
1853    let mut indirect = HashSet::default();
1854    let mut visited = HashSet::default();
1855    let mut stack: Vec<&Unit> = direct
1856        .iter()
1857        .filter(|d| !d.unit.target.is_custom_build())
1858        .map(|d| &d.unit)
1859        .collect();
1860    while let Some(u) = stack.pop() {
1861        if !visited.insert(u.clone()) {
1862            continue;
1863        }
1864        if is_proc_macro(u) {
1865            continue;
1866        }
1867        for dep in build_runner.unit_deps(u) {
1868            let v = &dep.unit;
1869            if v.target.is_custom_build() {
1870                continue;
1871            }
1872            indirect.insert(v.clone());
1873            if is_proc_macro(v) {
1874                continue;
1875            }
1876            if !visited.contains(v) {
1877                stack.push(v);
1878            }
1879        }
1880    }
1881
1882    let mut paths: Vec<PathBuf> = indirect
1883        .into_iter()
1884        .map(|u| build_runner.files().deps_dir(&u))
1885        .collect();
1886
1887    // We are in the hot path that needs to always run (even for rebuilds)
1888    // There could be a potentially large amount of paths for large workspaces.
1889    // We want to sort and dedup the paths but doing so is a bit expsensive when comparing PathBufs
1890    // directly. So instead we only sort valid Utf8 paths as &str's which is a bit faster.
1891    // We do not allow non utf8 paths when building, so we take advantage of that by treating them
1892    // as a single value when sorting/deduplicating.
1893    //
1894    // For reference this saves about ~30ms on Zed
1895    paths.sort_unstable_by(|a, b| match (a.to_str(), b.to_str()) {
1896        (Some(a), Some(b)) => a.cmp(b),
1897        (Some(_), None) => std::cmp::Ordering::Less,
1898        (None, Some(_)) => std::cmp::Ordering::Greater,
1899        (None, None) => std::cmp::Ordering::Less,
1900    });
1901    paths.dedup_by(|a, b| matches!((a.to_str(), b.to_str()), (Some(x), Some(y)) if x == y));
1902
1903    paths
1904}
1905
1906fn is_public_dependency_enabled(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> bool {
1907    unit.pkg
1908        .manifest()
1909        .unstable_features()
1910        .require(Feature::public_dependency())
1911        .is_ok()
1912        || build_runner.bcx.gctx.cli_unstable().public_dependency
1913}
1914
1915/// Generates a list of `--extern` arguments.
1916pub fn extern_args(
1917    build_runner: &BuildRunner<'_, '_>,
1918    unit: &Unit,
1919    unstable_opts: &mut bool,
1920) -> CargoResult<Vec<OsString>> {
1921    let mut result = Vec::new();
1922    let deps = build_runner.unit_deps(unit);
1923
1924    let no_embed_metadata = !build_runner
1925        .bcx
1926        .target_data
1927        .info(unit.kind)
1928        .should_embed_metadata();
1929    let public_dependency_enabled = is_public_dependency_enabled(build_runner, unit);
1930
1931    // Closure to add one dependency to `result`.
1932    let mut link_to = |dep: &UnitDep,
1933                       extern_crate_name: InternedString,
1934                       noprelude: bool,
1935                       nounused: bool|
1936     -> CargoResult<()> {
1937        let mut value = OsString::new();
1938        let mut opts = Vec::new();
1939        if !dep.public && unit.target.is_lib() && public_dependency_enabled {
1940            opts.push("priv");
1941            *unstable_opts = true;
1942        }
1943        if noprelude {
1944            opts.push("noprelude");
1945            *unstable_opts = true;
1946        }
1947        if nounused {
1948            opts.push("nounused");
1949            *unstable_opts = true;
1950        }
1951        if !opts.is_empty() {
1952            value.push(opts.join(","));
1953            value.push(":");
1954        }
1955        value.push(extern_crate_name.as_str());
1956        value.push("=");
1957
1958        let mut pass = |file| {
1959            let mut value = value.clone();
1960            value.push(file);
1961            result.push(OsString::from("--extern"));
1962            result.push(value);
1963        };
1964
1965        let outputs = build_runner.outputs(&dep.unit)?;
1966
1967        if build_runner.only_requires_rmeta(unit, &dep.unit) || dep.unit.mode.is_check() {
1968            // Example: rlib dependency for an rlib, rmeta is all that is required.
1969            let output = outputs
1970                .iter()
1971                .find(|output| output.flavor == FileFlavor::Rmeta)
1972                .expect("failed to find rmeta dep for pipelined dep");
1973            pass(&output.path);
1974        } else {
1975            // Example: a bin needs `rlib` for dependencies, it cannot use rmeta.
1976            for output in outputs.iter() {
1977                if output.flavor == FileFlavor::Linkable {
1978                    pass(&output.path);
1979                }
1980                // If we use -Zembed-metadata=no, we also need to pass the path to the
1981                // corresponding .rmeta file to the linkable artifact, because the
1982                // normal dependency (rlib) doesn't contain the full metadata.
1983                else if no_embed_metadata && output.flavor == FileFlavor::Rmeta {
1984                    pass(&output.path);
1985                }
1986            }
1987        }
1988        Ok(())
1989    };
1990
1991    for dep in deps {
1992        if dep.unit.target.is_linkable() && !dep.unit.mode.is_doc() {
1993            link_to(dep, dep.extern_crate_name, dep.noprelude, dep.nounused)?;
1994        }
1995    }
1996    if unit.target.proc_macro() {
1997        // Automatically import `proc_macro`.
1998        result.push(OsString::from("--extern"));
1999        result.push(OsString::from("proc_macro"));
2000    }
2001
2002    Ok(result)
2003}
2004
2005/// Adds `-C linker=<path>` if specified.
2006fn add_codegen_linker(
2007    cmd: &mut ProcessBuilder,
2008    build_runner: &BuildRunner<'_, '_>,
2009    unit: &Unit,
2010    target_applies_to_host: bool,
2011) {
2012    let linker = if unit.target.for_host() && !target_applies_to_host {
2013        build_runner
2014            .compilation
2015            .host_linker()
2016            .map(|s| s.as_os_str())
2017    } else {
2018        build_runner
2019            .compilation
2020            .target_linker(unit.kind)
2021            .map(|s| s.as_os_str())
2022    };
2023
2024    if let Some(linker) = linker {
2025        let mut arg = OsString::from("linker=");
2026        arg.push(linker);
2027        cmd.arg("-C").arg(arg);
2028    }
2029}
2030
2031/// Adds `-C incremental=<path>`.
2032fn add_codegen_incremental(
2033    cmd: &mut ProcessBuilder,
2034    build_runner: &BuildRunner<'_, '_>,
2035    unit: &Unit,
2036) {
2037    let dir = build_runner.files().incremental_dir(&unit);
2038    let mut arg = OsString::from("incremental=");
2039    arg.push(dir.as_os_str());
2040    cmd.arg("-C").arg(arg);
2041}
2042
2043fn envify(s: &str) -> String {
2044    s.chars()
2045        .flat_map(|c| c.to_uppercase())
2046        .map(|c| if c == '-' { '_' } else { c })
2047        .collect()
2048}
2049
2050/// Configuration of the display of messages emitted by the compiler,
2051/// e.g. diagnostics, warnings, errors, and message caching.
2052struct OutputOptions {
2053    /// What format we're emitting from Cargo itself.
2054    format: MessageFormat,
2055    /// Where to write the JSON messages to support playback later if the unit
2056    /// is fresh. The file is created lazily so that in the normal case, lots
2057    /// of empty files are not created. If this is None, the output will not
2058    /// be cached (such as when replaying cached messages).
2059    cache_cell: Option<(PathBuf, OnceCell<File>)>,
2060    /// If `true`, display any diagnostics.
2061    /// Other types of JSON messages are processed regardless
2062    /// of the value of this flag.
2063    ///
2064    /// This is used primarily for cache replay. If you build with `-vv`, the
2065    /// cache will be filled with diagnostics from dependencies. When the
2066    /// cache is replayed without `-vv`, we don't want to show them.
2067    show_diagnostics: bool,
2068    /// Tracks the number of warnings we've seen so far.
2069    warnings_seen: usize,
2070    /// Tracks the number of errors we've seen so far.
2071    errors_seen: usize,
2072}
2073
2074impl OutputOptions {
2075    fn for_dirty(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> OutputOptions {
2076        let path = build_runner.files().message_cache_path(unit);
2077        // Remove old cache, ignore ENOENT, which is the common case.
2078        drop(fs::remove_file(&path));
2079        let cache_cell = Some((path, OnceCell::new()));
2080
2081        let show_diagnostics = true;
2082
2083        let format = build_runner.bcx.build_config.message_format;
2084
2085        OutputOptions {
2086            format,
2087            cache_cell,
2088            show_diagnostics,
2089            warnings_seen: 0,
2090            errors_seen: 0,
2091        }
2092    }
2093
2094    fn for_fresh(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> OutputOptions {
2095        let cache_cell = None;
2096
2097        // We always replay the output cache,
2098        // since it might contain future-incompat-report messages
2099        let show_diagnostics = unit.show_warnings(build_runner.bcx.gctx);
2100
2101        let format = build_runner.bcx.build_config.message_format;
2102
2103        OutputOptions {
2104            format,
2105            cache_cell,
2106            show_diagnostics,
2107            warnings_seen: 0,
2108            errors_seen: 0,
2109        }
2110    }
2111}
2112
2113/// Cloned and sendable context about the manifest file.
2114///
2115/// Sometimes we enrich rustc's errors with some locations in the manifest file; this
2116/// contains a `Send`-able copy of the manifest information that we need for the
2117/// enriched errors.
2118struct ManifestErrorContext {
2119    /// The path to the manifest.
2120    path: PathBuf,
2121    /// The locations of various spans within the manifest.
2122    spans: Option<Arc<toml::Spanned<toml::de::DeTable<'static>>>>,
2123    /// The raw manifest contents.
2124    contents: Option<String>,
2125    /// A lookup for all the unambiguous renamings, mapping from the original package
2126    /// name to the renamed one.
2127    rename_table: HashMap<InternedString, InternedString>,
2128    /// A list of targets we're compiling for, to determine which of the `[target.<something>.dependencies]`
2129    /// tables might be of interest.
2130    requested_kinds: Vec<CompileKind>,
2131    /// A list of all the collections of cfg values, one collection for each target, to determine
2132    /// which of the `[target.'cfg(...)'.dependencies]` tables might be of interest.
2133    cfgs: Vec<Vec<Cfg>>,
2134    host_name: InternedString,
2135    /// Cargo's working directory (for printing out a more friendly manifest path).
2136    cwd: PathBuf,
2137    /// Terminal width for formatting diagnostics.
2138    term_width: usize,
2139}
2140
2141fn on_stdout_line(
2142    state: &JobState<'_, '_>,
2143    line: &str,
2144    _package_id: PackageId,
2145    _target: &Target,
2146) -> CargoResult<()> {
2147    state.stdout(line.to_string())?;
2148    Ok(())
2149}
2150
2151fn on_stderr_line(
2152    state: &JobState<'_, '_>,
2153    line: &str,
2154    package_id: PackageId,
2155    manifest: &ManifestErrorContext,
2156    target: &Target,
2157    options: &mut OutputOptions,
2158) -> CargoResult<()> {
2159    if on_stderr_line_inner(state, line, package_id, manifest, target, options)? {
2160        // Check if caching is enabled.
2161        if let Some((path, cell)) = &mut options.cache_cell {
2162            // Cache the output, which will be replayed later when Fresh.
2163            let f = cell.try_borrow_mut_with(|| paths::create(path))?;
2164            debug_assert!(!line.contains('\n'));
2165            f.write_all(line.as_bytes())?;
2166            f.write_all(&[b'\n'])?;
2167        }
2168    }
2169    Ok(())
2170}
2171
2172/// Returns true if the line should be cached.
2173fn on_stderr_line_inner(
2174    state: &JobState<'_, '_>,
2175    line: &str,
2176    package_id: PackageId,
2177    manifest: &ManifestErrorContext,
2178    target: &Target,
2179    options: &mut OutputOptions,
2180) -> CargoResult<bool> {
2181    // We primarily want to use this function to process JSON messages from
2182    // rustc. The compiler should always print one JSON message per line, and
2183    // otherwise it may have other output intermingled (think RUST_LOG or
2184    // something like that), so skip over everything that doesn't look like a
2185    // JSON message.
2186    if !line.starts_with('{') {
2187        state.stderr(line.to_string())?;
2188        return Ok(true);
2189    }
2190
2191    let mut compiler_message: Box<serde_json::value::RawValue> = match serde_json::from_str(line) {
2192        Ok(msg) => msg,
2193
2194        // If the compiler produced a line that started with `{` but it wasn't
2195        // valid JSON, maybe it wasn't JSON in the first place! Forward it along
2196        // to stderr.
2197        Err(e) => {
2198            debug!("failed to parse json: {:?}", e);
2199            state.stderr(line.to_string())?;
2200            return Ok(true);
2201        }
2202    };
2203
2204    let count_diagnostic = |level, options: &mut OutputOptions| {
2205        if level == "warning" {
2206            options.warnings_seen += 1;
2207        } else if level == "error" {
2208            options.errors_seen += 1;
2209        }
2210    };
2211
2212    if let Ok(report) = serde_json::from_str::<FutureIncompatReport>(compiler_message.get()) {
2213        for item in &report.future_incompat_report {
2214            count_diagnostic(&*item.diagnostic.level, options);
2215        }
2216        state.future_incompat_report(report.future_incompat_report);
2217        return Ok(true);
2218    }
2219
2220    let res = serde_json::from_str::<SectionTiming>(compiler_message.get());
2221    if let Ok(timing_record) = res {
2222        state.on_section_timing_emitted(timing_record);
2223        return Ok(false);
2224    }
2225
2226    // Returns `true` if the diagnostic was modified.
2227    let add_pub_in_priv_diagnostic = |diag: &mut String| -> bool {
2228        // We are parsing the compiler diagnostic here, as this information isn't
2229        // currently exposed elsewhere.
2230        // At the time of writing this comment, rustc emits two different
2231        // "exported_private_dependencies" errors:
2232        //  - type `FromPriv` from private dependency 'priv_dep' in public interface
2233        //  - struct `FromPriv` from private dependency 'priv_dep' is re-exported
2234        // This regex matches them both. To see if it needs to be updated, grep the rust
2235        // source for "EXPORTED_PRIVATE_DEPENDENCIES".
2236        static PRIV_DEP_REGEX: LazyLock<Regex> =
2237            LazyLock::new(|| Regex::new("from private dependency '([A-Za-z0-9-_]+)'").unwrap());
2238        if let Some(crate_name) = PRIV_DEP_REGEX.captures(diag).and_then(|m| m.get(1))
2239            && let Some(ref contents) = manifest.contents
2240            && let Some(span) = manifest.find_crate_span(crate_name.as_str())
2241        {
2242            let rel_path = pathdiff::diff_paths(&manifest.path, &manifest.cwd)
2243                .unwrap_or_else(|| manifest.path.clone())
2244                .display()
2245                .to_string();
2246            let report = [Group::with_title(Level::NOTE.secondary_title(format!(
2247                "dependency `{}` declared here",
2248                crate_name.as_str()
2249            )))
2250            .element(
2251                Snippet::source(contents)
2252                    .path(rel_path)
2253                    .annotation(AnnotationKind::Context.span(span)),
2254            )];
2255
2256            let rendered = Renderer::styled()
2257                .term_width(manifest.term_width)
2258                .render(&report);
2259            diag.push_str(&rendered);
2260            diag.push('\n');
2261            return true;
2262        }
2263        false
2264    };
2265
2266    // Depending on what we're emitting from Cargo itself, we figure out what to
2267    // do with this JSON message.
2268    match options.format {
2269        // In the "human" output formats (human/short) or if diagnostic messages
2270        // from rustc aren't being included in the output of Cargo's JSON
2271        // messages then we extract the diagnostic (if present) here and handle
2272        // it ourselves.
2273        MessageFormat::Human
2274        | MessageFormat::Short
2275        | MessageFormat::Json {
2276            render_diagnostics: true,
2277            ..
2278        } => {
2279            #[derive(serde::Deserialize)]
2280            struct CompilerMessage<'a> {
2281                // `rendered` contains escape sequences, which can't be
2282                // zero-copy deserialized by serde_json.
2283                // See https://github.com/serde-rs/json/issues/742
2284                rendered: String,
2285                #[serde(borrow)]
2286                message: Cow<'a, str>,
2287                #[serde(borrow)]
2288                level: Cow<'a, str>,
2289                children: Vec<PartialDiagnostic>,
2290                code: Option<DiagnosticCode>,
2291            }
2292
2293            // A partial rustfix::diagnostics::Diagnostic. We deserialize only a
2294            // subset of the fields because rustc's output can be extremely
2295            // deeply nested JSON in pathological cases involving macro
2296            // expansion. Rustfix's Diagnostic struct is recursive containing a
2297            // field `children: Vec<Self>`, and it can cause deserialization to
2298            // hit serde_json's default recursion limit, or overflow the stack
2299            // if we turn that off. Cargo only cares about the 1 field listed
2300            // here.
2301            #[derive(serde::Deserialize)]
2302            struct PartialDiagnostic {
2303                spans: Vec<PartialDiagnosticSpan>,
2304            }
2305
2306            // A partial rustfix::diagnostics::DiagnosticSpan.
2307            #[derive(serde::Deserialize)]
2308            struct PartialDiagnosticSpan {
2309                suggestion_applicability: Option<Applicability>,
2310            }
2311
2312            #[derive(serde::Deserialize)]
2313            struct DiagnosticCode {
2314                code: String,
2315            }
2316
2317            if let Ok(mut msg) = serde_json::from_str::<CompilerMessage<'_>>(compiler_message.get())
2318            {
2319                if msg.message.starts_with("aborting due to")
2320                    || msg.message.ends_with("warning emitted")
2321                    || msg.message.ends_with("warnings emitted")
2322                {
2323                    // Skip this line; we'll print our own summary at the end.
2324                    return Ok(true);
2325                }
2326                // state.stderr will add a newline
2327                if msg.rendered.ends_with('\n') {
2328                    msg.rendered.pop();
2329                }
2330                let mut rendered = msg.rendered;
2331                if options.show_diagnostics {
2332                    let machine_applicable: bool = msg
2333                        .children
2334                        .iter()
2335                        .map(|child| {
2336                            child
2337                                .spans
2338                                .iter()
2339                                .filter_map(|span| span.suggestion_applicability)
2340                                .any(|app| app == Applicability::MachineApplicable)
2341                        })
2342                        .any(|b| b);
2343                    count_diagnostic(&msg.level, options);
2344                    if msg
2345                        .code
2346                        .as_ref()
2347                        .is_some_and(|c| c.code == "exported_private_dependencies")
2348                        && options.format != MessageFormat::Short
2349                    {
2350                        add_pub_in_priv_diagnostic(&mut rendered);
2351                    }
2352                    let lint = msg.code.is_some();
2353                    state.emit_diag(&msg.level, rendered, lint, machine_applicable)?;
2354                }
2355                return Ok(true);
2356            }
2357        }
2358
2359        MessageFormat::Json { ansi, .. } => {
2360            #[derive(serde::Deserialize, serde::Serialize)]
2361            struct CompilerMessage<'a> {
2362                rendered: String,
2363                #[serde(flatten, borrow)]
2364                other: std::collections::BTreeMap<Cow<'a, str>, serde_json::Value>,
2365                code: Option<DiagnosticCode<'a>>,
2366            }
2367
2368            #[derive(serde::Deserialize, serde::Serialize)]
2369            struct DiagnosticCode<'a> {
2370                code: String,
2371                #[serde(flatten, borrow)]
2372                other: std::collections::BTreeMap<Cow<'a, str>, serde_json::Value>,
2373            }
2374
2375            if let Ok(mut error) =
2376                serde_json::from_str::<CompilerMessage<'_>>(compiler_message.get())
2377            {
2378                let modified_diag = if error
2379                    .code
2380                    .as_ref()
2381                    .is_some_and(|c| c.code == "exported_private_dependencies")
2382                {
2383                    add_pub_in_priv_diagnostic(&mut error.rendered)
2384                } else {
2385                    false
2386                };
2387
2388                // Remove color information from the rendered string if color is not
2389                // enabled. Cargo always asks for ANSI colors from rustc. This allows
2390                // cached replay to enable/disable colors without re-invoking rustc.
2391                if !ansi {
2392                    error.rendered = anstream::adapter::strip_str(&error.rendered).to_string();
2393                }
2394                if !ansi || modified_diag {
2395                    let new_line = serde_json::to_string(&error)?;
2396                    compiler_message = serde_json::value::RawValue::from_string(new_line)?;
2397                }
2398            }
2399        }
2400    }
2401
2402    // We always tell rustc to emit messages about artifacts being produced.
2403    // These messages feed into pipelined compilation, as well as timing
2404    // information.
2405    //
2406    // Look for a matching directive and inform Cargo internally that a
2407    // metadata file has been produced.
2408    #[derive(serde::Deserialize)]
2409    struct ArtifactNotification<'a> {
2410        #[serde(borrow)]
2411        artifact: Cow<'a, str>,
2412    }
2413
2414    if let Ok(artifact) = serde_json::from_str::<ArtifactNotification<'_>>(compiler_message.get()) {
2415        trace!("found directive from rustc: `{}`", artifact.artifact);
2416        if artifact.artifact.ends_with(".rmeta") {
2417            debug!("looks like metadata finished early!");
2418            state.rmeta_produced();
2419        }
2420        return Ok(false);
2421    }
2422
2423    #[derive(serde::Deserialize)]
2424    struct UnusedExterns {
2425        unused_extern_names: std::collections::BTreeSet<InternedString>,
2426    }
2427    if let Ok(uext) = serde_json::from_str::<UnusedExterns>(compiler_message.get()) {
2428        trace!(
2429            "obtained unused externs list from rustc: `{:?}`",
2430            uext.unused_extern_names
2431        );
2432        state.unused_externs(uext.unused_extern_names);
2433        return Ok(true);
2434    }
2435
2436    // And failing all that above we should have a legitimate JSON diagnostic
2437    // from the compiler, so wrap it in an external Cargo JSON message
2438    // indicating which package it came from and then emit it.
2439
2440    if !options.show_diagnostics {
2441        return Ok(true);
2442    }
2443
2444    #[derive(serde::Deserialize)]
2445    struct CompilerMessage<'a> {
2446        #[serde(borrow)]
2447        message: Cow<'a, str>,
2448        #[serde(borrow)]
2449        level: Cow<'a, str>,
2450    }
2451
2452    if let Ok(msg) = serde_json::from_str::<CompilerMessage<'_>>(compiler_message.get()) {
2453        if msg.message.starts_with("aborting due to")
2454            || msg.message.ends_with("warning emitted")
2455            || msg.message.ends_with("warnings emitted")
2456        {
2457            // Skip this line; we'll print our own summary at the end.
2458            return Ok(true);
2459        }
2460        count_diagnostic(&msg.level, options);
2461    }
2462
2463    let msg = machine_message::FromCompiler {
2464        package_id: package_id.to_spec(),
2465        manifest_path: &manifest.path,
2466        target,
2467        message: compiler_message,
2468    }
2469    .to_json_string();
2470
2471    // Switch json lines from rustc/rustdoc that appear on stderr to stdout
2472    // instead. We want the stdout of Cargo to always be machine parseable as
2473    // stderr has our colorized human-readable messages.
2474    state.stdout(msg)?;
2475    Ok(true)
2476}
2477
2478impl ManifestErrorContext {
2479    fn new(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> ManifestErrorContext {
2480        let mut duplicates = HashSet::default();
2481        let mut rename_table = HashMap::default();
2482
2483        for dep in build_runner.unit_deps(unit) {
2484            let unrenamed_id = dep.unit.pkg.package_id().name();
2485            if duplicates.contains(&unrenamed_id) {
2486                continue;
2487            }
2488            match rename_table.entry(unrenamed_id) {
2489                std::collections::hash_map::Entry::Occupied(occ) => {
2490                    occ.remove_entry();
2491                    duplicates.insert(unrenamed_id);
2492                }
2493                std::collections::hash_map::Entry::Vacant(vac) => {
2494                    vac.insert(dep.extern_crate_name);
2495                }
2496            }
2497        }
2498
2499        let bcx = build_runner.bcx;
2500        ManifestErrorContext {
2501            path: unit.pkg.manifest_path().to_owned(),
2502            spans: unit.pkg.manifest().document_rc(),
2503            contents: unit.pkg.manifest().contents().map(String::from),
2504            requested_kinds: bcx.target_data.requested_kinds().to_owned(),
2505            host_name: bcx.rustc().host,
2506            rename_table,
2507            cwd: path_args(build_runner.bcx.ws, unit).1,
2508            cfgs: bcx
2509                .target_data
2510                .requested_kinds()
2511                .iter()
2512                .map(|k| bcx.target_data.cfg(*k).to_owned())
2513                .collect(),
2514            term_width: bcx
2515                .gctx
2516                .shell()
2517                .err_width()
2518                .diagnostic_terminal_width()
2519                .unwrap_or(cargo_util_terminal::report::renderer::DEFAULT_TERM_WIDTH),
2520        }
2521    }
2522
2523    fn requested_target_names(&self) -> impl Iterator<Item = &str> {
2524        self.requested_kinds.iter().map(|kind| match kind {
2525            CompileKind::Host => &self.host_name,
2526            CompileKind::Target(target) => target.short_name(),
2527        })
2528    }
2529
2530    /// Find a span for the dependency that specifies this unrenamed crate, if it's unique.
2531    ///
2532    /// rustc diagnostics (at least for public-in-private) mention the un-renamed
2533    /// crate: if you have `foo = { package = "bar" }`, the rustc diagnostic will
2534    /// say "bar".
2535    ///
2536    /// This function does its best to find a span for "bar", but it could fail if
2537    /// there are multiple candidates:
2538    ///
2539    /// ```toml
2540    /// foo = { package = "bar" }
2541    /// baz = { path = "../bar", package = "bar" }
2542    /// ```
2543    fn find_crate_span(&self, unrenamed: &str) -> Option<Range<usize>> {
2544        let Some(ref spans) = self.spans else {
2545            return None;
2546        };
2547
2548        let orig_name = self.rename_table.get(unrenamed)?.as_str();
2549
2550        if let Some((k, v)) = get_key_value(&spans, &["dependencies", orig_name]) {
2551            // We make some effort to find the unrenamed text: in
2552            //
2553            // ```
2554            // foo = { package = "bar" }
2555            // ```
2556            //
2557            // we try to find the "bar", but fall back to "foo" if we can't (which might
2558            // happen if the renaming took place in the workspace, for example).
2559            if let Some(package) = v.get_ref().as_table().and_then(|t| t.get("package")) {
2560                return Some(package.span());
2561            } else {
2562                return Some(k.span());
2563            }
2564        }
2565
2566        // The dependency could also be in a target-specific table, like
2567        // [target.x86_64-unknown-linux-gnu.dependencies] or
2568        // [target.'cfg(something)'.dependencies]. We filter out target tables
2569        // that don't match a requested target or a requested cfg.
2570        if let Some(target) = spans
2571            .deref()
2572            .as_ref()
2573            .get("target")
2574            .and_then(|t| t.as_ref().as_table())
2575        {
2576            for (platform, platform_table) in target.iter() {
2577                match platform.as_ref().parse::<Platform>() {
2578                    Ok(Platform::Name(name)) => {
2579                        if !self.requested_target_names().any(|n| n == name) {
2580                            continue;
2581                        }
2582                    }
2583                    Ok(Platform::Cfg(cfg_expr)) => {
2584                        if !self.cfgs.iter().any(|cfgs| cfg_expr.matches(cfgs)) {
2585                            continue;
2586                        }
2587                    }
2588                    Err(_) => continue,
2589                }
2590
2591                let Some(platform_table) = platform_table.as_ref().as_table() else {
2592                    continue;
2593                };
2594
2595                if let Some(deps) = platform_table
2596                    .get("dependencies")
2597                    .and_then(|d| d.as_ref().as_table())
2598                {
2599                    if let Some((k, v)) = deps.get_key_value(orig_name) {
2600                        if let Some(package) = v.get_ref().as_table().and_then(|t| t.get("package"))
2601                        {
2602                            return Some(package.span());
2603                        } else {
2604                            return Some(k.span());
2605                        }
2606                    }
2607                }
2608            }
2609        }
2610        None
2611    }
2612}
2613
2614/// Creates a unit of work that replays the cached compiler message.
2615///
2616/// Usually used when a job is fresh and doesn't need to recompile.
2617fn replay_output_cache(
2618    package_id: PackageId,
2619    manifest: ManifestErrorContext,
2620    target: &Target,
2621    path: PathBuf,
2622    mut output_options: OutputOptions,
2623) -> Work {
2624    let target = target.clone();
2625    Work::new(move |state| {
2626        if !path.exists() {
2627            // No cached output, probably didn't emit anything.
2628            return Ok(());
2629        }
2630        // We sometimes have gigabytes of output from the compiler, so avoid
2631        // loading it all into memory at once, as that can cause OOM where
2632        // otherwise there would be none.
2633        let file = paths::open(&path)?;
2634        let mut reader = std::io::BufReader::new(file);
2635        let mut line = String::new();
2636        loop {
2637            let length = reader.read_line(&mut line)?;
2638            if length == 0 {
2639                break;
2640            }
2641            let trimmed = line.trim_end_matches(&['\n', '\r'][..]);
2642            on_stderr_line(
2643                state,
2644                trimmed,
2645                package_id,
2646                &manifest,
2647                &target,
2648                &mut output_options,
2649            )?;
2650            line.clear();
2651        }
2652        Ok(())
2653    })
2654}
2655
2656/// Provides a package name with descriptive target information,
2657/// e.g., '`foo` (bin "bar" test)', '`foo` (lib doctest)'.
2658fn descriptive_pkg_name(name: &str, target: &Target, mode: &CompileMode) -> String {
2659    let desc_name = target.description_named();
2660    let mode = if mode.is_rustc_test() && !(target.is_test() || target.is_bench()) {
2661        " test"
2662    } else if mode.is_doc_test() {
2663        " doctest"
2664    } else if mode.is_doc() {
2665        " doc"
2666    } else {
2667        ""
2668    };
2669    format!("`{name}` ({desc_name}{mode})")
2670}
2671
2672/// Applies environment variables from config `[env]` to [`ProcessBuilder`].
2673pub(crate) fn apply_env_config(
2674    gctx: &crate::GlobalContext,
2675    cmd: &mut ProcessBuilder,
2676) -> CargoResult<()> {
2677    for (key, value) in gctx.env_config()?.iter() {
2678        // never override a value that has already been set by cargo
2679        if cmd.get_envs().contains_key(key) {
2680            continue;
2681        }
2682        cmd.env(key, value);
2683    }
2684    Ok(())
2685}
2686
2687/// Checks if there are some scrape units waiting to be processed.
2688fn should_include_scrape_units(bcx: &BuildContext<'_, '_>, unit: &Unit) -> bool {
2689    unit.mode.is_doc() && bcx.scrape_units.len() > 0 && bcx.ws.unit_needs_doc_scrape(unit)
2690}
2691
2692/// Gets the file path of function call information output from `rustdoc`.
2693fn scrape_output_path(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<PathBuf> {
2694    assert!(unit.mode.is_doc() || unit.mode.is_doc_scrape());
2695    build_runner
2696        .outputs(unit)
2697        .map(|outputs| outputs[0].path.clone())
2698}
2699
2700/// Gets the dep-info file emitted by rustdoc.
2701fn rustdoc_dep_info_loc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> PathBuf {
2702    let mut loc = build_runner.files().fingerprint_file_path(unit, "");
2703    loc.set_extension("d");
2704    loc
2705}