Skip to content
Open
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
7 changes: 4 additions & 3 deletions src/cli/proxy_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
command::run_command_for_dir,

@rami3l rami3l Sep 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The category specified in the commit message is wrong. It should look more like feat(toolchain): or similar. Same thing for the title of this PR.

View changes since the review

config::{ActiveSource, Cfg},
process::Process,
toolchain::ResolvableLocalToolchainName,
toolchain::{Override, ResolvableLocalToolchainName},
};

#[tracing::instrument(level = "trace", skip(process))]
Expand All @@ -25,7 +25,7 @@ pub async fn main(
.as_ref()
.map(|arg| arg.to_string_lossy())
.filter(|arg| arg.starts_with('+'))
.map(|name| ResolvableLocalToolchainName::from_str(&name[1..]))
.map(|name| Override::<ResolvableLocalToolchainName>::from_str(&name[1..]))
.transpose()?;

// Build command args now while we know whether or not to skip arg 1.
Expand All @@ -38,9 +38,10 @@ pub async fn main(
let (toolchain, source) = cfg
.local_toolchain(match toolchain {
Some(name) => Some((
name.resolve(&cfg.default_host_tuple()?)?,
name.resolve(&cfg)?.resolve(&cfg.default_host_tuple()?)?,
ActiveSource::CommandLine,
)),

None => None,
})
.await?;
Expand Down
34 changes: 21 additions & 13 deletions src/cli/rustup_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ use crate::{
process::{ColorableTerminal, Process},
toolchain::{
CustomToolchainName, DistributableToolchain, LocalToolchainName,
MaybeResolvableToolchainName, ResolvableLocalToolchainName, ResolvableToolchainName,
Toolchain, ToolchainName,
MaybeResolvableToolchainName, Override, ResolvableLocalToolchainName,
ResolvableToolchainName, Toolchain, ToolchainName,
},
utils::{self, ExitCode},
};
Expand Down Expand Up @@ -107,16 +107,18 @@ struct Rustup {
value_parser = plus_toolchain_value_parser,
value_hint = ValueHint::Other,
)]
plus_toolchain: Option<ResolvableToolchainName>,
plus_toolchain: Option<Override<ResolvableLocalToolchainName>>,

#[command(subcommand)]
subcmd: Option<RustupSubcmd>,
}

fn plus_toolchain_value_parser(s: &str) -> clap::error::Result<ResolvableToolchainName> {
fn plus_toolchain_value_parser(
s: &str,
) -> clap::error::Result<Override<ResolvableLocalToolchainName>> {
use clap::{Error, error::ErrorKind};
if let Some(stripped) = s.strip_prefix('+') {
ResolvableToolchainName::from_str(stripped)
Override::<ResolvableLocalToolchainName>::from_str(stripped)
.map_err(|e| Error::raw(ErrorKind::InvalidValue, e))
} else {
Err(Error::raw(
Expand Down Expand Up @@ -159,7 +161,7 @@ enum RustupSubcmd {
#[command(after_help = default_help())]
Default {
#[arg(help = maybe_resolvable_toolchain_arg_help())]
toolchain: Option<MaybeResolvableToolchainName>,
toolchain: Option<Override<MaybeResolvableToolchainName>>,

/// Install toolchains that require an emulator. See https://github.com/rust-lang/rustup/wiki/Non-host-toolchains
#[arg(long)]
Expand Down Expand Up @@ -613,7 +615,7 @@ enum OverrideSubcmd {
#[command(alias = "add")]
Set {
#[arg(help = resolvable_toolchain_arg_help())]
toolchain: ResolvableToolchainName,
toolchain: Override<ResolvableToolchainName>,

/// Path to the directory
#[arg(long)]
Expand Down Expand Up @@ -924,13 +926,13 @@ fn completion_command(cfg: &Cfg<'_>) -> clap::Command {

async fn default_(
cfg: &Cfg<'_>,
toolchain: Option<MaybeResolvableToolchainName>,
toolchain: Option<Override<MaybeResolvableToolchainName>>,
force_non_host: bool,
) -> anyhow::Result<ExitCode> {
common::warn_if_host_is_emulated(cfg.process);

if let Some(toolchain) = toolchain {
match toolchain.to_owned() {
match toolchain.resolve(cfg)? {
MaybeResolvableToolchainName::None => {
cfg.set_default(None)?;
}
Expand Down Expand Up @@ -1180,7 +1182,7 @@ async fn update(
)?;

if opts.r#override {
cfg.make_override(&cfg.current_dir, &name.clone().into())?;
cfg.make_override(&cfg.current_dir, &name)?;
}

if opts.default
Expand Down Expand Up @@ -1794,10 +1796,13 @@ fn pin_active_toolchain(qualified: bool, cfg: &Cfg<'_>) -> anyhow::Result<ExitCo

async fn override_add(
cfg: &Cfg<'_>,
toolchain: ResolvableToolchainName,
toolchain: Override<ResolvableToolchainName>,
path: Option<&Path>,
) -> anyhow::Result<ExitCode> {
let toolchain_name = toolchain.clone().resolve(&cfg.default_host_tuple()?)?;
let resolved_toolchain = toolchain.clone().resolve(cfg)?;
let toolchain_name = resolved_toolchain
.clone()
.resolve(&cfg.default_host_tuple()?)?;
match Toolchain::new(cfg, toolchain_name.clone().into()) {
Ok(_) => {}
Err(e @ RustupError::ToolchainNotInstalled { .. }) => match &toolchain_name {
Expand Down Expand Up @@ -1955,7 +1960,10 @@ async fn display_version(cfg: &mut Cfg<'_>) -> anyhow::Result<()> {
cfg.toolchain_override = cfg
.process
.args()
.find_map(|arg| arg.strip_prefix('+').map(ResolvableToolchainName::from_str))
.find_map(|arg| {
arg.strip_prefix('+')
.map(Override::<ResolvableLocalToolchainName>::from_str)
})
.transpose()?;

match cfg.maybe_ensure_active_toolchain(None).await {
Expand Down
69 changes: 45 additions & 24 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,30 @@ use crate::{
process::Process,
settings::{MetadataVersion, Settings, SettingsFile},
toolchain::{
CustomToolchainName, DistributableToolchain, LocalToolchainName, PathBasedToolchainName,
ResolvableLocalToolchainName, ResolvableToolchainName, Toolchain, ToolchainName,
CustomToolchainName, DistributableToolchain, LocalToolchainName, Override,
PathBasedToolchainName, ResolvableLocalToolchainName, ResolvableToolchainName, Toolchain,
ToolchainAlias, ToolchainName,
},
utils,
};

impl<T> Override<T>
where
T: From<ResolvableToolchainName>,
{
pub(crate) fn resolve(self, cfg: &Cfg<'_>) -> anyhow::Result<T> {
match self {
Self::Aliased(ToolchainAlias::Default) => {
let default = cfg
.get_default_resolvable()?
.ok_or_else(|| no_toolchain_error(cfg.process))?;
Ok(T::from(default))
}
Self::Explicit(value) => Ok(value),
}
}
}

#[derive(Debug, ThisError)]
enum OverrideFileConfigError {
#[error(
Expand Down Expand Up @@ -199,7 +217,7 @@ pub(crate) enum OverrideCfg {
impl OverrideCfg {
fn from_file(cfg: &Cfg<'_>, file: OverrideFile) -> anyhow::Result<Self> {
let toolchain_name = match (file.toolchain.channel, file.toolchain.path) {
(Some(name), None) => ResolvableToolchainName::from_str(&name)?,
(Some(name), None) => Override::<ResolvableToolchainName>::from_str(&name)?,
(None, Some(path)) => {
if file.toolchain.targets.is_some()
|| file.toolchain.components.is_some()
Expand All @@ -226,10 +244,12 @@ impl OverrideCfg {
path.display()
)
}
(None, None) => cfg
.get_default_resolvable()?
.ok_or_else(|| no_toolchain_error(cfg.process))?,
(None, None) => Override::Explicit(
cfg.get_default_resolvable()?
.ok_or_else(|| no_toolchain_error(cfg.process))?,
),
};
let toolchain_name = toolchain_name.resolve(cfg)?;
Ok(match toolchain_name {
ResolvableToolchainName::Official(desc) => Self::Official {
toolchain: desc,
Expand Down Expand Up @@ -321,8 +341,8 @@ pub(crate) struct Cfg<'a> {
pub toolchains_dir: PathBuf,
update_hash_dir: PathBuf,
pub download_dir: PathBuf,
pub toolchain_override: Option<ResolvableToolchainName>,
env_override: Option<ResolvableLocalToolchainName>,
pub toolchain_override: Option<Override<ResolvableLocalToolchainName>>,
env_override: Option<Override<ResolvableLocalToolchainName>>,
pub(crate) dist_root_server: String,
pub dist_root_url: String,
pub quiet: bool,
Expand Down Expand Up @@ -383,7 +403,7 @@ impl<'a> Cfg<'a> {

// Environment override
let env_override = match &process.var_opt("RUSTUP_TOOLCHAIN")? {
Some(tc) => Some(ResolvableLocalToolchainName::from_str(tc)?),
Some(tc) => Some(Override::<ResolvableLocalToolchainName>::from_str(tc)?),
None => None,
};

Expand Down Expand Up @@ -646,15 +666,15 @@ impl<'a> Cfg<'a> {
let override_config: Option<(OverrideCfg, ActiveSource)> =
// First check +toolchain override from the command line
if let Some(name) = &self.toolchain_override {
Some((name.clone().into(), ActiveSource::CommandLine))
Some((name.clone().resolve(self)?.into(), ActiveSource::CommandLine))
}
// Then check the RUSTUP_TOOLCHAIN environment variable
else if let Some(name) = &self.env_override {
// Because path based toolchain files exist, this has to support
// custom, distributable, and absolute path toolchains otherwise
// rustup's export of a RUSTUP_TOOLCHAIN when running a process will
// error when a nested rustup invocation occurs
Some((name.clone().into(), ActiveSource::Environment))
Some((name.clone().resolve(self)?.into(), ActiveSource::Environment))
}
// Then walk up the directory tree from 'path' looking for either the
// directory in the override database, or a `rust-toolchain{.toml}` file,
Expand All @@ -668,7 +688,6 @@ impl<'a> Cfg<'a> {
else {
None
};

Ok(override_config)
}

Expand All @@ -684,7 +703,9 @@ impl<'a> Cfg<'a> {
if let Some(name) = settings.dir_override(d) {
let source = ActiveSource::OverrideDb(d.to_owned());
return Ok(Some((
ResolvableToolchainName::from_str(&name)?.into(),
Override::<ResolvableToolchainName>::from_str(&name)?
.resolve(self)?
.into(),
source,
)));
}
Expand Down Expand Up @@ -735,15 +756,15 @@ impl<'a> Cfg<'a> {
}
})?;
if let Some(toolchain_name_str) = &override_file.toolchain.channel {
let toolchain_name = ResolvableToolchainName::from_str(
toolchain_name_str.as_str(),
)
.map_err(|_| {
anyhow!(
"invalid toolchain name detected in override file '{}'",
toolchain_file.display()
)
})?;
let toolchain_override =
Override::<ResolvableToolchainName>::from_str(toolchain_name_str.as_str())
.map_err(|_| {
anyhow!(
"invalid toolchain name detected in override file '{}'",
toolchain_file.display()
)
})?;
let toolchain_name = toolchain_override.resolve(self)?;
let default_host = default_host_tuple(settings, self.process);
// Do not permit architecture/os selection in channels as
// these are host specific and toolchain files are portable.
Expand Down Expand Up @@ -1054,7 +1075,7 @@ impl<'a> Cfg<'a> {
pub(crate) fn make_override(
&self,
path: &Path,
toolchain: &ResolvableToolchainName,
toolchain: &impl Display,
) -> anyhow::Result<()> {
self.settings_file.with_mut(|s| {
s.add_override(path, toolchain.to_string());
Expand Down Expand Up @@ -1274,7 +1295,7 @@ pub(crate) fn default_host_tuple(s: &Settings, process: &Process) -> TargetTuple
.unwrap_or_else(|| TargetTuple::from_host_or_build(process))
}

fn no_toolchain_error(process: &Process) -> anyhow::Error {
pub(crate) fn no_toolchain_error(process: &Process) -> anyhow::Error {
RustupError::ToolchainNotSelected(process.name().unwrap_or_else(|| "Rust".into())).into()
}

Expand Down
4 changes: 2 additions & 2 deletions src/toolchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ pub(crate) use distributable::DistributableToolchain;
mod names;
pub(crate) use names::{
CustomToolchainName, LocalToolchainName, MaybeOfficialToolchainName,
MaybeResolvableToolchainName, PathBasedToolchainName, ResolvableLocalToolchainName,
ResolvableToolchainName, ToolchainName,
MaybeResolvableToolchainName, Override, PathBasedToolchainName, ResolvableLocalToolchainName,
ResolvableToolchainName, ToolchainAlias, ToolchainName,
};

/// A toolchain installed on the local disk
Expand Down
71 changes: 71 additions & 0 deletions src/toolchain/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,65 @@ pub enum InvalidName {
DashPrefix(String),
}

/// An alias for a toolchain name.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ToolchainAlias {
///Refers to rustup's configured default toolchain
Default,

@rami3l rami3l Sep 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest adding a docstring explaining the precise semantics of each variant.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

///Refers to rustup's configured default toolchain.
///Parsed from the literal string "default". is this docstring good at representing the variant

@rami3l rami3l Sep 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@14elias I think just keeping the first line would be fine. Also could you write your comments, PR descriptions etc in proper formatted markdown? It's not super easy to read to me in its current state.

}

impl FromStr for ToolchainAlias {
type Err = InvalidName;

fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"default" => Ok(Self::Default),
_ => Err(InvalidName::ToolchainName(value.into())),
}
}
}

impl Display for ToolchainAlias {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

@rami3l rami3l Sep 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest importing std::fmt.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the existing code uses this pattern that's why i used that but i can import std::fmt and change in all the places if you want.

match self {
Self::Default => write!(f, "default"),
}
}
}

/// A wrapper for types that can be overridden by an alias.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Override<T> {
Aliased(ToolchainAlias),
Explicit(T),
}

impl<T: FromStr> FromStr for Override<T>
where
T::Err: Into<InvalidName>,
{
type Err = InvalidName;

fn from_str(value: &str) -> Result<Self, Self::Err> {
if let Ok(alias) = ToolchainAlias::from_str(value) {
return Ok(Self::Aliased(alias));
}
match T::from_str(value) {
Ok(t) => Ok(Self::Explicit(t)),
Err(e) => Err(e.into()),
}
}
}

impl<T: Display> Display for Override<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Aliased(a) => write!(f, "{a}"),
Self::Explicit(t) => write!(f, "{t}"),
}
}
}

/// A toolchain name from user input.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum ResolvableToolchainName {
Expand Down Expand Up @@ -155,6 +214,12 @@ impl Display for MaybeResolvableToolchainName {
}
}

impl From<ResolvableToolchainName> for MaybeResolvableToolchainName {
fn from(value: ResolvableToolchainName) -> Self {
Self::Some(value)
}
}

/// ResolvableToolchainName + none, for overriding default-has-a-value
/// situations in the CLI with an official toolchain name or none
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -293,6 +358,12 @@ impl Display for ResolvableLocalToolchainName {
}
}

impl From<ResolvableToolchainName> for ResolvableLocalToolchainName {
fn from(value: ResolvableToolchainName) -> Self {
Self::Named(value)
}
}

/// LocalToolchainName can be used in calls to Cfg that alter configuration,
/// like setting overrides, or that depend on configuration, like calculating
/// the toolchain directory. It is not used to model the RUSTUP_TOOLCHAIN
Expand Down
Loading
Loading