1use serde::{Deserialize, Serialize};
4
5use super::identity::{
6 ArchiverIdentity, ArchiverKind, CompilerIdentity, CompilerKind, CompilerVersion,
7};
8use crate::language_standard::{CStandard, CxxStandard};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "kebab-case")]
15pub enum CapabilitySource {
16 Version,
18 AssumedDefault,
21 Unsupported,
24}
25
26impl CapabilitySource {
27 pub fn as_key(self) -> &'static str {
28 match self {
29 CapabilitySource::Version => "version",
30 CapabilitySource::AssumedDefault => "assumed-default",
31 CapabilitySource::Unsupported => "unsupported",
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
39pub struct Capability {
40 pub supported: bool,
41 pub source: CapabilitySource,
42}
43
44impl Capability {
45 pub fn supported_from(source: CapabilitySource) -> Self {
46 Self {
47 supported: true,
48 source,
49 }
50 }
51 pub fn unsupported_from(source: CapabilitySource) -> Self {
52 Self {
53 supported: false,
54 source,
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct CompilerCapabilities {
64 pub gcc_style_flags: Capability,
66 pub msvc_style_flags: Capability,
69 pub depfile_mmd_mf: Capability,
71 pub external_include_dirs: Capability,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82pub struct ArchiverCapabilities {
83 pub ar_crs: Capability,
85 pub static_library_output: Capability,
87}
88
89fn version_gated_capability(
102 version: Option<&CompilerVersion>,
103 min_major: u32,
104 min_minor: u32,
105) -> Capability {
106 match version.map(|v| (v.major, v.minor.unwrap_or(0))) {
107 Some((major, minor)) if major > min_major || (major == min_major && minor >= min_minor) => {
108 Capability::supported_from(CapabilitySource::Version)
109 }
110 Some(_) => Capability::unsupported_from(CapabilitySource::Version),
111 None => Capability::supported_from(CapabilitySource::AssumedDefault),
112 }
113}
114
115pub fn derive_cxx_capabilities(identity: &CompilerIdentity) -> CompilerCapabilities {
116 let gcc_style = if identity.kind.supports_gcc_style_command_line() {
117 Capability::supported_from(CapabilitySource::Version)
118 } else if identity.kind.speaks_msvc_dialect() {
119 Capability::unsupported_from(CapabilitySource::Unsupported)
120 } else {
121 Capability::unsupported_from(CapabilitySource::AssumedDefault)
122 };
123 let msvc_style = if identity.kind.speaks_msvc_dialect() {
124 Capability::supported_from(CapabilitySource::Version)
125 } else {
126 Capability::unsupported_from(CapabilitySource::AssumedDefault)
127 };
128 let depfile_mmd_mf = if identity.kind.supports_gcc_style_command_line() {
129 Capability::supported_from(CapabilitySource::Version)
130 } else if identity.kind.speaks_msvc_dialect() {
131 Capability::unsupported_from(CapabilitySource::Unsupported)
134 } else {
135 Capability::unsupported_from(CapabilitySource::AssumedDefault)
136 };
137 let external_include_dirs = match identity.kind {
145 CompilerKind::Clang | CompilerKind::AppleClang | CompilerKind::Gcc => {
146 Capability::supported_from(CapabilitySource::Version)
147 }
148 CompilerKind::ClangCl => version_gated_capability(identity.version.as_ref(), 13, 0),
149 CompilerKind::Msvc => version_gated_capability(identity.version.as_ref(), 19, 29),
150 CompilerKind::Unknown => Capability::unsupported_from(CapabilitySource::AssumedDefault),
151 };
152
153 CompilerCapabilities {
154 gcc_style_flags: gcc_style,
155 msvc_style_flags: msvc_style,
156 depfile_mmd_mf,
157 external_include_dirs,
158 }
159}
160
161#[must_use]
170pub fn c_standard_capability(identity: &CompilerIdentity, standard: CStandard) -> Capability {
171 use CStandard::{C11, C17, C23, C89, C99};
172 let version = identity.version.as_ref();
173 let always = Capability::supported_from(CapabilitySource::Version);
174 let no_flag = Capability::unsupported_from(CapabilitySource::Unsupported);
175 match identity.kind {
176 CompilerKind::Gcc => match standard {
177 C89 | C99 | C11 => always,
178 C17 => version_gated_capability(version, 8, 0),
180 C23 => version_gated_capability(version, 14, 0),
181 },
182 CompilerKind::Clang => match standard {
183 C89 | C99 | C11 => always,
184 C17 => version_gated_capability(version, 6, 0),
186 C23 => version_gated_capability(version, 18, 0),
187 },
188 CompilerKind::AppleClang => match standard {
189 C89 | C99 | C11 => always,
190 C17 => version_gated_capability(version, 10, 0),
193 C23 => version_gated_capability(version, 17, 0),
194 },
195 CompilerKind::ClangCl => match standard {
198 C11 | C17 => version_gated_capability(version, 13, 0),
199 C89 | C99 | C23 => no_flag,
200 },
201 CompilerKind::Msvc => match standard {
204 C11 | C17 => version_gated_capability(version, 19, 28),
205 C89 | C99 | C23 => no_flag,
206 },
207 CompilerKind::Unknown => Capability::unsupported_from(CapabilitySource::AssumedDefault),
208 }
209}
210
211#[must_use]
213pub fn cxx_standard_capability(identity: &CompilerIdentity, standard: CxxStandard) -> Capability {
214 use CxxStandard::{Cxx11, Cxx14, Cxx17, Cxx20, Cxx23, Cxx26, Cxx98};
215 let version = identity.version.as_ref();
216 let always = Capability::supported_from(CapabilitySource::Version);
217 let no_flag = Capability::unsupported_from(CapabilitySource::Unsupported);
218 match identity.kind {
219 CompilerKind::Gcc => match standard {
220 Cxx98 | Cxx11 => always,
221 Cxx14 | Cxx17 => version_gated_capability(version, 5, 0),
225 Cxx20 => version_gated_capability(version, 10, 0),
226 Cxx23 => version_gated_capability(version, 11, 0),
227 Cxx26 => version_gated_capability(version, 14, 0),
228 },
229 CompilerKind::Clang => match standard {
230 Cxx98 | Cxx11 | Cxx14 | Cxx17 => always,
231 Cxx20 => version_gated_capability(version, 10, 0),
237 Cxx23 | Cxx26 => version_gated_capability(version, 17, 0),
238 },
239 CompilerKind::AppleClang => match standard {
240 Cxx98 | Cxx11 | Cxx14 | Cxx17 => always,
241 Cxx20 => version_gated_capability(version, 12, 0),
246 Cxx23 | Cxx26 => version_gated_capability(version, 16, 0),
247 },
248 CompilerKind::ClangCl => match standard {
249 Cxx14 | Cxx17 => always,
250 Cxx20 => version_gated_capability(version, 13, 0),
252 Cxx98 | Cxx11 | Cxx23 | Cxx26 => no_flag,
256 },
257 CompilerKind::Msvc => match standard {
258 Cxx14 => version_gated_capability(version, 19, 10),
263 Cxx17 => version_gated_capability(version, 19, 11),
264 Cxx20 => version_gated_capability(version, 19, 29),
265 Cxx98 | Cxx11 | Cxx23 | Cxx26 => no_flag,
269 },
270 CompilerKind::Unknown => Capability::unsupported_from(CapabilitySource::AssumedDefault),
271 }
272}
273
274#[must_use]
279pub fn standard_support_detail(capability: Capability, kind: CompilerKind) -> String {
280 match capability.source {
281 CapabilitySource::Unsupported => {
282 format!("{kind} has no stable flag selecting this standard")
283 }
284 _ => "the detected compiler version predates support for this standard's flag".to_owned(),
285 }
286}
287
288pub fn derive_ar_capabilities(identity: &ArchiverIdentity) -> ArchiverCapabilities {
291 let ar_crs = if identity.kind.supports_ar_crs() {
292 Capability::supported_from(CapabilitySource::Version)
293 } else if identity.kind == ArchiverKind::Lib {
294 Capability::unsupported_from(CapabilitySource::Unsupported)
295 } else {
296 Capability::unsupported_from(CapabilitySource::AssumedDefault)
297 };
298 let static_library_output = if identity.kind.produces_static_library() {
303 Capability::supported_from(CapabilitySource::Version)
304 } else {
305 Capability::unsupported_from(CapabilitySource::AssumedDefault)
306 };
307 ArchiverCapabilities {
308 ar_crs,
309 static_library_output,
310 }
311}