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