mirror of https://github.com/astral-sh/uv
Allow `--constraints` and `--overrides` in `uv tool install` (#9547)
## Summary Closes https://github.com/astral-sh/uv/issues/9517.
This commit is contained in:
parent
63443f1984
commit
c30f53b295
|
|
@ -3778,6 +3778,28 @@ pub struct ToolInstallArgs {
|
||||||
#[arg(long, value_delimiter = ',', value_parser = parse_maybe_file_path)]
|
#[arg(long, value_delimiter = ',', value_parser = parse_maybe_file_path)]
|
||||||
pub with_requirements: Vec<Maybe<PathBuf>>,
|
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>>,
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub installer: ResolverInstallerArgs,
|
pub installer: ResolverInstallerArgs,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@ use uv_settings::ToolOptions;
|
||||||
pub struct Tool {
|
pub struct Tool {
|
||||||
/// The requirements requested by the user during installation.
|
/// The requirements requested by the user during installation.
|
||||||
requirements: Vec<Requirement>,
|
requirements: Vec<Requirement>,
|
||||||
|
/// The constraints requested by the user during installation.
|
||||||
|
constraints: Vec<Requirement>,
|
||||||
|
/// The overrides requested by the user during installation.
|
||||||
|
overrides: Vec<Requirement>,
|
||||||
/// The Python requested by the user during installation.
|
/// The Python requested by the user during installation.
|
||||||
python: Option<String>,
|
python: Option<String>,
|
||||||
/// A mapping of entry point names to their metadata.
|
/// A mapping of entry point names to their metadata.
|
||||||
|
|
@ -26,7 +30,12 @@ pub struct Tool {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
struct ToolWire {
|
struct ToolWire {
|
||||||
|
#[serde(default)]
|
||||||
requirements: Vec<RequirementWire>,
|
requirements: Vec<RequirementWire>,
|
||||||
|
#[serde(default)]
|
||||||
|
constraints: Vec<Requirement>,
|
||||||
|
#[serde(default)]
|
||||||
|
overrides: Vec<Requirement>,
|
||||||
python: Option<String>,
|
python: Option<String>,
|
||||||
entrypoints: Vec<ToolEntrypoint>,
|
entrypoints: Vec<ToolEntrypoint>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -51,6 +60,8 @@ impl From<Tool> for ToolWire {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(RequirementWire::Requirement)
|
.map(RequirementWire::Requirement)
|
||||||
.collect(),
|
.collect(),
|
||||||
|
constraints: tool.constraints,
|
||||||
|
overrides: tool.overrides,
|
||||||
python: tool.python,
|
python: tool.python,
|
||||||
entrypoints: tool.entrypoints,
|
entrypoints: tool.entrypoints,
|
||||||
options: tool.options,
|
options: tool.options,
|
||||||
|
|
@ -71,6 +82,8 @@ impl TryFrom<ToolWire> for Tool {
|
||||||
RequirementWire::Deprecated(requirement) => Requirement::from(requirement),
|
RequirementWire::Deprecated(requirement) => Requirement::from(requirement),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
constraints: tool.constraints,
|
||||||
|
overrides: tool.overrides,
|
||||||
python: tool.python,
|
python: tool.python,
|
||||||
entrypoints: tool.entrypoints,
|
entrypoints: tool.entrypoints,
|
||||||
options: tool.options,
|
options: tool.options,
|
||||||
|
|
@ -116,6 +129,8 @@ impl Tool {
|
||||||
/// Create a new `Tool`.
|
/// Create a new `Tool`.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
requirements: Vec<Requirement>,
|
requirements: Vec<Requirement>,
|
||||||
|
constraints: Vec<Requirement>,
|
||||||
|
overrides: Vec<Requirement>,
|
||||||
python: Option<String>,
|
python: Option<String>,
|
||||||
entrypoints: impl Iterator<Item = ToolEntrypoint>,
|
entrypoints: impl Iterator<Item = ToolEntrypoint>,
|
||||||
options: ToolOptions,
|
options: ToolOptions,
|
||||||
|
|
@ -124,6 +139,8 @@ impl Tool {
|
||||||
entrypoints.sort();
|
entrypoints.sort();
|
||||||
Self {
|
Self {
|
||||||
requirements,
|
requirements,
|
||||||
|
constraints,
|
||||||
|
overrides,
|
||||||
python,
|
python,
|
||||||
entrypoints,
|
entrypoints,
|
||||||
options,
|
options,
|
||||||
|
|
@ -140,6 +157,7 @@ impl Tool {
|
||||||
pub(crate) fn to_toml(&self) -> Result<Table, toml_edit::ser::Error> {
|
pub(crate) fn to_toml(&self) -> Result<Table, toml_edit::ser::Error> {
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
|
|
||||||
|
if !self.requirements.is_empty() {
|
||||||
table.insert("requirements", {
|
table.insert("requirements", {
|
||||||
let requirements = self
|
let requirements = self
|
||||||
.requirements
|
.requirements
|
||||||
|
|
@ -159,6 +177,51 @@ impl Tool {
|
||||||
};
|
};
|
||||||
value(requirements)
|
value(requirements)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.constraints.is_empty() {
|
||||||
|
table.insert("constraints", {
|
||||||
|
let constraints = self
|
||||||
|
.constraints
|
||||||
|
.iter()
|
||||||
|
.map(|constraint| {
|
||||||
|
serde::Serialize::serialize(
|
||||||
|
&constraint,
|
||||||
|
toml_edit::ser::ValueSerializer::new(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let constraints = match constraints.as_slice() {
|
||||||
|
[] => Array::new(),
|
||||||
|
[constraint] => Array::from_iter([constraint]),
|
||||||
|
constraints => each_element_on_its_line_array(constraints.iter()),
|
||||||
|
};
|
||||||
|
value(constraints)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.overrides.is_empty() {
|
||||||
|
table.insert("overrides", {
|
||||||
|
let overrides = self
|
||||||
|
.overrides
|
||||||
|
.iter()
|
||||||
|
.map(|r#override| {
|
||||||
|
serde::Serialize::serialize(
|
||||||
|
&r#override,
|
||||||
|
toml_edit::ser::ValueSerializer::new(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let overrides = match overrides.as_slice() {
|
||||||
|
[] => Array::new(),
|
||||||
|
[r#override] => Array::from_iter([r#override]),
|
||||||
|
overrides => each_element_on_its_line_array(overrides.iter()),
|
||||||
|
};
|
||||||
|
value(overrides)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ref python) = self.python {
|
if let Some(ref python) = self.python {
|
||||||
table.insert("python", value(python));
|
table.insert("python", value(python));
|
||||||
|
|
@ -196,6 +259,14 @@ impl Tool {
|
||||||
&self.requirements
|
&self.requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn constraints(&self) -> &[Requirement] {
|
||||||
|
&self.constraints
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn overrides(&self) -> &[Requirement] {
|
||||||
|
&self.overrides
|
||||||
|
}
|
||||||
|
|
||||||
pub fn python(&self) -> &Option<String> {
|
pub fn python(&self) -> &Option<String> {
|
||||||
&self.python
|
&self.python
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ pub(crate) fn install_executables(
|
||||||
force: bool,
|
force: bool,
|
||||||
python: Option<String>,
|
python: Option<String>,
|
||||||
requirements: Vec<Requirement>,
|
requirements: Vec<Requirement>,
|
||||||
|
constraints: Vec<Requirement>,
|
||||||
|
overrides: Vec<Requirement>,
|
||||||
printer: Printer,
|
printer: Printer,
|
||||||
) -> anyhow::Result<ExitStatus> {
|
) -> anyhow::Result<ExitStatus> {
|
||||||
let site_packages = SitePackages::from_environment(environment)?;
|
let site_packages = SitePackages::from_environment(environment)?;
|
||||||
|
|
@ -183,7 +185,9 @@ pub(crate) fn install_executables(
|
||||||
|
|
||||||
debug!("Adding receipt for tool `{name}`");
|
debug!("Adding receipt for tool `{name}`");
|
||||||
let tool = Tool::new(
|
let tool = Tool::new(
|
||||||
requirements.into_iter().collect(),
|
requirements,
|
||||||
|
constraints,
|
||||||
|
overrides,
|
||||||
python,
|
python,
|
||||||
target_entry_points
|
target_entry_points
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,13 @@ use std::str::FromStr;
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
use uv_cache::{Cache, Refresh};
|
use uv_cache::{Cache, Refresh};
|
||||||
use uv_cache_info::Timestamp;
|
use uv_cache_info::Timestamp;
|
||||||
use uv_client::{BaseClientBuilder, Connectivity};
|
use uv_client::{BaseClientBuilder, Connectivity};
|
||||||
use uv_configuration::{Concurrency, Reinstall, TrustedHost, Upgrade};
|
use uv_configuration::{Concurrency, Reinstall, TrustedHost, Upgrade};
|
||||||
use uv_dispatch::SharedState;
|
use uv_dispatch::SharedState;
|
||||||
use uv_distribution_types::UnresolvedRequirementSpecification;
|
use uv_distribution_types::{NameRequirementSpecification, UnresolvedRequirementSpecification};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_pep440::{VersionSpecifier, VersionSpecifiers};
|
use uv_pep440::{VersionSpecifier, VersionSpecifiers};
|
||||||
use uv_pep508::MarkerTree;
|
use uv_pep508::MarkerTree;
|
||||||
|
|
@ -43,6 +44,8 @@ pub(crate) async fn install(
|
||||||
editable: bool,
|
editable: bool,
|
||||||
from: Option<String>,
|
from: Option<String>,
|
||||||
with: &[RequirementsSource],
|
with: &[RequirementsSource],
|
||||||
|
constraints: &[RequirementsSource],
|
||||||
|
overrides: &[RequirementsSource],
|
||||||
python: Option<String>,
|
python: Option<String>,
|
||||||
install_mirrors: PythonInstallMirrors,
|
install_mirrors: PythonInstallMirrors,
|
||||||
force: bool,
|
force: bool,
|
||||||
|
|
@ -239,7 +242,9 @@ pub(crate) async fn install(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read the `--with` requirements.
|
// Read the `--with` requirements.
|
||||||
let spec = 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.
|
// Resolve the `--from` and `--with` requirements.
|
||||||
let requirements = {
|
let requirements = {
|
||||||
|
|
@ -263,6 +268,28 @@ pub(crate) async fn install(
|
||||||
requirements
|
requirements
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Resolve the constraints.
|
||||||
|
let constraints = spec
|
||||||
|
.constraints
|
||||||
|
.into_iter()
|
||||||
|
.map(|constraint| constraint.requirement)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Resolve the overrides.
|
||||||
|
let overrides = resolve_names(
|
||||||
|
spec.overrides,
|
||||||
|
&interpreter,
|
||||||
|
&settings,
|
||||||
|
&state,
|
||||||
|
connectivity,
|
||||||
|
concurrency,
|
||||||
|
native_tls,
|
||||||
|
allow_insecure_host,
|
||||||
|
&cache,
|
||||||
|
printer,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Convert to tool options.
|
// Convert to tool options.
|
||||||
let options = ToolOptions::from(options);
|
let options = ToolOptions::from(options);
|
||||||
|
|
||||||
|
|
@ -330,8 +357,10 @@ pub(crate) async fn install(
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
if let Some(tool_receipt) = existing_tool_receipt.as_ref() {
|
if let Some(tool_receipt) = existing_tool_receipt.as_ref() {
|
||||||
let receipt = tool_receipt.requirements().to_vec();
|
if requirements == tool_receipt.requirements()
|
||||||
if requirements == receipt {
|
&& constraints == tool_receipt.constraints()
|
||||||
|
&& overrides == tool_receipt.overrides()
|
||||||
|
{
|
||||||
if *tool_receipt.options() != options {
|
if *tool_receipt.options() != options {
|
||||||
// ...but the options differ, we need to update the receipt.
|
// ...but the options differ, we need to update the receipt.
|
||||||
installed_tools
|
installed_tools
|
||||||
|
|
@ -357,6 +386,16 @@ pub(crate) async fn install(
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(UnresolvedRequirementSpecification::from)
|
.map(UnresolvedRequirementSpecification::from)
|
||||||
.collect(),
|
.collect(),
|
||||||
|
constraints: constraints
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(NameRequirementSpecification::from)
|
||||||
|
.collect(),
|
||||||
|
overrides: overrides
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(UnresolvedRequirementSpecification::from)
|
||||||
|
.collect(),
|
||||||
..spec
|
..spec
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -470,6 +509,8 @@ pub(crate) async fn install(
|
||||||
force || invalid_tool_receipt,
|
force || invalid_tool_receipt,
|
||||||
python,
|
python,
|
||||||
requirements,
|
requirements,
|
||||||
|
constraints,
|
||||||
|
overrides,
|
||||||
printer,
|
printer,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -266,9 +266,16 @@ async fn upgrade_tool(
|
||||||
let settings = ResolverInstallerSettings::from(options.clone());
|
let settings = ResolverInstallerSettings::from(options.clone());
|
||||||
|
|
||||||
// Resolve the requirements.
|
// Resolve the requirements.
|
||||||
let requirements = existing_tool_receipt.requirements();
|
let spec = RequirementsSpecification::from_overrides(
|
||||||
let spec =
|
existing_tool_receipt.requirements().to_vec(),
|
||||||
RequirementsSpecification::from_constraints(requirements.to_vec(), constraints.to_vec());
|
existing_tool_receipt
|
||||||
|
.constraints()
|
||||||
|
.iter()
|
||||||
|
.chain(constraints)
|
||||||
|
.cloned()
|
||||||
|
.collect(),
|
||||||
|
existing_tool_receipt.overrides().to_vec(),
|
||||||
|
);
|
||||||
|
|
||||||
// Initialize any shared state.
|
// Initialize any shared state.
|
||||||
let state = SharedState::default();
|
let state = SharedState::default();
|
||||||
|
|
@ -362,7 +369,9 @@ async fn upgrade_tool(
|
||||||
ToolOptions::from(options),
|
ToolOptions::from(options),
|
||||||
true,
|
true,
|
||||||
existing_tool_receipt.python().to_owned(),
|
existing_tool_receipt.python().to_owned(),
|
||||||
requirements.to_vec(),
|
existing_tool_receipt.requirements().to_vec(),
|
||||||
|
existing_tool_receipt.constraints().to_vec(),
|
||||||
|
existing_tool_receipt.overrides().to_vec(),
|
||||||
printer,
|
printer,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -954,12 +954,24 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||||
.map(RequirementsSource::from_requirements_file),
|
.map(RequirementsSource::from_requirements_file),
|
||||||
)
|
)
|
||||||
.collect::<Vec<_>>();
|
.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<_>>();
|
||||||
|
|
||||||
Box::pin(commands::tool_install(
|
Box::pin(commands::tool_install(
|
||||||
args.package,
|
args.package,
|
||||||
args.editable,
|
args.editable,
|
||||||
args.from,
|
args.from,
|
||||||
&requirements,
|
&requirements,
|
||||||
|
&constraints,
|
||||||
|
&overrides,
|
||||||
args.python,
|
args.python,
|
||||||
args.install_mirrors,
|
args.install_mirrors,
|
||||||
args.force,
|
args.force,
|
||||||
|
|
|
||||||
|
|
@ -462,6 +462,8 @@ pub(crate) struct ToolInstallSettings {
|
||||||
pub(crate) with: Vec<String>,
|
pub(crate) with: Vec<String>,
|
||||||
pub(crate) with_requirements: Vec<PathBuf>,
|
pub(crate) with_requirements: Vec<PathBuf>,
|
||||||
pub(crate) with_editable: Vec<String>,
|
pub(crate) with_editable: Vec<String>,
|
||||||
|
pub(crate) constraints: Vec<PathBuf>,
|
||||||
|
pub(crate) overrides: Vec<PathBuf>,
|
||||||
pub(crate) python: Option<String>,
|
pub(crate) python: Option<String>,
|
||||||
pub(crate) refresh: Refresh,
|
pub(crate) refresh: Refresh,
|
||||||
pub(crate) options: ResolverInstallerOptions,
|
pub(crate) options: ResolverInstallerOptions,
|
||||||
|
|
@ -482,6 +484,8 @@ impl ToolInstallSettings {
|
||||||
with,
|
with,
|
||||||
with_editable,
|
with_editable,
|
||||||
with_requirements,
|
with_requirements,
|
||||||
|
constraints,
|
||||||
|
overrides,
|
||||||
installer,
|
installer,
|
||||||
force,
|
force,
|
||||||
build,
|
build,
|
||||||
|
|
@ -519,6 +523,14 @@ impl ToolInstallSettings {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(Maybe::into_option)
|
.filter_map(Maybe::into_option)
|
||||||
.collect(),
|
.collect(),
|
||||||
|
constraints: constraints
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(Maybe::into_option)
|
||||||
|
.collect(),
|
||||||
|
overrides: overrides
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(Maybe::into_option)
|
||||||
|
.collect(),
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
force,
|
force,
|
||||||
editable,
|
editable,
|
||||||
|
|
|
||||||
|
|
@ -2704,6 +2704,8 @@ fn resolve_tool() -> anyhow::Result<()> {
|
||||||
with: [],
|
with: [],
|
||||||
with_requirements: [],
|
with_requirements: [],
|
||||||
with_editable: [],
|
with_editable: [],
|
||||||
|
constraints: [],
|
||||||
|
overrides: [],
|
||||||
python: None,
|
python: None,
|
||||||
refresh: None(
|
refresh: None(
|
||||||
Timestamp(
|
Timestamp(
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ fn tool_install() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tool_install_with_editable() -> anyhow::Result<()> {
|
fn tool_install_with_editable() -> Result<()> {
|
||||||
let context = TestContext::new("3.12")
|
let context = TestContext::new("3.12")
|
||||||
.with_filtered_counts()
|
.with_filtered_counts()
|
||||||
.with_filtered_exe_suffix();
|
.with_filtered_exe_suffix();
|
||||||
|
|
@ -3114,3 +3114,174 @@ fn tool_install_at_latest_upgrade() {
|
||||||
"###);
|
"###);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Install a tool with `--constraints`.
|
||||||
|
#[test]
|
||||||
|
fn tool_install_constraints() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12")
|
||||||
|
.with_filtered_counts()
|
||||||
|
.with_filtered_exe_suffix();
|
||||||
|
let tool_dir = context.temp_dir.child("tools");
|
||||||
|
let bin_dir = context.temp_dir.child("bin");
|
||||||
|
|
||||||
|
let constraints_txt = context.temp_dir.child("constraints.txt");
|
||||||
|
constraints_txt.write_str(indoc::indoc! {r"
|
||||||
|
mypy-extensions<1
|
||||||
|
anyio>=3
|
||||||
|
"})?;
|
||||||
|
|
||||||
|
// Install `black`.
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.arg("--constraints")
|
||||||
|
.arg(constraints_txt.as_os_str())
|
||||||
|
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||||
|
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||||
|
.env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved [N] packages in [TIME]
|
||||||
|
Prepared [N] packages in [TIME]
|
||||||
|
Installed [N] packages in [TIME]
|
||||||
|
+ black==24.3.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ mypy-extensions==0.4.4
|
||||||
|
+ packaging==24.0
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.2.0
|
||||||
|
Installed 2 executables: black, blackd
|
||||||
|
"###);
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// We should have a tool receipt
|
||||||
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||||
|
[tool]
|
||||||
|
requirements = [{ name = "black" }]
|
||||||
|
constraints = [
|
||||||
|
{ name = "mypy-extensions", specifier = "<1" },
|
||||||
|
{ name = "anyio", specifier = ">=3" },
|
||||||
|
]
|
||||||
|
entrypoints = [
|
||||||
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||||
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.options]
|
||||||
|
exclude-newer = "2024-03-25T00:00:00Z"
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Installing with the same constraints should be a no-op.
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.arg("--constraints")
|
||||||
|
.arg(constraints_txt.as_os_str())
|
||||||
|
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||||
|
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||||
|
.env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
`black` is already installed
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let constraints_txt = context.temp_dir.child("constraints.txt");
|
||||||
|
constraints_txt.write_str(indoc::indoc! {r"
|
||||||
|
platformdirs<4
|
||||||
|
"})?;
|
||||||
|
|
||||||
|
// Installing with revised constraints should reinstall the tool.
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.arg("--constraints")
|
||||||
|
.arg(constraints_txt.as_os_str())
|
||||||
|
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||||
|
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||||
|
.env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved [N] packages in [TIME]
|
||||||
|
Prepared [N] packages in [TIME]
|
||||||
|
Uninstalled [N] packages in [TIME]
|
||||||
|
Installed [N] packages in [TIME]
|
||||||
|
- platformdirs==4.2.0
|
||||||
|
+ platformdirs==3.11.0
|
||||||
|
Installed 2 executables: black, blackd
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install a tool with `--overrides`.
|
||||||
|
#[test]
|
||||||
|
fn tool_install_overrides() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12")
|
||||||
|
.with_filtered_counts()
|
||||||
|
.with_filtered_exe_suffix();
|
||||||
|
let tool_dir = context.temp_dir.child("tools");
|
||||||
|
let bin_dir = context.temp_dir.child("bin");
|
||||||
|
|
||||||
|
let overrides_txt = context.temp_dir.child("overrides.txt");
|
||||||
|
overrides_txt.write_str(indoc::indoc! {r"
|
||||||
|
click<8
|
||||||
|
anyio>=3
|
||||||
|
"})?;
|
||||||
|
|
||||||
|
// Install `black`.
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.arg("--overrides")
|
||||||
|
.arg(overrides_txt.as_os_str())
|
||||||
|
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||||
|
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||||
|
.env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved [N] packages in [TIME]
|
||||||
|
Prepared [N] packages in [TIME]
|
||||||
|
Installed [N] packages in [TIME]
|
||||||
|
+ black==24.3.0
|
||||||
|
+ click==7.1.2
|
||||||
|
+ mypy-extensions==1.0.0
|
||||||
|
+ packaging==24.0
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.2.0
|
||||||
|
Installed 2 executables: black, blackd
|
||||||
|
"###);
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// We should have a tool receipt
|
||||||
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||||
|
[tool]
|
||||||
|
requirements = [{ name = "black" }]
|
||||||
|
overrides = [
|
||||||
|
{ name = "click", specifier = "<8" },
|
||||||
|
{ name = "anyio", specifier = ">=3" },
|
||||||
|
]
|
||||||
|
entrypoints = [
|
||||||
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||||
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.options]
|
||||||
|
exclude-newer = "2024-03-25T00:00:00Z"
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3241,6 +3241,13 @@ uv tool install [OPTIONS] <PACKAGE>
|
||||||
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
|
<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>--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’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’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: <https://pypi.org/simple>).</p>
|
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: <https://pypi.org/simple>).</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>
|
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
|
||||||
|
|
@ -3388,6 +3395,13 @@ uv tool install [OPTIONS] <PACKAGE>
|
||||||
|
|
||||||
<p>When disabled, uv will only use locally cached data and locally available files.</p>
|
<p>When disabled, uv will only use locally cached data and locally available files.</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’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>
|
</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>
|
<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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue