Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 88 additions & 34 deletions src/commands/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,19 +206,27 @@ fn run_for_crate(
}
}

if !skip_toolchains_check {
let missing_toolchains = check_installed_toolchains(&targets);
let nightly_toolchains = check_nightly_installed(&targets);
let toolchain_targets = ToolchainTargets::query(&targets);

let installation_required =
&[missing_toolchains.as_slice(), nightly_toolchains.as_slice()].concat();
if !skip_toolchains_check {
let missing_stable = check_stable_missing_targets(&targets, &toolchain_targets);
let missing_nightly_targets = check_nightly_missing_targets(&targets, &toolchain_targets);
let missing_nightly_src = check_nightly_src_installed(&targets, &toolchain_targets);

let installation_required = &[
missing_stable.as_slice(),
missing_nightly_targets.as_slice(),
missing_nightly_src.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_toolchains(&missing_stable, config.silent)?;
if !missing_nightly_targets.is_empty() || !missing_nightly_src.is_empty() {
install_nightly_src(config.silent)?;
}
install_nightly_targets(&missing_nightly_targets, config.silent)?;
} else {
Err("Toolchains for some target platforms were missing!")?;
}
Expand All @@ -227,7 +235,15 @@ fn run_for_crate(

let crate_name = lib.name.replace('-', "_");
for target in &targets {
build_with_output(target, &crate_name, mode, lib_type, config, &features)?;
build_with_output(
target,
&crate_name,
mode,
lib_type,
config,
&features,
&toolchain_targets,
)?;
}

let ffi_module_name =
Expand Down Expand Up @@ -419,41 +435,46 @@ fn prompt_platforms(accept_all: bool) -> Vec<PlatformSpec> {
.collect()
}

/// Checks if toolchains for all target architectures are installed and returns a
/// list containing the names of all missing toolchains
fn check_installed_toolchains(targets: &[Target]) -> Vec<&'static str> {
let mut rustup = command!("rustup target list");
rustup.stdout(Stdio::piped());
let output = rustup
.execute_output()
.expect("Failed to check installed toolchains. Is rustup installed on your system?");
let output = String::from_utf8_lossy(&output.stdout);

let installed: Vec<_> = output
.split('\n')
.filter(|s| s.contains("installed"))
.map(|s| s.replace("(installed)", "").trim().to_owned())
.collect();
/// Checks if toolchains for all tier 1/2 target architectures are installed on the
/// default (stable) toolchain and returns a list of missing ones.
fn check_stable_missing_targets(
targets: &[Target],
toolchain_targets: &ToolchainTargets,
) -> Vec<&'static str> {
targets
.iter()
.flat_map(|t| t.architectures())
.filter(|arch| toolchain_targets.is_stable_missing(arch))
.collect()
}

/// Checks if targets that are only available on nightly (tier 2 on nightly, tier 3 on stable)
/// are installed on the nightly toolchain.
fn check_nightly_missing_targets(
targets: &[Target],
toolchain_targets: &ToolchainTargets,
) -> Vec<&'static str> {
targets
.iter()
.filter(|t| !t.platform().is_tier_3())
.flat_map(|t| t.architectures())
.filter(|arch| {
!installed
.iter()
.any(|toolchain| toolchain.eq_ignore_ascii_case(arch))
})
.filter(|arch| toolchain_targets.is_nightly_missing(arch))
.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()) {
/// Checks if rust-src component for tier 3 targets (needing -Z build-std) is installed
fn check_nightly_src_installed(
targets: &[Target],
toolchain_targets: &ToolchainTargets,
) -> Vec<&'static str> {
let has_build_std = targets
.iter()
.flat_map(|t| t.architectures())
.any(|arch| toolchain_targets.needs_build_std(arch));

if !has_build_std {
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
Expand Down Expand Up @@ -529,6 +550,38 @@ fn install_toolchains(toolchains: &[&str], silent: bool) -> Result<()> {
Ok(())
}

/// Attempts to install the given targets on the nightly toolchain
fn install_nightly_targets(toolchains: &[&str], silent: bool) -> Result<()> {
if toolchains.is_empty() {
return Ok(());
};

let multi = silent.not().then(MultiProgress::new);
let spinner = silent
.not()
.then(|| MainSpinner::with_message("Installing Nightly Targets...".to_owned()));
multi.add(&spinner);
spinner.start();
for toolchain in toolchains {
let mut install = Command::new("rustup");
install.args(["target", "install", toolchain, "--toolchain", "nightly"]);
install.stdin(Stdio::null());

let step = silent.not().then(|| CommandSpinner::with_command(&install));
multi.add(&step);
step.start();

install
.execute()
.map_err(|e| format!("Error while installing nightly target {toolchain}: \n\t{e}"))?;

step.finish();
}
spinner.finish();

Ok(())
}

/// Attempts to install the "rust-src" component on nightly
fn install_nightly_src(silent: bool) -> Result<()> {
let multi = silent.not().then(MultiProgress::new);
Expand Down Expand Up @@ -638,8 +691,9 @@ fn build_with_output(
lib_type: LibType,
config: &Config,
features: &FeatureOptions,
toolchain_targets: &ToolchainTargets,
) -> Result<()> {
let mut commands = target.commands(lib_name, mode, lib_type, features);
let mut commands = target.commands(lib_name, mode, lib_type, features, toolchain_targets);
for command in &mut commands {
command.env("CARGO_TERM_COLOR", "always");
}
Expand Down
129 changes: 106 additions & 23 deletions src/targets.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,105 @@
use std::collections::HashSet;
use std::process::Stdio;
use std::{fmt::Display, process::Command};

use execute::command;
use execute::Execute;
use nonempty::{nonempty, NonEmpty};

use crate::lib_type::LibType;
use crate::metadata::{metadata, MetadataExt};
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;
/// Queries `rustup target list` for both the default and nightly toolchains,
/// caching which targets are available and installed on each. When any
/// architecture requires nightly, all builds use nightly for consistency
/// (same Rust compiler version across the entire build).
pub struct ToolchainTargets {
stable_available: HashSet<String>,
stable_installed: HashSet<String>,
nightly_available: HashSet<String>,
nightly_installed: HashSet<String>,
/// When true, all targets use `cargo +nightly` for compiler version consistency.
use_nightly: bool,
}

impl ToolchainTargets {
/// Run `rustup target list` for both default and nightly toolchains, then
/// determine whether nightly is needed for any of the given target architectures.
/// If nightly is not installed, its sets will be empty (conservative fallback).
pub fn query(targets: &[Target]) -> Self {
let (stable_available, stable_installed) = Self::parse_target_list(&["target", "list"]);
let (nightly_available, nightly_installed) =
Self::parse_target_list(&["target", "list", "--toolchain", "nightly"]);

let use_nightly = targets
.iter()
.flat_map(|t| t.architectures())
.any(|arch| !stable_available.contains(arch));

Self {
stable_available,
stable_installed,
nightly_available,
nightly_installed,
use_nightly,
}
}

fn parse_target_list(args: &[&str]) -> (HashSet<String>, HashSet<String>) {
let mut rustup = Command::new("rustup");
rustup.args(args);
rustup.stdout(Stdio::piped());
rustup.stderr(Stdio::null());

let output = match rustup.execute_output() {
Ok(output) if output.status.success() => output,
_ => return (HashSet::new(), HashSet::new()),
};
let output = String::from_utf8_lossy(&output.stdout);

let mut available = HashSet::new();
let mut installed = HashSet::new();

for line in output.lines() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if let Some(name) = trimmed.strip_suffix("(installed)") {
let name = name.trim().to_owned();
installed.insert(name.clone());
available.insert(name);
} else {
available.insert(trimmed.to_owned());
}
}

(available, installed)
}

/// Returns true if the arch requires `-Z build-std`
/// (not available on either stable or nightly).
pub fn needs_build_std(&self, arch: &str) -> bool {
!self.stable_available.contains(arch) && !self.nightly_available.contains(arch)
}

/// Returns true if any architecture in the build requires nightly.
pub fn use_nightly(&self) -> bool {
self.use_nightly
}

/// Returns true if the target is available on stable but not yet installed.
pub fn is_stable_missing(&self, arch: &str) -> bool {
self.stable_available.contains(arch) && !self.stable_installed.contains(arch)
}

/// Returns true if the target is only available on nightly and not yet installed there.
pub fn is_nightly_missing(&self, arch: &str) -> bool {
!self.stable_available.contains(arch)
&& self.nightly_available.contains(arch)
&& !self.nightly_installed.contains(arch)
}
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -46,13 +133,19 @@ impl Display for Mode {
}

impl Target {
fn cargo_build_commands(&self, mode: Mode, features: &FeatureOptions) -> Vec<Command> {
fn cargo_build_commands(
&self,
mode: Mode,
features: &FeatureOptions,
toolchain_targets: &ToolchainTargets,
) -> Vec<Command> {
self.architectures()
.into_iter()
.map(|arch| {
// FIXME: Remove nightly for Tier 3 targets here once build-std is stabilized
let mut cmd = if self.platform().is_tier_3() {
let mut cmd = if toolchain_targets.needs_build_std(arch) {
command("cargo +nightly build -Z build-std")
} else if toolchain_targets.use_nightly() {
command("cargo +nightly build")
} else {
command("cargo build")
};
Expand Down Expand Up @@ -129,8 +222,9 @@ impl Target {
mode: Mode,
lib_type: LibType,
features: &FeatureOptions,
toolchain_targets: &ToolchainTargets,
) -> Vec<Command> {
self.cargo_build_commands(mode, features)
self.cargo_build_commands(mode, features, toolchain_targets)
.into_iter()
.chain(self.lipo_commands(lib_name, mode, lib_type))
.chain(self.rpath_install_id_commands(lib_name, mode, lib_type))
Expand Down Expand Up @@ -203,8 +297,8 @@ pub enum ApplePlatform {
VisionOSSimulator,
}

impl TargetInfo for ApplePlatform {
fn target(&self) -> Target {
impl ApplePlatform {
pub fn target(&self) -> Target {
use ApplePlatform::*;
match self {
IOS => Target::Single {
Expand Down Expand Up @@ -269,15 +363,4 @@ impl TargetInfo for ApplePlatform {
},
}
}

fn is_tier_3(&self) -> bool {
match self {
ApplePlatform::IOS | ApplePlatform::IOSSimulator => false,
ApplePlatform::MacOS => false,
ApplePlatform::MacCatalyst => false,
ApplePlatform::TvOS | ApplePlatform::TvOSSimulator => true,
ApplePlatform::WatchOS | ApplePlatform::WatchOSSimulator => true,
ApplePlatform::VisionOS | ApplePlatform::VisionOSSimulator => true,
}
}
}