Skip to content

Commit

Permalink
Support unnamed requirements in uv tool install (#4716)
Browse files Browse the repository at this point in the history
## Summary

This PR adds support for (e.g.) `uv tool install
git+https://github.com/psf/black`.

Closes #4664.
  • Loading branch information
charliermarsh authored Jul 2, 2024
1 parent 368276d commit 8e935e2
Show file tree
Hide file tree
Showing 10 changed files with 522 additions and 66 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions crates/uv-requirements/src/specification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,13 @@ impl RequirementsSpecification {
Ok(spec)
}

/// Parse an individual package requirement.
pub fn parse_package(name: &str) -> Result<UnresolvedRequirementSpecification> {
let requirement = RequirementsTxtRequirement::parse(name, std::env::current_dir()?, false)
.with_context(|| format!("Failed to parse: `{name}`"))?;
Ok(UnresolvedRequirementSpecification::from(requirement))
}

/// Read the requirements from a set of sources.
pub async fn from_simple_sources(
requirements: &[RequirementsSource],
Expand Down
1 change: 0 additions & 1 deletion crates/uv-tool/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ workspace = true
install-wheel-rs = { workspace = true }
pep440_rs = { workspace = true }
pep508_rs = { workspace = true }
pypi-types = { workspace = true }
uv-cache = { workspace = true }
uv-fs = { workspace = true }
uv-state = { workspace = true }
Expand Down
9 changes: 4 additions & 5 deletions crates/uv-tool/src/tool.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::path::PathBuf;

use path_slash::PathBufExt;
use pypi_types::VerbatimParsedUrl;
use serde::Deserialize;
use toml_edit::value;
use toml_edit::Array;
Expand All @@ -14,11 +13,11 @@ use toml_edit::Value;
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Tool {
// The requirements requested by the user during installation.
requirements: Vec<pep508_rs::Requirement<VerbatimParsedUrl>>,
/// The requirements requested by the user during installation.
requirements: Vec<pep508_rs::Requirement>,
/// The Python requested by the user during installation.
python: Option<String>,
// A mapping of entry point names to their metadata.
/// A mapping of entry point names to their metadata.
entrypoints: Vec<ToolEntrypoint>,
}

Expand Down Expand Up @@ -59,7 +58,7 @@ fn each_element_on_its_line_array(elements: impl Iterator<Item = impl Into<Value
impl Tool {
/// Create a new `Tool`.
pub fn new(
requirements: Vec<pep508_rs::Requirement<VerbatimParsedUrl>>,
requirements: Vec<pep508_rs::Requirement>,
python: Option<String>,
entrypoints: impl Iterator<Item = ToolEntrypoint>,
) -> Self {
Expand Down
91 changes: 88 additions & 3 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ use itertools::Itertools;
use owo_colors::OwoColorize;
use tracing::debug;

use distribution_types::Resolution;
use distribution_types::{Resolution, UnresolvedRequirementSpecification};
use pep440_rs::Version;
use pypi_types::Requirement;
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{Concurrency, ExtrasSpecification, PreviewMode, SetupPyStrategy};
use uv_dispatch::BuildDispatch;
use uv_distribution::Workspace;
use uv_distribution::{DistributionDatabase, Workspace};
use uv_fs::Simplified;
use uv_git::GitResolver;
use uv_installer::{SatisfiesResult, SitePackages};
use uv_requirements::RequirementsSpecification;
use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification};
use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder, PythonRequirement, RequiresPython};
use uv_toolchain::{
request_from_version_file, EnvironmentPreference, Interpreter, PythonEnvironment, Toolchain,
Expand All @@ -23,6 +24,7 @@ use uv_toolchain::{
use uv_types::{BuildIsolation, HashStrategy, InFlight};

use crate::commands::pip;
use crate::commands::reporters::ResolverReporter;
use crate::printer::Printer;
use crate::settings::ResolverInstallerSettings;

Expand Down Expand Up @@ -282,6 +284,89 @@ pub(crate) struct SharedState {
index: InMemoryIndex,
}

/// Resolve any [`UnresolvedRequirementSpecification`] into a fully-qualified [`Requirement`].
pub(crate) async fn resolve_names(
requirements: Vec<UnresolvedRequirementSpecification>,
interpreter: &Interpreter,
settings: &ResolverInstallerSettings,
state: &SharedState,
preview: PreviewMode,
connectivity: Connectivity,
concurrency: Concurrency,
native_tls: bool,
cache: &Cache,
printer: Printer,
) -> anyhow::Result<Vec<Requirement>> {
// Extract the project settings.
let ResolverInstallerSettings {
index_locations,
index_strategy,
keyring_provider,
resolution: _,
prerelease: _,
config_setting,
exclude_newer,
link_mode,
compile_bytecode: _,
upgrade: _,
reinstall: _,
build_options,
} = settings;

// Initialize the registry client.
let client = RegistryClientBuilder::new(cache.clone())
.native_tls(native_tls)
.connectivity(connectivity)
.index_urls(index_locations.index_urls())
.index_strategy(*index_strategy)
.keyring(*keyring_provider)
.markers(interpreter.markers())
.platform(interpreter.platform())
.build();

// Initialize any shared state.
let in_flight = InFlight::default();

// TODO(charlie): These are all default values. We should consider whether we want to make them
// optional on the downstream APIs.
let build_isolation = BuildIsolation::default();
let hasher = HashStrategy::default();
let setup_py = SetupPyStrategy::default();
let flat_index = FlatIndex::default();

// Create a build dispatch.
let build_dispatch = BuildDispatch::new(
&client,
cache,
interpreter,
index_locations,
&flat_index,
&state.index,
&state.git,
&in_flight,
*index_strategy,
setup_py,
config_setting,
build_isolation,
*link_mode,
build_options,
*exclude_newer,
concurrency,
preview,
);

// Initialize the resolver.
let resolver = NamedRequirementsResolver::new(
requirements,
&hasher,
&state.index,
DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads, preview),
)
.with_reporter(ResolverReporter::from(printer));

Ok(resolver.resolve().await?)
}

/// Update a [`PythonEnvironment`] to satisfy a set of [`RequirementsSource`]s.
pub(crate) async fn update_environment(
venv: PythonEnvironment,
Expand Down
6 changes: 3 additions & 3 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ use crate::settings::ResolverInstallerSettings;

/// Run a command.
pub(crate) async fn run(
extras: ExtrasSpecification,
dev: bool,
command: ExternalCommand,
requirements: Vec<RequirementsSource>,
python: Option<String>,
package: Option<PackageName>,
extras: ExtrasSpecification,
dev: bool,
python: Option<String>,
settings: ResolverInstallerSettings,
isolated: bool,
preview: PreviewMode,
Expand Down
Loading

0 comments on commit 8e935e2

Please sign in to comment.