Skip to main content

cabin_core/compiler/
validation.rs

1//! Backend-compatibility validation for resolved tools.
2
3use thiserror::Error;
4
5use super::capabilities::{ArchiverCapabilities, CompilerCapabilities};
6use super::identity::{ArchiverIdentity, ArchiverKind, CompilerIdentity, CompilerKind};
7
8/// Errors produced while validating a detection report against
9/// the current C++ backend's required capability set.
10#[derive(Debug, Error, Clone, PartialEq, Eq)]
11pub enum ToolDetectionError {
12    #[error("selected C++ compiler `{spec}` cannot be matched to a supported C++ backend")]
13    UnsupportedCxxBackend { spec: String },
14
15    #[error(
16        "selected C++ compiler `{spec}` could not be identified and the current backend requires GCC-style flags"
17    )]
18    UnknownCxxRequiresGccStyle { spec: String },
19
20    #[error(
21        "selected C++ compiler `{spec}` ({kind}) does not support the required C++17 standard flag"
22    )]
23    CxxLacksStdCxx17 { spec: String, kind: CompilerKind },
24
25    #[error(
26        "selected C++ compiler `{spec}` ({kind}) does not support the depfile flags required by the Ninja backend"
27    )]
28    CxxLacksDepfile { spec: String, kind: CompilerKind },
29
30    #[error("selected C compiler `{spec}` cannot be matched to a supported C backend")]
31    UnsupportedCBackend { spec: String },
32
33    #[error(
34        "selected C compiler `{spec}` could not be identified and the current backend requires GCC-style flags"
35    )]
36    UnknownCRequiresGccStyle { spec: String },
37
38    #[error(
39        "selected C compiler `{spec}` ({kind}) does not support the depfile flags required by the Ninja backend"
40    )]
41    CLacksDepfile { spec: String, kind: CompilerKind },
42
43    #[error(
44        "selected C compiler `{spec}` ({kind}) does not support the required C11 standard flag (MSVC `/std:c11` needs VS2019 16.8 / `cl` 19.28 or newer)"
45    )]
46    CLacksStdC11 { spec: String, kind: CompilerKind },
47
48    #[error("selected archiver `{spec}` is not supported by the static-library backend")]
49    UnsupportedArchiver { spec: String },
50
51    #[error(
52        "selected archiver `{spec}` could not be identified and the current backend requires `ar crs`-compatible behavior"
53    )]
54    UnknownArchiverRequiresArCompatible { spec: String },
55}
56
57/// Validate that the resolved C++ compiler can drive one of
58/// Cabin's two C++ backends.
59///
60/// An MSVC compiler drives the `cl.exe` backend, which speaks the
61/// MSVC command-line dialect (`/std:c++17`, `/showIncludes`,
62/// `/D` / `/I` / `/c` / `/Fo`). Every other recognized compiler
63/// drives the GCC/Clang backend, which requires `-std=c++17`,
64/// `-MMD -MF`, and GCC-style `-D` / `-I` / `-c` / `-o`. A
65/// compiler that fits neither contract is a hard error.
66///
67/// # Errors
68/// Returns [`ToolDetectionError::UnsupportedCxxBackend`] when the compiler fits
69/// no backend, [`ToolDetectionError::UnknownCxxRequiresGccStyle`] when an
70/// unidentified compiler lacks GCC-style flags,
71/// [`ToolDetectionError::CxxLacksDepfile`] when `-MMD -MF` is unsupported, and
72/// [`ToolDetectionError::CxxLacksStdCxx17`] when `-std=c++17` is unsupported.
73pub fn validate_cxx_for_backend(
74    spec_display: &str,
75    identity: &CompilerIdentity,
76    capabilities: &CompilerCapabilities,
77) -> Result<(), ToolDetectionError> {
78    // MSVC-dialect compilers (`cl`, `clang-cl`) drive the `cl.exe`
79    // backend. They always report `msvc_style_flags`, but the planner
80    // also emits `/std:c++17`, which a `cl` older than VS2017 15.3
81    // rejects, so hold them to the C++17 capability too rather than
82    // letting an old toolset fail at the first compile.
83    if identity.kind.speaks_msvc_dialect() {
84        if !capabilities.msvc_style_flags.supported {
85            return Err(ToolDetectionError::UnsupportedCxxBackend {
86                spec: spec_display.to_owned(),
87            });
88        }
89        if !capabilities.cxx_standard_17.supported {
90            return Err(ToolDetectionError::CxxLacksStdCxx17 {
91                spec: spec_display.to_owned(),
92                kind: identity.kind,
93            });
94        }
95        return Ok(());
96    }
97    if !capabilities.gcc_style_flags.supported {
98        if identity.kind == CompilerKind::Unknown {
99            return Err(ToolDetectionError::UnknownCxxRequiresGccStyle {
100                spec: spec_display.to_owned(),
101            });
102        }
103        return Err(ToolDetectionError::UnsupportedCxxBackend {
104            spec: spec_display.to_owned(),
105        });
106    }
107    if !capabilities.depfile_mmd_mf.supported {
108        return Err(ToolDetectionError::CxxLacksDepfile {
109            spec: spec_display.to_owned(),
110            kind: identity.kind,
111        });
112    }
113    if !capabilities.cxx_standard_17.supported {
114        return Err(ToolDetectionError::CxxLacksStdCxx17 {
115            spec: spec_display.to_owned(),
116            kind: identity.kind,
117        });
118    }
119    Ok(())
120}
121
122/// Validate that the resolved C compiler supports the C-side
123/// command shape the active backend emits. An MSVC compiler
124/// drives the `cl.exe` backend; every other recognized compiler
125/// drives the GCC/Clang backend, which needs GCC-style flags
126/// plus `-MMD -MF` depfile generation. Unlike
127/// [`validate_cxx_for_backend`], the GCC/Clang path does **not**
128/// require `-std=c++17` support — a pure-C driver that lacks
129/// C++ mode is acceptable when the target only carries C
130/// translation units.
131///
132/// # Errors
133/// Returns [`ToolDetectionError::UnsupportedCBackend`] when the compiler fits
134/// no backend, [`ToolDetectionError::UnknownCRequiresGccStyle`] when an
135/// unidentified compiler lacks GCC-style flags, and
136/// [`ToolDetectionError::CLacksDepfile`] when `-MMD -MF` is unsupported.
137pub fn validate_cc_for_backend(
138    spec_display: &str,
139    identity: &CompilerIdentity,
140    capabilities: &CompilerCapabilities,
141) -> Result<(), ToolDetectionError> {
142    // MSVC-dialect compilers (`cl`, `clang-cl`) drive the `cl.exe`
143    // backend; the GCC/Clang contract below does not apply to them.
144    // The planner emits `/std:c11` for C compiles, which a `cl` older
145    // than VS2019 16.8 rejects, so hold them to the C11 capability
146    // rather than failing at the first compile.
147    if identity.kind.speaks_msvc_dialect() {
148        if !capabilities.msvc_style_flags.supported {
149            return Err(ToolDetectionError::UnsupportedCBackend {
150                spec: spec_display.to_owned(),
151            });
152        }
153        if !capabilities.c_standard_11.supported {
154            return Err(ToolDetectionError::CLacksStdC11 {
155                spec: spec_display.to_owned(),
156                kind: identity.kind,
157            });
158        }
159        return Ok(());
160    }
161    if !capabilities.gcc_style_flags.supported {
162        if identity.kind == CompilerKind::Unknown {
163            return Err(ToolDetectionError::UnknownCRequiresGccStyle {
164                spec: spec_display.to_owned(),
165            });
166        }
167        return Err(ToolDetectionError::UnsupportedCBackend {
168            spec: spec_display.to_owned(),
169        });
170    }
171    if !capabilities.depfile_mmd_mf.supported {
172        return Err(ToolDetectionError::CLacksDepfile {
173            spec: spec_display.to_owned(),
174            kind: identity.kind,
175        });
176    }
177    Ok(())
178}
179
180/// Validate that the resolved archiver can drive one of Cabin's
181/// two static-library backends: `lib.exe` for MSVC
182/// (`lib /OUT:<lib> <objs>`), or an `ar`-compatible archiver for
183/// GCC/Clang (`ar crs <lib> <objs>`).
184///
185/// # Errors
186/// Returns [`ToolDetectionError::UnsupportedArchiver`] when a known archiver
187/// lacks `ar crs` support, and
188/// [`ToolDetectionError::UnknownArchiverRequiresArCompatible`] when an
189/// unidentified archiver lacks `ar crs` support.
190pub fn validate_ar_for_backend(
191    spec_display: &str,
192    identity: &ArchiverIdentity,
193    capabilities: &ArchiverCapabilities,
194) -> Result<(), ToolDetectionError> {
195    // `lib.exe` is the MSVC static-library backend's archiver; it
196    // produces the `.lib` the `cl.exe` link step consumes.
197    if identity.kind == ArchiverKind::Lib {
198        return Ok(());
199    }
200    if !capabilities.ar_crs.supported {
201        if identity.kind == ArchiverKind::Unknown {
202            return Err(ToolDetectionError::UnknownArchiverRequiresArCompatible {
203                spec: spec_display.to_owned(),
204            });
205        }
206        return Err(ToolDetectionError::UnsupportedArchiver {
207            spec: spec_display.to_owned(),
208        });
209    }
210    Ok(())
211}