use crate::app::INDEX_TTL_DAYS;
use crate::app::utils::host_target;
use crate::types::{ResolvedZigVersion, TargetTriple};
use chrono::{DateTime, Utc};
use serde::{
Deserialize, Deserializer, Serialize,
de::{self, MapAccess, Visitor},
};
use std::collections::{BTreeMap, HashMap};
use std::fmt;
#[derive(Debug, Deserialize)]
pub struct NetworkZigIndex {
#[serde(flatten)]
pub releases: HashMap<String, NetworkZigRelease>,
}
#[derive(Debug)]
pub struct NetworkZigRelease {
pub date: String,
pub version: Option<String>, pub targets: HashMap<String, NetworkArtifact>,
}
#[derive(Debug, Deserialize)]
pub struct NetworkArtifact {
#[serde(rename = "tarball")]
pub ziglang_org_tarball: String,
pub shasum: String,
#[serde(deserialize_with = "deserialize_str_to_u64")]
pub size: u64,
}
fn deserialize_str_to_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
{
let s: String = String::deserialize(deserializer)?;
s.parse::<u64>().map_err(de::Error::custom)
}
impl<'de> Deserialize<'de> for NetworkZigRelease {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct NetworkZigReleaseVisitor;
impl<'de> Visitor<'de> for NetworkZigReleaseVisitor {
type Value = NetworkZigRelease;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a NetworkZigRelease object")
}
fn visit_map<V>(self, mut map: V) -> Result<NetworkZigRelease, V::Error>
where
V: MapAccess<'de>,
{
let mut date = None;
let mut version = None;
let mut targets = HashMap::new();
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"date" => {
date = Some(map.next_value()?);
}
"version" => {
version = Some(map.next_value()?);
}
"docs" | "stdDocs" | "langRef" | "notes" | "bootstrap" | "src" => {
let _: serde_json::Value = map.next_value()?;
}
_ => {
match map.next_value::<NetworkArtifact>() {
Ok(artifact) => {
targets.insert(key, artifact);
}
Err(_) => {
}
}
}
}
}
let date = date.ok_or_else(|| de::Error::missing_field("date"))?;
Ok(NetworkZigRelease {
date,
version,
targets,
})
}
}
deserializer.deserialize_map(NetworkZigReleaseVisitor)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CacheZigIndex {
pub releases: Vec<CacheZigRelease>,
pub last_synced: Option<DateTime<Utc>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CacheZigRelease {
pub version: String,
pub date: String,
pub artifacts: Vec<CacheArtifact>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CacheArtifact {
pub target: String,
pub tarball_url: String,
pub shasum: String,
pub size: u64,
}
#[derive(Debug, Clone)]
pub struct ArtifactInfo {
pub ziglang_org_tarball: String,
pub shasum: String,
pub size: u64,
}
#[derive(Debug, Clone)]
pub struct ZigRelease {
version: ResolvedZigVersion,
date: String,
artifacts: HashMap<TargetTriple, ArtifactInfo>,
}
impl ZigRelease {
pub fn is_master(&self) -> bool {
self.version.is_master()
}
pub fn new(
version: ResolvedZigVersion,
date: String,
artifacts: HashMap<TargetTriple, ArtifactInfo>,
) -> Self {
Self {
version,
date,
artifacts,
}
}
pub fn resolved_version(&self) -> &ResolvedZigVersion {
&self.version
}
pub fn date(&self) -> &str {
&self.date
}
pub fn artifacts(&self) -> &HashMap<TargetTriple, ArtifactInfo> {
&self.artifacts
}
pub fn zig_tarball_for_current_host(&self) -> Option<String> {
let host_target_str = host_target()?;
let target_triple = TargetTriple::from_key(&host_target_str)?;
self.zig_tarball_for_target(&target_triple)
}
pub fn zig_tarball_for_target(&self, target: &TargetTriple) -> Option<String> {
if !self.artifacts.contains_key(target) {
return None;
}
let semver_version = match &self.version {
ResolvedZigVersion::Semver(v) => v,
ResolvedZigVersion::Master(v) => v,
};
self.zig_tarball_for_target_and_version(&target.arch, &target.os, semver_version)
}
fn zig_tarball_for_target_and_version(
&self,
arch: &str,
os: &str,
semver_version: &semver::Version,
) -> Option<String> {
let ext = if os == "windows" { "zip" } else { "tar.xz" };
if semver_version.le(&semver::Version::new(0, 14, 0)) {
Some(format!("zig-{os}-{arch}-{semver_version}.{ext}"))
} else {
Some(format!("zig-{arch}-{os}-{semver_version}.{ext}"))
}
}
}
#[derive(Debug, Clone)]
pub struct ZigIndex {
releases: BTreeMap<ResolvedZigVersion, ZigRelease>,
last_synced: Option<DateTime<Utc>>,
}
impl ZigIndex {
pub fn new() -> Self {
Self {
releases: BTreeMap::new(),
last_synced: None,
}
}
pub fn with_releases(
releases: BTreeMap<ResolvedZigVersion, ZigRelease>,
last_synced: Option<DateTime<Utc>>,
) -> Self {
Self {
releases,
last_synced,
}
}
pub fn releases(&self) -> &BTreeMap<ResolvedZigVersion, ZigRelease> {
&self.releases
}
pub fn last_synced(&self) -> Option<DateTime<Utc>> {
self.last_synced
}
pub fn get_latest_stable(&self) -> Option<&ResolvedZigVersion> {
self.releases
.keys()
.rev() .find(|version| {
match version {
ResolvedZigVersion::Semver(v) => {
v.pre.is_empty() && v.build.is_empty()
}
_ => false, }
})
}
}
impl ZigIndex {
pub fn contains_version(&self, version: &semver::Version) -> Option<&ZigRelease> {
let resolved_version = ResolvedZigVersion::Semver(version.clone());
self.releases().get(&resolved_version)
}
pub fn get_latest_stable_release(&self) -> Option<&ZigRelease> {
if let Some(latest_version) = self.get_latest_stable() {
self.releases().get(latest_version)
} else {
None
}
}
pub fn get_master_version(&self) -> Option<&ZigRelease> {
for (version, release) in self.releases() {
if version.is_master() {
return Some(release);
}
}
None
}
pub fn is_expired(&self) -> bool {
if let Some(last_synced) = self.last_synced() {
let age = Utc::now() - last_synced;
age.num_days() >= *INDEX_TTL_DAYS
} else {
true }
}
#[allow(unused)]
fn find_highest_stable_version(&self) -> Option<ResolvedZigVersion> {
self.releases()
.keys()
.filter_map(|resolved_version| {
match resolved_version {
ResolvedZigVersion::Semver(v) => {
if v.pre.is_empty() && v.build.is_empty() {
Some(resolved_version.clone())
} else {
None
}
}
_ => None,
}
})
.max() }
}
impl Default for ZigIndex {
fn default() -> Self {
Self::new()
}
}
impl From<NetworkZigIndex> for ZigIndex {
fn from(network_index: NetworkZigIndex) -> Self {
let mut releases = BTreeMap::new();
for (version_key, network_release) in network_index.releases {
let resolved_version = if version_key == "master" {
if let Some(version_str) = &network_release.version {
match semver::Version::parse(version_str) {
Ok(version) => ResolvedZigVersion::Master(version),
Err(err) => {
tracing::warn!(
"Failed to parse master version: {}, {}",
version_str,
err
);
continue; }
}
} else {
tracing::warn!("Master release found without concrete version, skipping");
continue; }
} else {
match semver::Version::parse(&version_key) {
Ok(version) => ResolvedZigVersion::Semver(version),
Err(err) => {
tracing::warn!("Failed to parse version key: {}, {}", version_key, err);
continue; }
}
};
let mut runtime_artifacts = HashMap::new();
for (target_key, network_artifact) in network_release.targets {
if let Some(target_triple) = TargetTriple::from_key(&target_key) {
let artifact_info = ArtifactInfo {
ziglang_org_tarball: network_artifact.ziglang_org_tarball,
shasum: network_artifact.shasum,
size: network_artifact.size,
};
runtime_artifacts.insert(target_triple, artifact_info);
} else {
tracing::warn!("Failed to parse target key: {}", target_key);
}
}
let runtime_release = ZigRelease::new(
resolved_version.clone(),
network_release.date,
runtime_artifacts,
);
releases.insert(resolved_version, runtime_release);
}
ZigIndex::with_releases(releases, Some(chrono::Utc::now()))
}
}
impl From<&ZigIndex> for CacheZigIndex {
fn from(runtime_index: &ZigIndex) -> Self {
let mut cache_releases = Vec::new();
for (resolved_version, runtime_release) in runtime_index.releases.iter() {
let version_string = match resolved_version {
ResolvedZigVersion::Semver(v) => v.to_string(),
ResolvedZigVersion::Master(v) => format!("master@{}", v),
};
let mut cache_artifacts = Vec::new();
for (target_triple, artifact_info) in runtime_release.artifacts.iter() {
let cache_artifact = CacheArtifact {
target: target_triple.to_key(),
tarball_url: artifact_info.ziglang_org_tarball.clone(),
shasum: artifact_info.shasum.clone(),
size: artifact_info.size,
};
cache_artifacts.push(cache_artifact);
}
cache_artifacts.sort_by(|a, b| a.target.cmp(&b.target));
let cache_release = CacheZigRelease {
version: version_string,
date: runtime_release.date.clone(),
artifacts: cache_artifacts,
};
cache_releases.push(cache_release);
}
cache_releases.sort_by(|a, b| a.version.cmp(&b.version));
CacheZigIndex {
releases: cache_releases,
last_synced: runtime_index.last_synced,
}
}
}
impl From<CacheZigIndex> for ZigIndex {
fn from(cache_index: CacheZigIndex) -> Self {
let mut releases = BTreeMap::new();
for cache_release in cache_index.releases {
let resolved_version =
if let Some(version_str) = cache_release.version.strip_prefix("master@") {
match semver::Version::parse(version_str) {
Ok(version) => ResolvedZigVersion::Master(version),
Err(e) => {
tracing::warn!(
"Failed to parse cached master version '{}': {}",
version_str,
e
);
continue; }
}
} else {
match semver::Version::parse(&cache_release.version) {
Ok(version) => ResolvedZigVersion::Semver(version),
Err(e) => {
tracing::warn!(
"Failed to parse cached version '{}': {}",
cache_release.version,
e
);
continue; }
}
};
let mut runtime_artifacts = HashMap::new();
for cache_artifact in cache_release.artifacts {
if let Some(target_triple) = TargetTriple::from_key(&cache_artifact.target) {
let artifact_info = ArtifactInfo {
ziglang_org_tarball: cache_artifact.tarball_url,
shasum: cache_artifact.shasum,
size: cache_artifact.size,
};
runtime_artifacts.insert(target_triple, artifact_info);
} else {
tracing::warn!(
"Failed to parse cached target key: {}",
cache_artifact.target
);
}
}
let runtime_release = ZigRelease::new(
resolved_version.clone(),
cache_release.date,
runtime_artifacts,
);
releases.insert(resolved_version, runtime_release);
}
ZigIndex::with_releases(releases, cache_index.last_synced)
}
}