Skip to content

Commit

Permalink
Support build constraints (#5639)
Browse files Browse the repository at this point in the history
## Summary

Partially resolves #5561. Haven't added overrides support yet but I can
add it tomorrow if the current approach for constraints is ok.

## Test Plan

`cargo test`

Manually checked trace logs after changing the constraints.
  • Loading branch information
blueraft authored Aug 2, 2024
1 parent c558d70 commit ff9f3de
Show file tree
Hide file tree
Showing 25 changed files with 360 additions and 4 deletions.
13 changes: 13 additions & 0 deletions PIP_COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,19 @@ Specifically, uv does not support installing new `.egg-info`- or `.egg-link`-sty
but will respect any such existing distributions during resolution, list them with `uv pip list` and
`uv pip freeze`, and uninstall them with `uv pip uninstall`.

## Build constraints

When constraints are provided via `--constraint` (or `UV_CONSTRAINT`), uv will _not_ apply the
constraints when resolving build dependencies (i.e., to build a source distribution). Instead,
build constraints should be provided via the dedicated `--build-constraint` (or `UV_BUILD_CONSTRAINT`)
setting.

pip, meanwhile, applies constraints to build dependencies when specified via `PIP_CONSTRAINT`, but
not when provided via `--constraint` on the command line.

For example, to ensure that `setuptools 60.0.0` is used to build any packages with a build
dependency on `setuptools`, use `--build-constraint`, rather than `--constraint`.

## `pip compile` defaults

There are a few small but notable differences in the default behaviors of `pip compile` and
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,8 @@ uv accepts the following command-line arguments as environment variables:
uv will require that all dependencies have a hash specified in the requirements file.
- `UV_CONSTRAINT`: Equivalent to the `--constraint` command-line argument. If set, uv will use this
file as the constraints file. Uses space-separated list of files.
- `UV_BUILD_CONSTRAINT`: Equivalent to the `--build-constraint` command-line argument. If set, uv
will use this file as constraints for any source distribution builds. Uses space-separated list of files.
- `UV_OVERRIDE`: Equivalent to the `--override` command-line argument. If set, uv will use this
file as the overrides file. Uses space-separated list of files.
- `UV_LINK_MODE`: Equivalent to the `--link-mode` command-line argument. If set, uv will use this
Expand Down
2 changes: 2 additions & 0 deletions crates/bench/benches/uv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,12 @@ mod resolver {
let python_requirement = PythonRequirement::from_interpreter(interpreter);

let options = OptionsBuilder::new().exclude_newer(exclude_newer).build();
let build_constraints = [];

let build_context = BuildDispatch::new(
client,
&cache,
&build_constraints,
interpreter,
&index_locations,
&flat_index,
Expand Down
27 changes: 27 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,15 @@ pub struct PipCompileArgs {
#[arg(long, env = "UV_OVERRIDE", value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub r#override: 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, env = "UV_BUILD_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub build_constraint: Vec<Maybe<PathBuf>>,

/// Include optional dependencies from the extra group name; may be provided more than once.
///
/// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources.
Expand Down Expand Up @@ -838,6 +847,15 @@ pub struct PipSyncArgs {
#[arg(long, short, env = "UV_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub constraint: 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, env = "UV_BUILD_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub build_constraint: Vec<Maybe<PathBuf>>,

#[command(flatten)]
pub installer: InstallerArgs,

Expand Down Expand Up @@ -1111,6 +1129,15 @@ pub struct PipInstallArgs {
#[arg(long, env = "UV_OVERRIDE", value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub r#override: 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, env = "UV_BUILD_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub build_constraint: Vec<Maybe<PathBuf>>,

/// Include optional dependencies from the extra group name; may be provided more than once.
///
/// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources.
Expand Down
2 changes: 2 additions & 0 deletions crates/uv-dev/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,12 @@ pub(crate) async fn build(args: BuildArgs) -> Result<PathBuf> {
&cache,
)?;
let build_options = BuildOptions::default();
let build_constraints = [];

let build_dispatch = BuildDispatch::new(
&client,
&cache,
&build_constraints,
python.interpreter(),
&index_urls,
&flat_index,
Expand Down
8 changes: 6 additions & 2 deletions crates/uv-dispatch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use uv_build::{SourceBuild, SourceBuildContext};
use uv_cache::Cache;
use uv_client::RegistryClient;
use uv_configuration::{
BuildKind, BuildOptions, ConfigSettings, IndexStrategy, Reinstall, SetupPyStrategy,
BuildKind, BuildOptions, ConfigSettings, Constraints, IndexStrategy, Reinstall, SetupPyStrategy,
};
use uv_configuration::{Concurrency, PreviewMode};
use uv_distribution::DistributionDatabase;
Expand All @@ -35,6 +35,7 @@ use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrateg
pub struct BuildDispatch<'a> {
client: &'a RegistryClient,
cache: &'a Cache,
constraints: Constraints,
interpreter: &'a Interpreter,
index_locations: &'a IndexLocations,
index_strategy: IndexStrategy,
Expand All @@ -58,6 +59,7 @@ impl<'a> BuildDispatch<'a> {
pub fn new(
client: &'a RegistryClient,
cache: &'a Cache,
constraints: &'a [Requirement],
interpreter: &'a Interpreter,
index_locations: &'a IndexLocations,
flat_index: &'a FlatIndex,
Expand All @@ -77,6 +79,7 @@ impl<'a> BuildDispatch<'a> {
Self {
client,
cache,
constraints: Constraints::from_requirements(constraints.iter().cloned()),
interpreter,
index_locations,
flat_index,
Expand Down Expand Up @@ -140,8 +143,9 @@ impl<'a> BuildContext for BuildDispatch<'a> {
let python_requirement = PythonRequirement::from_interpreter(self.interpreter);
let markers = self.interpreter.markers();
let tags = self.interpreter.tags()?;

let resolver = Resolver::new(
Manifest::simple(requirements.to_vec()),
Manifest::simple(requirements.to_vec()).with_constraints(self.constraints.clone()),
OptionsBuilder::new()
.exclude_newer(self.exclude_newer)
.index_strategy(self.index_strategy)
Expand Down
6 changes: 6 additions & 0 deletions crates/uv-resolver/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ impl Manifest {
}
}

#[must_use]
pub fn with_constraints(mut self, constraints: Constraints) -> Self {
self.constraints = constraints;
self
}

/// Return an iterator over all requirements, constraints, and overrides, in priority order,
/// such that requirements come first, followed by constraints, followed by overrides.
///
Expand Down
6 changes: 6 additions & 0 deletions crates/uv/src/commands/pip/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub(crate) async fn pip_compile(
requirements: &[RequirementsSource],
constraints: &[RequirementsSource],
overrides: &[RequirementsSource],
build_constraints: &[RequirementsSource],
constraints_from_workspace: Vec<Requirement>,
overrides_from_workspace: Vec<Requirement>,
extras: ExtrasSpecification,
Expand Down Expand Up @@ -143,6 +144,10 @@ pub(crate) async fn pip_compile(
)
.collect();

// Read build constraints.
let build_constraints =
operations::read_constraints(build_constraints, &client_builder).await?;

// If all the metadata could be statically resolved, validate that every extra was used. If we
// need to resolve metadata via PEP 517, we don't know which extras are used until much later.
if source_trees.is_empty() {
Expand Down Expand Up @@ -304,6 +309,7 @@ pub(crate) async fn pip_compile(
let build_dispatch = BuildDispatch::new(
&client,
&cache,
&build_constraints,
&interpreter,
&index_locations,
&flat_index,
Expand Down
6 changes: 6 additions & 0 deletions crates/uv/src/commands/pip/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub(crate) async fn pip_install(
requirements: &[RequirementsSource],
constraints: &[RequirementsSource],
overrides: &[RequirementsSource],
build_constraints: &[RequirementsSource],
constraints_from_workspace: Vec<Requirement>,
overrides_from_workspace: Vec<Requirement>,
extras: &ExtrasSpecification,
Expand Down Expand Up @@ -105,6 +106,10 @@ pub(crate) async fn pip_install(
)
.await?;

// Read build constraints.
let build_constraints =
operations::read_constraints(build_constraints, &client_builder).await?;

let constraints: Vec<Requirement> = constraints
.iter()
.cloned()
Expand Down Expand Up @@ -294,6 +299,7 @@ pub(crate) async fn pip_install(
let build_dispatch = BuildDispatch::new(
&client,
&cache,
&build_constraints,
interpreter,
&index_locations,
&flat_index,
Expand Down
12 changes: 12 additions & 0 deletions crates/uv/src/commands/pip/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ pub(crate) async fn read_requirements(
.await?)
}

/// Resolve a set of constraints.
pub(crate) async fn read_constraints(
constraints: &[RequirementsSource],
client_builder: &BaseClientBuilder<'_>,
) -> Result<Vec<Requirement>, Error> {
Ok(
RequirementsSpecification::from_sources(&[], constraints, &[], client_builder)
.await?
.constraints,
)
}

/// Resolve a set of requirements, similar to running `pip compile`.
pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
requirements: Vec<UnresolvedRequirementSpecification>,
Expand Down
6 changes: 6 additions & 0 deletions crates/uv/src/commands/pip/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use crate::printer::Printer;
pub(crate) async fn pip_sync(
requirements: &[RequirementsSource],
constraints: &[RequirementsSource],
build_constraints: &[RequirementsSource],
reinstall: Reinstall,
link_mode: LinkMode,
compile: bool,
Expand Down Expand Up @@ -103,6 +104,10 @@ pub(crate) async fn pip_sync(
)
.await?;

// Read build constraints.
let build_constraints =
operations::read_constraints(build_constraints, &client_builder).await?;

// Validate that the requirements are non-empty.
if !allow_empty_requirements {
let num_requirements = requirements.len() + source_trees.len();
Expand Down Expand Up @@ -240,6 +245,7 @@ pub(crate) async fn pip_sync(
let build_dispatch = BuildDispatch::new(
&client,
&cache,
&build_constraints,
interpreter,
&index_locations,
&flat_index,
Expand Down
4 changes: 4 additions & 0 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,14 @@ pub(crate) async fn add(
FlatIndex::from_entries(entries, Some(&tags), &hasher, &settings.build_options)
};

// TODO: read locked build constraints
let build_constraints = [];

// Create a build dispatch.
let build_dispatch = BuildDispatch::new(
&client,
cache,
&build_constraints,
venv.interpreter(),
&settings.index_locations,
&flat_index,
Expand Down
6 changes: 6 additions & 0 deletions crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,10 +403,13 @@ async fn do_lock(
// Prefill the index with the lockfile metadata.
let index = lock.to_index(workspace.install_path(), upgrade)?;

// TODO: read locked build constraints
let build_constraints = [];
// Create a build dispatch.
let build_dispatch = BuildDispatch::new(
&client,
cache,
&build_constraints,
interpreter,
index_locations,
&flat_index,
Expand Down Expand Up @@ -479,10 +482,13 @@ async fn do_lock(
None => {
debug!("Starting clean resolution");

// TODO: read locked build constraints
let build_constraints = [];
// Create a build dispatch.
let build_dispatch = BuildDispatch::new(
&client,
cache,
&build_constraints,
interpreter,
index_locations,
&flat_index,
Expand Down
13 changes: 13 additions & 0 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,10 +405,13 @@ pub(crate) async fn resolve_names(
let setup_py = SetupPyStrategy::default();
let flat_index = FlatIndex::default();

// TODO: read locked build constraints
let build_constraints = [];
// Create a build dispatch.
let build_dispatch = BuildDispatch::new(
&client,
cache,
&build_constraints,
interpreter,
index_locations,
&flat_index,
Expand Down Expand Up @@ -525,10 +528,13 @@ pub(crate) async fn resolve_environment<'a>(
FlatIndex::from_entries(entries, Some(tags), &hasher, build_options)
};

// TODO: read locked build constraints
let build_constraints = [];
// Create a build dispatch.
let resolve_dispatch = BuildDispatch::new(
&client,
cache,
&build_constraints,
interpreter,
index_locations,
&flat_index,
Expand Down Expand Up @@ -638,10 +644,13 @@ pub(crate) async fn sync_environment(
FlatIndex::from_entries(entries, Some(tags), &hasher, build_options)
};

// TODO: read locked build constraints
let build_constraints = [];
// Create a build dispatch.
let build_dispatch = BuildDispatch::new(
&client,
cache,
&build_constraints,
interpreter,
index_locations,
&flat_index,
Expand Down Expand Up @@ -799,10 +808,14 @@ pub(crate) async fn update_environment(
FlatIndex::from_entries(entries, Some(tags), &hasher, build_options)
};

// TODO: read locked build constraints
let build_constraints = [];

// Create a build dispatch.
let build_dispatch = BuildDispatch::new(
&client,
cache,
&build_constraints,
interpreter,
index_locations,
&flat_index,
Expand Down
3 changes: 3 additions & 0 deletions crates/uv/src/commands/project/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,13 @@ pub(super) async fn do_sync(
FlatIndex::from_entries(entries, Some(tags), &hasher, build_options)
};

// TODO: read locked build constraints
let build_constraints = [];
// Create a build dispatch.
let build_dispatch = BuildDispatch::new(
&client,
cache,
&build_constraints,
venv.interpreter(),
index_locations,
&flat_index,
Expand Down
3 changes: 3 additions & 0 deletions crates/uv/src/commands/venv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,13 @@ async fn venv_impl(
// Do not allow builds
let build_options = BuildOptions::new(NoBinary::None, NoBuild::All);

let build_constraints = [];

// Prep the build context.
let build_dispatch = BuildDispatch::new(
&client,
cache,
&build_constraints,
interpreter,
index_locations,
&flat_index,
Expand Down
Loading

0 comments on commit ff9f3de

Please sign in to comment.