Skip to main content

cabin_core/compiler/
report.rs

1//! Whole-toolchain detection report and its deterministic JSON view.
2
3use serde::{Deserialize, Serialize};
4
5use super::capabilities::{ArchiverCapabilities, Capability, CompilerCapabilities};
6use super::identity::{ArchiverIdentity, CompilerIdentity};
7
8/// Whole-toolchain detection report. The CLI builds one per
9/// invocation that needs detection (build / metadata) and threads
10/// it into the planner and the metadata view.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct ToolchainDetectionReport {
13    pub cxx: ToolDetection<CompilerIdentity, CompilerCapabilities>,
14    /// Optional because `ResolvedToolchain.cc` is itself optional.
15    #[serde(default, skip_serializing_if = "Option::is_none")]
16    pub cc: Option<ToolDetection<CompilerIdentity, CompilerCapabilities>>,
17    pub ar: ToolDetection<ArchiverIdentity, ArchiverCapabilities>,
18}
19
20impl ToolchainDetectionReport {
21    /// Compact, deterministic JSON view used by `cabin metadata`
22    /// and any tooling that wants to inspect detection results
23    /// without re-deriving them. Each tool block carries
24    /// `path` / `identity` / `capabilities`; absent tools (a
25    /// missing C compiler) are omitted entirely so the JSON
26    /// shape stays stable.
27    pub fn as_json(&self) -> serde_json::Value {
28        let mut obj = serde_json::Map::new();
29        obj.insert(
30            "cxx".to_owned(),
31            serde_json::json!({
32                "path": self.cxx.path.as_str().to_owned(),
33                "identity": self.cxx.identity.as_json(),
34                "capabilities": cxx_capabilities_as_json(&self.cxx.capabilities),
35            }),
36        );
37        if let Some(cc) = &self.cc {
38            obj.insert(
39                "cc".to_owned(),
40                serde_json::json!({
41                    "path": cc.path.as_str().to_owned(),
42                    "identity": cc.identity.as_json(),
43                    "capabilities": cxx_capabilities_as_json(&cc.capabilities),
44                }),
45            );
46        }
47        obj.insert(
48            "ar".to_owned(),
49            serde_json::json!({
50                "path": self.ar.path.as_str().to_owned(),
51                "identity": self.ar.identity.as_json(),
52                "capabilities": ar_capabilities_as_json(&self.ar.capabilities),
53            }),
54        );
55        serde_json::Value::Object(obj)
56    }
57}
58
59/// One tool's detection outcome plus the path it was invoked at.
60/// `path` is the resolved absolute path from
61/// [`crate::ResolvedToolchain`]; it is preserved here so error
62/// messages can mention the exact executable.
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct ToolDetection<I, C> {
65    pub path: camino::Utf8PathBuf,
66    pub identity: I,
67    pub capabilities: C,
68}
69
70/// Render a [`CompilerCapabilities`] as a deterministic JSON map
71/// keyed by the public capability name, in alphabetical order.
72pub(crate) fn cxx_capabilities_as_json(caps: &CompilerCapabilities) -> serde_json::Value {
73    // Exhaustive destructure (no `..`) so adding a capability field
74    // is a compile error here until it is wired into the JSON, rather
75    // than being silently dropped from `cabin metadata`.
76    let CompilerCapabilities {
77        gcc_style_flags,
78        msvc_style_flags,
79        depfile_mmd_mf,
80        std_flag,
81        cxx_standard_17,
82        c_standard_11,
83    } = caps;
84    let mut entries: [(&'static str, &Capability); 6] = [
85        ("gcc_style_flags", gcc_style_flags),
86        ("msvc_style_flags", msvc_style_flags),
87        ("depfile_mmd_mf", depfile_mmd_mf),
88        ("std_flag", std_flag),
89        ("cxx_standard_17", cxx_standard_17),
90        ("c_standard_11", c_standard_11),
91    ];
92    capabilities_to_json(&mut entries)
93}
94
95pub(crate) fn ar_capabilities_as_json(caps: &ArchiverCapabilities) -> serde_json::Value {
96    let ArchiverCapabilities {
97        ar_crs,
98        static_library_output,
99    } = caps;
100    let mut entries: [(&'static str, &Capability); 2] = [
101        ("ar_crs", ar_crs),
102        ("static_library_output", static_library_output),
103    ];
104    capabilities_to_json(&mut entries)
105}
106
107/// Render `(key, capability)` pairs into an alphabetically-keyed JSON
108/// object — `{ "<key>": { "supported": <bool>, "source": <kebab> } }`.
109/// Sorting here keeps the output independent of the caller's field
110/// order, matching the historical BTreeSet-keyed rendering.
111fn capabilities_to_json(entries: &mut [(&'static str, &Capability)]) -> serde_json::Value {
112    entries.sort_by_key(|(key, _)| *key);
113    let mut obj = serde_json::Map::new();
114    for (key, cap) in entries {
115        obj.insert(
116            (*key).to_owned(),
117            serde_json::json!({
118                "supported": cap.supported,
119                "source": cap.source.as_key(),
120            }),
121        );
122    }
123    serde_json::Value::Object(obj)
124}