Skip to main content

supercov_engine/
rust_compiler_orchestration.rs

1//! Private production-shaped Cargo orchestration for the exact rustc companion.
2//!
3//! Supercov temporarily occupies Cargo's general and workspace wrapper slots
4//! inside the isolated run. The outer bridge reconstructs the user's original
5//! wrapper chain and the inner bridge selects an exact companion from Cargo's
6//! actual compiler token. This preserves non-workspace compilation and avoids
7//! guessing which toolchain a working command, custom `RUSTC`, or rustup
8//! override will actually use.
9
10use std::{
11    collections::BTreeMap,
12    ffi::{OsStr, OsString},
13    fs::{self, OpenOptions},
14    io::{self, Write},
15    path::{Component, Path, PathBuf},
16    process::Command,
17    thread,
18    time::{Duration, Instant, SystemTime, UNIX_EPOCH},
19};
20
21use nextest_metadata::TestListSummary;
22use serde::{Deserialize, Serialize};
23
24use crate::workspace::canonicalize_simplified;
25use crate::{
26    process_supervision::{
27        CommandSpec, ForwardedSignal, ProcessSupervisor, SupervisedOutput, SupervisionOptions,
28    },
29    rust_cargo_configuration::{RustCargoResolvedTargetRunner, RustCargoRunnerPlan},
30    rust_compiler_ctfe::{RustCompilerCtfeUnit, read_rust_compiler_ctfe},
31    rust_compiler_manifest::{NormalizedRustCompilerManifest, normalize_rust_compiler_candidates},
32    rust_compiler_selection::{SelectedRustCompilerCompanion, select_rust_compiler_companion},
33    rust_compiler_test_runner::{
34        RUST_CARGO_RUNNER_CONFIG_ENV, RUST_CARGO_RUNNER_VERSION, RustCargoRunnerArtifact,
35        RustCargoRunnerConfig, RustCargoRunnerUnit, read_cargo_runner_units,
36    },
37    rust_doctest::{
38        RustdocOutcomeResolution, join_rustdoc_outcomes, read_rustdoc_outcome_units,
39        resolve_merged_doctest_candidates,
40    },
41    rust_runner_attempt::parse_nextest_version_output,
42    rust_test_runner::{
43        RustCargoCommandKind, cargo_invocation, nextest_list_invocation, nextest_version_arguments,
44        rust_cargo_execution_selection,
45    },
46};
47
48fn inherited_environment(
49    overrides: impl IntoIterator<Item = (OsString, OsString)>,
50) -> Vec<(OsString, OsString)> {
51    let mut environment = std::env::vars_os().collect::<BTreeMap<_, _>>();
52    environment.extend(overrides);
53    environment.into_iter().collect()
54}
55
56fn supervised_success(output: &SupervisedOutput) -> bool {
57    output.result.status == Some(0)
58        && output.result.signal.is_none()
59        && !output.result.timed_out
60        && output.result.interrupted_signal.is_none()
61}
62
63fn interrupted_error(output: &SupervisedOutput) -> Option<RustCompilerOrchestrationError> {
64    output.result.interrupted_signal.map(|signal| {
65        let signal = match signal {
66            ForwardedSignal::Sighup => "SIGHUP",
67            ForwardedSignal::Sigint => "SIGINT",
68            ForwardedSignal::Sigterm => "SIGTERM",
69        };
70        RustCompilerOrchestrationError::Interrupted {
71            code: output.result.exit_code(),
72            signal: signal.into(),
73        }
74    })
75}
76
77fn cargo_runner_configuration_arguments(
78    wrapper: &Path,
79    plan: &RustCargoRunnerPlan,
80) -> Result<Vec<String>, RustCompilerOrchestrationError> {
81    let wrapper = wrapper.to_str().ok_or_else(|| {
82        RustCompilerOrchestrationError::InvalidRequest(
83            "the Cargo runner executable path is not UTF-8".into(),
84        )
85    })?;
86    let wrapper = serde_json::to_string(wrapper)
87        .map_err(|error| RustCompilerOrchestrationError::InvalidRequest(error.to_string()))?;
88    let mut seen = BTreeMap::new();
89    let mut arguments = Vec::with_capacity(plan.targets.len() * 2);
90    for target in &plan.targets {
91        if seen.insert(target.target.as_str(), ()).is_some() {
92            return Err(RustCompilerOrchestrationError::InvalidRequest(format!(
93                "Cargo runner plan contains duplicate target identity: {}",
94                target.target
95            )));
96        }
97        let target = serde_json::to_string(&target.target)
98            .map_err(|error| RustCompilerOrchestrationError::InvalidRequest(error.to_string()))?;
99        arguments.extend([
100            "--config".into(),
101            format!("target.{target}.runner=[{wrapper},\"__cargo-test-runner\",{target}]"),
102        ]);
103    }
104    if arguments.is_empty() {
105        return Err(RustCompilerOrchestrationError::InvalidRequest(
106            "Cargo runner plan has no selected targets".into(),
107        ));
108    }
109    Ok(arguments)
110}
111
112pub const RUST_COMPILER_WRAPPER_CONFIG_ENV: &str = "SUPERCOV_RUST_COMPILER_WRAPPER_CONFIG";
113pub const RUST_COMPILER_INNER_MODE_ENV: &str = "SUPERCOV_RUST_COMPILER_INNER_MODE";
114pub const RUST_ORIGINAL_COMPILER_ENV: &str = "SUPERCOV_RUST_ORIGINAL_COMPILER";
115pub const RUST_COMPILER_OUTPUT_ENV: &str = "SUPERCOV_RUST_COMPILER_OUTPUT";
116pub const RUST_SOURCE_ROOT_ENV: &str = "SUPERCOV_RUST_SOURCE_ROOT";
117pub const RUST_TARGET_ROOT_ENV: &str = "SUPERCOV_RUST_TARGET_ROOT";
118pub const RUST_INSTRUMENT_MIR_ENV: &str = "SUPERCOV_RUST_INSTRUMENT_MIR";
119pub const RUST_INSTRUMENT_CTFE_ENV: &str = "SUPERCOV_RUST_INSTRUMENT_CTFE";
120pub const RUST_STATIC_RUNTIME_DIRECTORY_ENV: &str = "SUPERCOV_RUST_STATIC_RUNTIME_DIRECTORY";
121pub const RUSTDOC_WRAPPER_MODE_ENV: &str = "SUPERCOV_RUSTDOC_WRAPPER_MODE";
122pub const RUST_REAL_RUSTDOC_ENV: &str = "SUPERCOV_RUST_REAL_RUSTDOC";
123pub const RUST_COMPANION_PATH_ENV: &str = "SUPERCOV_RUST_COMPANION_PATH";
124pub const RUSTDOC_CAPTURE_OUTCOMES_ENV: &str = "SUPERCOV_RUSTDOC_CAPTURE_OUTCOMES";
125pub const RUSTDOC_ENGINE_PATH_ENV: &str = "SUPERCOV_RUSTDOC_ENGINE_PATH";
126const SHARED_RUNTIME_TEMPLATE: &str = include_str!("../runtime-assets/rust-mmap-runtime.rs");
127const SHARED_RUNTIME_EXPORTS: &str = r#"
128#[unsafe(no_mangle)] pub extern "C" fn __supercov_rt_ordinal_hit(ordinal: u64) { __supercov_shared_runtime::ordinal_hit(ordinal) }
129#[unsafe(no_mangle)] pub extern "C" fn __supercov_rt_active_context() -> u64 { __supercov_shared_runtime::active_context() }
130#[unsafe(no_mangle)] pub extern "C" fn __supercov_rt_enter_context(context_id: u64) -> u64 { __supercov_shared_runtime::enter_context(context_id) }
131#[unsafe(no_mangle)] pub extern "C" fn __supercov_rt_exit_context(previous: u64) { __supercov_shared_runtime::exit_context(previous) }
132#[unsafe(no_mangle)] pub extern "C" fn __supercov_rt_exit_test_context(context_id: u64, previous: u64) { __supercov_shared_runtime::exit_test_context(context_id, previous) }
133#[unsafe(no_mangle)] pub extern "C" fn __supercov_rt_enter_assertion_context(id_high: u64, id_low: u32) -> u64 { __supercov_shared_runtime::enter_assertion_context(id_high, id_low) }
134#[unsafe(no_mangle)] pub extern "C" fn __supercov_rt_decision_start(id_high: u64, id_low: u32, conditions: u64) -> u64 { __supercov_shared_runtime::mir_decision_start(id_high, id_low, conditions) }
135#[unsafe(no_mangle)] pub extern "C" fn __supercov_rt_decision_condition(token: u64, index: u64, value: bool) { __supercov_shared_runtime::mir_decision_condition(token, index, value) }
136#[unsafe(no_mangle)] pub extern "C" fn __supercov_rt_decision_finish(token: u64, outcome: bool) { __supercov_shared_runtime::mir_decision_finish(token, outcome) }
137#[unsafe(no_mangle)] pub extern "C" fn __supercov_rt_branch_start() -> u64 { __supercov_shared_runtime::mir_branch_start() }
138#[unsafe(no_mangle)] pub extern "C" fn __supercov_rt_branch_hit(token: u64, ordinal: u64) { __supercov_shared_runtime::mir_branch_hit(token, ordinal) }
139"#;
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
142#[serde(rename_all = "camelCase", deny_unknown_fields)]
143pub struct RustCompilerWrapperConfig {
144    pub candidates: Vec<PathBuf>,
145    pub require_public_capabilities: bool,
146    pub selection_directory: PathBuf,
147    pub shared_runtime_directory: PathBuf,
148    pub target_runners: Vec<RustCargoResolvedTargetRunner>,
149    pub project_root: PathBuf,
150    pub compiler: crate::rust_cargo_configuration::RustCargoCompilerCommandPlan,
151    pub original_wrapper_environment: RustCompilerWrapperEnvironment,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155#[serde(tag = "encoding", rename_all = "kebab-case", deny_unknown_fields)]
156pub enum RustCompilerEnvironmentValue {
157    UnixBytes { value: Vec<u8> },
158    WindowsWide { value: Vec<u16> },
159}
160
161impl RustCompilerEnvironmentValue {
162    fn capture(value: &OsStr) -> Self {
163        #[cfg(unix)]
164        {
165            use std::os::unix::ffi::OsStrExt as _;
166            Self::UnixBytes {
167                value: value.as_bytes().to_vec(),
168            }
169        }
170        #[cfg(windows)]
171        {
172            use std::os::windows::ffi::OsStrExt as _;
173            Self::WindowsWide {
174                value: value.encode_wide().collect(),
175            }
176        }
177        #[cfg(not(any(unix, windows)))]
178        {
179            Self::UnixBytes {
180                value: value.to_string_lossy().as_bytes().to_vec(),
181            }
182        }
183    }
184
185    pub fn decode(&self) -> Result<OsString, RustCompilerOrchestrationError> {
186        match self {
187            Self::UnixBytes { value } => {
188                #[cfg(unix)]
189                {
190                    use std::os::unix::ffi::OsStringExt as _;
191                    Ok(OsString::from_vec(value.clone()))
192                }
193                #[cfg(not(unix))]
194                {
195                    let _ = value;
196                    Err(RustCompilerOrchestrationError::InvalidRequest(
197                        "Unix compiler-wrapper environment was read on a non-Unix host".into(),
198                    ))
199                }
200            }
201            Self::WindowsWide { value } => {
202                #[cfg(windows)]
203                {
204                    use std::os::windows::ffi::OsStringExt as _;
205                    Ok(OsString::from_wide(value))
206                }
207                #[cfg(not(windows))]
208                {
209                    let _ = value;
210                    Err(RustCompilerOrchestrationError::InvalidRequest(
211                        "Windows compiler-wrapper environment was read on a non-Windows host"
212                            .into(),
213                    ))
214                }
215            }
216        }
217    }
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase", deny_unknown_fields)]
222pub struct RustCompilerWrapperEnvironment {
223    pub rustc_wrapper: Option<RustCompilerEnvironmentValue>,
224    pub rustc_workspace_wrapper: Option<RustCompilerEnvironmentValue>,
225}
226
227impl RustCompilerWrapperEnvironment {
228    fn capture() -> Self {
229        Self {
230            rustc_wrapper: std::env::var_os("RUSTC_WRAPPER")
231                .as_deref()
232                .map(RustCompilerEnvironmentValue::capture),
233            rustc_workspace_wrapper: std::env::var_os("RUSTC_WORKSPACE_WRAPPER")
234                .as_deref()
235                .map(RustCompilerEnvironmentValue::capture),
236        }
237    }
238
239    pub fn restore(&self, command: &mut Command) -> Result<(), RustCompilerOrchestrationError> {
240        for (name, value) in [
241            ("RUSTC_WRAPPER", &self.rustc_wrapper),
242            ("RUSTC_WORKSPACE_WRAPPER", &self.rustc_workspace_wrapper),
243        ] {
244            if let Some(value) = value {
245                command.env(name, value.decode()?);
246            } else {
247                command.env_remove(name);
248            }
249        }
250        Ok(())
251    }
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
255#[serde(rename_all = "camelCase", deny_unknown_fields)]
256pub struct RustCompilerBuildRequest {
257    pub project_root: PathBuf,
258    pub command: Vec<String>,
259    pub run_id: String,
260    pub wrapper_path: PathBuf,
261    pub companion_candidates: Vec<PathBuf>,
262    pub require_public_capabilities: bool,
263    pub cargo_runner_plan: RustCargoRunnerPlan,
264}
265
266#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
267#[serde(rename_all = "camelCase")]
268pub struct RustCompilerTestArtifact {
269    pub executable: PathBuf,
270    pub package: String,
271    pub target_name: String,
272    pub target_kinds: Vec<String>,
273    pub source_path: PathBuf,
274    pub test_harness: bool,
275}
276
277#[derive(Debug, Clone, PartialEq, Serialize)]
278#[serde(rename_all = "camelCase")]
279pub struct RustCompilerBuild {
280    pub selection: SelectedRustCompilerCompanion,
281    pub normalized: NormalizedRustCompilerManifest,
282    pub artifacts: Vec<RustCompilerTestArtifact>,
283    pub target_directory: PathBuf,
284    pub compiler_output_directory: PathBuf,
285    pub ctfe_units: Vec<RustCompilerCtfeUnit>,
286    pub doctest_outcomes: RustdocOutcomeResolution,
287    pub cargo_runner_units: Vec<RustCargoRunnerUnit>,
288    #[serde(skip)]
289    pub(crate) command_kind: RustCargoCommandKind,
290    #[serde(skip)]
291    pub(crate) nextest_version: Option<String>,
292    #[serde(skip)]
293    pub(crate) nextest_catalog: Option<TestListSummary>,
294    pub run_libtests: bool,
295    pub run_doctests: bool,
296    pub execution_exit_code: i32,
297    pub execution_stdout: Vec<u8>,
298    pub execution_stderr: Vec<u8>,
299    pub build_started_at_ms: i64,
300    pub build_ended_at_ms: i64,
301    pub build_ms: f64,
302    pub execution_ms: f64,
303}
304
305#[derive(Debug)]
306pub enum RustCompilerOrchestrationError {
307    InvalidRequest(String),
308    Io { path: PathBuf, reason: String },
309    Cargo(String),
310    CargoOutput(String),
311    CompilerOutput(String),
312    Selection(String),
313    Manifest(String),
314    UnverifiedExecution { code: i32, reason: String },
315    Interrupted { code: i32, signal: String },
316}
317
318impl std::fmt::Display for RustCompilerOrchestrationError {
319    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320        match self {
321            Self::InvalidRequest(reason) => {
322                write!(formatter, "invalid Rust compiler build: {reason}")
323            }
324            Self::Io { path, reason } => write!(formatter, "{}: {reason}", path.display()),
325            Self::Cargo(reason) => write!(formatter, "Cargo compiler build failed: {reason}"),
326            Self::CargoOutput(reason) => {
327                write!(formatter, "invalid Cargo compiler output: {reason}")
328            }
329            Self::CompilerOutput(reason) => {
330                write!(formatter, "invalid Rust compiler output: {reason}")
331            }
332            Self::Selection(reason) => {
333                write!(formatter, "Rust compiler selection failed: {reason}")
334            }
335            Self::Manifest(reason) => write!(formatter, "Rust compiler manifest failed: {reason}"),
336            Self::UnverifiedExecution { code, reason } => write!(
337                formatter,
338                "Rust test command exited {code}, but Supercov could not authenticate complete coverage evidence: {reason}"
339            ),
340            Self::Interrupted { signal, .. } => {
341                write!(formatter, "Rust compiler run was interrupted by {signal}")
342            }
343        }
344    }
345}
346
347impl std::error::Error for RustCompilerOrchestrationError {}
348
349#[derive(Debug, Deserialize)]
350struct CargoMessage {
351    reason: String,
352    #[serde(default)]
353    manifest_path: Option<PathBuf>,
354    #[serde(default)]
355    target: Option<CargoTarget>,
356    #[serde(default)]
357    profile: Option<CargoProfile>,
358    executable: Option<PathBuf>,
359    #[serde(default)]
360    message: Option<CargoDiagnostic>,
361}
362
363#[derive(Debug, Deserialize)]
364struct CargoDiagnostic {
365    rendered: Option<String>,
366}
367
368#[derive(Debug, Deserialize)]
369struct CargoTarget {
370    name: String,
371    kind: Vec<String>,
372    src_path: PathBuf,
373}
374
375#[derive(Debug, Deserialize)]
376struct CargoProfile {
377    test: bool,
378}
379
380#[derive(Debug, Deserialize)]
381struct CargoMetadataOutput {
382    packages: Vec<CargoMetadataPackage>,
383}
384
385#[derive(Debug, Deserialize)]
386struct CargoMetadataPackage {
387    id: String,
388    name: String,
389    manifest_path: PathBuf,
390    targets: Vec<CargoMetadataTarget>,
391}
392
393#[derive(Debug, Deserialize)]
394struct CargoMetadataTarget {
395    name: String,
396    kind: Vec<String>,
397    src_path: PathBuf,
398}
399
400fn cargo_metadata_arguments(
401    invocation: &crate::rust_test_runner::CargoTestInvocation,
402) -> Result<Vec<String>, RustCompilerOrchestrationError> {
403    let command = invocation.command_position().ok_or_else(|| {
404        RustCompilerOrchestrationError::InvalidRequest(
405            "the Cargo invocation lost its test subcommand".into(),
406        )
407    })?;
408    let mut arguments = invocation.arguments[..command]
409        .iter()
410        .filter(|argument| argument.starts_with('+'))
411        .cloned()
412        .collect::<Vec<_>>();
413    arguments.extend([
414        "metadata".into(),
415        "--format-version=1".into(),
416        "--no-deps".into(),
417    ]);
418    let command_width = match invocation.kind {
419        RustCargoCommandKind::CargoTest => 1,
420        RustCargoCommandKind::NextestRun => 2,
421    };
422    let mut index = command + command_width;
423    while index < invocation.arguments.len() {
424        let argument = &invocation.arguments[index];
425        let name = argument
426            .split_once('=')
427            .map_or(argument.as_str(), |(name, _)| name);
428        let takes_value = match name {
429            "--manifest-path" | "--config" | "-Z" => Some(!argument.contains('=')),
430            "--frozen" | "--locked" | "--offline" | "--ignore-rust-version" => Some(false),
431            _ => None,
432        };
433        if let Some(takes_value) = takes_value {
434            arguments.push(argument.clone());
435            if takes_value {
436                index += 1;
437                let value = invocation.arguments.get(index).ok_or_else(|| {
438                    RustCompilerOrchestrationError::InvalidRequest(format!(
439                        "Cargo option {argument} has no value"
440                    ))
441                })?;
442                arguments.push(value.clone());
443            }
444        }
445        index += 1;
446    }
447    Ok(arguments)
448}
449
450fn package_identity(
451    manifest_path: &Path,
452    project_root: &Path,
453) -> Result<String, RustCompilerOrchestrationError> {
454    let manifest_metadata =
455        fs::symlink_metadata(manifest_path).map_err(|error| io_error(manifest_path, error))?;
456    let manifest =
457        canonicalize_simplified(manifest_path).map_err(|error| io_error(manifest_path, error))?;
458    let package_root = manifest
459        .parent()
460        .and_then(|path| path.strip_prefix(project_root).ok())
461        .filter(|_| {
462            manifest_metadata.file_type().is_file()
463                && manifest
464                    .file_name()
465                    .is_some_and(|name| name == "Cargo.toml")
466        })
467        .ok_or_else(|| {
468            RustCompilerOrchestrationError::CargoOutput(format!(
469                "test artifact manifest escaped the owned project: {}",
470                manifest.display()
471            ))
472        })?;
473    if package_root.as_os_str().is_empty() {
474        Ok("package:.".into())
475    } else if package_root
476        .components()
477        .all(|component| matches!(component, Component::Normal(_)))
478    {
479        Ok(format!(
480            "package:{}",
481            package_root.to_string_lossy().replace('\\', "/")
482        ))
483    } else {
484        Err(RustCompilerOrchestrationError::CargoOutput(format!(
485            "test artifact has a noncanonical package root: {}",
486            package_root.display()
487        )))
488    }
489}
490
491fn nextest_artifacts(
492    catalog: &TestListSummary,
493    metadata: &CargoMetadataOutput,
494    target_directory: &Path,
495    project_root: &Path,
496) -> Result<Vec<RustCompilerTestArtifact>, RustCompilerOrchestrationError> {
497    let canonical_target = canonicalize_simplified(target_directory)
498        .map_err(|error| io_error(target_directory, error))?;
499    let canonical_project =
500        canonicalize_simplified(project_root).map_err(|error| io_error(project_root, error))?;
501    let packages = metadata
502        .packages
503        .iter()
504        .map(|package| (package.id.as_str(), package))
505        .collect::<BTreeMap<_, _>>();
506    let mut artifacts = Vec::new();
507    for (binary_id, suite) in &catalog.rust_suites {
508        if suite.binary.binary_id != *binary_id {
509            return Err(RustCompilerOrchestrationError::CargoOutput(format!(
510                "nextest suite key disagrees with binary identity {binary_id}"
511            )));
512        }
513        let package = packages
514            .get(suite.binary.package_id.as_str())
515            .ok_or_else(|| {
516                RustCompilerOrchestrationError::CargoOutput(format!(
517                    "nextest binary {binary_id} names an unknown Cargo package {}",
518                    suite.binary.package_id
519                ))
520            })?;
521        if package.name != suite.package_name {
522            return Err(RustCompilerOrchestrationError::CargoOutput(format!(
523                "nextest binary {binary_id} package name disagrees with Cargo metadata"
524            )));
525        }
526        let mut targets = package.targets.iter().filter(|target| {
527            target.name == suite.binary.binary_name
528                && target
529                    .kind
530                    .iter()
531                    .any(|kind| kind == suite.binary.kind.as_str())
532        });
533        let target = targets.next().ok_or_else(|| {
534            RustCompilerOrchestrationError::CargoOutput(format!(
535                "nextest binary {binary_id} has no exact Cargo metadata target"
536            ))
537        })?;
538        if targets.next().is_some() {
539            return Err(RustCompilerOrchestrationError::CargoOutput(format!(
540                "nextest binary {binary_id} ambiguously matches Cargo metadata targets"
541            )));
542        }
543        let executable = canonicalize_simplified(suite.binary.binary_path.as_std_path())
544            .map_err(|error| io_error(suite.binary.binary_path.as_std_path(), error))?;
545        let executable_metadata =
546            fs::symlink_metadata(&executable).map_err(|error| io_error(&executable, error))?;
547        if !executable.starts_with(&canonical_target) || !executable_metadata.file_type().is_file()
548        {
549            return Err(RustCompilerOrchestrationError::CargoOutput(format!(
550                "nextest test artifact escaped the private target: {}",
551                executable.display()
552            )));
553        }
554        let source_path = canonicalize_simplified(&target.src_path)
555            .map_err(|error| io_error(&target.src_path, error))?;
556        if !source_path.starts_with(&canonical_project) {
557            return Err(RustCompilerOrchestrationError::CargoOutput(format!(
558                "nextest target source escaped the owned project: {}",
559                source_path.display()
560            )));
561        }
562        artifacts.push(RustCompilerTestArtifact {
563            executable,
564            package: package_identity(&package.manifest_path, &canonical_project)?,
565            target_name: target.name.clone(),
566            target_kinds: target.kind.clone(),
567            source_path,
568            test_harness: true,
569        });
570    }
571    artifacts.sort_by(|left, right| left.executable.cmp(&right.executable));
572    artifacts.dedup_by(|left, right| left.executable == right.executable);
573    Ok(artifacts)
574}
575
576fn io_error(path: &Path, error: impl std::fmt::Display) -> RustCompilerOrchestrationError {
577    RustCompilerOrchestrationError::Io {
578        path: path.to_path_buf(),
579        reason: error.to_string(),
580    }
581}
582
583fn epoch_ms() -> Result<i64, RustCompilerOrchestrationError> {
584    let millis = SystemTime::now()
585        .duration_since(UNIX_EPOCH)
586        .map_err(|error| RustCompilerOrchestrationError::Cargo(error.to_string()))?
587        .as_millis();
588    i64::try_from(millis).map_err(|error| RustCompilerOrchestrationError::Cargo(error.to_string()))
589}
590
591fn valid_run_id(value: &str) -> bool {
592    !value.is_empty()
593        && value != "."
594        && value != ".."
595        && value
596            .chars()
597            .all(|character| character.is_ascii_alphanumeric() || matches!(character, '-' | '_'))
598}
599
600fn ensure_directories(
601    root: &Path,
602    relative: &Path,
603) -> Result<PathBuf, RustCompilerOrchestrationError> {
604    if relative.is_absolute()
605        || relative
606            .components()
607            .any(|component| !matches!(component, Component::Normal(_)))
608    {
609        return Err(RustCompilerOrchestrationError::InvalidRequest(format!(
610            "unsafe storage path {}",
611            relative.display()
612        )));
613    }
614    let mut current = root.to_path_buf();
615    for component in relative.components() {
616        current.push(component.as_os_str());
617        match fs::symlink_metadata(&current) {
618            Ok(metadata) if metadata.file_type().is_dir() => {}
619            Ok(_) => return Err(io_error(&current, "expected a non-symlink directory")),
620            Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
621                fs::create_dir(&current).map_err(|error| io_error(&current, error))?;
622            }
623            Err(error) => return Err(io_error(&current, error)),
624        }
625    }
626    Ok(current)
627}
628
629fn regular_executable(path: &Path) -> Result<PathBuf, RustCompilerOrchestrationError> {
630    let path = canonicalize_simplified(path).map_err(|error| io_error(path, error))?;
631    let metadata = fs::symlink_metadata(&path).map_err(|error| io_error(&path, error))?;
632    if !metadata.file_type().is_file() {
633        return Err(io_error(&path, "expected a regular executable"));
634    }
635    Ok(path)
636}
637
638fn write_json_config<T: Serialize>(
639    path: &Path,
640    config: &T,
641) -> Result<(), RustCompilerOrchestrationError> {
642    let bytes = serde_json::to_vec(config)
643        .map_err(|error| RustCompilerOrchestrationError::InvalidRequest(error.to_string()))?;
644    let mut options = OpenOptions::new();
645    options.write(true).create_new(true);
646    #[cfg(unix)]
647    {
648        use std::os::unix::fs::OpenOptionsExt as _;
649        options.mode(0o600);
650    }
651    let mut file = options.open(path).map_err(|error| io_error(path, error))?;
652    file.write_all(&bytes)
653        .map_err(|error| io_error(path, error))?;
654    file.sync_all().map_err(|error| io_error(path, error))
655}
656
657pub fn publish_compiler_selection_attestation(
658    directory: &Path,
659    selection: &SelectedRustCompilerCompanion,
660) -> Result<PathBuf, RustCompilerOrchestrationError> {
661    if !fs::symlink_metadata(directory).is_ok_and(|metadata| metadata.file_type().is_dir()) {
662        return Err(io_error(
663            directory,
664            "compiler selection root is not a directory",
665        ));
666    }
667    let now = SystemTime::now()
668        .duration_since(UNIX_EPOCH)
669        .map_err(|error| RustCompilerOrchestrationError::Selection(error.to_string()))?
670        .as_nanos();
671    let stem = format!("selection-{}-{now}", std::process::id());
672    let partial = directory.join(format!(".{stem}.partial"));
673    let final_path = directory.join(format!("{stem}.json"));
674    let bytes = serde_json::to_vec(selection)
675        .map_err(|error| RustCompilerOrchestrationError::Selection(error.to_string()))?;
676    let mut options = OpenOptions::new();
677    options.write(true).create_new(true);
678    #[cfg(unix)]
679    {
680        use std::os::unix::fs::OpenOptionsExt as _;
681        options.mode(0o600);
682    }
683    let mut cleanup = RemoveFileOnDrop(Some(partial.clone()));
684    let mut file = options
685        .open(&partial)
686        .map_err(|error| io_error(&partial, error))?;
687    file.write_all(&bytes)
688        .map_err(|error| io_error(&partial, error))?;
689    file.sync_all().map_err(|error| io_error(&partial, error))?;
690    drop(file);
691    fs::rename(&partial, &final_path).map_err(|error| io_error(&final_path, error))?;
692    sync_directory(directory)?;
693    cleanup.0 = None;
694    Ok(final_path)
695}
696
697fn write_shared_runtime_source(directory: &Path) -> Result<(), RustCompilerOrchestrationError> {
698    let source = directory.join("runtime.rs");
699    let runtime = format!(
700        "{}\n{}",
701        SHARED_RUNTIME_TEMPLATE.replace("__SUPERCOV_MODULE__", "__supercov_shared_runtime"),
702        SHARED_RUNTIME_EXPORTS
703    );
704    let mut options = OpenOptions::new();
705    options.write(true).create_new(true);
706    #[cfg(unix)]
707    {
708        use std::os::unix::fs::OpenOptionsExt as _;
709        options.mode(0o600);
710    }
711    let mut file = options
712        .open(&source)
713        .map_err(|error| io_error(&source, error))?;
714    file.write_all(runtime.as_bytes())
715        .map_err(|error| io_error(&source, error))?;
716    file.sync_all().map_err(|error| io_error(&source, error))
717}
718
719fn shared_runtime_archive(directory: &Path) -> PathBuf {
720    #[cfg(windows)]
721    let name = "supercov_runtime.lib";
722    #[cfg(not(windows))]
723    let name = "libsupercov_runtime.a";
724    directory.join(name)
725}
726
727fn valid_shared_runtime_archive(path: &Path) -> bool {
728    fs::symlink_metadata(path)
729        .is_ok_and(|metadata| metadata.file_type().is_file() && metadata.len() != 0)
730}
731
732#[cfg(unix)]
733fn sync_directory(path: &Path) -> Result<(), RustCompilerOrchestrationError> {
734    let directory = OpenOptions::new()
735        .read(true)
736        .open(path)
737        .map_err(|error| io_error(path, error))?;
738    directory.sync_all().map_err(|error| io_error(path, error))
739}
740
741#[cfg(not(unix))]
742fn sync_directory(_path: &Path) -> Result<(), RustCompilerOrchestrationError> {
743    Ok(())
744}
745
746struct RemoveFileOnDrop(Option<PathBuf>);
747
748impl Drop for RemoveFileOnDrop {
749    fn drop(&mut self) {
750        if let Some(path) = self.0.take() {
751            let _ = fs::remove_file(path);
752        }
753    }
754}
755
756enum SharedRuntimeBuildFault {
757    None,
758    #[cfg(test)]
759    NoSpaceAfterCompile,
760    // Constructed only by the Unix-only lock-contention test.
761    #[cfg(all(test, unix))]
762    WaitAfterLock {
763        ready: PathBuf,
764    },
765}
766
767/// Compile the one process-wide Rust probe runtime with the exact rustc path
768/// Cargo supplied to its wrapper. Concurrent rustc wrapper processes converge
769/// on one atomically published archive; a killed builder cannot create a
770/// partially valid archive or make peers wait indefinitely.
771pub fn prepare_shared_rust_runtime(
772    rustc: &Path,
773    directory: &Path,
774) -> Result<PathBuf, RustCompilerOrchestrationError> {
775    prepare_shared_rust_runtime_with_fault(rustc, directory, SharedRuntimeBuildFault::None)
776}
777
778fn prepare_shared_rust_runtime_with_fault(
779    rustc: &Path,
780    directory: &Path,
781    _fault: SharedRuntimeBuildFault,
782) -> Result<PathBuf, RustCompilerOrchestrationError> {
783    let metadata = fs::symlink_metadata(directory).map_err(|error| io_error(directory, error))?;
784    if !metadata.file_type().is_dir() {
785        return Err(io_error(
786            directory,
787            "shared Rust runtime root is not a directory",
788        ));
789    }
790    let source = directory.join("runtime.rs");
791    if !fs::symlink_metadata(&source).is_ok_and(|metadata| metadata.file_type().is_file()) {
792        return Err(io_error(
793            &source,
794            "shared Rust runtime source is not a regular file",
795        ));
796    }
797    let archive = shared_runtime_archive(directory);
798    if valid_shared_runtime_archive(&archive) {
799        return Ok(archive);
800    }
801    let lock = directory.join("build.lock");
802    let started = Instant::now();
803    loop {
804        let mut options = OpenOptions::new();
805        options.read(true).write(true).create(true);
806        #[cfg(unix)]
807        {
808            use std::os::unix::fs::OpenOptionsExt as _;
809            options.mode(0o600);
810        }
811        let mut lock_file = options
812            .open(&lock)
813            .map_err(|error| io_error(&lock, error))?;
814        match lock_file.try_lock() {
815            Ok(()) => {
816                if valid_shared_runtime_archive(&archive) {
817                    return Ok(archive);
818                }
819                lock_file
820                    .set_len(0)
821                    .and_then(|()| writeln!(lock_file, "{}", std::process::id()))
822                    .and_then(|()| lock_file.sync_all())
823                    .map_err(|error| io_error(&lock, error))?;
824                #[cfg(all(test, unix))]
825                if let SharedRuntimeBuildFault::WaitAfterLock { ready } = &_fault {
826                    fs::write(ready, b"locked\n").map_err(|error| io_error(ready, error))?;
827                    loop {
828                        thread::sleep(Duration::from_secs(1));
829                    }
830                }
831                let partial = directory.join(format!(
832                    ".supercov-runtime-{}-{}.partial",
833                    std::process::id(),
834                    SystemTime::now()
835                        .duration_since(UNIX_EPOCH)
836                        .map_err(|error| RustCompilerOrchestrationError::Cargo(error.to_string()))?
837                        .as_nanos()
838                ));
839                let mut partial_cleanup = RemoveFileOnDrop(Some(partial.clone()));
840                let output = Command::new(rustc)
841                    .args([
842                        "--edition=2024",
843                        "--crate-name=supercov_runtime",
844                        "--crate-type=staticlib",
845                        "-o",
846                    ])
847                    .arg(&partial)
848                    .arg(&source)
849                    .env_remove("RUSTC_WRAPPER")
850                    .env_remove("RUSTC_WORKSPACE_WRAPPER")
851                    .env_remove(RUST_COMPILER_WRAPPER_CONFIG_ENV)
852                    .env_remove(RUST_INSTRUMENT_MIR_ENV)
853                    .env_remove(RUST_INSTRUMENT_CTFE_ENV)
854                    .output()
855                    .map_err(|error| io_error(rustc, error));
856                let result = match output {
857                    Ok(output) if output.status.success() => {
858                        #[cfg(test)]
859                        if matches!(_fault, SharedRuntimeBuildFault::NoSpaceAfterCompile) {
860                            return Err(io_error(
861                                &partial,
862                                io::Error::from_raw_os_error(libc::ENOSPC),
863                            ));
864                        }
865                        // rustc has closed the archive; it is reopened only to
866                        // make it durable before publication. Windows needs
867                        // write access for that flush and answers a read-only
868                        // handle with "Access is denied".
869                        let file = OpenOptions::new()
870                            .read(true)
871                            .write(true)
872                            .open(&partial)
873                            .map_err(|error| io_error(&partial, error))?;
874                        file.sync_all().map_err(|error| io_error(&partial, error))?;
875                        drop(file);
876                        fs::rename(&partial, &archive)
877                            .map_err(|error| io_error(&archive, error))?;
878                        sync_directory(directory)?;
879                        partial_cleanup.0 = None;
880                        Ok(archive.clone())
881                    }
882                    Ok(output) => Err(RustCompilerOrchestrationError::Cargo(format!(
883                        "exact rustc could not compile the shared Supercov runtime: {}{}",
884                        String::from_utf8_lossy(&output.stderr),
885                        String::from_utf8_lossy(&output.stdout)
886                    ))),
887                    Err(error) => Err(error),
888                };
889                return result;
890            }
891            Err(fs::TryLockError::WouldBlock) => {
892                if valid_shared_runtime_archive(&archive) {
893                    return Ok(archive);
894                }
895                if started.elapsed() >= Duration::from_secs(30) {
896                    return Err(io_error(
897                        &lock,
898                        "timed out waiting for the exact shared Rust runtime build",
899                    ));
900                }
901                thread::sleep(Duration::from_millis(10));
902            }
903            Err(fs::TryLockError::Error(error)) => return Err(io_error(&lock, error)),
904        }
905    }
906}
907
908fn compiler_candidates(
909    directory: &Path,
910) -> Result<crate::rust_doctest::RustdocResolvedCandidates, RustCompilerOrchestrationError> {
911    let mut manifests = BTreeMap::<String, PathBuf>::new();
912    let mut snapshots = BTreeMap::<String, PathBuf>::new();
913    let mut merged_maps = Vec::new();
914    let entries = fs::read_dir(directory)
915        .map_err(|error| io_error(directory, error))?
916        .collect::<Result<Vec<_>, _>>()
917        .map_err(|error| io_error(directory, error))?;
918    for entry in entries {
919        let path = entry.path();
920        let metadata = entry.file_type().map_err(|error| io_error(&path, error))?;
921        if !metadata.is_file() {
922            return Err(RustCompilerOrchestrationError::CompilerOutput(format!(
923                "compiler output contains a non-file entry: {}",
924                path.display()
925            )));
926        }
927        let name = entry.file_name().into_string().map_err(|_| {
928            RustCompilerOrchestrationError::CompilerOutput(
929                "compiler output contains a non-UTF-8 name".into(),
930            )
931        })?;
932        if name.starts_with("doctest-map-") && name.ends_with(".json") {
933            merged_maps.push(fs::read(&path).map_err(|error| io_error(&path, error))?);
934            continue;
935        }
936        let destination = if let Some(key) = name
937            .strip_prefix("manifest-")
938            .and_then(|name| name.strip_suffix(".json"))
939        {
940            Some((&mut manifests, key))
941        } else {
942            name.strip_prefix("sources-")
943                .and_then(|name| name.strip_suffix(".json"))
944                .map(|key| (&mut snapshots, key))
945        };
946        if let Some((destination, key)) = destination
947            && destination.insert(key.into(), path.clone()).is_some()
948        {
949            return Err(RustCompilerOrchestrationError::CompilerOutput(format!(
950                "duplicate compiler output identity {key}"
951            )));
952        }
953    }
954    if manifests.is_empty() || manifests.keys().ne(snapshots.keys()) {
955        return Err(RustCompilerOrchestrationError::CompilerOutput(format!(
956            "manifest/source snapshot identities differ (manifests: {}, snapshots: {})",
957            manifests.len(),
958            snapshots.len()
959        )));
960    }
961    let pairs = manifests
962        .into_iter()
963        .map(|(key, manifest)| {
964            let snapshot = &snapshots[&key];
965            let manifest = fs::read(&manifest).map_err(|error| io_error(&manifest, error))?;
966            let snapshot = fs::read(snapshot).map_err(|error| io_error(snapshot, error))?;
967            Ok((manifest, snapshot))
968        })
969        .collect::<Result<Vec<_>, RustCompilerOrchestrationError>>()?;
970    resolve_merged_doctest_candidates(pairs, merged_maps)
971        .map_err(|error| RustCompilerOrchestrationError::Manifest(error.to_string()))
972}
973
974pub fn verified_compiler_selection(
975    directory: &Path,
976    candidates: &[PathBuf],
977    require_public_capabilities: bool,
978    allow_in_progress: bool,
979) -> Result<Option<SelectedRustCompilerCompanion>, RustCompilerOrchestrationError> {
980    let mut attestations = Vec::new();
981    let entries = fs::read_dir(directory)
982        .map_err(|error| io_error(directory, error))?
983        .collect::<Result<Vec<_>, _>>()
984        .map_err(|error| io_error(directory, error))?;
985    for entry in entries {
986        let path = entry.path();
987        let Some(name) = entry.file_name().to_str().map(str::to_owned) else {
988            return Err(RustCompilerOrchestrationError::CompilerOutput(
989                "selection output contains a non-UTF-8 name".into(),
990            ));
991        };
992        if name.starts_with(".selection-") && name.ends_with(".partial") && allow_in_progress {
993            continue;
994        }
995        if !name.starts_with("selection-") || !name.ends_with(".json") {
996            return Err(RustCompilerOrchestrationError::CompilerOutput(format!(
997                "unexpected selection output {name}"
998            )));
999        }
1000        if !entry
1001            .file_type()
1002            .map_err(|error| io_error(&path, error))?
1003            .is_file()
1004        {
1005            return Err(RustCompilerOrchestrationError::CompilerOutput(format!(
1006                "selection output is not a regular file: {}",
1007                path.display()
1008            )));
1009        }
1010        let bytes = fs::read(&path).map_err(|error| io_error(&path, error))?;
1011        attestations.push(
1012            serde_json::from_slice::<SelectedRustCompilerCompanion>(&bytes).map_err(|error| {
1013                RustCompilerOrchestrationError::Selection(format!(
1014                    "invalid wrapper attestation {}: {error}",
1015                    path.display()
1016                ))
1017            })?,
1018        );
1019    }
1020    let Some(first) = attestations.first() else {
1021        return Ok(None);
1022    };
1023    if attestations.iter().any(|selection| selection != first) {
1024        return Err(RustCompilerOrchestrationError::Selection(
1025            "Cargo used more than one compiler identity or companion".into(),
1026        ));
1027    }
1028    let verified =
1029        select_rust_compiler_companion(&first.rustc_path, candidates, require_public_capabilities)
1030            .map_err(|error| RustCompilerOrchestrationError::Selection(error.to_string()))?;
1031    if &verified != first {
1032        return Err(RustCompilerOrchestrationError::Selection(
1033            "wrapper attestation changed during post-build verification".into(),
1034        ));
1035    }
1036    Ok(Some(verified))
1037}
1038
1039fn cargo_artifacts(
1040    stdout: &[u8],
1041    target_directory: &Path,
1042    project_root: &Path,
1043) -> Result<Vec<RustCompilerTestArtifact>, RustCompilerOrchestrationError> {
1044    let canonical_target = canonicalize_simplified(target_directory)
1045        .map_err(|error| io_error(target_directory, error))?;
1046    let canonical_project =
1047        canonicalize_simplified(project_root).map_err(|error| io_error(project_root, error))?;
1048    let mut artifacts = Vec::new();
1049    for line in stdout
1050        .split(|byte| *byte == b'\n')
1051        .filter(|line| !line.is_empty())
1052    {
1053        let message: CargoMessage = serde_json::from_slice(line)
1054            .map_err(|error| RustCompilerOrchestrationError::CargoOutput(error.to_string()))?;
1055        if message.reason != "compiler-artifact"
1056            || !message.profile.as_ref().is_some_and(|profile| profile.test)
1057        {
1058            continue;
1059        }
1060        let (Some(executable), Some(manifest_path), Some(target)) =
1061            (message.executable, message.manifest_path, message.target)
1062        else {
1063            continue;
1064        };
1065        let executable =
1066            canonicalize_simplified(&executable).map_err(|error| io_error(&executable, error))?;
1067        let metadata =
1068            fs::symlink_metadata(&executable).map_err(|error| io_error(&executable, error))?;
1069        if !executable.starts_with(&canonical_target) || !metadata.file_type().is_file() {
1070            return Err(RustCompilerOrchestrationError::CargoOutput(format!(
1071                "test artifact escaped the private target: {}",
1072                executable.display()
1073            )));
1074        }
1075        let manifest_metadata = fs::symlink_metadata(&manifest_path)
1076            .map_err(|error| io_error(&manifest_path, error))?;
1077        let manifest = canonicalize_simplified(&manifest_path)
1078            .map_err(|error| io_error(&manifest_path, error))?;
1079        let package_root = manifest
1080            .parent()
1081            .and_then(|path| path.strip_prefix(&canonical_project).ok())
1082            .filter(|_| {
1083                manifest_metadata.file_type().is_file()
1084                    && manifest
1085                        .file_name()
1086                        .is_some_and(|name| name == "Cargo.toml")
1087            })
1088            .ok_or_else(|| {
1089                RustCompilerOrchestrationError::CargoOutput(format!(
1090                    "test artifact manifest escaped the owned project: {}",
1091                    manifest.display()
1092                ))
1093            })?;
1094        let package = if package_root.as_os_str().is_empty() {
1095            "package:.".to_owned()
1096        } else if package_root
1097            .components()
1098            .all(|component| matches!(component, std::path::Component::Normal(_)))
1099        {
1100            format!(
1101                "package:{}",
1102                package_root.to_string_lossy().replace('\\', "/")
1103            )
1104        } else {
1105            return Err(RustCompilerOrchestrationError::CargoOutput(format!(
1106                "test artifact has a noncanonical package root: {}",
1107                package_root.display()
1108            )));
1109        };
1110        let test_harness = cargo_target_uses_test_harness(&manifest, &target)?;
1111        artifacts.push(RustCompilerTestArtifact {
1112            executable,
1113            package,
1114            target_name: target.name,
1115            target_kinds: target.kind,
1116            source_path: target.src_path,
1117            test_harness,
1118        });
1119    }
1120    artifacts.sort_by(|left, right| left.executable.cmp(&right.executable));
1121    artifacts.dedup_by(|left, right| left.executable == right.executable);
1122    if artifacts.is_empty() {
1123        return Err(RustCompilerOrchestrationError::CargoOutput(
1124            "Cargo emitted no executable test artifacts".into(),
1125        ));
1126    }
1127    Ok(artifacts)
1128}
1129
1130fn cargo_target_uses_test_harness(
1131    manifest: &Path,
1132    target: &CargoTarget,
1133) -> Result<bool, RustCompilerOrchestrationError> {
1134    let source = fs::read_to_string(manifest).map_err(|error| io_error(manifest, error))?;
1135    let document = toml::from_str::<toml::Value>(&source).map_err(|error| {
1136        RustCompilerOrchestrationError::CargoOutput(format!(
1137            "cannot classify test harness from {}: {error}",
1138            manifest.display()
1139        ))
1140    })?;
1141    let kind = match target.kind.as_slice() {
1142        [kind] => kind.as_str(),
1143        _ => {
1144            return Err(RustCompilerOrchestrationError::CargoOutput(format!(
1145                "Cargo target {} has ambiguous target kinds: {}",
1146                target.name,
1147                target.kind.join(", ")
1148            )));
1149        }
1150    };
1151    let harness = match kind {
1152        "lib" | "proc-macro" => document
1153            .get("lib")
1154            .and_then(toml::Value::as_table)
1155            .and_then(|table| table.get("harness")),
1156        "bin" | "test" | "bench" | "example" => document
1157            .get(kind)
1158            .and_then(toml::Value::as_array)
1159            .and_then(|targets| {
1160                targets.iter().find_map(|candidate| {
1161                    let table = candidate.as_table()?;
1162                    (table.get("name")?.as_str()? == target.name)
1163                        .then(|| table.get("harness"))
1164                        .flatten()
1165                })
1166            }),
1167        _ => {
1168            return Err(RustCompilerOrchestrationError::CargoOutput(format!(
1169                "Cargo test artifact {} has unsupported target kind {kind}",
1170                target.name
1171            )));
1172        }
1173    };
1174    match harness {
1175        None => Ok(true),
1176        Some(value) => value.as_bool().ok_or_else(|| {
1177            RustCompilerOrchestrationError::CargoOutput(format!(
1178                "Cargo target {} has a non-Boolean harness setting in {}",
1179                target.name,
1180                manifest.display()
1181            ))
1182        }),
1183    }
1184}
1185
1186fn rendered_cargo_diagnostics(stdout: &[u8]) -> String {
1187    stdout
1188        .split(|byte| *byte == b'\n')
1189        .filter(|line| !line.is_empty())
1190        .filter_map(|line| serde_json::from_slice::<CargoMessage>(line).ok())
1191        .filter_map(|message| message.message.and_then(|message| message.rendered))
1192        .collect::<Vec<_>>()
1193        .join("")
1194}
1195
1196pub fn build_with_rust_compiler_companion(
1197    request: &RustCompilerBuildRequest,
1198) -> Result<RustCompilerBuild, RustCompilerOrchestrationError> {
1199    let supervisor = ProcessSupervisor::new()
1200        .map_err(|error| RustCompilerOrchestrationError::Cargo(error.to_string()))?;
1201    let options = SupervisionOptions::from_environment()
1202        .map_err(|error| RustCompilerOrchestrationError::Cargo(error.to_string()))?;
1203    build_with_rust_compiler_companion_supervised(request, &supervisor, options, &mut io::sink())
1204}
1205
1206pub fn build_with_rust_compiler_companion_supervised(
1207    request: &RustCompilerBuildRequest,
1208    supervisor: &ProcessSupervisor,
1209    options: SupervisionOptions,
1210    diagnostics: &mut dyn Write,
1211) -> Result<RustCompilerBuild, RustCompilerOrchestrationError> {
1212    if request.command.is_empty()
1213        || !valid_run_id(&request.run_id)
1214        || request.companion_candidates.is_empty()
1215    {
1216        return Err(RustCompilerOrchestrationError::InvalidRequest(
1217            "command, safe run ID and companion candidates are required".into(),
1218        ));
1219    }
1220    if std::env::var_os("RUSTDOC").is_some() {
1221        return Err(RustCompilerOrchestrationError::InvalidRequest(
1222            "an existing RUSTDOC executable cannot yet be composed without changing rustdoc semantics"
1223                .into(),
1224        ));
1225    }
1226    let project_root = canonicalize_simplified(&request.project_root)
1227        .map_err(|error| io_error(&request.project_root, error))?;
1228    if !fs::symlink_metadata(&project_root).is_ok_and(|metadata| metadata.file_type().is_dir()) {
1229        return Err(io_error(&project_root, "expected a project directory"));
1230    }
1231    let wrapper = regular_executable(&request.wrapper_path)?;
1232    let run_root = ensure_directories(
1233        &project_root,
1234        &PathBuf::from(".supercov/work").join(&request.run_id),
1235    )?;
1236    let compiler_output_directory = run_root.join("rust-compiler");
1237    fs::create_dir(&compiler_output_directory)
1238        .map_err(|error| io_error(&compiler_output_directory, error))?;
1239    let selection_directory = compiler_output_directory.join("selections");
1240    let candidate_directory = compiler_output_directory.join("candidates");
1241    fs::create_dir(&selection_directory).map_err(|error| io_error(&selection_directory, error))?;
1242    fs::create_dir(&candidate_directory).map_err(|error| io_error(&candidate_directory, error))?;
1243    let target_directory = run_root.join("rust-target");
1244    fs::create_dir(&target_directory).map_err(|error| io_error(&target_directory, error))?;
1245    let shared_runtime_directory = compiler_output_directory.join("shared-runtime");
1246    fs::create_dir(&shared_runtime_directory)
1247        .map_err(|error| io_error(&shared_runtime_directory, error))?;
1248    let cargo_runner_directory = compiler_output_directory.join("cargo-runner");
1249    fs::create_dir(&cargo_runner_directory)
1250        .map_err(|error| io_error(&cargo_runner_directory, error))?;
1251    write_shared_runtime_source(&shared_runtime_directory)?;
1252    let target_runners = request
1253        .cargo_runner_plan
1254        .targets
1255        .iter()
1256        .map(|target| target.resolve(&project_root))
1257        .collect::<Vec<_>>();
1258    let config_path = compiler_output_directory.join("wrapper.json");
1259    write_json_config(
1260        &config_path,
1261        &RustCompilerWrapperConfig {
1262            candidates: request.companion_candidates.clone(),
1263            require_public_capabilities: request.require_public_capabilities,
1264            selection_directory: selection_directory.clone(),
1265            shared_runtime_directory: shared_runtime_directory.clone(),
1266            target_runners: target_runners.clone(),
1267            project_root: project_root.clone(),
1268            compiler: request.cargo_runner_plan.compiler.clone(),
1269            original_wrapper_environment: RustCompilerWrapperEnvironment::capture(),
1270        },
1271    )?;
1272    let cargo_runner_list_config_path = compiler_output_directory.join("cargo-runner-list.json");
1273    write_json_config(
1274        &cargo_runner_list_config_path,
1275        &RustCargoRunnerConfig {
1276            version: RUST_CARGO_RUNNER_VERSION,
1277            run_id: request.run_id.clone(),
1278            target_directory: target_directory.clone(),
1279            output_directory: cargo_runner_directory.clone(),
1280            target_runners: target_runners.clone(),
1281            artifacts: Vec::new(),
1282        },
1283    )?;
1284    let cargo_runner_config_path = compiler_output_directory.join("cargo-runner.json");
1285
1286    let mut invocation = cargo_invocation(&project_root, &request.command)
1287        .map_err(|error| RustCompilerOrchestrationError::InvalidRequest(error.to_string()))?;
1288    let execution = rust_cargo_execution_selection(&invocation)
1289        .map_err(|error| RustCompilerOrchestrationError::InvalidRequest(error.to_string()))?;
1290    let command_kind = invocation.kind;
1291    let execution_arguments = invocation.arguments.clone();
1292    let build_started_at_ms = epoch_ms()?;
1293    let started = Instant::now();
1294    let (nextest_version, nextest_catalog, nextest_metadata) = if command_kind
1295        == RustCargoCommandKind::NextestRun
1296    {
1297        let version_output = supervisor
1298            .supervise_captured(
1299                &CommandSpec {
1300                    program: invocation.program.clone().into(),
1301                    arguments: nextest_version_arguments(&invocation)
1302                        .map_err(|error| {
1303                            RustCompilerOrchestrationError::InvalidRequest(error.to_string())
1304                        })?
1305                        .into_iter()
1306                        .map(OsString::from)
1307                        .collect(),
1308                    cwd: project_root.clone(),
1309                    environment: Some(inherited_environment([(
1310                        OsString::from("CARGO_TARGET_DIR"),
1311                        target_directory.clone().into_os_string(),
1312                    )])),
1313                    captured_output: None,
1314                },
1315                options,
1316                diagnostics,
1317            )
1318            .map_err(|error| RustCompilerOrchestrationError::Cargo(error.to_string()))?;
1319        if let Some(error) = interrupted_error(&version_output) {
1320            return Err(error);
1321        }
1322        if !supervised_success(&version_output) {
1323            return Err(RustCompilerOrchestrationError::Cargo(
1324                format!(
1325                    "{}{}",
1326                    String::from_utf8_lossy(&version_output.stderr),
1327                    String::from_utf8_lossy(&version_output.stdout)
1328                )
1329                .trim()
1330                .to_owned(),
1331            ));
1332        }
1333        let nextest_version = parse_nextest_version_output(&version_output.stdout)
1334            .map_err(|error| RustCompilerOrchestrationError::CargoOutput(error.to_string()))?;
1335        let projected = nextest_list_invocation(&invocation)
1336            .map_err(|error| RustCompilerOrchestrationError::InvalidRequest(error.to_string()))?;
1337        let mut list_arguments = projected.arguments;
1338        list_arguments.extend(cargo_runner_configuration_arguments(
1339            &wrapper,
1340            &request.cargo_runner_plan,
1341        )?);
1342        if !projected.runner_arguments.is_empty() {
1343            list_arguments.push("--".into());
1344            list_arguments.extend(projected.runner_arguments);
1345        }
1346        let list_output = supervisor
1347            .supervise_captured(
1348                &CommandSpec {
1349                    program: invocation.program.clone().into(),
1350                    arguments: list_arguments.into_iter().map(OsString::from).collect(),
1351                    cwd: project_root.clone(),
1352                    environment: Some(inherited_environment([
1353                        (
1354                            OsString::from("CARGO_TARGET_DIR"),
1355                            target_directory.clone().into_os_string(),
1356                        ),
1357                        (
1358                            OsString::from("RUSTC_WRAPPER"),
1359                            wrapper.clone().into_os_string(),
1360                        ),
1361                        (
1362                            OsString::from("RUSTC_WORKSPACE_WRAPPER"),
1363                            wrapper.clone().into_os_string(),
1364                        ),
1365                        (
1366                            OsString::from(RUST_COMPILER_WRAPPER_CONFIG_ENV),
1367                            config_path.clone().into_os_string(),
1368                        ),
1369                        (
1370                            OsString::from(RUST_COMPILER_OUTPUT_ENV),
1371                            candidate_directory.clone().into_os_string(),
1372                        ),
1373                        (
1374                            OsString::from(RUST_SOURCE_ROOT_ENV),
1375                            project_root.clone().into_os_string(),
1376                        ),
1377                        (
1378                            OsString::from(RUST_TARGET_ROOT_ENV),
1379                            target_directory.clone().into_os_string(),
1380                        ),
1381                        (OsString::from(RUST_INSTRUMENT_MIR_ENV), OsString::from("1")),
1382                        (
1383                            OsString::from(RUST_INSTRUMENT_CTFE_ENV),
1384                            OsString::from("1"),
1385                        ),
1386                        (
1387                            OsString::from(RUST_STATIC_RUNTIME_DIRECTORY_ENV),
1388                            shared_runtime_directory.clone().into_os_string(),
1389                        ),
1390                        (
1391                            OsString::from(RUST_CARGO_RUNNER_CONFIG_ENV),
1392                            cargo_runner_list_config_path.clone().into_os_string(),
1393                        ),
1394                    ])),
1395                    captured_output: None,
1396                },
1397                options,
1398                diagnostics,
1399            )
1400            .map_err(|error| RustCompilerOrchestrationError::Cargo(error.to_string()))?;
1401        if let Some(error) = interrupted_error(&list_output) {
1402            return Err(error);
1403        }
1404        if !supervised_success(&list_output) {
1405            return Err(RustCompilerOrchestrationError::Cargo(
1406                format!(
1407                    "{}{}",
1408                    String::from_utf8_lossy(&list_output.stderr),
1409                    String::from_utf8_lossy(&list_output.stdout)
1410                )
1411                .trim()
1412                .to_owned(),
1413            ));
1414        }
1415        let catalog = TestListSummary::parse_json(String::from_utf8_lossy(&list_output.stdout))
1416            .map_err(|error| {
1417                RustCompilerOrchestrationError::CargoOutput(format!(
1418                    "invalid nextest JSON test catalog: {error}"
1419                ))
1420            })?;
1421
1422        let metadata_output = supervisor
1423            .supervise_captured(
1424                &CommandSpec {
1425                    program: invocation.program.clone().into(),
1426                    arguments: cargo_metadata_arguments(&invocation)?
1427                        .into_iter()
1428                        .map(OsString::from)
1429                        .collect(),
1430                    cwd: project_root.clone(),
1431                    environment: Some(inherited_environment([(
1432                        OsString::from("CARGO_TARGET_DIR"),
1433                        target_directory.clone().into_os_string(),
1434                    )])),
1435                    captured_output: None,
1436                },
1437                options,
1438                diagnostics,
1439            )
1440            .map_err(|error| RustCompilerOrchestrationError::Cargo(error.to_string()))?;
1441        if let Some(error) = interrupted_error(&metadata_output) {
1442            return Err(error);
1443        }
1444        if !supervised_success(&metadata_output) {
1445            return Err(RustCompilerOrchestrationError::Cargo(
1446                String::from_utf8_lossy(&metadata_output.stderr)
1447                    .trim()
1448                    .to_owned(),
1449            ));
1450        }
1451        let metadata = serde_json::from_slice(&metadata_output.stdout).map_err(|error| {
1452            RustCompilerOrchestrationError::CargoOutput(format!(
1453                "invalid Cargo metadata for nextest: {error}"
1454            ))
1455        })?;
1456        (Some(nextest_version), Some(catalog), Some(metadata))
1457    } else {
1458        (None, None, None)
1459    };
1460    let output = if execution.run_libtests && command_kind == RustCargoCommandKind::CargoTest {
1461        invocation.arguments.retain(|argument| {
1462            argument != "--no-run" && !argument.starts_with("--message-format=")
1463        });
1464        invocation
1465            .arguments
1466            .extend(["--no-run".into(), "--message-format=json".into()]);
1467        let environment = inherited_environment([
1468            (
1469                OsString::from("CARGO_TARGET_DIR"),
1470                target_directory.clone().into_os_string(),
1471            ),
1472            (
1473                OsString::from("RUSTC_WRAPPER"),
1474                wrapper.clone().into_os_string(),
1475            ),
1476            (
1477                OsString::from("RUSTC_WORKSPACE_WRAPPER"),
1478                wrapper.clone().into_os_string(),
1479            ),
1480            (
1481                OsString::from(RUST_COMPILER_WRAPPER_CONFIG_ENV),
1482                config_path.clone().into_os_string(),
1483            ),
1484            (
1485                OsString::from(RUST_COMPILER_OUTPUT_ENV),
1486                candidate_directory.clone().into_os_string(),
1487            ),
1488            (
1489                OsString::from(RUST_SOURCE_ROOT_ENV),
1490                project_root.clone().into_os_string(),
1491            ),
1492            (
1493                OsString::from(RUST_TARGET_ROOT_ENV),
1494                target_directory.clone().into_os_string(),
1495            ),
1496            (OsString::from(RUST_INSTRUMENT_MIR_ENV), OsString::from("1")),
1497            (
1498                OsString::from(RUST_INSTRUMENT_CTFE_ENV),
1499                OsString::from("1"),
1500            ),
1501        ]);
1502        let output = supervisor
1503            .supervise_captured(
1504                &CommandSpec {
1505                    program: invocation.program.clone().into(),
1506                    arguments: invocation.arguments.iter().map(OsString::from).collect(),
1507                    cwd: project_root.clone(),
1508                    environment: Some(environment),
1509                    captured_output: None,
1510                },
1511                options,
1512                diagnostics,
1513            )
1514            .map_err(|error| RustCompilerOrchestrationError::Cargo(error.to_string()))?;
1515        if let Some(error) = interrupted_error(&output) {
1516            return Err(error);
1517        }
1518        if !supervised_success(&output) {
1519            let rendered = rendered_cargo_diagnostics(&output.stdout);
1520            let stderr = String::from_utf8_lossy(&output.stderr);
1521            return Err(RustCompilerOrchestrationError::Cargo(
1522                format!("{stderr}{rendered}").trim().to_owned(),
1523            ));
1524        }
1525        Some(output)
1526    } else {
1527        None
1528    };
1529    let cargo_test_artifacts = output
1530        .as_ref()
1531        .map(|output| cargo_artifacts(&output.stdout, &target_directory, &project_root))
1532        .transpose()?
1533        .unwrap_or_default();
1534    let planned_artifacts = match (&nextest_catalog, &nextest_metadata) {
1535        (Some(catalog), Some(metadata)) => {
1536            nextest_artifacts(catalog, metadata, &target_directory, &project_root)?
1537        }
1538        (None, None) => cargo_test_artifacts,
1539        _ => {
1540            return Err(RustCompilerOrchestrationError::CargoOutput(
1541                "nextest catalog and Cargo metadata were only partially captured".into(),
1542            ));
1543        }
1544    };
1545    write_json_config(
1546        &cargo_runner_config_path,
1547        &RustCargoRunnerConfig {
1548            version: RUST_CARGO_RUNNER_VERSION,
1549            run_id: request.run_id.clone(),
1550            target_directory: target_directory.clone(),
1551            output_directory: cargo_runner_directory.clone(),
1552            target_runners,
1553            artifacts: planned_artifacts
1554                .iter()
1555                .map(|artifact| RustCargoRunnerArtifact {
1556                    executable: artifact.executable.clone(),
1557                    test_harness: artifact.test_harness,
1558                })
1559                .collect(),
1560        },
1561    )?;
1562    let mut full_arguments = execution_arguments;
1563    full_arguments.extend(cargo_runner_configuration_arguments(
1564        &wrapper,
1565        &request.cargo_runner_plan,
1566    )?);
1567    if !invocation.runner_arguments.is_empty() {
1568        full_arguments.push("--".into());
1569        full_arguments.extend(invocation.runner_arguments.iter().cloned());
1570    }
1571    let execution_started = Instant::now();
1572    let execution_output = supervisor
1573        .supervise_captured(
1574            &CommandSpec {
1575                program: invocation.program.clone().into(),
1576                arguments: full_arguments.into_iter().map(OsString::from).collect(),
1577                cwd: project_root.clone(),
1578                environment: Some(inherited_environment([
1579                    (
1580                        OsString::from("CARGO_TARGET_DIR"),
1581                        target_directory.clone().into_os_string(),
1582                    ),
1583                    (
1584                        OsString::from("RUSTC_WRAPPER"),
1585                        wrapper.clone().into_os_string(),
1586                    ),
1587                    (
1588                        OsString::from("RUSTC_WORKSPACE_WRAPPER"),
1589                        wrapper.clone().into_os_string(),
1590                    ),
1591                    (
1592                        OsString::from(RUST_COMPILER_WRAPPER_CONFIG_ENV),
1593                        config_path.clone().into_os_string(),
1594                    ),
1595                    (
1596                        OsString::from(RUST_COMPILER_OUTPUT_ENV),
1597                        candidate_directory.clone().into_os_string(),
1598                    ),
1599                    (
1600                        OsString::from(RUST_SOURCE_ROOT_ENV),
1601                        project_root.clone().into_os_string(),
1602                    ),
1603                    (
1604                        OsString::from(RUST_TARGET_ROOT_ENV),
1605                        target_directory.clone().into_os_string(),
1606                    ),
1607                    (OsString::from(RUST_INSTRUMENT_MIR_ENV), OsString::from("1")),
1608                    (
1609                        OsString::from(RUST_INSTRUMENT_CTFE_ENV),
1610                        OsString::from("1"),
1611                    ),
1612                    (
1613                        OsString::from(RUST_STATIC_RUNTIME_DIRECTORY_ENV),
1614                        shared_runtime_directory.clone().into_os_string(),
1615                    ),
1616                    (OsString::from("RUSTDOC"), wrapper.clone().into_os_string()),
1617                    (
1618                        OsString::from(RUSTDOC_WRAPPER_MODE_ENV),
1619                        OsString::from("1"),
1620                    ),
1621                    (
1622                        OsString::from(RUST_CARGO_RUNNER_CONFIG_ENV),
1623                        cargo_runner_config_path.clone().into_os_string(),
1624                    ),
1625                ])),
1626                captured_output: None,
1627            },
1628            options,
1629            diagnostics,
1630        )
1631        .map_err(|error| RustCompilerOrchestrationError::Cargo(error.to_string()))?;
1632    if let Some(error) = interrupted_error(&execution_output) {
1633        return Err(error);
1634    }
1635    let execution_ms = execution_started.elapsed().as_secs_f64() * 1000.0;
1636    let build_ms = started.elapsed().as_secs_f64() * 1000.0;
1637    let build_ended_at_ms = epoch_ms()?;
1638    let execution_exit_code = execution_output.result.exit_code();
1639    let selection = verified_compiler_selection(
1640        &selection_directory,
1641        &request.companion_candidates,
1642        request.require_public_capabilities,
1643        false,
1644    )?
1645    .ok_or_else(|| {
1646        RustCompilerOrchestrationError::Selection(
1647            "Cargo invoked no authenticated compiler companion".into(),
1648        )
1649    })?;
1650    let resolved = compiler_candidates(&candidate_directory)?;
1651    let normalized = normalize_rust_compiler_candidates(resolved.candidates)
1652        .map_err(|error| RustCompilerOrchestrationError::Manifest(error.to_string()))?;
1653    let ctfe_units =
1654        read_rust_compiler_ctfe(&candidate_directory, &normalized, build_started_at_ms)
1655            .map_err(|error| RustCompilerOrchestrationError::CompilerOutput(error.to_string()))?;
1656    let doctest_outcomes = read_rustdoc_outcome_units(&candidate_directory)
1657        .map_err(|error| RustCompilerOrchestrationError::CompilerOutput(error.to_string()))?;
1658    if doctest_outcomes
1659        .iter()
1660        .any(|unit| unit.companion_build_id != selection.handshake.companion_build_id)
1661    {
1662        return Err(RustCompilerOrchestrationError::CompilerOutput(
1663            "rustdoc outcome unit was produced by a different compiler companion".into(),
1664        ));
1665    }
1666    let doctest_outcomes = join_rustdoc_outcomes(resolved.merged_units, doctest_outcomes)
1667        .map_err(|error| RustCompilerOrchestrationError::CompilerOutput(error.to_string()))?;
1668    let artifacts = planned_artifacts;
1669    let expected_targets = request
1670        .cargo_runner_plan
1671        .targets
1672        .iter()
1673        .map(|target| target.target.clone())
1674        .collect::<Vec<_>>();
1675    let cargo_runner_units =
1676        read_cargo_runner_units(&cargo_runner_directory, &request.run_id, &expected_targets)
1677            .map_err(|error| {
1678                let stderr = String::from_utf8_lossy(&execution_output.stderr);
1679                RustCompilerOrchestrationError::UnverifiedExecution {
1680                    code: if execution_exit_code == 0 {
1681                        2
1682                    } else {
1683                        execution_exit_code
1684                    },
1685                    reason: format!("{error}\n{stderr}").trim().to_owned(),
1686                }
1687            })?;
1688    Ok(RustCompilerBuild {
1689        selection,
1690        normalized,
1691        artifacts,
1692        target_directory,
1693        compiler_output_directory,
1694        ctfe_units,
1695        doctest_outcomes,
1696        cargo_runner_units,
1697        command_kind,
1698        nextest_version,
1699        nextest_catalog,
1700        run_libtests: execution.run_libtests,
1701        run_doctests: execution.run_doctests,
1702        execution_exit_code,
1703        execution_stdout: execution_output.stdout,
1704        execution_stderr: execution_output.stderr,
1705        build_started_at_ms,
1706        build_ended_at_ms,
1707        build_ms,
1708        execution_ms,
1709    })
1710}
1711
1712#[cfg(test)]
1713mod tests {
1714    use std::{
1715        collections::BTreeSet,
1716        sync::atomic::{AtomicU64, Ordering},
1717    };
1718
1719    use super::*;
1720
1721    struct TemporaryDirectory(PathBuf);
1722
1723    static TEMPORARY_DIRECTORY_NONCE: AtomicU64 = AtomicU64::new(0);
1724
1725    impl TemporaryDirectory {
1726        fn new() -> Self {
1727            let path = std::env::temp_dir().join(format!(
1728                "supercov-shared-rust-runtime-{}-{}-{}",
1729                std::process::id(),
1730                SystemTime::now()
1731                    .duration_since(UNIX_EPOCH)
1732                    .unwrap()
1733                    .as_nanos(),
1734                TEMPORARY_DIRECTORY_NONCE.fetch_add(1, Ordering::Relaxed)
1735            ));
1736            fs::create_dir(&path).unwrap();
1737            Self(path)
1738        }
1739    }
1740
1741    impl Drop for TemporaryDirectory {
1742        fn drop(&mut self) {
1743            let _ = fs::remove_dir_all(&self.0);
1744        }
1745    }
1746
1747    #[cfg(unix)]
1748    #[test]
1749    fn compiler_wrapper_environment_round_trips_non_utf8_and_exact_absence() {
1750        use std::os::unix::ffi::{OsStrExt as _, OsStringExt as _};
1751
1752        let original = OsString::from_vec(vec![b'w', 0xff, b'r']);
1753        let snapshot = RustCompilerWrapperEnvironment {
1754            rustc_wrapper: Some(RustCompilerEnvironmentValue::capture(&original)),
1755            rustc_workspace_wrapper: None,
1756        };
1757        assert_eq!(
1758            snapshot.rustc_wrapper.as_ref().unwrap().decode().unwrap(),
1759            original
1760        );
1761        assert!(
1762            RustCompilerEnvironmentValue::WindowsWide { value: vec![1] }
1763                .decode()
1764                .unwrap_err()
1765                .to_string()
1766                .contains("non-Windows")
1767        );
1768
1769        let mut command = Command::new("rustc");
1770        command
1771            .env("RUSTC_WRAPPER", "temporary")
1772            .env("RUSTC_WORKSPACE_WRAPPER", "temporary");
1773        snapshot.restore(&mut command).unwrap();
1774        let environment = command
1775            .get_envs()
1776            .map(|(key, value)| {
1777                (
1778                    key.as_bytes().to_vec(),
1779                    value.map(|value| value.as_bytes().to_vec()),
1780                )
1781            })
1782            .collect::<BTreeMap<_, _>>();
1783        assert_eq!(
1784            environment.get(b"RUSTC_WRAPPER".as_slice()),
1785            Some(&Some(vec![b'w', 0xff, b'r']))
1786        );
1787        assert_eq!(
1788            environment.get(b"RUSTC_WORKSPACE_WRAPPER".as_slice()),
1789            Some(&None)
1790        );
1791    }
1792
1793    #[test]
1794    fn exact_rustc_concurrently_publishes_one_shared_runtime_without_debris() {
1795        let directory = TemporaryDirectory::new();
1796        write_shared_runtime_source(&directory.0).unwrap();
1797        let archives = std::thread::scope(|scope| {
1798            (0..4)
1799                .map(|_| {
1800                    scope.spawn(|| {
1801                        prepare_shared_rust_runtime(Path::new("rustc"), &directory.0).unwrap()
1802                    })
1803                })
1804                .collect::<Vec<_>>()
1805                .into_iter()
1806                .map(|thread| thread.join().unwrap())
1807                .collect::<Vec<_>>()
1808        });
1809        assert!(archives.windows(2).all(|pair| pair[0] == pair[1]));
1810        assert!(valid_shared_runtime_archive(&archives[0]));
1811        let names = fs::read_dir(&directory.0)
1812            .unwrap()
1813            .map(|entry| entry.unwrap().file_name().into_string().unwrap())
1814            .collect::<BTreeSet<_>>();
1815        assert_eq!(
1816            names,
1817            BTreeSet::from([
1818                "build.lock".into(),
1819                "runtime.rs".into(),
1820                archives[0]
1821                    .file_name()
1822                    .unwrap()
1823                    .to_str()
1824                    .unwrap()
1825                    .to_owned()
1826            ])
1827        );
1828    }
1829
1830    #[test]
1831    fn failed_shared_runtime_builder_releases_lock_and_leaves_no_partial_archive() {
1832        let directory = TemporaryDirectory::new();
1833        write_shared_runtime_source(&directory.0).unwrap();
1834        let missing_rustc = directory.0.join("missing-rustc");
1835        let error = prepare_shared_rust_runtime(&missing_rustc, &directory.0).unwrap_err();
1836        assert!(error.to_string().contains("missing-rustc"));
1837        let names = fs::read_dir(&directory.0)
1838            .unwrap()
1839            .map(|entry| entry.unwrap().file_name().into_string().unwrap())
1840            .collect::<BTreeSet<_>>();
1841        assert_eq!(
1842            names,
1843            BTreeSet::from(["build.lock".into(), "runtime.rs".into()])
1844        );
1845        let recovery_started = Instant::now();
1846        let archive = prepare_shared_rust_runtime(Path::new("rustc"), &directory.0).unwrap();
1847        assert!(recovery_started.elapsed() < Duration::from_secs(5));
1848        assert!(valid_shared_runtime_archive(&archive));
1849    }
1850
1851    #[test]
1852    fn shared_runtime_enospc_is_recoverable_without_partial_archive() {
1853        let directory = TemporaryDirectory::new();
1854        write_shared_runtime_source(&directory.0).unwrap();
1855        let error = prepare_shared_rust_runtime_with_fault(
1856            Path::new("rustc"),
1857            &directory.0,
1858            SharedRuntimeBuildFault::NoSpaceAfterCompile,
1859        )
1860        .unwrap_err();
1861        assert!(matches!(
1862            error,
1863            RustCompilerOrchestrationError::Io { reason, .. }
1864                if reason == io::Error::from_raw_os_error(libc::ENOSPC).to_string()
1865        ));
1866        assert!(!valid_shared_runtime_archive(&shared_runtime_archive(
1867            &directory.0
1868        )));
1869        assert!(fs::read_dir(&directory.0).unwrap().all(|entry| {
1870            !entry
1871                .unwrap()
1872                .file_name()
1873                .to_string_lossy()
1874                .ends_with(".partial")
1875        }));
1876
1877        let archive = prepare_shared_rust_runtime(Path::new("rustc"), &directory.0).unwrap();
1878        assert!(valid_shared_runtime_archive(&archive));
1879    }
1880
1881    #[cfg(unix)]
1882    #[test]
1883    fn shared_runtime_lock_holder_helper() {
1884        let Some(directory) = std::env::var_os("SUPERCOV_TEST_RUNTIME_LOCK_DIRECTORY") else {
1885            return;
1886        };
1887        let ready = std::env::var_os("SUPERCOV_TEST_RUNTIME_LOCK_READY")
1888            .expect("runtime lock helper ready path");
1889        let _ = prepare_shared_rust_runtime_with_fault(
1890            Path::new("rustc"),
1891            Path::new(&directory),
1892            SharedRuntimeBuildFault::WaitAfterLock {
1893                ready: PathBuf::from(ready),
1894            },
1895        );
1896    }
1897
1898    #[cfg(unix)]
1899    #[test]
1900    fn killed_shared_runtime_builder_releases_lock_immediately() {
1901        use std::process::Stdio;
1902
1903        let directory = TemporaryDirectory::new();
1904        write_shared_runtime_source(&directory.0).unwrap();
1905        let ready = directory.0.join("builder-ready");
1906        let mut child = Command::new(std::env::current_exe().unwrap())
1907            .args([
1908                "--exact",
1909                "rust_compiler_orchestration::tests::shared_runtime_lock_holder_helper",
1910                "--nocapture",
1911            ])
1912            .env("SUPERCOV_TEST_RUNTIME_LOCK_DIRECTORY", &directory.0)
1913            .env("SUPERCOV_TEST_RUNTIME_LOCK_READY", &ready)
1914            .stdin(Stdio::null())
1915            .stdout(Stdio::null())
1916            .stderr(Stdio::null())
1917            .spawn()
1918            .unwrap();
1919        let wait_started = Instant::now();
1920        while !ready.is_file() {
1921            assert!(
1922                wait_started.elapsed() < Duration::from_secs(10),
1923                "runtime lock helper did not acquire its kernel lock"
1924            );
1925            thread::sleep(Duration::from_millis(10));
1926        }
1927        assert_eq!(
1928            unsafe { libc::kill(child.id().try_into().unwrap(), libc::SIGKILL) },
1929            0
1930        );
1931        let status = child.wait().unwrap();
1932        assert_eq!(status.code(), None);
1933        fs::remove_file(&ready).unwrap();
1934
1935        let recovery_started = Instant::now();
1936        let archive = prepare_shared_rust_runtime(Path::new("rustc"), &directory.0).unwrap();
1937        assert!(recovery_started.elapsed() < Duration::from_secs(5));
1938        assert!(valid_shared_runtime_archive(&archive));
1939        assert!(fs::read_dir(&directory.0).unwrap().all(|entry| {
1940            !entry
1941                .unwrap()
1942                .file_name()
1943                .to_string_lossy()
1944                .ends_with(".partial")
1945        }));
1946    }
1947
1948    #[test]
1949    fn cargo_artifacts_bind_relocatable_workspace_package_identity() {
1950        fn fixture(root: &Path) -> (PathBuf, Vec<u8>) {
1951            let target = root.join("target");
1952            fs::create_dir(&target).unwrap();
1953            let mut messages = Vec::new();
1954            for (index, package_root) in [Path::new("."), Path::new("crates/sibling")]
1955                .into_iter()
1956                .enumerate()
1957            {
1958                let package = root.join(package_root);
1959                let source = package.join("src/lib.rs");
1960                fs::create_dir_all(source.parent().unwrap()).unwrap();
1961                fs::write(
1962                    package.join("Cargo.toml"),
1963                    "[package]\nname='fixture'\nversion='0.0.0'\n",
1964                )
1965                .unwrap();
1966                fs::write(&source, "#[test] fn same_name() {}\n").unwrap();
1967                let executable = target.join(format!("same-target-{index}"));
1968                fs::write(&executable, b"artifact").unwrap();
1969                messages.extend(
1970                    serde_json::to_vec(&serde_json::json!({
1971                        "reason": "compiler-artifact",
1972                        "package_id": format!("opaque-{index}"),
1973                        "manifest_path": package.join("Cargo.toml"),
1974                        "target": {
1975                            "name": "same_target",
1976                            "kind": ["lib"],
1977                            "src_path": source,
1978                        },
1979                        "profile": { "test": true },
1980                        "executable": executable,
1981                    }))
1982                    .unwrap(),
1983                );
1984                messages.push(b'\n');
1985            }
1986            (target, messages)
1987        }
1988
1989        let first = TemporaryDirectory::new();
1990        let second = TemporaryDirectory::new();
1991        let (first_target, first_messages) = fixture(&first.0);
1992        let (second_target, second_messages) = fixture(&second.0);
1993        let first_artifacts = cargo_artifacts(&first_messages, &first_target, &first.0).unwrap();
1994        let second_artifacts =
1995            cargo_artifacts(&second_messages, &second_target, &second.0).unwrap();
1996        assert_eq!(
1997            first_artifacts
1998                .iter()
1999                .map(|artifact| artifact.package.as_str())
2000                .collect::<BTreeSet<_>>(),
2001            BTreeSet::from(["package:.", "package:crates/sibling"])
2002        );
2003        assert_eq!(
2004            first_artifacts
2005                .iter()
2006                .map(|artifact| &artifact.package)
2007                .collect::<Vec<_>>(),
2008            second_artifacts
2009                .iter()
2010                .map(|artifact| &artifact.package)
2011                .collect::<Vec<_>>(),
2012            "package identities changed when the workspace moved"
2013        );
2014    }
2015
2016    #[test]
2017    fn cargo_manifest_classifies_only_the_selected_custom_harness() {
2018        let directory = TemporaryDirectory::new();
2019        let manifest = directory.0.join("Cargo.toml");
2020        fs::write(
2021            &manifest,
2022            r#"
2023[package]
2024name = "fixture"
2025version = "0.0.0"
2026
2027[lib]
2028harness = true
2029
2030[[test]]
2031name = "custom"
2032harness = false
2033
2034[[test]]
2035name = "ordinary"
2036"#,
2037        )
2038        .unwrap();
2039        let target = |name: &str, kind: &str| CargoTarget {
2040            name: name.into(),
2041            kind: vec![kind.into()],
2042            src_path: directory.0.join("unused.rs"),
2043        };
2044        assert!(cargo_target_uses_test_harness(&manifest, &target("fixture", "lib")).unwrap());
2045        assert!(!cargo_target_uses_test_harness(&manifest, &target("custom", "test")).unwrap());
2046        assert!(cargo_target_uses_test_harness(&manifest, &target("ordinary", "test")).unwrap());
2047        assert!(cargo_target_uses_test_harness(&manifest, &target("implicit", "test")).unwrap());
2048    }
2049
2050    #[test]
2051    fn cargo_runner_configuration_is_target_indexed_and_rejects_aliases() {
2052        use crate::rust_cargo_configuration::{
2053            RustCargoCompilerCommandPlan, RustCargoRunnerProgram, RustCargoTargetRunnerPlan,
2054        };
2055
2056        let plan = RustCargoRunnerPlan {
2057            compiler: RustCargoCompilerCommandPlan {
2058                rustc: RustCargoRunnerProgram::SearchPath {
2059                    value: "rustc".into(),
2060                },
2061                rustc_wrapper: None,
2062                rustc_workspace_wrapper: None,
2063            },
2064            targets: vec![
2065                RustCargoTargetRunnerPlan {
2066                    target: "aarch64-apple-darwin".into(),
2067                    underlying_runner: None,
2068                },
2069                RustCargoTargetRunnerPlan {
2070                    target: "x86_64-unknown-linux-gnu".into(),
2071                    underlying_runner: None,
2072                },
2073            ],
2074        };
2075        assert_eq!(
2076            cargo_runner_configuration_arguments(Path::new("/opt/super cov"), &plan).unwrap(),
2077            [
2078                "--config",
2079                "target.\"aarch64-apple-darwin\".runner=[\"/opt/super cov\",\"__cargo-test-runner\",\"aarch64-apple-darwin\"]",
2080                "--config",
2081                "target.\"x86_64-unknown-linux-gnu\".runner=[\"/opt/super cov\",\"__cargo-test-runner\",\"x86_64-unknown-linux-gnu\"]",
2082            ]
2083        );
2084        let duplicate = RustCargoRunnerPlan {
2085            compiler: plan.compiler.clone(),
2086            targets: vec![plan.targets[0].clone(), plan.targets[0].clone()],
2087        };
2088        assert!(
2089            cargo_runner_configuration_arguments(Path::new("/opt/supercov"), &duplicate)
2090                .unwrap_err()
2091                .to_string()
2092                .contains("duplicate target identity")
2093        );
2094    }
2095}