Skip to content

Commit

Permalink
Allow --constraints and --overrides in uvx
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Dec 27, 2024
1 parent 1fb7f35 commit b5f4395
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 28 deletions.
22 changes: 22 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3773,6 +3773,28 @@ pub struct ToolRunArgs {
#[arg(long, value_delimiter = ',', value_parser = parse_maybe_file_path)]
pub with_requirements: Vec<Maybe<PathBuf>>,

/// Constrain versions using the given requirements files.
///
/// Constraints files are `requirements.txt`-like files that only control the _version_ of a
/// requirement that's installed. However, including a package in a constraints file will _not_
/// trigger the installation of that package.
///
/// This is equivalent to pip's `--constraint` option.
#[arg(long, short, alias = "constraint", env = EnvVars::UV_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub constraints: Vec<Maybe<PathBuf>>,

/// Override versions using the given requirements files.
///
/// Overrides files are `requirements.txt`-like files that force a specific version of a
/// requirement to be installed, regardless of the requirements declared by any constituent
/// package, and regardless of whether this would be considered an invalid resolution.
///
/// While constraints are _additive_, in that they're combined with the requirements of the
/// constituent packages, overrides are _absolute_, in that they completely replace the
/// requirements of the constituent packages.
#[arg(long, alias = "override", env = EnvVars::UV_OVERRIDE, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub overrides: Vec<Maybe<PathBuf>>,

/// Run the tool in an isolated virtual environment, ignoring any already-installed tools.
#[arg(long)]
pub isolated: bool,
Expand Down
84 changes: 57 additions & 27 deletions crates/uv/src/commands/tool/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ use uv_cli::ExternalCommand;
use uv_client::{BaseClientBuilder, Connectivity};
use uv_configuration::{Concurrency, PreviewMode, TrustedHost};
use uv_dispatch::SharedState;
use uv_distribution_types::{Name, UnresolvedRequirementSpecification};
use uv_distribution_types::{
Name, NameRequirementSpecification, UnresolvedRequirementSpecification,
};
use uv_installer::{SatisfiesResult, SitePackages};
use uv_normalize::PackageName;
use uv_pep440::{VersionSpecifier, VersionSpecifiers};
Expand Down Expand Up @@ -69,6 +71,8 @@ pub(crate) async fn run(
command: Option<ExternalCommand>,
from: Option<String>,
with: &[RequirementsSource],
constraints: &[RequirementsSource],
overrides: &[RequirementsSource],
show_resolution: bool,
python: Option<String>,
install_mirrors: PythonInstallMirrors,
Expand Down Expand Up @@ -115,6 +119,8 @@ pub(crate) async fn run(
let result = get_or_create_environment(
&target,
with,
constraints,
overrides,
show_resolution,
python.as_deref(),
install_mirrors,
Expand Down Expand Up @@ -434,6 +440,8 @@ fn warn_executable_not_provided_by_package(
async fn get_or_create_environment(
target: &Target<'_>,
with: &[RequirementsSource],
constraints: &[RequirementsSource],
overrides: &[RequirementsSource],
show_resolution: bool,
python: Option<&str>,
install_mirrors: PythonInstallMirrors,
Expand Down Expand Up @@ -477,6 +485,11 @@ async fn get_or_create_environment(
// Initialize any shared state.
let state = SharedState::default();

let client_builder = BaseClientBuilder::new()
.connectivity(connectivity)
.native_tls(native_tls)
.allow_insecure_host(allow_insecure_host.to_vec());

// Resolve the `--from` requirement.
let from = match target {
// Ex) `ruff`
Expand Down Expand Up @@ -540,13 +553,9 @@ async fn get_or_create_environment(
};

// Read the `--with` requirements.
let spec = {
let client_builder = BaseClientBuilder::new()
.connectivity(connectivity)
.native_tls(native_tls)
.allow_insecure_host(allow_insecure_host.to_vec());
RequirementsSpecification::from_simple_sources(with, &client_builder).await?
};
let spec =
RequirementsSpecification::from_sources(with, constraints, overrides, &client_builder)
.await?;

// Resolve the `--from` and `--with` requirements.
let requirements = {
Expand All @@ -571,6 +580,30 @@ async fn get_or_create_environment(
requirements
};

// Resolve the constraints.
let constraints = spec
.constraints
.clone()
.into_iter()
.map(|constraint| constraint.requirement)
.collect::<Vec<_>>();

// Resolve the overrides.
let overrides = resolve_names(
spec.overrides.clone(),
&interpreter,
&settings,
&state,
connectivity,
concurrency,
native_tls,
allow_insecure_host,
&cache,
printer,
preview,
)
.await?;

// Check if the tool is already installed in a compatible environment.
if !isolated && !target.is_latest() {
let installed_tools = InstalledTools::from_settings()?.init()?;
Expand All @@ -586,25 +619,14 @@ async fn get_or_create_environment(
});
if let Some(environment) = existing_environment {
// Check if the installed packages meet the requirements.
let site_packages = SitePackages::from_environment(&environment)?;

let requirements = requirements
.iter()
.cloned()
.map(UnresolvedRequirementSpecification::from)
.collect::<Vec<_>>();
let constraints = [];

if matches!(
site_packages.satisfies(
&requirements,
&constraints,
&interpreter.resolver_marker_environment()
),
Ok(SatisfiesResult::Fresh { .. })
) {
debug!("Using existing tool `{}`", from.name);
return Ok((from, environment));
if let Ok(Some(tool_receipt)) = installed_tools.get_tool_receipt(&from.name) {
if requirements == tool_receipt.requirements()
&& constraints == tool_receipt.constraints()
&& overrides == tool_receipt.overrides()
{
debug!("Using existing tool `{}`", from.name);
return Ok((from, environment));
}
}
}
}
Expand All @@ -615,6 +637,14 @@ async fn get_or_create_environment(
.into_iter()
.map(UnresolvedRequirementSpecification::from)
.collect(),
constraints: constraints
.into_iter()
.map(NameRequirementSpecification::from)
.collect(),
overrides: overrides
.into_iter()
.map(UnresolvedRequirementSpecification::from)
.collect(),
..spec
};

Expand Down
12 changes: 12 additions & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -926,11 +926,23 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
.map(RequirementsSource::from_requirements_file),
)
.collect::<Vec<_>>();
let constraints = args
.constraints
.into_iter()
.map(RequirementsSource::from_constraints_txt)
.collect::<Vec<_>>();
let overrides = args
.overrides
.into_iter()
.map(RequirementsSource::from_overrides_txt)
.collect::<Vec<_>>();

commands::tool_run(
args.command,
args.from,
&requirements,
&constraints,
&overrides,
args.show_resolution || globals.verbose > 0,
args.python,
args.install_mirrors,
Expand Down
14 changes: 13 additions & 1 deletion crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,10 @@ pub(crate) struct ToolRunSettings {
pub(crate) command: Option<ExternalCommand>,
pub(crate) from: Option<String>,
pub(crate) with: Vec<String>,
pub(crate) with_editable: Vec<String>,
pub(crate) with_requirements: Vec<PathBuf>,
pub(crate) with_editable: Vec<String>,
pub(crate) constraints: Vec<PathBuf>,
pub(crate) overrides: Vec<PathBuf>,
pub(crate) isolated: bool,
pub(crate) show_resolution: bool,
pub(crate) python: Option<String>,
Expand All @@ -406,6 +408,8 @@ impl ToolRunSettings {
with,
with_editable,
with_requirements,
constraints,
overrides,
isolated,
show_resolution,
installer,
Expand Down Expand Up @@ -453,6 +457,14 @@ impl ToolRunSettings {
.into_iter()
.filter_map(Maybe::into_option)
.collect(),
constraints: constraints
.into_iter()
.filter_map(Maybe::into_option)
.collect(),
overrides: overrides
.into_iter()
.filter_map(Maybe::into_option)
.collect(),
isolated,
show_resolution,
python: python.and_then(Maybe::into_option),
Expand Down
14 changes: 14 additions & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -3053,6 +3053,13 @@ uv tool run [OPTIONS] [COMMAND]
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--config-setting</code>, <code>-C</code> <i>config-setting</i></dt><dd><p>Settings to pass to the PEP 517 build backend, specified as <code>KEY=VALUE</code> pairs</p>

</dd><dt><code>--constraints</code>, <code>-c</code> <i>constraints</i></dt><dd><p>Constrain versions using the given requirements files.</p>

<p>Constraints files are <code>requirements.txt</code>-like files that only control the <em>version</em> of a requirement that&#8217;s installed. However, including a package in a constraints file will <em>not</em> trigger the installation of that package.</p>

<p>This is equivalent to pip&#8217;s <code>--constraint</code> option.</p>

<p>May also be set with the <code>UV_CONSTRAINT</code> environment variable.</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>

<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
Expand Down Expand Up @@ -3217,6 +3224,13 @@ uv tool run [OPTIONS] [COMMAND]
<p>When disabled, uv will only use locally cached data and locally available files.</p>

<p>May also be set with the <code>UV_OFFLINE</code> environment variable.</p>
</dd><dt><code>--overrides</code> <i>overrides</i></dt><dd><p>Override versions using the given requirements files.</p>

<p>Overrides files are <code>requirements.txt</code>-like files that force a specific version of a requirement to be installed, regardless of the requirements declared by any constituent package, and regardless of whether this would be considered an invalid resolution.</p>

<p>While constraints are <em>additive</em>, in that they&#8217;re combined with the requirements of the constituent packages, overrides are <em>absolute</em>, in that they completely replace the requirements of the constituent packages.</p>

<p>May also be set with the <code>UV_OVERRIDE</code> environment variable.</p>
</dd><dt><code>--prerelease</code> <i>prerelease</i></dt><dd><p>The strategy to use when considering pre-release versions.</p>

<p>By default, uv will accept pre-releases for packages that <em>only</em> publish pre-releases, along with first-party requirements that contain an explicit pre-release marker in the declared specifiers (<code>if-necessary-or-explicit</code>).</p>
Expand Down

0 comments on commit b5f4395

Please sign in to comment.