diff --git a/Cargo.toml b/Cargo.toml index 076c0fb..296494c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ test-log = "0.2" flate2 = "1.0" walkdir = "2.5" indicatif = { version = "0.17", features = ["improved_unicode"] } +regex = "1.5" [dev-dependencies] pretty_assertions = "1.4" diff --git a/README.md b/README.md index f5e1058..5b6b854 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,7 @@ cargo test - Windows is not supported because Pact does not support Windows anyway. - The Pact binaries are problematic; they are not consistent in each release, and often, releases are missing binaries. For example, the latest release, 4.12, does not have any Mac binaries on GitHub. Expect some issues with this. +- Some older versions might require older system libs (eg. libncurses5). ## Credit diff --git a/src/arch.rs b/src/arch.rs deleted file mode 100644 index 91a303f..0000000 --- a/src/arch.rs +++ /dev/null @@ -1,76 +0,0 @@ -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum Arch { - X86, - X64, - Arm64, - Armv7l, - Ppc64le, - Ppc64, - S390x, -} - -impl Default for Arch { - fn default() -> Arch { - match crate::system_info::platform_arch().parse() { - Ok(arch) => arch, - Err(e) => panic!("{}", e.details), - } - } -} - -impl std::str::FromStr for Arch { - type Err = ArchError; - fn from_str(s: &str) -> Result { - match s { - "x86" => Ok(Arch::X86), - "x64" => Ok(Arch::X64), - "arm64" => Ok(Arch::Arm64), - "armv7l" => Ok(Arch::Armv7l), - "ppc64le" => Ok(Arch::Ppc64le), - "ppc64" => Ok(Arch::Ppc64), - "s390x" => Ok(Arch::S390x), - unknown => Err(ArchError::new(&format!("Unknown Arch: {unknown}"))), - } - } -} - -impl std::fmt::Display for Arch { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let arch_str = match self { - Arch::X86 => String::from("x86"), - Arch::X64 => String::from("x64"), - Arch::Arm64 => String::from("arm64"), - Arch::Armv7l => String::from("armv7l"), - Arch::Ppc64le => String::from("ppc64le"), - Arch::Ppc64 => String::from("ppc64"), - Arch::S390x => String::from("s390x"), - }; - - write!(f, "{arch_str}") - } -} - -#[derive(Debug)] -pub struct ArchError { - details: String, -} - -impl ArchError { - fn new(msg: &str) -> ArchError { - ArchError { - details: msg.to_string(), - } - } -} - -impl std::fmt::Display for ArchError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.details) - } -} - -impl std::error::Error for ArchError { - fn description(&self) -> &str { - &self.details - } -} diff --git a/src/commands/install.rs b/src/commands/install.rs index 2d5d46e..3e383f1 100644 --- a/src/commands/install.rs +++ b/src/commands/install.rs @@ -144,7 +144,7 @@ impl Command for Install { ); match install_pact_dist( version, - &release.download_url(&config.arch), + &release.download_url(), config.installations_dir(), &config.arch, show_progress, diff --git a/src/commands/ls_remote.rs b/src/commands/ls_remote.rs index 7888c77..c543822 100644 --- a/src/commands/ls_remote.rs +++ b/src/commands/ls_remote.rs @@ -87,6 +87,10 @@ impl super::command::Command for LsRemote { print!("{}", " (nightly)".to_string().cyan()); } + if !version.has_supported_asset() { + print!("{}", " (can't install)".to_string().red()); + } + println!(); } diff --git a/src/config.rs b/src/config.rs index f1cf2e4..b23af98 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,7 @@ use crate::log_level::LogLevel; use crate::path_ext::PathExt; use crate::version_file_strategy::VersionFileStrategy; -use crate::{arch::Arch, directories::Directories}; +use crate::{directories::Directories, system_info::Arch}; #[derive(clap::Parser, Debug)] pub struct PactupConfig { diff --git a/src/downloader.rs b/src/downloader.rs index 9feed6c..bff8ec2 100644 --- a/src/downloader.rs +++ b/src/downloader.rs @@ -1,9 +1,9 @@ -use crate::arch::Arch; use crate::archive; use crate::archive::extract::ArchiveType; use crate::archive::{Error as ExtractError, Extract}; use crate::directory_portal::DirectoryPortal; use crate::progress::ResponseProgress; +use crate::system_info::Arch; use crate::version::Version; use indicatif::ProgressDrawTarget; use log::debug; diff --git a/src/main.rs b/src/main.rs index c864574..fd07bdc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,6 @@ )] mod alias; -mod arch; mod archive; mod choose_version_for_user_input; mod cli; diff --git a/src/remote_pact_index.rs b/src/remote_pact_index.rs index adc7957..452b001 100644 --- a/src/remote_pact_index.rs +++ b/src/remote_pact_index.rs @@ -1,159 +1,96 @@ -use crate::arch::Arch; -use crate::version::Version; +use crate::{ + system_info::{get_platform, Arch, Platform}, + version::Version, +}; use chrono::{DateTime, Utc}; +use regex::Regex; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub struct Uploader { - pub name: Option, - pub email: Option, - pub login: String, - pub id: usize, - pub node_id: String, - pub avatar_url: Url, - pub gravatar_id: Option, - pub url: Url, - pub html_url: Url, - pub followers_url: Url, - pub following_url: Url, - pub gists_url: Url, - pub starred_url: Url, - pub subscriptions_url: Url, - pub organizations_url: Url, - pub repos_url: Url, - pub events_url: Url, - pub received_events_url: Url, - pub r#type: String, - pub site_admin: bool, - pub starred_at: Option, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Author { - pub login: String, - pub id: usize, - pub node_id: String, - pub avatar_url: Url, - pub gravatar_id: String, - pub url: Url, - pub html_url: Url, - pub followers_url: Url, - pub following_url: Url, - pub gists_url: Url, - pub starred_url: Url, - pub subscriptions_url: Url, - pub organizations_url: Url, - pub repos_url: Url, - pub events_url: Url, - pub received_events_url: Url, - pub r#type: String, - pub site_admin: bool, - pub patch_url: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub email: Option, -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub struct Asset { pub url: Url, pub browser_download_url: Url, pub id: usize, - pub node_id: String, pub name: String, - pub label: Option, - pub state: String, - pub content_type: String, pub size: i64, - pub download_count: i64, pub created_at: DateTime, pub updated_at: DateTime, - pub uploader: Option, } +/// The Release struct holds release information from the repository. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub struct Release { - pub url: Url, - pub html_url: Url, - pub assets_url: Url, - pub upload_url: String, - pub tarball_url: Option, - pub zipball_url: Option, - pub id: usize, - pub node_id: String, pub tag_name: Version, - pub target_commitish: String, - pub name: Option, - pub body: Option, - pub draft: bool, - pub prerelease: bool, - pub created_at: Option>, - pub published_at: Option>, - pub author: Option, pub assets: Vec, + pub prerelease: bool, + pub draft: bool, } -impl Release { - #[cfg(target_os = "linux")] - pub fn filename_for_version(&self, _arch: &Arch) -> String { - let version = &self.tag_name; - if version.is_nightly() { - "pact-binary-bundle.ubuntu-latest".to_string() - } else { - format!( - "pact-{pact_ver}-linux-22.04", - pact_ver = &version.digits_only().unwrap(), - // platform = crate::system_info::platform_name(), - // arch = arch, - ) - } - } - #[cfg(target_os = "macos")] - fn filename_for_version(&self, arch: &Arch) -> String { +impl Release { + /// Infers the current architecture and platform, and returns the appropriate version matcher. + pub fn version_matcher(&self) -> Result { let version = &self.tag_name; - if version.is_nightly() { - match arch { - Arch::X64 => "pact-binary-bundle.macos-latest".to_string(), - Arch::Arm64 => "pact-binary-bundle.macos-m1".to_string(), - _ => unimplemented!(), + let platform = get_platform(); + match platform { + Platform { + os: "linux", + arch: Arch::X64, + } => { + let regex = if version.is_nightly() { + // match the nightly version format for linux pact-binary-bundle.ubuntu-*. + r"pact-binary-bundle\.(ubuntu-latest)\.(tar\.gz|zip)$" + } else { + // match the stable version format for linux pact---. + r"^pact-(\d+(\.\d+){0,2})(-(linux|ubuntu))?(-\d+\.\d+)?\.(tar\.gz|zip)$" + }; + Regex::new(regex).map_err(|e| format!("Regex creation error: {e}")) } - } else { - match arch { - Arch::X64 => format!( - "pact-{pact_ver}-osx", - pact_ver = &version.digits_only().unwrap(), - ), - Arch::Arm64 => format!( - "pact-{pact_ver}-aarch64-osx", - pact_ver = &version.digits_only().unwrap(), - ), - _ => unimplemented!(), + Platform { + os: "macos", + arch: Arch::X64, + } => { + let regex = if version.is_nightly() { + // match the nightly version format for mac pact-binary-bundle.macos-latest. + r"pact-binary-bundle\.macos-latest\.(tar\.gz|zip)$" + } else { + // match the stable version format for mac pact--osx. + r"^pact-(\d+(\.\d+){0,2})-osx\.(tar\.gz|zip)$" + }; + Regex::new(regex).map_err(|e| format!("Regex creation error: {e}")) } + Platform { + os: "macos", + arch: Arch::Arm64, + } => { + let regex = if version.is_nightly() { + // match the nightly version format for mac pact-binary-bundle.macos-m1. + r"pact-binary-bundle\.macos-m1\.(tar\.gz|zip)$" + } else { + // match the stable version format for mac pact--aarch64-osx. + r"^pact-(\d+(\.\d+){0,2})-aarch64-osx\.(tar\.gz|zip)$" + }; + Regex::new(regex).map_err(|e| format!("Regex creation error: {e}")) + } + _ => Err("Unsupported platform".to_string()), } } - #[cfg(windows)] - fn filename_for_version(&self, arch: &Arch) -> String { - // format!( - // "pact-{pact_ver}-win-{arch}.zip", - // pact = &version, - // arch = arch, - // ) - unimplemented!() + /// Finds the asset for the current architecture and platform. + pub fn asset_for_current_platform(&self) -> Option<&Asset> { + let regex = self.version_matcher().ok()?; + self.assets.iter().find(|x| regex.is_match(&x.name)) } - pub fn download_url(&self, arch: &Arch) -> Url { - let name = self.filename_for_version(arch); - let asset = self - .assets - .iter() - .find(|x| x.name.starts_with(name.as_str())) - .expect("Can't find asset"); + /// Checks if the release has a supported asset for the current platform. + pub fn has_supported_asset(&self) -> bool { + self.asset_for_current_platform().is_some() + } + + pub fn download_url(&self) -> Url { + let asset = self.asset_for_current_platform().expect("Can't find asset"); asset.browser_download_url.clone() } } @@ -213,19 +150,57 @@ pub fn get_by_tag(repo_url: &String, tag: &String) -> Result &'static str { -// "windows" -// } +#[cfg(target_os = "windows")] +pub fn platform_os() -> &'static str { + "windows" +} -// #[cfg(target_os = "macos")] -// pub fn platform_name() -> &'static str { -// "darwin" -// } +#[cfg(target_os = "macos")] +pub fn platform_os() -> &'static str { + "darwin" +} -// #[cfg(target_os = "linux")] -// pub fn platform_name() -> &'static str { -// "linux" -// } +#[cfg(target_os = "linux")] +pub fn platform_os() -> &'static str { + "linux" +} #[cfg(all( target_pointer_width = "32", @@ -44,3 +44,104 @@ pub fn platform_arch() -> &'static str { pub fn platform_arch() -> &'static str { "x64" } + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Arch { + X86, + X64, + Arm64, + Armv7l, + Ppc64le, + Ppc64, + S390x, +} + +impl Default for Arch { + fn default() -> Arch { + match platform_arch().parse() { + Ok(arch) => arch, + Err(e) => panic!("{}", e.details), + } + } +} + +impl std::str::FromStr for Arch { + type Err = ArchError; + fn from_str(s: &str) -> Result { + match s { + "x86" => Ok(Arch::X86), + "x64" => Ok(Arch::X64), + "arm64" => Ok(Arch::Arm64), + "armv7l" => Ok(Arch::Armv7l), + "ppc64le" => Ok(Arch::Ppc64le), + "ppc64" => Ok(Arch::Ppc64), + "s390x" => Ok(Arch::S390x), + unknown => Err(ArchError::new(&format!("Unknown Arch: {unknown}"))), + } + } +} + +impl std::fmt::Display for Arch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let arch_str = match self { + Arch::X86 => String::from("x86"), + Arch::X64 => String::from("x64"), + Arch::Arm64 => String::from("arm64"), + Arch::Armv7l => String::from("armv7l"), + Arch::Ppc64le => String::from("ppc64le"), + Arch::Ppc64 => String::from("ppc64"), + Arch::S390x => String::from("s390x"), + }; + + write!(f, "{arch_str}") + } +} + +#[derive(Debug)] +pub struct ArchError { + details: String, +} + +impl ArchError { + fn new(msg: &str) -> ArchError { + ArchError { + details: msg.to_string(), + } + } +} + +impl std::fmt::Display for ArchError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.details) + } +} + +impl std::error::Error for ArchError { + fn description(&self) -> &str { + &self.details + } +} + +pub struct Platform { + pub os: &'static str, + pub arch: Arch, +} + +impl Default for Platform { + fn default() -> Platform { + Platform { + os: platform_os(), + arch: Arch::default(), + } + } +} + +impl std::fmt::Display for Platform { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{name}-{arch}", name = self.os, arch = self.arch) + } +} + +pub fn get_platform() -> Platform { + Platform::default() +}