mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-09 03:51:47 +00:00
* feat(update): add craft-update crate for release checking, install, and self-updating * fix(update): provide non-windows stub for finish * fix(update): address CodeRabbit review feedback on craft-update * feat(dialog,self_update): enhance prompt styling, support standalone self-updates * fix(update): make startup prompt modal with Escape, skip non-file tar entries
424 lines
18 KiB
Rust
424 lines
18 KiB
Rust
//! Platforms, package formats, and choosing the right release asset.
|
|
//!
|
|
//! Every Craft App release names its files `<app>-<version>-<os>-<arch>[-portable].<ext>`, for
|
|
//! example `photocraft-0.5.0-windows-x64.msi` or `photocraft-0.5.0-linux-aarch64.AppImage`.
|
|
//!
|
|
//! Labels are English. The UI translates [`PackageKind::label`]; OS and architecture names are
|
|
//! shown as they are.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::release::{Asset, Release};
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub enum Os {
|
|
Windows,
|
|
MacOs,
|
|
Linux,
|
|
FreeBsd,
|
|
}
|
|
|
|
impl Os {
|
|
pub fn label(self) -> &'static str {
|
|
match self {
|
|
Self::Windows => "Windows",
|
|
Self::MacOs => "macOS",
|
|
Self::Linux => "Linux",
|
|
Self::FreeBsd => "FreeBSD",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub enum Arch {
|
|
X64,
|
|
X86,
|
|
Arm64,
|
|
Riscv64,
|
|
}
|
|
|
|
impl Arch {
|
|
pub fn label(self) -> &'static str {
|
|
match self {
|
|
Self::X64 => "x64",
|
|
Self::X86 => "x86",
|
|
Self::Arm64 => "ARM64",
|
|
Self::Riscv64 => "RISC-V",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A package format a release ships.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub enum PackageKind {
|
|
/// Windows Installer package (installs for all users into Program Files).
|
|
Msi,
|
|
/// Windows zip with the exe at the top level.
|
|
PortableZip,
|
|
/// macOS disk image with the `.app` bundle.
|
|
Dmg,
|
|
AppImage,
|
|
Deb,
|
|
Rpm,
|
|
/// Arch Linux package (`.pkg.tar.zst`).
|
|
ArchPkg,
|
|
Flatpak,
|
|
/// Linux or FreeBSD tarball (`bin/<app>`, `share/…`).
|
|
TarGz,
|
|
}
|
|
|
|
impl PackageKind {
|
|
pub fn label(self) -> &'static str {
|
|
match self {
|
|
Self::Msi => "Installer (.msi)",
|
|
Self::PortableZip => "Portable (.zip)",
|
|
Self::Dmg => "Disk image (.dmg)",
|
|
Self::AppImage => "AppImage",
|
|
Self::Deb => "Debian package (.deb)",
|
|
Self::Rpm => "RPM package (.rpm)",
|
|
Self::ArchPkg => "Arch package (.pkg.tar.zst)",
|
|
Self::Flatpak => "Flatpak bundle",
|
|
Self::TarGz => "Tarball (.tar.gz)",
|
|
}
|
|
}
|
|
|
|
fn suffix(self) -> &'static str {
|
|
match self {
|
|
Self::Msi => ".msi",
|
|
Self::PortableZip => "-portable.zip",
|
|
Self::Dmg => ".dmg",
|
|
Self::AppImage => ".AppImage",
|
|
Self::Deb => ".deb",
|
|
Self::Rpm => ".rpm",
|
|
Self::ArchPkg => ".pkg.tar.zst",
|
|
Self::Flatpak => ".flatpak",
|
|
Self::TarGz => ".tar.gz",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Where the launcher is running (or, on the web, the visitor's machine).
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub struct Platform {
|
|
pub os: Os,
|
|
pub arch: Arch,
|
|
}
|
|
|
|
impl Platform {
|
|
/// The platform this binary was built for, or `None` for wasm and unsupported targets.
|
|
pub fn current() -> Option<Self> {
|
|
let os = if cfg!(target_os = "windows") {
|
|
Os::Windows
|
|
} else if cfg!(target_os = "macos") {
|
|
Os::MacOs
|
|
} else if cfg!(target_os = "linux") {
|
|
Os::Linux
|
|
} else if cfg!(target_os = "freebsd") {
|
|
Os::FreeBsd
|
|
} else {
|
|
return None;
|
|
};
|
|
let arch = if cfg!(target_arch = "x86_64") {
|
|
Arch::X64
|
|
} else if cfg!(target_arch = "x86") {
|
|
Arch::X86
|
|
} else if cfg!(target_arch = "aarch64") {
|
|
Arch::Arm64
|
|
} else if cfg!(target_arch = "riscv64") {
|
|
Arch::Riscv64
|
|
} else {
|
|
return None;
|
|
};
|
|
Some(Self { os, arch })
|
|
}
|
|
|
|
pub fn label(self) -> String {
|
|
match self.os {
|
|
Os::MacOs => "macOS".into(),
|
|
os => format!("{} {}", os.label(), self.arch.label()),
|
|
}
|
|
}
|
|
|
|
/// Asset platform tokens to try, best first. Windows on ARM and 64-bit Windows can fall back
|
|
/// to emulated builds; macOS prefers universal, then native builds. Apple silicon can fall
|
|
/// back to Intel builds, but Intel Macs cannot run ARM builds.
|
|
fn tokens(self) -> &'static [&'static str] {
|
|
match (self.os, self.arch) {
|
|
(Os::Windows, Arch::X64) => &["windows-x64", "windows-x86"],
|
|
(Os::Windows, Arch::X86) => &["windows-x86"],
|
|
(Os::Windows, Arch::Arm64) => &["windows-arm64", "windows-x64", "windows-x86"],
|
|
(Os::Windows, _) => &[],
|
|
(Os::MacOs, Arch::X64) => &["macos-universal", "macos-x86_64"],
|
|
(Os::MacOs, Arch::Arm64) => &["macos-universal", "macos-aarch64", "macos-arm64", "macos-x86_64"],
|
|
(Os::MacOs, _) => &[],
|
|
(Os::Linux, Arch::X64) => &["linux-x86_64"],
|
|
(Os::Linux, Arch::Arm64) => &["linux-aarch64"],
|
|
(Os::Linux, Arch::Riscv64) => &["linux-riscv64"],
|
|
(Os::Linux, Arch::X86) => &[],
|
|
(Os::FreeBsd, Arch::X64) => &["freebsd-x86_64"],
|
|
(Os::FreeBsd, Arch::Arm64) => &["freebsd-aarch64"],
|
|
(Os::FreeBsd, _) => &[],
|
|
}
|
|
}
|
|
|
|
/// Package formats the launcher installs on this OS, in order of preference.
|
|
pub fn default_kinds(self) -> &'static [PackageKind] {
|
|
match self.os {
|
|
Os::Windows => &[PackageKind::Msi, PackageKind::PortableZip],
|
|
Os::MacOs => &[PackageKind::Dmg],
|
|
Os::Linux => &[PackageKind::AppImage, PackageKind::TarGz],
|
|
Os::FreeBsd => &[PackageKind::TarGz],
|
|
}
|
|
}
|
|
|
|
/// Every format offered as a manual download on this OS (the web build's download menu).
|
|
pub fn download_kinds(self) -> &'static [PackageKind] {
|
|
match self.os {
|
|
Os::Windows => &[PackageKind::Msi, PackageKind::PortableZip],
|
|
Os::MacOs => &[PackageKind::Dmg],
|
|
Os::Linux => &[PackageKind::AppImage, PackageKind::Deb, PackageKind::Rpm, PackageKind::ArchPkg, PackageKind::Flatpak, PackageKind::TarGz],
|
|
Os::FreeBsd => &[PackageKind::TarGz],
|
|
}
|
|
}
|
|
}
|
|
|
|
/// What an asset name says about itself.
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct AssetName {
|
|
pub stem: String,
|
|
pub version: String,
|
|
pub token: String,
|
|
pub kind: PackageKind,
|
|
}
|
|
|
|
/// Reads `<stem>-<version>-<os>-<arch>[-portable].<ext>`; `None` for checksums, zsync files, CLI
|
|
/// and web bundles, or anything else.
|
|
pub fn parse_asset_name(name: &str, stems: &[&str]) -> Option<AssetName> {
|
|
const KINDS: [PackageKind; 9] = [
|
|
PackageKind::PortableZip,
|
|
PackageKind::TarGz,
|
|
PackageKind::Msi,
|
|
PackageKind::Dmg,
|
|
PackageKind::AppImage,
|
|
PackageKind::Deb,
|
|
PackageKind::Rpm,
|
|
PackageKind::ArchPkg,
|
|
PackageKind::Flatpak,
|
|
];
|
|
let (kind, base) = KINDS.iter().find_map(|k| name.strip_suffix(k.suffix()).map(|b| (*k, b)))?;
|
|
for stem in stems {
|
|
let Some(rest) = base.strip_prefix(stem).and_then(|r| r.strip_prefix('-')) else { continue };
|
|
// rest = <version>-<os>-<arch>; os and arch never contain '-', versions may.
|
|
let mut parts = rest.rsplitn(3, '-');
|
|
let (arch, os, version) = (parts.next()?, parts.next()?, parts.next()?);
|
|
if crate::version::parse(version).is_none() || !version.starts_with(|c: char| c.is_ascii_digit()) {
|
|
continue;
|
|
}
|
|
return Some(AssetName { stem: (*stem).to_string(), version: version.to_string(), token: format!("{os}-{arch}"), kind });
|
|
}
|
|
None
|
|
}
|
|
|
|
/// The best asset of `release` for `platform`, trying `kinds` in order and, within a kind, the
|
|
/// platform's tokens in order (native before emulated).
|
|
pub fn pick_asset<'a>(release: &'a Release, stems: &[&str], platform: Platform, kinds: &[PackageKind]) -> Option<(&'a Asset, PackageKind)> {
|
|
let parsed: Vec<(&Asset, AssetName)> = release.assets.iter().filter_map(|a| parse_asset_name(&a.name, stems).map(|n| (a, n))).collect();
|
|
for kind in kinds {
|
|
for token in platform.tokens() {
|
|
if let Some((asset, _)) = parsed.iter().find(|(_, n)| n.kind == *kind && n.token == *token) {
|
|
return Some((asset, *kind));
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn release(names: &[&str]) -> Release {
|
|
Release {
|
|
assets: names.iter().map(|n| Asset { name: (*n).into(), size: 1, url: format!("https://x/{n}"), sha256: None }).collect(),
|
|
..Release::default()
|
|
}
|
|
}
|
|
|
|
const PHOTO: &[&str] = &[
|
|
"photocraft-0.5.0-freebsd-x86_64.tar.gz",
|
|
"photocraft-0.5.0-linux-aarch64.AppImage",
|
|
"photocraft-0.5.0-linux-aarch64.AppImage.zsync",
|
|
"photocraft-0.5.0-linux-aarch64.deb",
|
|
"photocraft-0.5.0-linux-riscv64.tar.gz",
|
|
"photocraft-0.5.0-linux-x86_64.AppImage",
|
|
"photocraft-0.5.0-linux-x86_64.tar.gz",
|
|
"photocraft-0.5.0-macos-universal.dmg",
|
|
"photocraft-0.5.0-windows-x64-portable.zip",
|
|
"photocraft-0.5.0-windows-x64.msi",
|
|
"photocraft-0.5.0-windows-x86.msi",
|
|
"photocraft-cli-0.5.0-macos-universal.zip",
|
|
"photocraft-web-0.5.0.zip",
|
|
"SHA256SUMS.txt",
|
|
];
|
|
|
|
#[test]
|
|
fn macos_prefers_the_published_universal_build() {
|
|
// PhotoCraft v0.9.0's GitHub metadata, trimmed to its desktop DMG.
|
|
let mut r = crate::release::parse_github_release(include_str!("../tests/fixtures/photocraft-release.json"), "storytold/photocraft").unwrap();
|
|
// Split builds must not outrank the published universal DMG, even when listed first.
|
|
r.assets.splice(0..0, release(&["photocraft-0.9.0-macos-arm64.dmg", "photocraft-0.9.0-macos-x86_64.dmg"]).assets);
|
|
for arch in [Arch::X64, Arch::Arm64] {
|
|
let (asset, kind) = pick_asset(&r, &["photocraft"], Platform { os: Os::MacOs, arch }, &[PackageKind::Dmg]).unwrap();
|
|
assert_eq!(asset.name, "photocraft-0.9.0-macos-universal.dmg");
|
|
assert_eq!(kind, PackageKind::Dmg);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn freebsd_picks_the_tarball_for_the_processor() {
|
|
// SoundCraft v0.10.0's GitHub metadata, trimmed to its FreeBSD tarballs.
|
|
let r = crate::release::parse_github_release(include_str!("../tests/fixtures/soundcraft-release.json"), "storytold/soundcraft").unwrap();
|
|
let pick = |arch| {
|
|
let platform = Platform { os: Os::FreeBsd, arch };
|
|
pick_asset(&r, &["soundcraft"], platform, platform.default_kinds()).map(|(a, k)| (a.name.as_str(), k))
|
|
};
|
|
assert_eq!(pick(Arch::X64), Some(("soundcraft-0.10.0-freebsd-x86_64.tar.gz", PackageKind::TarGz)));
|
|
assert_eq!(pick(Arch::Arm64), Some(("soundcraft-0.10.0-freebsd-aarch64.tar.gz", PackageKind::TarGz)));
|
|
for arch in [Arch::X86, Arch::Riscv64] {
|
|
assert_eq!(pick(arch), None);
|
|
}
|
|
// An x86_64 machine never takes the ARM tarball, nor an ARM machine the x86_64 one.
|
|
let only_arm = release(&["soundcraft-0.10.0-freebsd-aarch64.tar.gz"]);
|
|
assert_eq!(pick_asset(&only_arm, &["soundcraft"], Platform { os: Os::FreeBsd, arch: Arch::X64 }, &[PackageKind::TarGz]), None);
|
|
let only_x64 = release(&["soundcraft-0.10.0-freebsd-x86_64.tar.gz"]);
|
|
assert_eq!(pick_asset(&only_x64, &["soundcraft"], Platform { os: Os::FreeBsd, arch: Arch::Arm64 }, &[PackageKind::TarGz]), None);
|
|
}
|
|
|
|
#[test]
|
|
fn macos_split_builds_match_the_processor() {
|
|
let r = release(&["photocraft-0.5.0-macos-arm64.dmg", "photocraft-0.5.0-macos-x86_64.dmg"]);
|
|
let pick = |arch| pick_asset(&r, &["photocraft"], Platform { os: Os::MacOs, arch }, &[PackageKind::Dmg]).map(|(a, _)| a.name.as_str());
|
|
assert_eq!(pick(Arch::X64), Some("photocraft-0.5.0-macos-x86_64.dmg"));
|
|
assert_eq!(pick(Arch::Arm64), Some("photocraft-0.5.0-macos-arm64.dmg"));
|
|
}
|
|
|
|
#[test]
|
|
fn intel_macos_never_selects_an_arm_only_build() {
|
|
for token in ["macos-aarch64", "macos-arm64"] {
|
|
let name = format!("photocraft-0.5.0-{token}.dmg");
|
|
let r = release(&[&name]);
|
|
assert_eq!(pick_asset(&r, &["photocraft"], Platform { os: Os::MacOs, arch: Arch::X64 }, &[PackageKind::Dmg]), None);
|
|
assert_eq!(
|
|
pick_asset(&r, &["photocraft"], Platform { os: Os::MacOs, arch: Arch::Arm64 }, &[PackageKind::Dmg]).map(|(a, _)| a.name.as_str()),
|
|
Some(name.as_str())
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn apple_silicon_keeps_the_intel_fallback() {
|
|
let r = release(&["photocraft-0.5.0-macos-x86_64.dmg"]);
|
|
assert_eq!(
|
|
pick_asset(&r, &["photocraft"], Platform { os: Os::MacOs, arch: Arch::Arm64 }, &[PackageKind::Dmg]).map(|(a, _)| a.name.as_str()),
|
|
Some("photocraft-0.5.0-macos-x86_64.dmg")
|
|
);
|
|
for arch in [Arch::X86, Arch::Riscv64] {
|
|
assert_eq!(pick_asset(&r, &["photocraft"], Platform { os: Os::MacOs, arch }, &[PackageKind::Dmg]), None);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn reads_asset_names() {
|
|
let n = parse_asset_name("photocraft-0.2.0-rc.1-windows-arm64-portable.zip", &["photocraft"]).unwrap();
|
|
assert_eq!((n.version.as_str(), n.token.as_str(), n.kind), ("0.2.0-rc.1", "windows-arm64", PackageKind::PortableZip));
|
|
let n = parse_asset_name("printcraft-0.2.1-linux-x86_64.tar.gz", &["pdfcraft", "printcraft"]).unwrap();
|
|
assert_eq!((n.stem.as_str(), n.kind), ("printcraft", PackageKind::TarGz));
|
|
for other in
|
|
["photocraft-cli-0.5.0-macos-universal.zip", "photocraft-web-0.5.0.zip", "SHA256SUMS.txt", "photocraft-0.5.0-linux-x86_64.AppImage.zsync"]
|
|
{
|
|
assert_eq!(parse_asset_name(other, &["photocraft"]), None, "{other}");
|
|
}
|
|
// FigCraft also ships its collaboration server; that tarball is not the app.
|
|
assert_eq!(parse_asset_name("figcraft-server-0.5.1-linux-x86_64.tar.gz", &["figcraft"]), None);
|
|
assert!(parse_asset_name("figcraft-0.5.1-linux-x86_64.tar.gz", &["figcraft"]).is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn picks_the_native_build_first_and_falls_back_to_emulation() {
|
|
let r = release(PHOTO);
|
|
let pick = |os, arch, kinds: &[PackageKind]| pick_asset(&r, &["photocraft"], Platform { os, arch }, kinds).map(|(a, _)| a.name.clone());
|
|
assert_eq!(pick(Os::MacOs, Arch::Arm64, &[PackageKind::Dmg]).as_deref(), Some("photocraft-0.5.0-macos-universal.dmg"));
|
|
assert_eq!(pick(Os::Windows, Arch::X64, &[PackageKind::Msi]).as_deref(), Some("photocraft-0.5.0-windows-x64.msi"));
|
|
// No arm64 MSI in this release: Windows on ARM runs the x64 build.
|
|
assert_eq!(pick(Os::Windows, Arch::Arm64, &[PackageKind::Msi]).as_deref(), Some("photocraft-0.5.0-windows-x64.msi"));
|
|
assert_eq!(pick(Os::Linux, Arch::Arm64, &[PackageKind::AppImage]).as_deref(), Some("photocraft-0.5.0-linux-aarch64.AppImage"));
|
|
assert_eq!(pick(Os::Linux, Arch::Arm64, &[PackageKind::TarGz]), None);
|
|
assert_eq!(pick(Os::Linux, Arch::Riscv64, &[PackageKind::TarGz]).as_deref(), Some("photocraft-0.5.0-linux-riscv64.tar.gz"));
|
|
assert_eq!(pick(Os::FreeBsd, Arch::X64, &[PackageKind::TarGz]).as_deref(), Some("photocraft-0.5.0-freebsd-x86_64.tar.gz"));
|
|
// Preference order across kinds.
|
|
assert_eq!(
|
|
pick(Os::Windows, Arch::X64, &[PackageKind::PortableZip, PackageKind::Msi]).as_deref(),
|
|
Some("photocraft-0.5.0-windows-x64-portable.zip")
|
|
);
|
|
assert_eq!(pick(Os::Windows, Arch::X86, &[PackageKind::PortableZip, PackageKind::Msi]).as_deref(), Some("photocraft-0.5.0-windows-x86.msi"));
|
|
|
|
let p_riscv = Platform { os: Os::Linux, arch: Arch::Riscv64 };
|
|
assert_eq!(
|
|
pick_asset(&r, &["photocraft"], p_riscv, p_riscv.default_kinds()).map(|(a, _)| a.name.clone()).as_deref(),
|
|
Some("photocraft-0.5.0-linux-riscv64.tar.gz")
|
|
);
|
|
assert_eq!(
|
|
pick_asset(&r, &["photocraft"], p_riscv, p_riscv.download_kinds()).map(|(a, _)| a.name.clone()).as_deref(),
|
|
Some("photocraft-0.5.0-linux-riscv64.tar.gz")
|
|
);
|
|
let r_riscv_deb =
|
|
release(&["photocraft-0.5.0-linux-riscv64.tar.gz", "photocraft-0.5.0-linux-riscv64.deb", "photocraft-0.5.0-linux-riscv64.rpm"]);
|
|
assert_eq!(
|
|
pick_asset(&r_riscv_deb, &["photocraft"], p_riscv, p_riscv.download_kinds()).map(|(a, _)| a.name.clone()).as_deref(),
|
|
Some("photocraft-0.5.0-linux-riscv64.deb")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn strictly_matches_architecture_for_all_linux_package_formats() {
|
|
let multi_arch_release = release(&[
|
|
"photocraft-0.5.0-linux-aarch64.AppImage",
|
|
"photocraft-0.5.0-linux-x86_64.AppImage",
|
|
"photocraft-0.5.0-linux-aarch64.deb",
|
|
"photocraft-0.5.0-linux-x86_64.deb",
|
|
"photocraft-0.5.0-linux-aarch64.rpm",
|
|
"photocraft-0.5.0-linux-x86_64.rpm",
|
|
"photocraft-0.5.0-linux-aarch64.pkg.tar.zst",
|
|
"photocraft-0.5.0-linux-x86_64.pkg.tar.zst",
|
|
"photocraft-0.5.0-linux-aarch64.flatpak",
|
|
"photocraft-0.5.0-linux-x86_64.flatpak",
|
|
"photocraft-0.5.0-linux-aarch64.tar.gz",
|
|
"photocraft-0.5.0-linux-x86_64.tar.gz",
|
|
]);
|
|
|
|
let p_x64 = Platform { os: Os::Linux, arch: Arch::X64 };
|
|
let p_arm64 = Platform { os: Os::Linux, arch: Arch::Arm64 };
|
|
|
|
for kind in [PackageKind::AppImage, PackageKind::Deb, PackageKind::Rpm, PackageKind::ArchPkg, PackageKind::Flatpak, PackageKind::TarGz] {
|
|
let pick_x64 = pick_asset(&multi_arch_release, &["photocraft"], p_x64, &[kind]).map(|(a, _)| a.name.clone());
|
|
assert!(pick_x64.as_deref().unwrap().contains("linux-x86_64"), "Expected x86_64 asset for {kind:?}, got {pick_x64:?}");
|
|
|
|
let pick_arm64 = pick_asset(&multi_arch_release, &["photocraft"], p_arm64, &[kind]).map(|(a, _)| a.name.clone());
|
|
assert!(pick_arm64.as_deref().unwrap().contains("linux-aarch64"), "Expected aarch64 asset for {kind:?}, got {pick_arm64:?}");
|
|
}
|
|
|
|
// On x86_64 Linux, an aarch64-only asset must NEVER match or fall back
|
|
let arm_only_release = release(&[
|
|
"photocraft-0.5.0-linux-aarch64.AppImage",
|
|
"photocraft-0.5.0-linux-aarch64.deb",
|
|
"photocraft-0.5.0-linux-aarch64.rpm",
|
|
"photocraft-0.5.0-linux-aarch64.pkg.tar.zst",
|
|
"photocraft-0.5.0-linux-aarch64.flatpak",
|
|
"photocraft-0.5.0-linux-aarch64.tar.gz",
|
|
]);
|
|
for kind in [PackageKind::AppImage, PackageKind::Deb, PackageKind::Rpm, PackageKind::ArchPkg, PackageKind::Flatpak, PackageKind::TarGz] {
|
|
assert_eq!(pick_asset(&arm_only_release, &["photocraft"], p_x64, &[kind]), None, "x86_64 must never match an aarch64 asset for {kind:?}");
|
|
}
|
|
}
|
|
}
|