1use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "kebab-case")]
10pub enum CompilerKind {
11 Clang,
13 AppleClang,
17 ClangCl,
22 Gcc,
24 Msvc,
28 Unknown,
31}
32
33impl CompilerKind {
34 pub fn as_key(self) -> &'static str {
36 match self {
37 CompilerKind::Clang => "clang",
38 CompilerKind::AppleClang => "apple-clang",
39 CompilerKind::ClangCl => "clang-cl",
40 CompilerKind::Gcc => "gcc",
41 CompilerKind::Msvc => "msvc",
42 CompilerKind::Unknown => "unknown",
43 }
44 }
45
46 pub fn from_key(key: &str) -> Option<Self> {
50 match key {
51 "clang" => Some(CompilerKind::Clang),
52 "apple-clang" => Some(CompilerKind::AppleClang),
53 "clang-cl" => Some(CompilerKind::ClangCl),
54 "gcc" => Some(CompilerKind::Gcc),
55 "msvc" => Some(CompilerKind::Msvc),
56 "unknown" => Some(CompilerKind::Unknown),
57 _ => None,
58 }
59 }
60
61 pub fn is_clang_like(self) -> bool {
66 matches!(
67 self,
68 CompilerKind::Clang | CompilerKind::AppleClang | CompilerKind::ClangCl
69 )
70 }
71
72 pub fn supports_gcc_style_command_line(self) -> bool {
77 matches!(
78 self,
79 CompilerKind::Clang | CompilerKind::AppleClang | CompilerKind::Gcc
80 )
81 }
82
83 pub fn speaks_msvc_dialect(self) -> bool {
87 matches!(self, CompilerKind::Msvc | CompilerKind::ClangCl)
88 }
89}
90
91impl fmt::Display for CompilerKind {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 f.write_str(self.as_key())
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
99#[serde(rename_all = "kebab-case")]
100pub enum ArchiverKind {
101 Ar,
104 LlvmAr,
106 Lib,
109 Unknown,
111}
112
113impl ArchiverKind {
114 pub fn as_key(self) -> &'static str {
115 match self {
116 ArchiverKind::Ar => "ar",
117 ArchiverKind::LlvmAr => "llvm-ar",
118 ArchiverKind::Lib => "lib",
119 ArchiverKind::Unknown => "unknown",
120 }
121 }
122
123 pub fn supports_ar_crs(self) -> bool {
126 matches!(self, ArchiverKind::Ar | ArchiverKind::LlvmAr)
127 }
128
129 pub fn produces_static_library(self) -> bool {
135 matches!(
136 self,
137 ArchiverKind::Ar | ArchiverKind::LlvmAr | ArchiverKind::Lib
138 )
139 }
140}
141
142impl fmt::Display for ArchiverKind {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 f.write_str(self.as_key())
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct CompilerVersion {
155 pub major: u32,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
157 pub minor: Option<u32>,
158 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub patch: Option<u32>,
160 pub raw: String,
161}
162
163impl CompilerVersion {
164 pub fn parse(raw: &str) -> Option<Self> {
168 let mut parts = raw.split('.');
169 let major: u32 = parts.next()?.parse().ok()?;
170 let minor = parts.next().and_then(|s| s.parse().ok());
171 let patch = parts.next().and_then(|s| s.parse().ok());
172 Some(Self {
173 major,
174 minor,
175 patch,
176 raw: raw.to_owned(),
177 })
178 }
179
180 pub fn parse_with_suffix(raw: &str) -> Option<Self> {
187 let mut parts = raw.split('.');
188 let major = leading_digits(parts.next()?)?;
189 let minor = parts.next().and_then(leading_digits);
190 let patch = parts.next().and_then(leading_digits);
191 Some(Self {
192 major,
193 minor,
194 patch,
195 raw: raw.to_owned(),
196 })
197 }
198
199 pub fn to_display_string(&self) -> String {
202 match (self.minor, self.patch) {
203 (Some(min), Some(pat)) => format!("{}.{}.{}", self.major, min, pat),
204 (Some(min), None) => format!("{}.{}", self.major, min),
205 _ => self.major.to_string(),
206 }
207 }
208}
209
210impl fmt::Display for CompilerVersion {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 f.write_str(&self.to_display_string())
213 }
214}
215
216fn leading_digits(part: &str) -> Option<u32> {
217 let end = part
218 .find(|c: char| !c.is_ascii_digit())
219 .unwrap_or(part.len());
220 if end == 0 {
221 return None;
222 }
223 part[..end].parse().ok()
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
228pub struct CompilerIdentity {
229 pub kind: CompilerKind,
230 #[serde(default, skip_serializing_if = "Option::is_none")]
234 pub version: Option<CompilerVersion>,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub target: Option<String>,
239 pub raw_version_line: String,
242}
243
244impl CompilerIdentity {
245 pub fn unknown(raw_version_line: impl Into<String>) -> Self {
247 Self {
248 kind: CompilerKind::Unknown,
249 version: None,
250 target: None,
251 raw_version_line: raw_version_line.into(),
252 }
253 }
254
255 pub fn as_json(&self) -> serde_json::Value {
257 identity_json(
258 self.kind.as_key(),
259 self.version.as_ref(),
260 self.target.as_deref(),
261 &self.raw_version_line,
262 )
263 }
264}
265
266fn identity_json(
270 kind: &str,
271 version: Option<&CompilerVersion>,
272 target: Option<&str>,
273 raw_version_line: &str,
274) -> serde_json::Value {
275 let mut obj = serde_json::Map::new();
276 obj.insert(
277 "kind".to_owned(),
278 serde_json::Value::String(kind.to_owned()),
279 );
280 if let Some(v) = version {
281 obj.insert(
282 "version".to_owned(),
283 serde_json::Value::String(v.to_display_string()),
284 );
285 }
286 if let Some(t) = target {
287 obj.insert("target".to_owned(), serde_json::Value::String(t.to_owned()));
288 }
289 obj.insert(
290 "raw_version_line".to_owned(),
291 serde_json::Value::String(raw_version_line.to_owned()),
292 );
293 serde_json::Value::Object(obj)
294}
295
296#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
298pub struct ArchiverIdentity {
299 pub kind: ArchiverKind,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
301 pub version: Option<CompilerVersion>,
302 pub raw_version_line: String,
303}
304
305impl ArchiverIdentity {
306 pub fn unknown(raw_version_line: impl Into<String>) -> Self {
307 Self {
308 kind: ArchiverKind::Unknown,
309 version: None,
310 raw_version_line: raw_version_line.into(),
311 }
312 }
313
314 pub fn as_json(&self) -> serde_json::Value {
315 identity_json(
316 self.kind.as_key(),
317 self.version.as_ref(),
318 None,
319 &self.raw_version_line,
320 )
321 }
322}