1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//! In some cases, parts of bootstrap need to change part of a target spec just for one or a few
//! steps. Adding these targets to rustc proper would "leak" this implementation detail of
//! bootstrap, and would make it more complex to apply additional changes if the need arises.
//!
//! To address that problem, this module implements support for "synthetic targets". Synthetic
//! targets are custom target specs generated using builtin target specs as their base. You can use
//! one of the target specs already defined in this module, or create new ones by adding a new step
//! that calls create_synthetic_target.

use crate::builder::{Builder, ShouldRun, Step};
use crate::config::TargetSelection;
use crate::Compiler;
use std::process::{Command, Stdio};

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct MirOptPanicAbortSyntheticTarget {
    pub(crate) compiler: Compiler,
    pub(crate) base: TargetSelection,
}

impl Step for MirOptPanicAbortSyntheticTarget {
    type Output = TargetSelection;
    const DEFAULT: bool = true;
    const ONLY_HOSTS: bool = false;

    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
        run.never()
    }

    fn run(self, builder: &Builder<'_>) -> Self::Output {
        create_synthetic_target(builder, self.compiler, "miropt-abort", self.base, |spec| {
            spec.insert("panic-strategy".into(), "abort".into());
        })
    }
}

fn create_synthetic_target(
    builder: &Builder<'_>,
    compiler: Compiler,
    suffix: &str,
    base: TargetSelection,
    customize: impl FnOnce(&mut serde_json::Map<String, serde_json::Value>),
) -> TargetSelection {
    if base.contains("synthetic") {
        // This check is not strictly needed, but nothing currently needs recursive synthetic
        // targets. If the need arises, removing this in the future *SHOULD* be safe.
        panic!("cannot create synthetic targets with other synthetic targets as their base");
    }

    let name = format!("{base}-synthetic-{suffix}");
    let path = builder.out.join("synthetic-target-specs").join(format!("{name}.json"));
    std::fs::create_dir_all(path.parent().unwrap()).unwrap();

    if builder.config.dry_run() {
        std::fs::write(&path, b"dry run\n").unwrap();
        return TargetSelection::create_synthetic(&name, path.to_str().unwrap());
    }

    let mut cmd = Command::new(builder.rustc(compiler));
    cmd.arg("--target").arg(base.rustc_target_arg());
    cmd.args(["-Zunstable-options", "--print", "target-spec-json"]);
    cmd.stdout(Stdio::piped());

    let output = cmd.spawn().unwrap().wait_with_output().unwrap();
    if !output.status.success() {
        panic!("failed to gather the target spec for {base}");
    }

    let mut spec: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
    let spec_map = spec.as_object_mut().unwrap();

    // The `is-builtin` attribute of a spec needs to be removed, otherwise rustc will complain.
    spec_map.remove("is-builtin");

    customize(spec_map);

    std::fs::write(&path, &serde_json::to_vec_pretty(&spec).unwrap()).unwrap();
    let target = TargetSelection::create_synthetic(&name, path.to_str().unwrap());
    crate::cc_detect::find_target(builder, target);

    target
}