diff --git a/Cargo.lock b/Cargo.lock index 3a851e7..e5c5e57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,9 +118,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "camino" @@ -199,9 +199,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -209,9 +209,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", @@ -221,9 +221,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", @@ -606,9 +606,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.121" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", "memchr", diff --git a/src/commands/package.rs b/src/commands/package.rs index 731858a..624beb1 100644 --- a/src/commands/package.rs +++ b/src/commands/package.rs @@ -162,9 +162,17 @@ fn run_for_crate( if !skip_toolchains_check { let missing_toolchains = check_installed_toolchains(&targets); - if !missing_toolchains.is_empty() { - if config.accept_all || prompt_toolchain_installation(&missing_toolchains) { + let nightly_toolchains = check_nightly_installed(&targets); + + let installation_required = + &[missing_toolchains.as_slice(), nightly_toolchains.as_slice()].concat(); + + if !installation_required.is_empty() { + if config.accept_all || prompt_toolchain_installation(installation_required) { install_toolchains(&missing_toolchains, config.silent)?; + if !nightly_toolchains.is_empty() { + install_nightly_src(config.silent)?; + } } else { Err("Toolchains for some target platforms were missing!")?; } @@ -193,14 +201,18 @@ fn run_for_crate( Ok(()) } +// FIXME: This can be removed once variant_count is stabilized: https://doc.rust-lang.org/std/mem/fn.variant_count.html#:~:text=Function%20std%3A%3Amem%3A%3Avariant_count&text=Returns%20the%20number%20of%20variants,the%20return%20value%20is%20unspecified. +const PLATFORM_COUNT: usize = 5; + #[derive(ValueEnum, Copy, Clone, Debug)] #[value()] pub enum Platform { Macos, Ios, - // Platforms below are removed until they are appropriately supported - // Tvos, - // Watchos, + // Platforms below are experimental + Tvos, + Watchos, + Visionos, } impl Platform { @@ -208,36 +220,55 @@ impl Platform { match self { Platform::Macos => vec![ApplePlatform::MacOS], Platform::Ios => vec![ApplePlatform::IOS, ApplePlatform::IOSSimulator], - // Platform::Tvos => vec![ApplePlatform::TvOS], - // Platform::Watchos => vec![ApplePlatform::WatchOS], + Platform::Tvos => vec![ApplePlatform::TvOS, ApplePlatform::TvOSSimulator], + Platform::Watchos => vec![ApplePlatform::WatchOS, ApplePlatform::WatchOSSimulator], + Platform::Visionos => vec![ApplePlatform::VisionOS, ApplePlatform::VisionOSSimulator], } } - fn display_name(&self) -> &'static str { - match self { + fn display_name(&self) -> String { + let name = match self { Platform::Macos => "macOS", Platform::Ios => "iOS", - // Platform::Tvos => "tvOS", - // Platform::Watchos => "watchOS", + Platform::Tvos => "tvOS", + Platform::Watchos => "watchOS", + Platform::Visionos => "visionOS", + }; + + format!( + "{name}{}", + if self.is_experimental() { + " (Experimental)" + } else { + "" + } + ) + } + + fn is_experimental(&self) -> bool { + match self { + Platform::Macos | Platform::Ios => false, + Platform::Tvos | Platform::Watchos | Platform::Visionos => true, } } - fn all() -> Vec { - vec![ + fn all() -> [Self; PLATFORM_COUNT] { + [ Self::Macos, Self::Ios, - // Self::Tvos, - // Self::Watchos + Self::Tvos, + Self::Watchos, + Self::Visionos, ] } } fn prompt_platforms(accept_all: bool) -> Vec { let platforms = Platform::all(); - let items: Vec<_> = platforms.iter().map(|p| p.display_name()).collect(); + let items = platforms.map(|p| p.display_name()); if accept_all { - return platforms; + return platforms.to_vec(); } let theme = prompt_theme(); @@ -246,7 +277,7 @@ fn prompt_platforms(accept_all: bool) -> Vec { .with_prompt("Select Target Platforms") // TODO: Move this to separate class and disable reporting to change style on success // .report(false) - .defaults(&[true, true, true, false]); + .defaults(&platforms.map(|p| !p.is_experimental())); let chosen: Vec = selector.interact().unwrap(); @@ -271,6 +302,7 @@ fn check_installed_toolchains(targets: &[Target]) -> Vec<&'static str> { targets .iter() + .filter(|t| !t.platform().is_tier_3()) .flat_map(|t| t.architectures()) .filter(|arch| { !installed @@ -280,6 +312,35 @@ fn check_installed_toolchains(targets: &[Target]) -> Vec<&'static str> { .collect() } +/// Checks if rust-src component for tier 3 targets are installed +fn check_nightly_installed(targets: &[Target]) -> Vec<&'static str> { + if !targets.iter().any(|t| t.platform().is_tier_3()) { + return vec![]; + } + + // TODO: Check if the correct nightly toolchain itself is installed + let mut rustup = command("rustup component list --toolchain nightly"); + rustup.stdout(Stdio::piped()); + // HACK: Silence error that toolchain is not installed + rustup.stderr(Stdio::null()); + + let output = rustup + .execute_output() + .expect("Failed to check installed components. Is rustup installed on your system?"); + let output = String::from_utf8_lossy(&output.stdout); + + if output + .split('\n') + .filter(|s| s.contains("installed")) + .map(|s| s.replace("(installed)", "").trim().to_owned()) + .any(|s| s.eq_ignore_ascii_case("rust-src")) + { + vec![] + } else { + vec!["rust-src (nightly)"] + } +} + /// Prompts the user to install the given **toolchains** by name fn prompt_toolchain_installation(toolchains: &[&str]) -> bool { println!("The following toolchains are not installed:"); @@ -302,6 +363,10 @@ fn prompt_toolchain_installation(toolchains: &[&str]) -> bool { /// Attempts to install the given **toolchains** fn install_toolchains(toolchains: &[&str], silent: bool) -> Result<()> { + if toolchains.is_empty() { + return Ok(()); + }; + let multi = silent.not().then(MultiProgress::new); let spinner = silent .not() @@ -320,7 +385,7 @@ fn install_toolchains(toolchains: &[&str], silent: bool) -> Result<()> { // TODO: make this a separate function and show error spinner on fail install .execute() - .map_err(|e| format!("Error while donwloading toolchain {toolchain}: \n\t{e}"))?; + .map_err(|e| format!("Error while downloading toolchain {toolchain}: \n\t{e}"))?; step.finish(); } @@ -329,6 +394,47 @@ fn install_toolchains(toolchains: &[&str], silent: bool) -> Result<()> { Ok(()) } +/// Attempts to install the "rust-src" component on nightly +fn install_nightly_src(silent: bool) -> Result<()> { + let multi = silent.not().then(MultiProgress::new); + let spinner = silent + .not() + .then(|| MainSpinner::with_message("Installing Toolchains...".to_owned())); + multi.add(&spinner); + spinner.start(); + + let mut install = command("rustup toolchain install nightly"); + install.stdin(Stdio::null()); + + let step = silent.not().then(|| CommandSpinner::with_command(&install)); + multi.add(&step); + step.start(); + + // TODO: make this a separate function and show error spinner on fail + install + .execute() + .map_err(|e| format!("Error while installing rust-src on nightly: \n\t{e}"))?; + + step.finish(); + + let mut install = command("rustup component add rust-src --toolchain nightly"); + install.stdin(Stdio::null()); + + let step = silent.not().then(|| CommandSpinner::with_command(&install)); + multi.add(&step); + step.start(); + + // TODO: make this a separate function and show error spinner on fail + install + .execute() + .map_err(|e| format!("Error while installing rust-src on nightly: \n\t{e}"))?; + + step.finish(); + spinner.finish(); + + Ok(()) +} + fn prompt_package_name(crate_name: &str, accept_all: bool) -> String { let default = crate_name.to_case(Case::UpperCamel); diff --git a/src/targets.rs b/src/targets.rs index 85cd7e1..be11097 100644 --- a/src/targets.rs +++ b/src/targets.rs @@ -9,6 +9,10 @@ use crate::package::FeatureOptions; pub trait TargetInfo { fn target(&self) -> Target; + /// Marks whether a pre-built std-lib is provided for this target (Tier 1 and Tier 2) via rustup or target needs to + /// be build (Tier 3) + /// See: https://doc.rust-lang.org/nightly/rustc/platform-support.html + fn is_tier_3(&self) -> bool; } #[derive(Debug, Clone)] @@ -46,7 +50,8 @@ impl Target { self.architectures() .into_iter() .map(|arch| { - let mut cmd = command("cargo build"); + // FIXME: Remove nightly for Tier 3 targets here once build-std is stabilized + let mut cmd = if self.platform().is_tier_3() { command("cargo +nightly build -Z build-std") } else { command("cargo build") }; cmd.arg("--target").arg(arch); match mode { @@ -185,12 +190,13 @@ pub enum ApplePlatform { IOS, IOSSimulator, MacOS, - MacCatalyst, + // MacCatalyst, TvOS, + TvOSSimulator, WatchOS, WatchOSSimulator, - CarPlayOS, - CarPlayOSSimulator, + VisionOS, + VisionOSSimulator, } impl TargetInfo for ApplePlatform { @@ -214,25 +220,53 @@ impl TargetInfo for ApplePlatform { display_name: "macOS", platform: *self, }, - MacCatalyst => { - unimplemented!("No official Rust target for platform \"Mac Catalyst\"!") - } - TvOS => Target::Universal { - universal_name: "universal-tvos", - architectures: nonempty!["aarch64-apple-tvos", "x86_64-apple-tvos"], + TvOS => Target::Single { + architecture: "aarch64-apple-tvos", display_name: "tvOS", platform: *self, }, - WatchOS => { - unimplemented!("No official Rust target for platform \"watchOS\"!") - } - WatchOSSimulator => { - unimplemented!("No official Rust target for platform \"watchOS Simulator\"!") - } - CarPlayOS => unimplemented!("No official Rust target for platform \"CarPlay\"!"), - CarPlayOSSimulator => { - unimplemented!("No official Rust target for platform \"CarPlay Simulator\"!") - } + TvOSSimulator => Target::Universal { + universal_name: "universal-tvos-simulator", + architectures: nonempty!["aarch64-apple-tvos-sim", "x86_64-apple-tvos"], + display_name: "tvOS Simulator", + platform: *self, + }, + WatchOS => Target::Universal { + universal_name: "universal-watchos", + architectures: nonempty![ + "aarch64-apple-watchos", + "arm64_32-apple-watchos", + "armv7k-apple-watchos" + ], + display_name: "watchOS", + platform: *self, + }, + WatchOSSimulator => Target::Universal { + universal_name: "universal-watchos-sim", + architectures: nonempty!["aarch64-apple-watchos-sim", "x86_64-apple-watchos-sim"], + display_name: "watchOS Simulator", + platform: *self, + }, + VisionOS => Target::Single { + architecture: "aarch64-apple-visionos", + display_name: "visionOS", + platform: *self, + }, + VisionOSSimulator => Target::Single { + architecture: "aarch64-apple-visionos-sim", + display_name: "visionOS Simulator", + platform: *self, + }, + } + } + + fn is_tier_3(&self) -> bool { + match self { + ApplePlatform::IOS | ApplePlatform::IOSSimulator => false, + ApplePlatform::MacOS => false, + ApplePlatform::TvOS | ApplePlatform::TvOSSimulator => true, + ApplePlatform::WatchOS | ApplePlatform::WatchOSSimulator => true, + ApplePlatform::VisionOS | ApplePlatform::VisionOSSimulator => true, } } }