use crate::compiler::CompilerFamily;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputRole {
RustMetadata,
RustArchive,
Object,
DepInfo,
PrecompiledHeaderOrModule,
ExecutableOrLibrary,
LinkerSideOutput,
MutableOpaque,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MutationContract {
ImmutableConsumer,
AtomicReplaceOnly,
MayEditInPlace,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeliveryPolicy {
ReflinkPreferred,
HardlinkEligible,
IndependentOnly,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OutputClassification {
pub role: OutputRole,
pub mutation: MutationContract,
pub delivery: DeliveryPolicy,
}
impl OutputClassification {
#[must_use]
pub fn for_compiler(family: CompilerFamily, output_name: &str) -> Self {
let extension = output_name
.rsplit_once('.')
.map_or("", |(_, extension)| extension);
match family {
CompilerFamily::Rustc if matches!(extension, "rmeta") => Self {
role: OutputRole::RustMetadata,
mutation: MutationContract::ImmutableConsumer,
delivery: DeliveryPolicy::HardlinkEligible,
},
CompilerFamily::Rustc if matches!(extension, "rlib") => Self {
role: OutputRole::RustArchive,
mutation: MutationContract::ImmutableConsumer,
delivery: DeliveryPolicy::HardlinkEligible,
},
CompilerFamily::Gcc | CompilerFamily::Clang | CompilerFamily::Msvc
if extension == "o" || extension == "obj" =>
{
Self {
role: OutputRole::Object,
mutation: MutationContract::AtomicReplaceOnly,
delivery: DeliveryPolicy::ReflinkPreferred,
}
}
CompilerFamily::Gcc | CompilerFamily::Clang | CompilerFamily::Msvc
if matches!(extension, "pch" | "gch" | "pcm") =>
{
Self {
role: OutputRole::PrecompiledHeaderOrModule,
mutation: MutationContract::AtomicReplaceOnly,
delivery: DeliveryPolicy::ReflinkPreferred,
}
}
_ if extension == "d" => Self {
role: OutputRole::DepInfo,
mutation: MutationContract::MayEditInPlace,
delivery: DeliveryPolicy::IndependentOnly,
},
_ if matches!(extension, "pch" | "gch" | "pcm") => Self {
role: OutputRole::PrecompiledHeaderOrModule,
mutation: MutationContract::Unknown,
delivery: DeliveryPolicy::IndependentOnly,
},
_ => Self {
role: if matches!(
family,
CompilerFamily::Gcc | CompilerFamily::Clang | CompilerFamily::Msvc
) {
OutputRole::ExecutableOrLibrary
} else {
OutputRole::MutableOpaque
},
mutation: MutationContract::Unknown,
delivery: DeliveryPolicy::IndependentOnly,
},
}
}
}
#[must_use]
pub fn rustc_output_delivery(
archive_hardlink_eligible: bool,
output: &std::path::Path,
) -> DeliveryPolicy {
let extension = output.extension().and_then(|ext| ext.to_str());
if extension == Some("rmeta") {
return DeliveryPolicy::HardlinkEligible;
}
if extension != Some("rlib") {
return DeliveryPolicy::IndependentOnly;
}
if archive_hardlink_eligible {
DeliveryPolicy::HardlinkEligible
} else {
DeliveryPolicy::IndependentOnly
}
}
#[must_use]
pub fn rustc_archive_hardlink_eligible(args: &[String]) -> bool {
let mut crate_types = Vec::new();
let mut i = 0;
while i < args.len() {
if args[i] == "--crate-type" {
if let Some(value) = args.get(i + 1) {
crate_types.extend(value.split(','));
}
i += 2;
continue;
}
if let Some(value) = args[i].strip_prefix("--crate-type=") {
crate_types.extend(value.split(','));
}
i += 1;
}
crate_types
.into_iter()
.any(|crate_type| matches!(crate_type, "lib" | "rlib"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_proven_rust_interfaces_are_hardlink_candidates() {
assert_eq!(
OutputClassification::for_compiler(CompilerFamily::Rustc, "libx.rlib").delivery,
DeliveryPolicy::HardlinkEligible
);
assert_eq!(
OutputClassification::for_compiler(CompilerFamily::Rustc, "x.exe").delivery,
DeliveryPolicy::IndependentOnly
);
assert_eq!(
OutputClassification::for_compiler(CompilerFamily::Clang, "x.o").delivery,
DeliveryPolicy::ReflinkPreferred
);
assert_eq!(
OutputClassification::for_compiler(CompilerFamily::Clang, "x.pch").role,
OutputRole::PrecompiledHeaderOrModule
);
}
#[test]
fn depfiles_and_unknown_outputs_are_never_shared() {
assert_eq!(
OutputClassification::for_compiler(CompilerFamily::Gcc, "x.d").delivery,
DeliveryPolicy::IndependentOnly
);
assert_eq!(
OutputClassification::for_compiler(CompilerFamily::Gcc, "custom.output").delivery,
DeliveryPolicy::IndependentOnly
);
}
#[test]
fn rust_archive_policy_requires_semantic_crate_type() {
assert_eq!(
rustc_output_delivery(true, std::path::Path::new("x.rlib")),
DeliveryPolicy::HardlinkEligible
);
assert_eq!(
rustc_output_delivery(false, std::path::Path::new("x.rlib")),
DeliveryPolicy::IndependentOnly
);
assert_eq!(
rustc_output_delivery(false, std::path::Path::new("x.rmeta")),
DeliveryPolicy::HardlinkEligible
);
assert!(rustc_archive_hardlink_eligible(&[
"--crate-type=rlib".into()
]));
assert!(!rustc_archive_hardlink_eligible(&[
"--crate-type=bin".into()
]));
}
}