use serde::{Deserialize, Serialize};
use super::identity::{
ArchiverIdentity, ArchiverKind, CompilerIdentity, CompilerKind, CompilerVersion,
};
use crate::language_standard::{CStandard, CxxStandard};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CapabilitySource {
Version,
AssumedDefault,
Unsupported,
}
impl CapabilitySource {
pub fn as_key(self) -> &'static str {
match self {
CapabilitySource::Version => "version",
CapabilitySource::AssumedDefault => "assumed-default",
CapabilitySource::Unsupported => "unsupported",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Capability {
pub supported: bool,
pub source: CapabilitySource,
}
impl Capability {
pub fn supported_from(source: CapabilitySource) -> Self {
Self {
supported: true,
source,
}
}
pub fn unsupported_from(source: CapabilitySource) -> Self {
Self {
supported: false,
source,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CompilerCapabilities {
pub gcc_style_flags: Capability,
pub msvc_style_flags: Capability,
pub depfile_mmd_mf: Capability,
pub external_include_dirs: Capability,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArchiverCapabilities {
pub ar_crs: Capability,
pub static_library_output: Capability,
}
fn version_gated_capability(
version: Option<&CompilerVersion>,
min_major: u32,
min_minor: u32,
) -> Capability {
match version.map(|v| (v.major, v.minor.unwrap_or(0))) {
Some((major, minor)) if major > min_major || (major == min_major && minor >= min_minor) => {
Capability::supported_from(CapabilitySource::Version)
}
Some(_) => Capability::unsupported_from(CapabilitySource::Version),
None => Capability::supported_from(CapabilitySource::AssumedDefault),
}
}
pub fn derive_cxx_capabilities(identity: &CompilerIdentity) -> CompilerCapabilities {
let gcc_style = if identity.kind.supports_gcc_style_command_line() {
Capability::supported_from(CapabilitySource::Version)
} else if identity.kind.speaks_msvc_dialect() {
Capability::unsupported_from(CapabilitySource::Unsupported)
} else {
Capability::unsupported_from(CapabilitySource::AssumedDefault)
};
let msvc_style = if identity.kind.speaks_msvc_dialect() {
Capability::supported_from(CapabilitySource::Version)
} else {
Capability::unsupported_from(CapabilitySource::AssumedDefault)
};
let depfile_mmd_mf = if identity.kind.supports_gcc_style_command_line() {
Capability::supported_from(CapabilitySource::Version)
} else if identity.kind.speaks_msvc_dialect() {
Capability::unsupported_from(CapabilitySource::Unsupported)
} else {
Capability::unsupported_from(CapabilitySource::AssumedDefault)
};
let external_include_dirs = match identity.kind {
CompilerKind::Clang | CompilerKind::AppleClang | CompilerKind::Gcc => {
Capability::supported_from(CapabilitySource::Version)
}
CompilerKind::ClangCl => version_gated_capability(identity.version.as_ref(), 13, 0),
CompilerKind::Msvc => version_gated_capability(identity.version.as_ref(), 19, 29),
CompilerKind::Unknown => Capability::unsupported_from(CapabilitySource::AssumedDefault),
};
CompilerCapabilities {
gcc_style_flags: gcc_style,
msvc_style_flags: msvc_style,
depfile_mmd_mf,
external_include_dirs,
}
}
#[must_use]
pub fn c_standard_capability(identity: &CompilerIdentity, standard: CStandard) -> Capability {
use CStandard::{C11, C17, C23, C89, C99};
let version = identity.version.as_ref();
let always = Capability::supported_from(CapabilitySource::Version);
let no_flag = Capability::unsupported_from(CapabilitySource::Unsupported);
match identity.kind {
CompilerKind::Gcc => match standard {
C89 | C99 | C11 => always,
C17 => version_gated_capability(version, 8, 0),
C23 => version_gated_capability(version, 14, 0),
},
CompilerKind::Clang => match standard {
C89 | C99 | C11 => always,
C17 => version_gated_capability(version, 6, 0),
C23 => version_gated_capability(version, 18, 0),
},
CompilerKind::AppleClang => match standard {
C89 | C99 | C11 => always,
C17 => version_gated_capability(version, 10, 0),
C23 => version_gated_capability(version, 17, 0),
},
CompilerKind::ClangCl => match standard {
C11 | C17 => version_gated_capability(version, 13, 0),
C89 | C99 | C23 => no_flag,
},
CompilerKind::Msvc => match standard {
C11 | C17 => version_gated_capability(version, 19, 28),
C89 | C99 | C23 => no_flag,
},
CompilerKind::Unknown => Capability::unsupported_from(CapabilitySource::AssumedDefault),
}
}
#[must_use]
pub fn cxx_standard_capability(identity: &CompilerIdentity, standard: CxxStandard) -> Capability {
use CxxStandard::{Cxx11, Cxx14, Cxx17, Cxx20, Cxx23, Cxx26, Cxx98};
let version = identity.version.as_ref();
let always = Capability::supported_from(CapabilitySource::Version);
let no_flag = Capability::unsupported_from(CapabilitySource::Unsupported);
match identity.kind {
CompilerKind::Gcc => match standard {
Cxx98 | Cxx11 => always,
Cxx14 | Cxx17 => version_gated_capability(version, 5, 0),
Cxx20 => version_gated_capability(version, 10, 0),
Cxx23 => version_gated_capability(version, 11, 0),
Cxx26 => version_gated_capability(version, 14, 0),
},
CompilerKind::Clang => match standard {
Cxx98 | Cxx11 | Cxx14 | Cxx17 => always,
Cxx20 => version_gated_capability(version, 10, 0),
Cxx23 | Cxx26 => version_gated_capability(version, 17, 0),
},
CompilerKind::AppleClang => match standard {
Cxx98 | Cxx11 | Cxx14 | Cxx17 => always,
Cxx20 => version_gated_capability(version, 12, 0),
Cxx23 | Cxx26 => version_gated_capability(version, 16, 0),
},
CompilerKind::ClangCl => match standard {
Cxx14 | Cxx17 => always,
Cxx20 => version_gated_capability(version, 13, 0),
Cxx98 | Cxx11 | Cxx23 | Cxx26 => no_flag,
},
CompilerKind::Msvc => match standard {
Cxx14 => version_gated_capability(version, 19, 10),
Cxx17 => version_gated_capability(version, 19, 11),
Cxx20 => version_gated_capability(version, 19, 29),
Cxx98 | Cxx11 | Cxx23 | Cxx26 => no_flag,
},
CompilerKind::Unknown => Capability::unsupported_from(CapabilitySource::AssumedDefault),
}
}
#[must_use]
pub fn standard_support_detail(capability: Capability, kind: CompilerKind) -> String {
match capability.source {
CapabilitySource::Unsupported => {
format!("{kind} has no stable flag selecting this standard")
}
_ => "the detected compiler version predates support for this standard's flag".to_owned(),
}
}
pub fn derive_ar_capabilities(identity: &ArchiverIdentity) -> ArchiverCapabilities {
let ar_crs = if identity.kind.supports_ar_crs() {
Capability::supported_from(CapabilitySource::Version)
} else if identity.kind == ArchiverKind::Lib {
Capability::unsupported_from(CapabilitySource::Unsupported)
} else {
Capability::unsupported_from(CapabilitySource::AssumedDefault)
};
let static_library_output = if identity.kind.produces_static_library() {
Capability::supported_from(CapabilitySource::Version)
} else {
Capability::unsupported_from(CapabilitySource::AssumedDefault)
};
ArchiverCapabilities {
ar_crs,
static_library_output,
}
}