use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CompilerKind {
Clang,
AppleClang,
ClangCl,
Gcc,
Msvc,
Unknown,
}
impl CompilerKind {
pub fn as_key(self) -> &'static str {
match self {
CompilerKind::Clang => "clang",
CompilerKind::AppleClang => "apple-clang",
CompilerKind::ClangCl => "clang-cl",
CompilerKind::Gcc => "gcc",
CompilerKind::Msvc => "msvc",
CompilerKind::Unknown => "unknown",
}
}
pub fn from_key(key: &str) -> Option<Self> {
match key {
"clang" => Some(CompilerKind::Clang),
"apple-clang" => Some(CompilerKind::AppleClang),
"clang-cl" => Some(CompilerKind::ClangCl),
"gcc" => Some(CompilerKind::Gcc),
"msvc" => Some(CompilerKind::Msvc),
"unknown" => Some(CompilerKind::Unknown),
_ => None,
}
}
pub fn is_clang_like(self) -> bool {
matches!(
self,
CompilerKind::Clang | CompilerKind::AppleClang | CompilerKind::ClangCl
)
}
pub fn supports_gcc_style_command_line(self) -> bool {
matches!(
self,
CompilerKind::Clang | CompilerKind::AppleClang | CompilerKind::Gcc
)
}
pub fn speaks_msvc_dialect(self) -> bool {
matches!(self, CompilerKind::Msvc | CompilerKind::ClangCl)
}
}
impl fmt::Display for CompilerKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_key())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ArchiverKind {
Ar,
LlvmAr,
Lib,
Unknown,
}
impl ArchiverKind {
pub fn as_key(self) -> &'static str {
match self {
ArchiverKind::Ar => "ar",
ArchiverKind::LlvmAr => "llvm-ar",
ArchiverKind::Lib => "lib",
ArchiverKind::Unknown => "unknown",
}
}
pub fn supports_ar_crs(self) -> bool {
matches!(self, ArchiverKind::Ar | ArchiverKind::LlvmAr)
}
pub fn produces_static_library(self) -> bool {
matches!(
self,
ArchiverKind::Ar | ArchiverKind::LlvmAr | ArchiverKind::Lib
)
}
}
impl fmt::Display for ArchiverKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_key())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CompilerVersion {
pub major: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub minor: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub patch: Option<u32>,
pub raw: String,
}
impl CompilerVersion {
pub fn parse(raw: &str) -> Option<Self> {
let mut parts = raw.split('.');
let major: u32 = parts.next()?.parse().ok()?;
let minor = parts.next().and_then(|s| s.parse().ok());
let patch = parts.next().and_then(|s| s.parse().ok());
Some(Self {
major,
minor,
patch,
raw: raw.to_owned(),
})
}
pub fn parse_with_suffix(raw: &str) -> Option<Self> {
let mut parts = raw.split('.');
let major = leading_digits(parts.next()?)?;
let minor = parts.next().and_then(leading_digits);
let patch = parts.next().and_then(leading_digits);
Some(Self {
major,
minor,
patch,
raw: raw.to_owned(),
})
}
pub fn to_display_string(&self) -> String {
match (self.minor, self.patch) {
(Some(min), Some(pat)) => format!("{}.{}.{}", self.major, min, pat),
(Some(min), None) => format!("{}.{}", self.major, min),
_ => self.major.to_string(),
}
}
}
impl fmt::Display for CompilerVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_display_string())
}
}
fn leading_digits(part: &str) -> Option<u32> {
let end = part
.find(|c: char| !c.is_ascii_digit())
.unwrap_or(part.len());
if end == 0 {
return None;
}
part[..end].parse().ok()
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CompilerIdentity {
pub kind: CompilerKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<CompilerVersion>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<String>,
pub raw_version_line: String,
}
impl CompilerIdentity {
pub fn unknown(raw_version_line: impl Into<String>) -> Self {
Self {
kind: CompilerKind::Unknown,
version: None,
target: None,
raw_version_line: raw_version_line.into(),
}
}
pub fn as_json(&self) -> serde_json::Value {
identity_json(
self.kind.as_key(),
self.version.as_ref(),
self.target.as_deref(),
&self.raw_version_line,
)
}
}
fn identity_json(
kind: &str,
version: Option<&CompilerVersion>,
target: Option<&str>,
raw_version_line: &str,
) -> serde_json::Value {
let mut obj = serde_json::Map::new();
obj.insert(
"kind".to_owned(),
serde_json::Value::String(kind.to_owned()),
);
if let Some(v) = version {
obj.insert(
"version".to_owned(),
serde_json::Value::String(v.to_display_string()),
);
}
if let Some(t) = target {
obj.insert("target".to_owned(), serde_json::Value::String(t.to_owned()));
}
obj.insert(
"raw_version_line".to_owned(),
serde_json::Value::String(raw_version_line.to_owned()),
);
serde_json::Value::Object(obj)
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArchiverIdentity {
pub kind: ArchiverKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<CompilerVersion>,
pub raw_version_line: String,
}
impl ArchiverIdentity {
pub fn unknown(raw_version_line: impl Into<String>) -> Self {
Self {
kind: ArchiverKind::Unknown,
version: None,
raw_version_line: raw_version_line.into(),
}
}
pub fn as_json(&self) -> serde_json::Value {
identity_json(
self.kind.as_key(),
self.version.as_ref(),
None,
&self.raw_version_line,
)
}
}