Skip to main content

cabin_core/compiler/
validation.rs

1//! Backend-compatibility validation for resolved tools.
2
3use std::collections::BTreeSet;
4
5use thiserror::Error;
6
7use super::capabilities::{
8    ArchiverCapabilities, CompilerCapabilities, c_standard_capability, cxx_standard_capability,
9    standard_support_detail,
10};
11use super::identity::{ArchiverIdentity, ArchiverKind, CompilerIdentity, CompilerKind};
12use crate::language_standard::{CStandard, CxxStandard};
13
14/// Errors produced while validating a detection report against
15/// the current C++ backend's required capability set.
16#[derive(Debug, Error, Clone, PartialEq, Eq)]
17pub enum ToolDetectionError {
18    #[error("selected C++ compiler `{spec}` cannot be matched to a supported C++ backend")]
19    UnsupportedCxxBackend { spec: String },
20
21    #[error(
22        "selected C++ compiler `{spec}` could not be identified and the current backend requires GCC-style flags"
23    )]
24    UnknownCxxRequiresGccStyle { spec: String },
25
26    #[error(
27        "selected C++ compiler `{spec}` ({kind}) does not support the requested C++ standard `{standard}`: {detail}"
28    )]
29    CxxLacksStandard {
30        spec: String,
31        kind: CompilerKind,
32        standard: CxxStandard,
33        detail: String,
34    },
35
36    #[error(
37        "selected C++ compiler `{spec}` ({kind}) does not support the depfile flags required by the Ninja backend"
38    )]
39    CxxLacksDepfile { spec: String, kind: CompilerKind },
40
41    #[error("selected C compiler `{spec}` cannot be matched to a supported C backend")]
42    UnsupportedCBackend { spec: String },
43
44    #[error(
45        "selected C compiler `{spec}` could not be identified and the current backend requires GCC-style flags"
46    )]
47    UnknownCRequiresGccStyle { spec: String },
48
49    #[error(
50        "selected C compiler `{spec}` ({kind}) does not support the depfile flags required by the Ninja backend"
51    )]
52    CLacksDepfile { spec: String, kind: CompilerKind },
53
54    #[error(
55        "selected C compiler `{spec}` ({kind}) does not support the requested C standard `{standard}`: {detail}"
56    )]
57    CLacksStandard {
58        spec: String,
59        kind: CompilerKind,
60        standard: CStandard,
61        detail: String,
62    },
63
64    #[error("selected archiver `{spec}` is not supported by the static-library backend")]
65    UnsupportedArchiver { spec: String },
66
67    #[error(
68        "selected archiver `{spec}` could not be identified and the current backend requires `ar crs`-compatible behavior"
69    )]
70    UnknownArchiverRequiresArCompatible { spec: String },
71}
72
73/// Validate that the resolved C++ compiler can drive one of
74/// Cabin's two C++ backends.
75///
76/// An MSVC compiler drives the `cl.exe` backend, which speaks the
77/// MSVC command-line dialect (`/std:`, `/showIncludes`,
78/// `/D` / `/I` / `/c` / `/Fo`). Every other recognized compiler
79/// drives the GCC/Clang backend, which requires `-MMD -MF` and
80/// GCC-style `-D` / `-I` / `-c` / `-o`. A compiler that fits
81/// neither contract is a hard error. Support for the *requested*
82/// language standards is validated separately by
83/// [`validate_cxx_standards`] / [`validate_c_standards`].
84///
85/// # Errors
86/// Returns [`ToolDetectionError::UnsupportedCxxBackend`] when the compiler fits
87/// no backend, [`ToolDetectionError::UnknownCxxRequiresGccStyle`] when an
88/// unidentified compiler lacks GCC-style flags, and
89/// [`ToolDetectionError::CxxLacksDepfile`] when `-MMD -MF` is unsupported.
90pub fn validate_cxx_for_backend(
91    spec_display: &str,
92    identity: &CompilerIdentity,
93    capabilities: &CompilerCapabilities,
94) -> Result<(), ToolDetectionError> {
95    // MSVC-dialect compilers (`cl`, `clang-cl`) drive the `cl.exe`
96    // backend.
97    if identity.kind.speaks_msvc_dialect() {
98        if !capabilities.msvc_style_flags.supported {
99            return Err(ToolDetectionError::UnsupportedCxxBackend {
100                spec: spec_display.to_owned(),
101            });
102        }
103        return Ok(());
104    }
105    if !capabilities.gcc_style_flags.supported {
106        if identity.kind == CompilerKind::Unknown {
107            return Err(ToolDetectionError::UnknownCxxRequiresGccStyle {
108                spec: spec_display.to_owned(),
109            });
110        }
111        return Err(ToolDetectionError::UnsupportedCxxBackend {
112            spec: spec_display.to_owned(),
113        });
114    }
115    if !capabilities.depfile_mmd_mf.supported {
116        return Err(ToolDetectionError::CxxLacksDepfile {
117            spec: spec_display.to_owned(),
118            kind: identity.kind,
119        });
120    }
121    Ok(())
122}
123
124/// Validate that the C++ compiler accepts every requested C++
125/// standard. The whole set is checked, not the maximum: MSVC
126/// support is non-monotonic (`/std:c++20` exists, `/std:c++11`
127/// does not).
128///
129/// # Errors
130/// Returns [`ToolDetectionError::CxxLacksStandard`] for the first
131/// unsupported standard, with a version or no-stable-flag detail.
132pub fn validate_cxx_standards(
133    spec_display: &str,
134    identity: &CompilerIdentity,
135    requested: &BTreeSet<CxxStandard>,
136) -> Result<(), ToolDetectionError> {
137    for &standard in requested {
138        let capability = cxx_standard_capability(identity, standard);
139        if !capability.supported {
140            return Err(ToolDetectionError::CxxLacksStandard {
141                spec: spec_display.to_owned(),
142                kind: identity.kind,
143                standard,
144                detail: standard_support_detail(capability, identity.kind),
145            });
146        }
147    }
148    Ok(())
149}
150
151/// C-side twin of [`validate_cxx_standards`].
152///
153/// # Errors
154/// Returns [`ToolDetectionError::CLacksStandard`] for the first
155/// unsupported standard, with a version or no-stable-flag detail.
156pub fn validate_c_standards(
157    spec_display: &str,
158    identity: &CompilerIdentity,
159    requested: &BTreeSet<CStandard>,
160) -> Result<(), ToolDetectionError> {
161    for &standard in requested {
162        let capability = c_standard_capability(identity, standard);
163        if !capability.supported {
164            return Err(ToolDetectionError::CLacksStandard {
165                spec: spec_display.to_owned(),
166                kind: identity.kind,
167                standard,
168                detail: standard_support_detail(capability, identity.kind),
169            });
170        }
171    }
172    Ok(())
173}
174
175/// Validate that the resolved C compiler supports the C-side
176/// command shape the active backend emits. An MSVC compiler
177/// drives the `cl.exe` backend; every other recognized compiler
178/// drives the GCC/Clang backend, which needs GCC-style flags
179/// plus `-MMD -MF` depfile generation. Support for the requested
180/// C standards is validated separately by
181/// [`validate_c_standards`].
182///
183/// # Errors
184/// Returns [`ToolDetectionError::UnsupportedCBackend`] when the compiler fits
185/// no backend, [`ToolDetectionError::UnknownCRequiresGccStyle`] when an
186/// unidentified compiler lacks GCC-style flags, and
187/// [`ToolDetectionError::CLacksDepfile`] when `-MMD -MF` is unsupported.
188pub fn validate_cc_for_backend(
189    spec_display: &str,
190    identity: &CompilerIdentity,
191    capabilities: &CompilerCapabilities,
192) -> Result<(), ToolDetectionError> {
193    // MSVC-dialect compilers (`cl`, `clang-cl`) drive the `cl.exe`
194    // backend; the GCC/Clang contract below does not apply to them.
195    // Support for the requested C standards is validated separately
196    // by `validate_c_standards`.
197    if identity.kind.speaks_msvc_dialect() {
198        if !capabilities.msvc_style_flags.supported {
199            return Err(ToolDetectionError::UnsupportedCBackend {
200                spec: spec_display.to_owned(),
201            });
202        }
203        return Ok(());
204    }
205    if !capabilities.gcc_style_flags.supported {
206        if identity.kind == CompilerKind::Unknown {
207            return Err(ToolDetectionError::UnknownCRequiresGccStyle {
208                spec: spec_display.to_owned(),
209            });
210        }
211        return Err(ToolDetectionError::UnsupportedCBackend {
212            spec: spec_display.to_owned(),
213        });
214    }
215    if !capabilities.depfile_mmd_mf.supported {
216        return Err(ToolDetectionError::CLacksDepfile {
217            spec: spec_display.to_owned(),
218            kind: identity.kind,
219        });
220    }
221    Ok(())
222}
223
224/// Validate that the resolved archiver can drive one of Cabin's
225/// two static-library backends: `lib.exe` for MSVC
226/// (`lib /OUT:<lib> <objs>`), or an `ar`-compatible archiver for
227/// GCC/Clang (`ar crs <lib> <objs>`).
228///
229/// # Errors
230/// Returns [`ToolDetectionError::UnsupportedArchiver`] when a known archiver
231/// lacks `ar crs` support, and
232/// [`ToolDetectionError::UnknownArchiverRequiresArCompatible`] when an
233/// unidentified archiver lacks `ar crs` support.
234pub fn validate_ar_for_backend(
235    spec_display: &str,
236    identity: &ArchiverIdentity,
237    capabilities: &ArchiverCapabilities,
238) -> Result<(), ToolDetectionError> {
239    // `lib.exe` is the MSVC static-library backend's archiver; it
240    // produces the `.lib` the `cl.exe` link step consumes.
241    if identity.kind == ArchiverKind::Lib {
242        return Ok(());
243    }
244    if !capabilities.ar_crs.supported {
245        if identity.kind == ArchiverKind::Unknown {
246            return Err(ToolDetectionError::UnknownArchiverRequiresArCompatible {
247                spec: spec_display.to_owned(),
248            });
249        }
250        return Err(ToolDetectionError::UnsupportedArchiver {
251            spec: spec_display.to_owned(),
252        });
253    }
254    Ok(())
255}