#![allow(rustdoc::private_intra_doc_links)]
pub mod github_runners;
pub mod targets;
use std::collections::HashMap;
use cargo_dist_schema::{
ArtifactId, AssetId, BuildEnvironment, ChecksumExtension, ChecksumValue, DistManifest,
GlibcVersion, Linkage, SystemInfo, TripleName, TripleNameRef,
};
use serde::Serialize;
use crate::{
backend::installer::{ExecutableZipFragment, UpdaterFragment},
config::ZipStyle,
tasks::Artifact,
BinaryKind, DistGraphBuilder, ReleaseIdx, SortedMap,
};
use targets::{
TARGET_ARM64_MAC, TARGET_ARM64_MINGW, TARGET_ARM64_WINDOWS, TARGET_X64_MAC, TARGET_X64_MINGW,
TARGET_X64_WINDOWS, TARGET_X86_MINGW, TARGET_X86_WINDOWS,
};
pub type MinGlibcVersion = SortedMap<String, LibcVersion>;
const LINUX_STATIC_LIBCS: &[&str] = &["linux-musl-static"];
const LINUX_STATIC_REPLACEABLE_LIBCS: &[&str] = &["linux-gnu", "linux-musl-dynamic"];
const TARGET_MACOS_UNIVERSAL2: &str = "universal2-apple-darwin";
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub enum SupportQuality {
HostNative,
BulkyNative,
ImperfectNative,
Emulated,
Hellmulated,
HighwayToHellmulated,
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize)]
pub struct LibcVersion {
pub major: u64,
pub series: u64,
}
impl LibcVersion {
pub fn default_glibc() -> Self {
Self {
major: 2,
series: 31,
}
}
fn glibc_from_schema(schema: &GlibcVersion) -> Self {
Self {
major: schema.major,
series: schema.series,
}
}
}
impl std::fmt::Display for LibcVersion {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}.{}", self.major, self.series)
}
}
impl<'de> serde::de::Deserialize<'de> for LibcVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let version_str = String::deserialize(deserializer)?;
let parts: Vec<&str> = version_str.split('.').collect();
if parts.len() != 2 {
return Err(serde::de::Error::custom(
"libc version must be {major}.{series} where major and series are numbers",
));
}
let major = parts[0].parse().map_err(|_| {
serde::de::Error::custom(format!(
"expected {{major}}.{{series}} where major and series are numbers, but got input with major={}",
parts[0],
))
})?;
let series = parts[1].parse().map_err(|_| {
serde::de::Error::custom(format!(
"expected {{major}}.{{series}} where major and series are numbers, but got input with series={}",
parts[1],
))
})?;
Ok(LibcVersion { major, series })
}
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct RuntimeConditions {
#[serde(skip_serializing_if = "Option::is_none")]
pub min_glibc_version: Option<LibcVersion>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_musl_version: Option<LibcVersion>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub rosetta2: bool,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct PlatformSupport {
pub archives: Vec<FetchableArchive>,
pub updaters: Vec<FetchableUpdater>,
pub platforms: SortedMap<TripleName, Vec<PlatformEntry>>,
}
#[derive(Debug, Clone, Serialize)]
pub struct FetchableArchive {
pub id: ArtifactId,
pub native_runtime_conditions: RuntimeConditions,
pub target_triple: TripleName,
pub target_triples: Vec<TripleName>,
pub checksum: Option<FetchableArchiveChecksum>,
pub executables: Vec<String>,
pub cdylibs: Vec<String>,
pub cstaticlibs: Vec<String>,
pub zip_style: ZipStyle,
pub updater: Option<FetchableUpdaterIdx>,
}
#[derive(Debug, Clone, Serialize)]
pub struct FetchableArchiveChecksum {
pub style: ChecksumExtension,
pub value: ChecksumValue,
}
#[derive(Debug, Clone, Serialize)]
pub struct FetchableUpdater {
pub id: ArtifactId,
pub binary: ArtifactId,
}
pub type FetchableArchiveIdx = usize;
pub type FetchableUpdaterIdx = usize;
#[derive(Debug, Clone, Serialize)]
pub struct PlatformEntry {
pub quality: SupportQuality,
pub runtime_conditions: RuntimeConditions,
pub archive_idx: FetchableArchiveIdx,
}
impl PlatformSupport {
pub(crate) fn new(dist: &DistGraphBuilder, release_idx: ReleaseIdx) -> PlatformSupport {
let mut platforms = SortedMap::<TripleName, Vec<PlatformEntry>>::new();
let release = dist.release(release_idx);
let mut archives = vec![];
let mut updaters = vec![];
for &variant_idx in &release.variants {
let updater_idx = if dist.inner.config.installers.updater {
let updater_artifact = dist.make_updater_for_variant(variant_idx);
let updater = FetchableUpdater {
id: updater_artifact.id.clone(),
binary: updater_artifact.id.clone(),
};
let updater_idx = updaters.len();
updaters.push(updater);
Some(updater_idx)
} else {
None
};
let (artifact, binaries) =
dist.make_executable_zip_for_variant(release_idx, variant_idx);
let native_runtime_conditions = native_runtime_conditions_for_artifact(dist, &artifact);
let executables = binaries
.iter()
.filter(|(idx, _)| dist.binary(*idx).kind == BinaryKind::Executable);
let cdylibs = binaries
.iter()
.filter(|(idx, _)| dist.binary(*idx).kind == BinaryKind::DynamicLibrary);
let cstaticlibs = binaries
.iter()
.filter(|(idx, _)| dist.binary(*idx).kind == BinaryKind::StaticLibrary);
let archive = FetchableArchive {
id: artifact.id,
target_triple: TripleName::new("".to_owned()),
target_triples: artifact.target_triples,
executables: executables
.map(|(_, dest_path)| dest_path.file_name().unwrap().to_owned())
.collect(),
cdylibs: cdylibs
.map(|(_, dest_path)| dest_path.file_name().unwrap().to_owned())
.collect(),
cstaticlibs: cstaticlibs
.map(|(_, dest_path)| dest_path.file_name().unwrap().to_owned())
.collect(),
zip_style: artifact.archive.as_ref().unwrap().zip_style,
checksum: None,
native_runtime_conditions,
updater: updater_idx,
};
archives.push(archive);
}
for (archive_idx, archive) in archives.iter_mut().enumerate() {
let supports = supports(archive_idx, archive);
if let Some((target, _)) = supports.first() {
archive.target_triple.clone_from(target);
}
for (target, support) in supports {
platforms.entry(target).or_default().push(support);
}
}
for support in platforms.values_mut() {
support.sort_by(|a, b| {
a.quality.cmp(&b.quality).then_with(|| {
let archive_a = &archives[a.archive_idx];
let archive_b = &archives[b.archive_idx];
archive_a.id.cmp(&archive_b.id)
})
});
}
PlatformSupport {
archives,
updaters,
platforms,
}
}
pub fn fragments(&self) -> Vec<ExecutableZipFragment> {
let mut fragments = vec![];
for (target, options) in &self.platforms {
let Some(option) = options.first() else {
continue;
};
let archive = &self.archives[option.archive_idx];
let updater = if let Some(updater_idx) = archive.updater {
let updater = &self.updaters[updater_idx];
Some(UpdaterFragment {
id: updater.id.clone(),
binary: updater.binary.clone(),
})
} else {
None
};
let fragment = ExecutableZipFragment {
id: archive.id.clone(),
target_triple: target.clone(),
zip_style: archive.zip_style,
executables: archive.executables.clone(),
cdylibs: archive.cdylibs.clone(),
cstaticlibs: archive.cstaticlibs.clone(),
runtime_conditions: option.runtime_conditions.clone(),
updater,
};
fragments.push(fragment);
}
fragments
}
pub fn conflated_runtime_conditions(&self) -> RuntimeConditions {
let mut runtime_conditions = RuntimeConditions::default();
for options in self.platforms.values() {
let Some(option) = options.first() else {
continue;
};
runtime_conditions.merge(&option.runtime_conditions);
}
runtime_conditions
}
pub fn safe_conflated_runtime_conditions(&self) -> RuntimeConditions {
let mut runtime_conditions = self.conflated_runtime_conditions();
if runtime_conditions.min_glibc_version.is_none() {
runtime_conditions.min_glibc_version = Some(LibcVersion::default_glibc());
}
runtime_conditions
}
pub fn fill_in_checksums_from_manifest(&mut self, manifest: &DistManifest) {
for archive in &mut self.archives {
if let Some(manifest_archive) = manifest.artifacts.get(&archive.id) {
if let Some((style, value)) = manifest_archive.checksums.first_key_value() {
archive.checksum = Some(FetchableArchiveChecksum {
style: style.clone(),
value: value.clone(),
});
}
}
}
}
pub fn with_checksums_from_manifest(mut self, manifest: &DistManifest) -> Self {
self.fill_in_checksums_from_manifest(manifest);
self
}
}
fn supports(
archive_idx: FetchableArchiveIdx,
archive: &FetchableArchive,
) -> Vec<(TripleName, PlatformEntry)> {
let mut res: Vec<(TripleName, PlatformEntry)> = Vec::new();
for target in &archive.target_triples {
let target = target.as_str();
let (degunked_target, abigunk) = if let Some(inner_target) = target.strip_suffix("eabihf") {
(inner_target, "eabihf")
} else {
(target, "")
};
let (target, degunked_target) = if let Some(system) = degunked_target.strip_suffix("musl") {
(
format!("{system}musl-static{abigunk}"),
format!("{degunked_target}-static"),
)
} else {
(target.to_owned(), degunked_target.to_owned())
};
res.push((
TripleName::new(target.clone()),
PlatformEntry {
quality: SupportQuality::HostNative,
runtime_conditions: archive.native_runtime_conditions.clone(),
archive_idx,
},
));
for &static_libc in LINUX_STATIC_LIBCS {
let Some(system) = degunked_target.strip_suffix(static_libc) else {
continue;
};
for &libc in LINUX_STATIC_REPLACEABLE_LIBCS {
res.push((
TripleName::new(format!("{system}{libc}{abigunk}")),
PlatformEntry {
quality: SupportQuality::ImperfectNative,
runtime_conditions: archive.native_runtime_conditions.clone(),
archive_idx,
},
));
}
break;
}
if target == TARGET_MACOS_UNIVERSAL2 {
res.push((
TARGET_X64_MAC.to_owned(),
PlatformEntry {
quality: SupportQuality::BulkyNative,
runtime_conditions: archive.native_runtime_conditions.clone(),
archive_idx,
},
));
res.push((
TARGET_ARM64_MAC.to_owned(),
PlatformEntry {
quality: SupportQuality::BulkyNative,
runtime_conditions: archive.native_runtime_conditions.clone(),
archive_idx,
},
));
}
let target = TripleName::new(target);
if target == TARGET_X64_MAC {
let runtime_conditions = RuntimeConditions {
rosetta2: true,
..archive.native_runtime_conditions.clone()
};
res.push((
TARGET_ARM64_MAC.to_owned(),
PlatformEntry {
quality: SupportQuality::Emulated,
runtime_conditions,
archive_idx,
},
));
}
if target == TARGET_X86_WINDOWS {
res.push((
TARGET_X64_WINDOWS.to_owned(),
PlatformEntry {
quality: SupportQuality::ImperfectNative,
runtime_conditions: archive.native_runtime_conditions.clone(),
archive_idx,
},
));
}
if target == TARGET_X86_MINGW {
res.push((
TARGET_X64_MINGW.to_owned(),
PlatformEntry {
quality: SupportQuality::ImperfectNative,
runtime_conditions: archive.native_runtime_conditions.clone(),
archive_idx,
},
));
}
if target == TARGET_X64_WINDOWS || target == TARGET_X86_WINDOWS {
let quality = if target == TARGET_X86_WINDOWS {
SupportQuality::Hellmulated
} else {
SupportQuality::Emulated
};
res.push((
TARGET_ARM64_WINDOWS.to_owned(),
PlatformEntry {
quality,
runtime_conditions: archive.native_runtime_conditions.clone(),
archive_idx,
},
));
}
if target == TARGET_X64_MINGW || target == TARGET_X86_MINGW {
let quality = if target == TARGET_X86_MINGW {
SupportQuality::Hellmulated
} else {
SupportQuality::Emulated
};
res.push((
TARGET_ARM64_MINGW.to_owned(),
PlatformEntry {
quality,
runtime_conditions: archive.native_runtime_conditions.clone(),
archive_idx,
},
));
}
if let Some(system) = target.as_str().strip_suffix("windows-msvc") {
res.push((
TripleName::new(format!("{system}windows-gnu")),
PlatformEntry {
quality: SupportQuality::ImperfectNative,
runtime_conditions: archive.native_runtime_conditions.clone(),
archive_idx,
},
));
}
}
res
}
impl RuntimeConditions {
fn merge(&mut self, other: &Self) {
let RuntimeConditions {
min_glibc_version,
min_musl_version,
rosetta2,
} = other;
self.min_glibc_version =
max_of_min_libc_versions(&self.min_glibc_version, min_glibc_version);
self.min_musl_version = max_of_min_libc_versions(&self.min_musl_version, min_musl_version);
self.rosetta2 |= rosetta2;
}
}
fn max_of_min_libc_versions(
lhs: &Option<LibcVersion>,
rhs: &Option<LibcVersion>,
) -> Option<LibcVersion> {
match (*lhs, *rhs) {
(None, None) => None,
(Some(ver), None) | (None, Some(ver)) => Some(ver),
(Some(lhs), Some(rhs)) => Some(lhs.max(rhs)),
}
}
fn native_runtime_conditions_for_artifact(
dist: &DistGraphBuilder,
artifact: &Artifact,
) -> RuntimeConditions {
let manifest = &dist.manifest;
let mut runtime_conditions = RuntimeConditions::default();
let artifact_id = &artifact.id;
if let Some(artifact) = manifest.artifacts.get(artifact_id) {
for asset in &artifact.assets {
let asset_conditions = native_runtime_conditions_for_asset(manifest, &asset.id);
runtime_conditions.merge(&asset_conditions);
}
};
if artifact_id.to_string().contains("linux") && artifact_id.to_string().contains("-gnu") {
if let Some(version) = get_glibc_override(dist, artifact) {
runtime_conditions.min_glibc_version = Some(version);
}
if runtime_conditions.min_glibc_version.is_none() {
runtime_conditions.min_glibc_version = Some(LibcVersion::default_glibc());
}
}
runtime_conditions
}
fn get_glibc_override(dist: &DistGraphBuilder, artifact: &Artifact) -> Option<LibcVersion> {
let version_map = dist.inner.config.builds.min_glibc_version.clone();
version_map.and_then(|vmap| {
artifact
.target_triples
.first()
.and_then(|t: &TripleName| vmap.get(&t.to_string()).copied())
.or_else(|| vmap.get("*").copied())
})
}
fn native_runtime_conditions_for_asset(
manifest: &DistManifest,
asset_id: &Option<AssetId>,
) -> RuntimeConditions {
let Some(asset_id) = asset_id else {
return RuntimeConditions::default();
};
let Some(asset) = &manifest.assets.get(asset_id) else {
return RuntimeConditions::default();
};
let Some(linkage) = &asset.linkage else {
return RuntimeConditions::default();
};
let Some(system) = manifest.systems.get(&asset.system) else {
return RuntimeConditions::default();
};
let min_glibc_version = native_glibc_version(system, linkage);
let min_musl_version = native_musl_version(system, linkage);
let rosetta2 = false;
RuntimeConditions {
min_glibc_version,
min_musl_version,
rosetta2,
}
}
fn native_glibc_version(system: &SystemInfo, linkage: &Linkage) -> Option<LibcVersion> {
for lib in &linkage.system {
if lib.is_glibc() {
if let BuildEnvironment::Linux {
glibc_version: Some(system_glibc),
} = &system.build_environment
{
return Some(LibcVersion::glibc_from_schema(system_glibc));
} else {
return Some(LibcVersion::default_glibc());
}
}
}
None
}
fn native_musl_version(_system: &SystemInfo, _linkage: &Linkage) -> Option<LibcVersion> {
None
}
pub fn triple_to_display_name(name: &TripleNameRef) -> Option<&'static str> {
if name.as_str() == "all" {
Some("All Platforms")
} else {
TARGET_TRIPLE_DISPLAY_NAMES.get(name).copied()
}
}
lazy_static::lazy_static! {
static ref TARGET_TRIPLE_DISPLAY_NAMES: HashMap<&'static TripleNameRef, &'static str> =
{
use targets::*;
let mut map = HashMap::new();
map.insert(TARGET_X86_LINUX_GNU, "x86 Linux");
map.insert(TARGET_X64_LINUX_GNU, "x64 Linux");
map.insert(TARGET_ARM64_LINUX_GNU, "ARM64 Linux");
map.insert(TARGET_ARMV7_LINUX_GNU, "ARMv7 Linux");
map.insert(TARGET_ARMV6_LINUX_GNU, "ARMv6 Linux");
map.insert(TARGET_ARMV6_LINUX_GNU_HARDFLOAT, "ARMv6 Linux (Hardfloat)");
map.insert(TARGET_PPC64_LINUX_GNU, "PPC64 Linux");
map.insert(TARGET_PPC64LE_LINUX_GNU, "PPC64LE Linux");
map.insert(TARGET_S390X_LINUX_GNU, "S390x Linux");
map.insert(TARGET_RISCV_LINUX_GNU, "RISCV Linux");
map.insert(TARGET_LOONGARCH64_LINUX_GNU, "LoongArch64 Linux");
map.insert(TARGET_SPARC64_LINUX_GNU, "SPARC64 Linux");
map.insert(TARGET_X86_LINUX_MUSL, "x86 MUSL Linux");
map.insert(TARGET_X64_LINUX_MUSL, "x64 MUSL Linux");
map.insert(TARGET_ARM64_LINUX_MUSL, "ARM64 MUSL Linux");
map.insert(TARGET_ARMV7_LINUX_MUSL, "ARMv7 MUSL Linux");
map.insert(TARGET_ARMV6_LINUX_MUSL, "ARMv6 MUSL Linux");
map.insert(
TARGET_ARMV6_LINUX_MUSL_HARDFLOAT,
"ARMv6 MUSL Linux (Hardfloat)",
);
map.insert(TARGET_PPC64_LINUX_MUSL, "PPC64 MUSL Linux");
map.insert(TARGET_PPC64LE_LINUX_MUSL, "PPC64LE MUSL Linux");
map.insert(TARGET_S390X_LINUX_MUSL, "S390x MUSL Linux");
map.insert(TARGET_RISCV_LINUX_MUSL, "RISCV MUSL Linux");
map.insert(TARGET_LOONGARCH64_LINUX_MUSL, "LoongArch64 MUSL Linux");
map.insert(TARGET_SPARC64_LINUX_MUSL, "SPARC64 MUSL Linux");
map.insert(TARGET_X86_WINDOWS, "x86 Windows");
map.insert(TARGET_X64_WINDOWS, "x64 Windows");
map.insert(TARGET_ARM64_WINDOWS, "ARM64 Windows");
map.insert(TARGET_X86_MINGW, "x86 MinGW");
map.insert(TARGET_X64_MINGW, "x64 MinGW");
map.insert(TARGET_ARM64_MINGW, "ARM64 MinGW");
map.insert(TARGET_X86_MAC, "x86 macOS");
map.insert(TARGET_X64_MAC, "Intel macOS");
map.insert(TARGET_ARM64_MAC, "Apple Silicon macOS");
map.insert(TARGET_X64_FREEBSD, "x64 FreeBSD");
map.insert(TARGET_X64_ILLUMOS, "x64 IllumOS");
map.insert(TARGET_X64_NETBSD, "x64 NetBSD");
map.insert(TARGET_ARM64_IOS, "iOS");
map.insert(TARGET_ARM64_IOS_SIM, "ARM64 iOS SIM");
map.insert(TARGET_X64_IOS, "x64 iOS");
map.insert(TARGET_ARM64_FUCHSIA, "ARM64 Fuchsia");
map.insert(TARGET_ARM64_ANDROID, "Android");
map.insert(TARGET_X64_ANDROID, "x64 Android");
map.insert(TARGET_ASMJS_EMSCRIPTEN, "asm.js Emscripten");
map.insert(TARGET_WASM32_WASI, "WASI");
map.insert(TARGET_WASM32, "WASM");
map.insert(TARGET_SPARC_SOLARIS, "SPARC Solaris");
map.insert(TARGET_X64_SOLARIS, "x64 Solaris");
map
};
}