mirror of https://github.com/astral-sh/uv
Support build constraints in `uv tool` and PEP723 scripts. (#12842)
## Summary Closes https://github.com/astral-sh/uv/issues/12496. ## Test Plan `cargo test` --------- Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
parent
66df255a9c
commit
e4047e5888
|
|
@ -4102,6 +4102,15 @@ pub struct ToolRunArgs {
|
|||
#[arg(long, short, alias = "constraint", env = EnvVars::UV_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
|
||||
pub constraints: Vec<Maybe<PathBuf>>,
|
||||
|
||||
/// Constrain build dependencies using the given requirements files when building source
|
||||
/// distributions.
|
||||
///
|
||||
/// 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.
|
||||
#[arg(long, short, alias = "build-constraint", env = EnvVars::UV_BUILD_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
|
||||
pub build_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
|
||||
|
|
@ -4212,6 +4221,15 @@ pub struct ToolInstallArgs {
|
|||
#[arg(long, alias = "override", env = EnvVars::UV_OVERRIDE, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
|
||||
pub overrides: Vec<Maybe<PathBuf>>,
|
||||
|
||||
/// Constrain build dependencies using the given requirements files when building source
|
||||
/// distributions.
|
||||
///
|
||||
/// 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.
|
||||
#[arg(long, short, alias = "build-constraint", env = EnvVars::UV_BUILD_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
|
||||
pub build_constraints: Vec<Maybe<PathBuf>>,
|
||||
|
||||
#[command(flatten)]
|
||||
pub installer: ResolverInstallerArgs,
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ pub struct Tool {
|
|||
constraints: Vec<Requirement>,
|
||||
/// The overrides requested by the user during installation.
|
||||
overrides: Vec<Requirement>,
|
||||
/// The build constraints requested by the user during installation.
|
||||
build_constraints: Vec<Requirement>,
|
||||
/// The Python requested by the user during installation.
|
||||
python: Option<String>,
|
||||
/// A mapping of entry point names to their metadata.
|
||||
|
|
@ -28,6 +30,7 @@ pub struct Tool {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct ToolWire {
|
||||
#[serde(default)]
|
||||
requirements: Vec<RequirementWire>,
|
||||
|
|
@ -35,6 +38,8 @@ struct ToolWire {
|
|||
constraints: Vec<Requirement>,
|
||||
#[serde(default)]
|
||||
overrides: Vec<Requirement>,
|
||||
#[serde(default)]
|
||||
build_constraint_dependencies: Vec<Requirement>,
|
||||
python: Option<String>,
|
||||
entrypoints: Vec<ToolEntrypoint>,
|
||||
#[serde(default)]
|
||||
|
|
@ -61,6 +66,7 @@ impl From<Tool> for ToolWire {
|
|||
.collect(),
|
||||
constraints: tool.constraints,
|
||||
overrides: tool.overrides,
|
||||
build_constraint_dependencies: tool.build_constraints,
|
||||
python: tool.python,
|
||||
entrypoints: tool.entrypoints,
|
||||
options: tool.options,
|
||||
|
|
@ -83,6 +89,7 @@ impl TryFrom<ToolWire> for Tool {
|
|||
.collect(),
|
||||
constraints: tool.constraints,
|
||||
overrides: tool.overrides,
|
||||
build_constraints: tool.build_constraint_dependencies,
|
||||
python: tool.python,
|
||||
entrypoints: tool.entrypoints,
|
||||
options: tool.options,
|
||||
|
|
@ -156,6 +163,7 @@ impl Tool {
|
|||
requirements: Vec<Requirement>,
|
||||
constraints: Vec<Requirement>,
|
||||
overrides: Vec<Requirement>,
|
||||
build_constraints: Vec<Requirement>,
|
||||
python: Option<String>,
|
||||
entrypoints: impl Iterator<Item = ToolEntrypoint>,
|
||||
options: ToolOptions,
|
||||
|
|
@ -166,6 +174,7 @@ impl Tool {
|
|||
requirements,
|
||||
constraints,
|
||||
overrides,
|
||||
build_constraints,
|
||||
python,
|
||||
entrypoints,
|
||||
options,
|
||||
|
|
@ -248,6 +257,28 @@ impl Tool {
|
|||
});
|
||||
}
|
||||
|
||||
if !self.build_constraints.is_empty() {
|
||||
table.insert("build-constraint-dependencies", {
|
||||
let build_constraints = self
|
||||
.build_constraints
|
||||
.iter()
|
||||
.map(|r#build_constraint| {
|
||||
serde::Serialize::serialize(
|
||||
&r#build_constraint,
|
||||
toml_edit::ser::ValueSerializer::new(),
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let build_constraints = match build_constraints.as_slice() {
|
||||
[] => Array::new(),
|
||||
[r#build_constraint] => Array::from_iter([r#build_constraint]),
|
||||
build_constraints => each_element_on_its_line_array(build_constraints.iter()),
|
||||
};
|
||||
value(build_constraints)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(ref python) = self.python {
|
||||
table.insert("python", value(python));
|
||||
}
|
||||
|
|
@ -292,6 +323,10 @@ impl Tool {
|
|||
&self.overrides
|
||||
}
|
||||
|
||||
pub fn build_constraints(&self) -> &[Requirement] {
|
||||
&self.build_constraints
|
||||
}
|
||||
|
||||
pub fn python(&self) -> &Option<String> {
|
||||
&self.python
|
||||
}
|
||||
|
|
|
|||
|
|
@ -519,8 +519,8 @@ async fn build_package(
|
|||
|
||||
let build_constraints = Constraints::from_requirements(
|
||||
build_constraints
|
||||
.iter()
|
||||
.map(|constraint| constraint.requirement.clone()),
|
||||
.into_iter()
|
||||
.map(|constraint| constraint.requirement),
|
||||
);
|
||||
|
||||
// Initialize the registry client.
|
||||
|
|
|
|||
|
|
@ -213,8 +213,7 @@ pub(crate) async fn pip_compile(
|
|||
let build_constraints: Vec<NameRequirementSpecification> =
|
||||
operations::read_constraints(build_constraints, &client_builder)
|
||||
.await?
|
||||
.iter()
|
||||
.cloned()
|
||||
.into_iter()
|
||||
.chain(
|
||||
build_constraints_from_workspace
|
||||
.into_iter()
|
||||
|
|
|
|||
|
|
@ -154,8 +154,7 @@ pub(crate) async fn pip_install(
|
|||
let build_constraints: Vec<NameRequirementSpecification> =
|
||||
operations::read_constraints(build_constraints, &client_builder)
|
||||
.await?
|
||||
.iter()
|
||||
.cloned()
|
||||
.into_iter()
|
||||
.chain(
|
||||
build_constraints_from_workspace
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use tracing::debug;
|
|||
|
||||
use uv_cache::{Cache, CacheBucket};
|
||||
use uv_cache_key::{cache_digest, hash_digest};
|
||||
use uv_configuration::{Concurrency, PreviewMode};
|
||||
use uv_configuration::{Concurrency, Constraints, PreviewMode};
|
||||
use uv_distribution_types::{Name, Resolution};
|
||||
use uv_python::{Interpreter, PythonEnvironment};
|
||||
|
||||
|
|
@ -28,6 +28,7 @@ impl CachedEnvironment {
|
|||
/// Get or create an [`CachedEnvironment`] based on a given set of requirements.
|
||||
pub(crate) async fn from_spec(
|
||||
spec: EnvironmentSpecification<'_>,
|
||||
build_constraints: Constraints,
|
||||
interpreter: &Interpreter,
|
||||
settings: &ResolverInstallerSettings,
|
||||
network_settings: &NetworkSettings,
|
||||
|
|
@ -99,6 +100,7 @@ impl CachedEnvironment {
|
|||
venv,
|
||||
&resolution,
|
||||
Modifications::Exact,
|
||||
build_constraints,
|
||||
settings.into(),
|
||||
network_settings,
|
||||
state,
|
||||
|
|
|
|||
|
|
@ -1824,6 +1824,7 @@ pub(crate) async fn sync_environment(
|
|||
venv: PythonEnvironment,
|
||||
resolution: &Resolution,
|
||||
modifications: Modifications,
|
||||
build_constraints: Constraints,
|
||||
settings: InstallerSettingsRef<'_>,
|
||||
network_settings: &NetworkSettings,
|
||||
state: &PlatformState,
|
||||
|
|
@ -1891,7 +1892,6 @@ pub(crate) async fn sync_environment(
|
|||
|
||||
// TODO(charlie): These are all default values. We should consider whether we want to make them
|
||||
// optional on the downstream APIs.
|
||||
let build_constraints = Constraints::default();
|
||||
let build_hasher = HashStrategy::default();
|
||||
let dry_run = DryRun::default();
|
||||
let hasher = HashStrategy::default();
|
||||
|
|
@ -1982,6 +1982,7 @@ pub(crate) async fn update_environment(
|
|||
venv: PythonEnvironment,
|
||||
spec: RequirementsSpecification,
|
||||
modifications: Modifications,
|
||||
build_constraints: Constraints,
|
||||
settings: &ResolverInstallerSettings,
|
||||
network_settings: &NetworkSettings,
|
||||
state: &SharedState,
|
||||
|
|
@ -2113,7 +2114,6 @@ pub(crate) async fn update_environment(
|
|||
|
||||
// TODO(charlie): These are all default values. We should consider whether we want to make them
|
||||
// optional on the downstream APIs.
|
||||
let build_constraints = Constraints::default();
|
||||
let build_hasher = HashStrategy::default();
|
||||
let extras = ExtrasSpecification::default();
|
||||
let groups = BTreeMap::new();
|
||||
|
|
|
|||
|
|
@ -17,9 +17,10 @@ use uv_cache::Cache;
|
|||
use uv_cli::ExternalCommand;
|
||||
use uv_client::BaseClientBuilder;
|
||||
use uv_configuration::{
|
||||
Concurrency, DependencyGroups, DryRun, EditableMode, ExtrasSpecification, InstallOptions,
|
||||
PreviewMode,
|
||||
Concurrency, Constraints, DependencyGroups, DryRun, EditableMode, ExtrasSpecification,
|
||||
InstallOptions, PreviewMode,
|
||||
};
|
||||
use uv_distribution_types::Requirement;
|
||||
use uv_fs::which::is_executable;
|
||||
use uv_fs::{PythonExt, Simplified};
|
||||
use uv_installer::{SatisfiesResult, SitePackages};
|
||||
|
|
@ -351,10 +352,28 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
.await?
|
||||
.into_environment()?;
|
||||
|
||||
let build_constraints = script
|
||||
.metadata()
|
||||
.tool
|
||||
.as_ref()
|
||||
.and_then(|tool| {
|
||||
tool.uv
|
||||
.as_ref()
|
||||
.and_then(|uv| uv.build_constraint_dependencies.as_ref())
|
||||
})
|
||||
.map(|constraints| {
|
||||
Constraints::from_requirements(
|
||||
constraints
|
||||
.iter()
|
||||
.map(|constraint| Requirement::from(constraint.clone())),
|
||||
)
|
||||
});
|
||||
|
||||
match update_environment(
|
||||
environment,
|
||||
spec,
|
||||
modifications,
|
||||
build_constraints.unwrap_or_default(),
|
||||
&settings,
|
||||
&network_settings,
|
||||
&sync_state,
|
||||
|
|
@ -880,6 +899,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
lock.as_ref()
|
||||
.map(|(lock, install_path)| (lock, install_path.as_ref())),
|
||||
),
|
||||
// TODO(bluefact): Respect build constraints for `uv run --with` dependencies.
|
||||
Constraints::default(),
|
||||
&base_interpreter,
|
||||
&settings,
|
||||
&network_settings,
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ use uv_auth::UrlAuthPolicies;
|
|||
use uv_cache::Cache;
|
||||
use uv_client::{FlatIndexClient, RegistryClientBuilder};
|
||||
use uv_configuration::{
|
||||
Concurrency, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode,
|
||||
Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode,
|
||||
ExtrasSpecification, HashCheckingMode, InstallOptions, PreviewMode,
|
||||
};
|
||||
use uv_dispatch::BuildDispatch;
|
||||
use uv_distribution_types::{
|
||||
DirectorySourceDist, Dist, Index, Resolution, ResolvedDist, SourceDist,
|
||||
DirectorySourceDist, Dist, Index, Requirement, Resolution, ResolvedDist, SourceDist,
|
||||
};
|
||||
use uv_fs::Simplified;
|
||||
use uv_installer::SitePackages;
|
||||
|
|
@ -274,12 +274,33 @@ pub(crate) async fn sync(
|
|||
));
|
||||
}
|
||||
|
||||
// Parse the requirements from the script.
|
||||
let spec = script_specification(Pep723ItemRef::Script(script), &settings.resolver)?
|
||||
.unwrap_or_default();
|
||||
|
||||
// Parse the build constraints from the script.
|
||||
let build_constraints = script
|
||||
.metadata
|
||||
.tool
|
||||
.as_ref()
|
||||
.and_then(|tool| {
|
||||
tool.uv
|
||||
.as_ref()
|
||||
.and_then(|uv| uv.build_constraint_dependencies.as_ref())
|
||||
})
|
||||
.map(|constraints| {
|
||||
Constraints::from_requirements(
|
||||
constraints
|
||||
.iter()
|
||||
.map(|constraint| Requirement::from(constraint.clone())),
|
||||
)
|
||||
});
|
||||
|
||||
match update_environment(
|
||||
Deref::deref(&environment).clone(),
|
||||
spec,
|
||||
modifications,
|
||||
build_constraints.unwrap_or_default(),
|
||||
&settings,
|
||||
&network_settings,
|
||||
&PlatformState::default(),
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ pub(crate) fn install_executables(
|
|||
requirements: Vec<Requirement>,
|
||||
constraints: Vec<Requirement>,
|
||||
overrides: Vec<Requirement>,
|
||||
build_constraints: Vec<Requirement>,
|
||||
printer: Printer,
|
||||
) -> anyhow::Result<ExitStatus> {
|
||||
let site_packages = SitePackages::from_environment(environment)?;
|
||||
|
|
@ -289,6 +290,7 @@ pub(crate) fn install_executables(
|
|||
requirements,
|
||||
constraints,
|
||||
overrides,
|
||||
build_constraints,
|
||||
python,
|
||||
target_entry_points
|
||||
.into_iter()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use tracing::{debug, trace};
|
|||
use uv_cache::{Cache, Refresh};
|
||||
use uv_cache_info::Timestamp;
|
||||
use uv_client::BaseClientBuilder;
|
||||
use uv_configuration::{Concurrency, DryRun, PreviewMode, Reinstall, Upgrade};
|
||||
use uv_configuration::{Concurrency, Constraints, DryRun, PreviewMode, Reinstall, Upgrade};
|
||||
use uv_distribution_types::{
|
||||
NameRequirementSpecification, Requirement, RequirementSource,
|
||||
UnresolvedRequirementSpecification,
|
||||
|
|
@ -27,7 +27,7 @@ use uv_warnings::warn_user;
|
|||
use uv_workspace::WorkspaceCache;
|
||||
|
||||
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger};
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::pip::operations::{self, Modifications};
|
||||
use crate::commands::project::{
|
||||
resolve_environment, resolve_names, sync_environment, update_environment,
|
||||
EnvironmentSpecification, PlatformState, ProjectError,
|
||||
|
|
@ -48,6 +48,7 @@ pub(crate) async fn install(
|
|||
with: &[RequirementsSource],
|
||||
constraints: &[RequirementsSource],
|
||||
overrides: &[RequirementsSource],
|
||||
build_constraints: &[RequirementsSource],
|
||||
python: Option<String>,
|
||||
install_mirrors: PythonInstallMirrors,
|
||||
force: bool,
|
||||
|
|
@ -290,6 +291,14 @@ pub(crate) async fn install(
|
|||
)
|
||||
.await?;
|
||||
|
||||
// Resolve the build constraints.
|
||||
let build_constraints: Vec<Requirement> =
|
||||
operations::read_constraints(build_constraints, &client_builder)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|constraint| constraint.requirement)
|
||||
.collect();
|
||||
|
||||
// Convert to tool options.
|
||||
let options = ToolOptions::from(options);
|
||||
|
||||
|
|
@ -362,6 +371,7 @@ pub(crate) async fn install(
|
|||
if requirements == tool_receipt.requirements()
|
||||
&& constraints == tool_receipt.constraints()
|
||||
&& overrides == tool_receipt.overrides()
|
||||
&& build_constraints == tool_receipt.build_constraints()
|
||||
{
|
||||
if *tool_receipt.options() != options {
|
||||
// ...but the options differ, we need to update the receipt.
|
||||
|
|
@ -410,6 +420,7 @@ pub(crate) async fn install(
|
|||
environment,
|
||||
spec,
|
||||
Modifications::Exact,
|
||||
Constraints::from_requirements(build_constraints.iter().cloned()),
|
||||
&settings,
|
||||
&network_settings,
|
||||
&state,
|
||||
|
|
@ -540,6 +551,7 @@ pub(crate) async fn install(
|
|||
environment,
|
||||
&resolution.into(),
|
||||
Modifications::Exact,
|
||||
Constraints::from_requirements(build_constraints.iter().cloned()),
|
||||
(&settings).into(),
|
||||
&network_settings,
|
||||
&state,
|
||||
|
|
@ -576,6 +588,7 @@ pub(crate) async fn install(
|
|||
requirements,
|
||||
constraints,
|
||||
overrides,
|
||||
build_constraints,
|
||||
printer,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use uv_cache::{Cache, Refresh};
|
|||
use uv_cache_info::Timestamp;
|
||||
use uv_cli::ExternalCommand;
|
||||
use uv_client::BaseClientBuilder;
|
||||
use uv_configuration::Constraints;
|
||||
use uv_configuration::{Concurrency, PreviewMode};
|
||||
use uv_distribution_types::{
|
||||
IndexUrl, Name, NameRequirementSpecification, Requirement, RequirementSource,
|
||||
|
|
@ -43,6 +44,7 @@ use uv_workspace::WorkspaceCache;
|
|||
use crate::commands::pip::loggers::{
|
||||
DefaultInstallLogger, DefaultResolveLogger, SummaryInstallLogger, SummaryResolveLogger,
|
||||
};
|
||||
use crate::commands::pip::operations;
|
||||
use crate::commands::project::{
|
||||
resolve_names, EnvironmentSpecification, PlatformState, ProjectError,
|
||||
};
|
||||
|
|
@ -82,6 +84,7 @@ pub(crate) async fn run(
|
|||
with: &[RequirementsSource],
|
||||
constraints: &[RequirementsSource],
|
||||
overrides: &[RequirementsSource],
|
||||
build_constraints: &[RequirementsSource],
|
||||
show_resolution: bool,
|
||||
python: Option<String>,
|
||||
install_mirrors: PythonInstallMirrors,
|
||||
|
|
@ -244,11 +247,12 @@ pub(crate) async fn run(
|
|||
};
|
||||
|
||||
// Get or create a compatible environment in which to execute the tool.
|
||||
let result = get_or_create_environment(
|
||||
let result = Box::pin(get_or_create_environment(
|
||||
&request,
|
||||
with,
|
||||
constraints,
|
||||
overrides,
|
||||
build_constraints,
|
||||
show_resolution,
|
||||
python.as_deref(),
|
||||
install_mirrors,
|
||||
|
|
@ -263,7 +267,7 @@ pub(crate) async fn run(
|
|||
&cache,
|
||||
printer,
|
||||
preview,
|
||||
)
|
||||
))
|
||||
.await;
|
||||
|
||||
let (from, environment) = match result {
|
||||
|
|
@ -602,6 +606,7 @@ async fn get_or_create_environment(
|
|||
with: &[RequirementsSource],
|
||||
constraints: &[RequirementsSource],
|
||||
overrides: &[RequirementsSource],
|
||||
build_constraints: &[RequirementsSource],
|
||||
show_resolution: bool,
|
||||
python: Option<&str>,
|
||||
install_mirrors: PythonInstallMirrors,
|
||||
|
|
@ -905,11 +910,20 @@ async fn get_or_create_environment(
|
|||
..spec
|
||||
});
|
||||
|
||||
// Read the `--build-constraints` requirements.
|
||||
let build_constraints = Constraints::from_requirements(
|
||||
operations::read_constraints(build_constraints, &client_builder)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|constraint| constraint.requirement),
|
||||
);
|
||||
|
||||
// TODO(zanieb): When implementing project-level tools, discover the project and check if it has the tool.
|
||||
// TODO(zanieb): Determine if we should layer on top of the project environment if it is present.
|
||||
|
||||
let result = CachedEnvironment::from_spec(
|
||||
spec.clone(),
|
||||
build_constraints.clone(),
|
||||
&interpreter,
|
||||
settings,
|
||||
network_settings,
|
||||
|
|
@ -967,6 +981,7 @@ async fn get_or_create_environment(
|
|||
|
||||
CachedEnvironment::from_spec(
|
||||
spec,
|
||||
build_constraints,
|
||||
&interpreter,
|
||||
settings,
|
||||
network_settings,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use tracing::debug;
|
|||
|
||||
use uv_cache::Cache;
|
||||
use uv_client::BaseClientBuilder;
|
||||
use uv_configuration::{Concurrency, DryRun, PreviewMode};
|
||||
use uv_configuration::{Concurrency, Constraints, DryRun, PreviewMode};
|
||||
use uv_distribution_types::Requirement;
|
||||
use uv_fs::CWD;
|
||||
use uv_normalize::PackageName;
|
||||
|
|
@ -268,6 +268,9 @@ async fn upgrade_tool(
|
|||
);
|
||||
let settings = ResolverInstallerSettings::from(options.clone());
|
||||
|
||||
let build_constraints =
|
||||
Constraints::from_requirements(existing_tool_receipt.build_constraints().iter().cloned());
|
||||
|
||||
// Resolve the requirements.
|
||||
let spec = RequirementsSpecification::from_overrides(
|
||||
existing_tool_receipt.requirements().to_vec(),
|
||||
|
|
@ -310,6 +313,7 @@ async fn upgrade_tool(
|
|||
environment,
|
||||
&resolution.into(),
|
||||
Modifications::Exact,
|
||||
build_constraints,
|
||||
(&settings).into(),
|
||||
network_settings,
|
||||
&state,
|
||||
|
|
@ -334,6 +338,7 @@ async fn upgrade_tool(
|
|||
environment,
|
||||
spec,
|
||||
Modifications::Exact,
|
||||
build_constraints,
|
||||
&settings,
|
||||
network_settings,
|
||||
&state,
|
||||
|
|
@ -379,6 +384,7 @@ async fn upgrade_tool(
|
|||
existing_tool_receipt.requirements().to_vec(),
|
||||
existing_tool_receipt.constraints().to_vec(),
|
||||
existing_tool_receipt.overrides().to_vec(),
|
||||
existing_tool_receipt.build_constraints().to_vec(),
|
||||
printer,
|
||||
)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1104,12 +1104,19 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
.map(RequirementsSource::from_overrides_txt)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let build_constraints = args
|
||||
.build_constraints
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_constraints_txt)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Box::pin(commands::tool_run(
|
||||
args.command,
|
||||
args.from,
|
||||
&requirements,
|
||||
&constraints,
|
||||
&overrides,
|
||||
&build_constraints,
|
||||
args.show_resolution || globals.verbose > 0,
|
||||
args.python,
|
||||
args.install_mirrors,
|
||||
|
|
@ -1169,6 +1176,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
.into_iter()
|
||||
.map(RequirementsSource::from_overrides_txt)
|
||||
.collect::<Vec<_>>();
|
||||
let build_constraints = args
|
||||
.build_constraints
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_constraints_txt)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Box::pin(commands::tool_install(
|
||||
args.package,
|
||||
|
|
@ -1177,6 +1189,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
&requirements,
|
||||
&constraints,
|
||||
&overrides,
|
||||
&build_constraints,
|
||||
args.python,
|
||||
args.install_mirrors,
|
||||
args.force,
|
||||
|
|
|
|||
|
|
@ -459,6 +459,7 @@ pub(crate) struct ToolRunSettings {
|
|||
pub(crate) with_editable: Vec<String>,
|
||||
pub(crate) constraints: Vec<PathBuf>,
|
||||
pub(crate) overrides: Vec<PathBuf>,
|
||||
pub(crate) build_constraints: Vec<PathBuf>,
|
||||
pub(crate) isolated: bool,
|
||||
pub(crate) show_resolution: bool,
|
||||
pub(crate) python: Option<String>,
|
||||
|
|
@ -486,6 +487,7 @@ impl ToolRunSettings {
|
|||
with_requirements,
|
||||
constraints,
|
||||
overrides,
|
||||
build_constraints,
|
||||
isolated,
|
||||
env_file,
|
||||
no_env_file,
|
||||
|
|
@ -553,6 +555,10 @@ impl ToolRunSettings {
|
|||
.into_iter()
|
||||
.filter_map(Maybe::into_option)
|
||||
.collect(),
|
||||
build_constraints: build_constraints
|
||||
.into_iter()
|
||||
.filter_map(Maybe::into_option)
|
||||
.collect(),
|
||||
isolated,
|
||||
show_resolution,
|
||||
python: python.and_then(Maybe::into_option),
|
||||
|
|
@ -577,6 +583,7 @@ pub(crate) struct ToolInstallSettings {
|
|||
pub(crate) with_editable: Vec<String>,
|
||||
pub(crate) constraints: Vec<PathBuf>,
|
||||
pub(crate) overrides: Vec<PathBuf>,
|
||||
pub(crate) build_constraints: Vec<PathBuf>,
|
||||
pub(crate) python: Option<String>,
|
||||
pub(crate) refresh: Refresh,
|
||||
pub(crate) options: ResolverInstallerOptions,
|
||||
|
|
@ -599,6 +606,7 @@ impl ToolInstallSettings {
|
|||
with_requirements,
|
||||
constraints,
|
||||
overrides,
|
||||
build_constraints,
|
||||
installer,
|
||||
force,
|
||||
build,
|
||||
|
|
@ -644,6 +652,10 @@ impl ToolInstallSettings {
|
|||
.into_iter()
|
||||
.filter_map(Maybe::into_option)
|
||||
.collect(),
|
||||
build_constraints: build_constraints
|
||||
.into_iter()
|
||||
.filter_map(Maybe::into_option)
|
||||
.collect(),
|
||||
python: python.and_then(Maybe::into_option),
|
||||
force,
|
||||
editable,
|
||||
|
|
|
|||
|
|
@ -764,6 +764,79 @@ fn run_pep723_script_overrides() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a PEP 723-compatible script with `tool.uv` build constraints.
|
||||
#[test]
|
||||
fn run_pep723_script_build_constraints() -> Result<()> {
|
||||
let context = TestContext::new("3.8");
|
||||
|
||||
let test_script = context.temp_dir.child("main.py");
|
||||
|
||||
// Incompatible build constraints.
|
||||
test_script.write_str(indoc! { r#"
|
||||
# /// script
|
||||
# requires-python = ">=3.8"
|
||||
# dependencies = [
|
||||
# "anyio>=3",
|
||||
# "requests==1.2"
|
||||
# ]
|
||||
#
|
||||
# [tool.uv]
|
||||
# build-constraint-dependencies = ["setuptools==1"]
|
||||
# ///
|
||||
|
||||
import anyio
|
||||
"#
|
||||
})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().arg("main.py"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× Failed to download and build `requests==1.2.0`
|
||||
├─▶ Failed to resolve requirements from `setup.py` build
|
||||
├─▶ No solution found when resolving: `setuptools>=40.8.0`
|
||||
╰─▶ Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that your requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
// Compatible build constraints.
|
||||
test_script.write_str(indoc! { r#"
|
||||
# /// script
|
||||
# requires-python = ">=3.8"
|
||||
# dependencies = [
|
||||
# "anyio>=3",
|
||||
# "requests==1.2"
|
||||
# ]
|
||||
#
|
||||
# [tool.uv]
|
||||
# build-constraint-dependencies = ["setuptools>=40"]
|
||||
# ///
|
||||
|
||||
import anyio
|
||||
"#
|
||||
})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().arg("main.py"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 6 packages in [TIME]
|
||||
Prepared 6 packages in [TIME]
|
||||
Installed 6 packages in [TIME]
|
||||
+ anyio==4.3.0
|
||||
+ exceptiongroup==1.2.0
|
||||
+ idna==3.6
|
||||
+ requests==1.2.0
|
||||
+ sniffio==1.3.1
|
||||
+ typing-extensions==4.10.0
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a PEP 723-compatible script with a lockfile.
|
||||
#[test]
|
||||
fn run_pep723_script_lock() -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -2833,6 +2833,7 @@ fn resolve_tool() -> anyhow::Result<()> {
|
|||
with_editable: [],
|
||||
constraints: [],
|
||||
overrides: [],
|
||||
build_constraints: [],
|
||||
python: None,
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
|
|
|
|||
|
|
@ -8399,6 +8399,106 @@ fn sync_locked_script() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_script_with_compatible_build_constraints() -> Result<()> {
|
||||
let context = TestContext::new("3.8");
|
||||
|
||||
let test_script = context.temp_dir.child("script.py");
|
||||
|
||||
// Compatible build constraints.
|
||||
test_script.write_str(indoc! { r#"
|
||||
# /// script
|
||||
# requires-python = ">=3.8"
|
||||
# dependencies = [
|
||||
# "anyio>=3",
|
||||
# "requests==1.2"
|
||||
# ]
|
||||
#
|
||||
# [tool.uv]
|
||||
# build-constraint-dependencies = ["setuptools>=40"]
|
||||
# ///
|
||||
|
||||
import anyio
|
||||
"#
|
||||
})?;
|
||||
|
||||
let filters = context
|
||||
.filters()
|
||||
.into_iter()
|
||||
.chain(vec![(
|
||||
r"environments-v2/script-\w+",
|
||||
"environments-v2/script-[HASH]",
|
||||
)])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Creating script environment at: [CACHE_DIR]/environments-v2/script-[HASH]
|
||||
Resolved 6 packages in [TIME]
|
||||
Prepared 6 packages in [TIME]
|
||||
Installed 6 packages in [TIME]
|
||||
+ anyio==4.3.0
|
||||
+ exceptiongroup==1.2.0
|
||||
+ idna==3.6
|
||||
+ requests==1.2.0
|
||||
+ sniffio==1.3.1
|
||||
+ typing-extensions==4.10.0
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_script_with_incompatible_build_constraints() -> Result<()> {
|
||||
let context = TestContext::new("3.8");
|
||||
|
||||
let test_script = context.temp_dir.child("script.py");
|
||||
let filters = context
|
||||
.filters()
|
||||
.into_iter()
|
||||
.chain(vec![(
|
||||
r"environments-v2/script-\w+",
|
||||
"environments-v2/script-[HASH]",
|
||||
)])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Incompatible build constraints.
|
||||
test_script.write_str(indoc! { r#"
|
||||
# /// script
|
||||
# requires-python = ">=3.8"
|
||||
# dependencies = [
|
||||
# "anyio>=3",
|
||||
# "requests==1.2"
|
||||
# ]
|
||||
#
|
||||
# [tool.uv]
|
||||
# build-constraint-dependencies = ["setuptools==1"]
|
||||
# ///
|
||||
|
||||
import anyio
|
||||
"#
|
||||
})?;
|
||||
|
||||
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Creating script environment at: [CACHE_DIR]/environments-v2/script-[HASH]
|
||||
× Failed to download and build `requests==1.2.0`
|
||||
├─▶ Failed to resolve requirements from `setup.py` build
|
||||
├─▶ No solution found when resolving: `setuptools>=40.8.0`
|
||||
╰─▶ Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that your requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_git_scheme() -> Result<()> {
|
||||
let context = TestContext::new_with_versions(&["3.12"]);
|
||||
|
|
|
|||
|
|
@ -320,6 +320,117 @@ fn tool_install_with_editable() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_install_with_compatible_build_constraints() -> Result<()> {
|
||||
let context = TestContext::new("3.8")
|
||||
.with_exclude_newer("2024-05-04T00:00:00Z")
|
||||
.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("build_constraints.txt");
|
||||
constraints_txt.write_str("setuptools>=40")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("black")
|
||||
.arg("--with")
|
||||
.arg("requests==1.2")
|
||||
.arg("--build-constraints")
|
||||
.arg("build_constraints.txt")
|
||||
.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.4.2
|
||||
+ click==8.1.7
|
||||
+ mypy-extensions==1.0.0
|
||||
+ packaging==24.0
|
||||
+ pathspec==0.12.1
|
||||
+ platformdirs==4.2.1
|
||||
+ requests==1.2.0
|
||||
+ tomli==2.0.1
|
||||
+ typing-extensions==4.11.0
|
||||
Installed 2 executables: black, blackd
|
||||
"###);
|
||||
|
||||
tool_dir
|
||||
.child("black")
|
||||
.child("uv-receipt.toml")
|
||||
.assert(predicate::path::exists());
|
||||
|
||||
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" },
|
||||
{ name = "requests", specifier = "==1.2" },
|
||||
]
|
||||
build-constraint-dependencies = [{ name = "setuptools", specifier = ">=40" }]
|
||||
entrypoints = [
|
||||
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||
]
|
||||
|
||||
[tool.options]
|
||||
exclude-newer = "2024-05-04T00:00:00Z"
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_install_with_incompatible_build_constraints() -> Result<()> {
|
||||
let context = TestContext::new("3.8")
|
||||
.with_exclude_newer("2024-05-04T00:00:00Z")
|
||||
.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("build_constraints.txt");
|
||||
constraints_txt.write_str("setuptools==2")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("black")
|
||||
.arg("--with")
|
||||
.arg("requests==1.2")
|
||||
.arg("--build-constraints")
|
||||
.arg("build_constraints.txt")
|
||||
.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: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
× Failed to download and build `requests==1.2.0`
|
||||
├─▶ Failed to resolve requirements from `setup.py` build
|
||||
├─▶ No solution found when resolving: `setuptools>=40.8.0`
|
||||
╰─▶ Because you require setuptools>=40.8.0 and setuptools==2, we can conclude that your requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
tool_dir
|
||||
.child("black")
|
||||
.child("uv-receipt.toml")
|
||||
.assert(predicate::path::missing());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_install_suggest_other_packages_with_executable() {
|
||||
// FastAPI 0.111 is only available from this date onwards.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::common::{uv_snapshot, TestContext};
|
||||
use anyhow::Result;
|
||||
use assert_cmd::prelude::*;
|
||||
use assert_fs::prelude::*;
|
||||
use indoc::indoc;
|
||||
|
|
@ -2354,6 +2355,80 @@ fn tool_run_with_script_and_from_script() {
|
|||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_run_with_compatible_build_constraints() -> Result<()> {
|
||||
let context = TestContext::new("3.8")
|
||||
.with_exclude_newer("2024-05-04T00:00:00Z")
|
||||
.with_filtered_counts()
|
||||
.with_filtered_exe_suffix();
|
||||
let constraints_txt = context.temp_dir.child("build_constraints.txt");
|
||||
constraints_txt.write_str("setuptools>=40")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.tool_run()
|
||||
.arg("--with")
|
||||
.arg("requests==1.2")
|
||||
.arg("--build-constraints")
|
||||
.arg("build_constraints.txt")
|
||||
.arg("pytest")
|
||||
.arg("--version"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
pytest 8.2.0
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ exceptiongroup==1.2.1
|
||||
+ iniconfig==2.0.0
|
||||
+ packaging==24.0
|
||||
+ pluggy==1.5.0
|
||||
+ pytest==8.2.0
|
||||
+ requests==1.2.0
|
||||
+ tomli==2.0.1
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_run_with_incompatible_build_constraints() -> Result<()> {
|
||||
let context = TestContext::new("3.8")
|
||||
.with_exclude_newer("2024-05-04T00:00:00Z")
|
||||
.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("build_constraints.txt");
|
||||
constraints_txt.write_str("setuptools==2")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.tool_run()
|
||||
.arg("--with")
|
||||
.arg("requests==1.2")
|
||||
.arg("--build-constraints")
|
||||
.arg("build_constraints.txt")
|
||||
.arg("pytest")
|
||||
.arg("--version")
|
||||
.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: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
× Failed to download and build `requests==1.2.0`
|
||||
├─▶ Failed to resolve requirements from `setup.py` build
|
||||
├─▶ No solution found when resolving: `setuptools>=40.8.0`
|
||||
╰─▶ Because you require setuptools>=40.8.0 and setuptools==2, we can conclude that your requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test windows runnable types, namely console scripts and legacy setuptools scripts.
|
||||
/// Console Scripts <https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#console-scripts>
|
||||
/// Legacy Scripts <https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#scripts>.
|
||||
|
|
|
|||
|
|
@ -3123,6 +3123,11 @@ uv tool run [OPTIONS] [COMMAND]
|
|||
<p>WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
|
||||
|
||||
<p>May also be set with the <code>UV_INSECURE_HOST</code> environment variable.</p>
|
||||
</dd><dt id="uv-tool-run--build-constraints"><a href="#uv-tool-run--build-constraints"><code>--build-constraints</code></a>, <code>--build-constraint</code>, <code>-b</code> <i>build-constraints</i></dt><dd><p>Constrain build dependencies using the given requirements files when building source distributions.</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>May also be set with the <code>UV_BUILD_CONSTRAINT</code> environment variable.</p>
|
||||
</dd><dt id="uv-tool-run--cache-dir"><a href="#uv-tool-run--cache-dir"><code>--cache-dir</code></a> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
|
||||
|
||||
<p>Defaults to <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on macOS and Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
|
||||
|
|
@ -3468,6 +3473,11 @@ uv tool install [OPTIONS] <PACKAGE>
|
|||
<p>WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
|
||||
|
||||
<p>May also be set with the <code>UV_INSECURE_HOST</code> environment variable.</p>
|
||||
</dd><dt id="uv-tool-install--build-constraints"><a href="#uv-tool-install--build-constraints"><code>--build-constraints</code></a>, <code>--build-constraint</code>, <code>-b</code> <i>build-constraints</i></dt><dd><p>Constrain build dependencies using the given requirements files when building source distributions.</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>May also be set with the <code>UV_BUILD_CONSTRAINT</code> environment variable.</p>
|
||||
</dd><dt id="uv-tool-install--cache-dir"><a href="#uv-tool-install--cache-dir"><code>--cache-dir</code></a> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
|
||||
|
||||
<p>Defaults to <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on macOS and Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue