use std::collections::BTreeMap; use std::fmt::Display; use std::path::PathBuf; use anyhow::Context; use cargo_metadata::semver::Version; use serde::{Deserialize, Serialize}; use super::Edition; use crate::common::assets::{Asset, AssetFormat}; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Release { #[serde(default)] pub edition: Edition, pub version: Version, } impl Display for Release { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "v{}", self.version)?; match self.edition { Edition::Community => f.write_str(" Community Edition"), Edition::Enterprise => f.write_str(" Enterprise Edition"), } } } impl Release { pub fn binary_path(&self, asset_folder: &str) -> anyhow::Result { let mut asset_folder: PathBuf = asset_folder .parse() .with_context(|| format!("parsing asset folder `{asset_folder}` as a path"))?; asset_folder.push(self.local_filename()?); Ok(asset_folder) } fn local_filename(&self) -> anyhow::Result { let version = &self.version; let arch = get_arch()?; let base = self.edition.binary_base(); Ok(format!("{base}-{version}-{arch}")) } fn remote_filename(&self) -> anyhow::Result { let arch = get_arch()?; let base = self.edition.binary_base(); Ok(format!("{base}-{arch}")) } async fn fetch_sha256(&self) -> anyhow::Result { let version = &self.version; let asset_name = self.remote_filename()?; // If version is lower than 1.15 there is no point in trying to get the sha256, GitHub didn't support it if *version < Version::parse("1.15.0")? { anyhow::bail!("version is lower than 1.15, sha256 not available"); } #[derive(Deserialize)] struct GithubReleaseAsset { name: String, digest: Option, } #[derive(Deserialize)] struct GithubRelease { assets: Vec, } let url = format!( "https://api.github.com/repos/meilisearch/meilisearch/releases/tags/v{version}" ); let client = reqwest::Client::builder() .user_agent("Meilisearch bench xtask") .build() .context("failed to build reqwest client")?; let body = client.get(url).send().await?.text().await?; let data: GithubRelease = serde_json::from_str(&body)?; let digest = data .assets .into_iter() .find(|asset| asset.name.as_str() == asset_name.as_str()) .with_context(|| format!("asset {asset_name} not found in release {self}"))? .digest .with_context(|| format!("asset {asset_name} has no digest"))?; let sha256 = digest .strip_prefix("sha256:") .map(|s| s.to_string()) .context("invalid sha256 format")?; Ok(sha256) } async fn add_asset(&self, assets: &mut BTreeMap) -> anyhow::Result<()> { let local_filename = self.local_filename()?; let version = &self.version; if assets.contains_key(&local_filename) { return Ok(()); } let remote_filename = self.remote_filename()?; // Try to get the sha256 but it may fail if Github is rate limiting us // We hardcode some values to speed up tests and avoid hitting Github // Also, versions prior to 1.15 don't have sha256 available anyway let sha256 = match local_filename.as_str() { "meilisearch-1.12.0-macos-apple-silicon" => { Some("3b384707a5df9edf66f9157f0ddb70dcd3ac84d4887149169cf93067d06717b7".into()) } "meilisearch-1.12.0-linux-amd64" => { Some("865a3fc222e3b3bd1f4b64346cb114b9669af691aae28d71fa68dbf39427abcf".into()) } _ => match self.fetch_sha256().await { Ok(sha256) => Some(sha256), Err(err) => { tracing::warn!("failed to get sha256 for release {self}: {err}"); None } }, }; let url = format!( "https://github.com/meilisearch/meilisearch/releases/download/v{version}/{remote_filename}" ); let asset = Asset { local_location: Some(local_filename.clone()), remote_location: Some(url), format: AssetFormat::Raw, sha256, }; assets.insert(local_filename, asset); Ok(()) } } pub fn get_arch() -> anyhow::Result<&'static str> { // linux-aarch64 #[cfg(all(target_os = "linux", target_arch = "aarch64"))] { Ok("linux-aarch64") } // linux-amd64 #[cfg(all(target_os = "linux", target_arch = "x86_64"))] { Ok("linux-amd64") } // macos-amd64 #[cfg(all(target_os = "macos", target_arch = "x86_64"))] { Ok("macos-amd64") } // macos-apple-silicon #[cfg(all(target_os = "macos", target_arch = "aarch64"))] { Ok("macos-apple-silicon") } // windows-amd64 #[cfg(all(target_os = "windows", target_arch = "x86_64"))] { Ok("windows-amd64") } #[cfg(not(all(target_os = "windows", target_arch = "x86_64")))] #[cfg(not(all(target_os = "linux", target_arch = "aarch64")))] #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))] #[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] anyhow::bail!("unsupported platform") } pub async fn add_releases_to_assets( assets: &mut BTreeMap, releases: impl IntoIterator, ) -> anyhow::Result<()> { for release in releases { release.add_asset(assets).await?; } Ok(()) }