Skip to content

Commit

Permalink
Bring parity to uvx and uv tool install requests
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Feb 9, 2025
1 parent 455d01f commit d47f319
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 290 deletions.
106 changes: 41 additions & 65 deletions crates/uv/src/commands/tool/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::commands::project::{
EnvironmentSpecification, PlatformState, ProjectError,
};
use crate::commands::tool::common::{install_executables, refine_interpreter, remove_entrypoints};
use crate::commands::tool::Target;
use crate::commands::tool::{Target, ToolRequest};
use crate::commands::ExitStatus;
use crate::commands::{diagnostics, reporters::PythonDownloadReporter};
use crate::printer::Printer;
Expand Down Expand Up @@ -94,29 +94,40 @@ pub(crate) async fn install(
.allow_insecure_host(allow_insecure_host.to_vec());

// Parse the input requirement.
let target = Target::parse(&package, from.as_deref());
let request = ToolRequest::parse(&package, from.as_deref());

// If the user passed, e.g., `ruff@latest`, refresh the cache.
let cache = if target.is_latest() {
let cache = if request.is_latest() {
cache.with_refresh(Refresh::All(Timestamp::now()))
} else {
cache
};

// Resolve the `--from` requirement.
let from = match target {
let from = match &request.target {
// Ex) `ruff`
Target::Unspecified(name) => {
Target::Unspecified(from) => {
let source = if editable {
RequirementsSource::Editable(name.to_string())
RequirementsSource::Editable((*from).to_string())
} else {
RequirementsSource::Package(name.to_string())
RequirementsSource::Package((*from).to_string())
};
let requirements = RequirementsSpecification::from_source(&source, &client_builder)
let requirement = RequirementsSpecification::from_source(&source, &client_builder)
.await?
.requirements;
resolve_names(
requirements,

// If the user provided an executable name, verify that it matches the `--from` requirement.
let executable = if let Some(executable) = request.executable {
let Ok(executable) = PackageName::from_str(executable) else {
bail!("Package requirement (`{from}`) provided with `--from` conflicts with install request (`{executable}`)", from = from.cyan(), executable = executable.cyan())
};
Some(executable)
} else {
None
};

let requirement = resolve_names(
requirement,
&interpreter,
&settings,
&state,
Expand All @@ -130,17 +141,29 @@ pub(crate) async fn install(
)
.await?
.pop()
.unwrap()
.unwrap();

// Determine if it's an entirely different package (e.g., `uv install foo --from bar`).
if let Some(executable) = executable {
if requirement.name != executable {
bail!(
"Package name (`{}`) provided with `--from` does not match install request (`{}`)",
requirement.name.cyan(),
executable.cyan()
);
}
}

requirement
}
// Ex) `ruff@0.6.0`
Target::Version(name, ref extras, ref version)
| Target::FromVersion(_, name, ref extras, ref version) => {
Target::Version(.., name, ref extras, ref version) => {
if editable {
bail!("`--editable` is only supported for local packages");
}

Requirement {
name: PackageName::from_str(name)?,
name: name.clone(),
extras: extras.clone(),
groups: vec![],
marker: MarkerTree::default(),
Expand All @@ -155,13 +178,13 @@ pub(crate) async fn install(
}
}
// Ex) `ruff@latest`
Target::Latest(name, ref extras) | Target::FromLatest(_, name, ref extras) => {
Target::Latest(.., name, ref extras) => {
if editable {
bail!("`--editable` is only supported for local packages");
}

Requirement {
name: PackageName::from_str(name)?,
name: name.clone(),
extras: extras.clone(),
groups: vec![],
marker: MarkerTree::default(),
Expand All @@ -173,53 +196,6 @@ pub(crate) async fn install(
origin: None,
}
}
// Ex) `ruff>=0.6.0`
Target::From(package, from) => {
// Parse the positional name. If the user provided more than a package name, it's an error
// (e.g., `uv install foo==1.0 --from foo`).
let Ok(package) = PackageName::from_str(package) else {
bail!("Package requirement (`{from}`) provided with `--from` conflicts with install request (`{package}`)", from = from.cyan(), package = package.cyan())
};

let source = if editable {
RequirementsSource::Editable(from.to_string())
} else {
RequirementsSource::Package(from.to_string())
};
let requirements = RequirementsSpecification::from_source(&source, &client_builder)
.await?
.requirements;

// Parse the `--from` requirement.
let from_requirement = resolve_names(
requirements,
&interpreter,
&settings,
&state,
connectivity,
concurrency,
native_tls,
allow_insecure_host,
&cache,
printer,
preview,
)
.await?
.pop()
.unwrap();

// Check if the positional name conflicts with `--from`.
if from_requirement.name != package {
// Determine if it's an entirely different package (e.g., `uv install foo --from bar`).
bail!(
"Package name (`{}`) provided with `--from` does not match install request (`{}`)",
from_requirement.name.cyan(),
package.cyan()
);
}

from_requirement
}
};

if from.name.as_str().eq_ignore_ascii_case("python") {
Expand All @@ -231,7 +207,7 @@ pub(crate) async fn install(
}

// If the user passed, e.g., `ruff@latest`, we need to mark it as upgradable.
let settings = if target.is_latest() {
let settings = if request.is_latest() {
ResolverInstallerSettings {
upgrade: settings
.upgrade
Expand Down Expand Up @@ -367,7 +343,7 @@ pub(crate) async fn install(
.as_ref()
.filter(|_| {
// And the user didn't request a reinstall or upgrade...
!target.is_latest() && settings.reinstall.is_none() && settings.upgrade.is_none()
!request.is_latest() && settings.reinstall.is_none() && settings.upgrade.is_none()
})
.is_some()
{
Expand Down
Loading

0 comments on commit d47f319

Please sign in to comment.