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        external_include_dirs,
81    } = caps;
82    let mut entries: [(&'static str, &Capability); 4] = [
83        ("gcc_style_flags", gcc_style_flags),
84        ("msvc_style_flags", msvc_style_flags),
85        ("depfile_mmd_mf", depfile_mmd_mf),
86        ("external_include_dirs", external_include_dirs),
87    ];
88    capabilities_to_json(&mut entries)
89}
90
91pub(crate) fn ar_capabilities_as_json(caps: &ArchiverCapabilities) -> serde_json::Value {
92    let ArchiverCapabilities {
93        ar_crs,
94        static_library_output,
95    } = caps;
96    let mut entries: [(&'static str, &Capability); 2] = [
97        ("ar_crs", ar_crs),
98        ("static_library_output", static_library_output),
99    ];
100    capabilities_to_json(&mut entries)
101}
102
103/// Render `(key, capability)` pairs into an alphabetically-keyed JSON
104/// object — `{ "<key>": { "supported": <bool>, "source": <kebab> } }`.
105/// Sorting here keeps the output independent of the caller's field
106/// order, matching the historical BTreeSet-keyed rendering.
107fn capabilities_to_json(entries: &mut [(&'static str, &Capability)]) -> serde_json::Value {
108    entries.sort_by_key(|(key, _)| *key);
109    let mut obj = serde_json::Map::new();
110    for (key, cap) in entries {
111        obj.insert(
112            (*key).to_owned(),
113            serde_json::json!({
114                "supported": cap.supported,
115                "source": cap.source.as_key(),
116            }),
117        );
118    }
119    serde_json::Value::Object(obj)
120}