Skip to content

Commit

Permalink
Add a universal resolution mode to pip compile
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Jun 25, 2024
1 parent a651af0 commit 7a1c526
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 20 deletions.
8 changes: 8 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,14 @@ pub struct PipCompileArgs {
#[arg(long)]
pub python_platform: Option<TargetTriple>,

/// Perform a universal resolution, attempting to generate a single `requirements.txt` output
/// file that is compatible with all Python platforms.
#[arg(long, overrides_with("no_universal"))]
pub universal: bool,

#[arg(long, overrides_with("universal"), hide = true)]
pub no_universal: bool,

/// Specify a package to omit from the output resolution. Its dependencies will still be
/// included in the resolution. Equivalent to pip-compile's `--unsafe-package` option.
#[arg(long, alias = "unsafe-package")]
Expand Down
3 changes: 1 addition & 2 deletions crates/uv-resolver/src/python_requirement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ impl PythonRequirement {
self.target.as_ref()
}

/// Return the target version of Python as a "requires python" type,
/// if available.
/// Return the target version of Python as a "requires-python" type, i.e., a lower bound.
pub(crate) fn requires_python(&self) -> Option<&RequiresPython> {
self.target().and_then(|target| target.as_requires_python())
}
Expand Down
1 change: 1 addition & 0 deletions crates/uv-settings/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ pub struct PipOptions {
pub config_settings: Option<ConfigSettings>,
pub python_version: Option<PythonVersion>,
pub python_platform: Option<TargetTriple>,
pub universal: Option<bool>,
pub exclude_newer: Option<ExcludeNewer>,
pub no_emit_package: Option<Vec<PackageName>>,
pub emit_index_url: Option<bool>,
Expand Down
55 changes: 37 additions & 18 deletions crates/uv/src/commands/pip/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ use uv_requirements::{
};
use uv_resolver::{
AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, FlatIndex,
InMemoryIndex, OptionsBuilder, PreReleaseMode, PythonRequirement, ResolutionMode,
InMemoryIndex, OptionsBuilder, PreReleaseMode, PythonRequirement, RequiresPython,
ResolutionMode,
};
use uv_toolchain::{
EnvironmentPreference, PythonEnvironment, PythonVersion, Toolchain, ToolchainPreference,
Expand Down Expand Up @@ -77,6 +78,7 @@ pub(crate) async fn pip_compile(
build_options: BuildOptions,
python_version: Option<PythonVersion>,
python_platform: Option<TargetTriple>,
universal: bool,
exclude_newer: Option<ExcludeNewer>,
annotation_style: AnnotationStyle,
link_mode: LinkMode,
Expand Down Expand Up @@ -212,14 +214,29 @@ pub(crate) async fn pip_compile(
};

// Determine the Python requirement, if the user requested a specific version.
let python_requirement = if let Some(python_version) = python_version.as_ref() {
PythonRequirement::from_python_version(&interpreter, python_version)
let python_requirement = if universal {
let requires_python = if let Some(python_version) = python_version.as_ref() {
RequiresPython::greater_than_equal_version(python_version.python_version())
} else {
RequiresPython::greater_than_equal_version(interpreter.python_version().clone())
};
PythonRequirement::from_requires_python(&interpreter, &requires_python)
} else {
PythonRequirement::from_interpreter(&interpreter)
if let Some(python_version) = python_version.as_ref() {
PythonRequirement::from_python_version(&interpreter, python_version)
} else {
PythonRequirement::from_interpreter(&interpreter)
}
};

// Determine the environment for the resolution.
let (tags, markers) = resolution_environment(python_version, python_platform, &interpreter)?;
let (tags, markers) = if universal {
(None, None)
} else {
let (tags, markers) =
resolution_environment(python_version, python_platform, &interpreter)?;
(Some(tags), Some(markers))
};

// Generate, but don't enforce hashes for the requirements.
let hasher = if generate_hashes {
Expand Down Expand Up @@ -247,7 +264,7 @@ pub(crate) async fn pip_compile(
.index_urls(index_locations.index_urls())
.index_strategy(index_strategy)
.keyring(keyring_provider)
.markers(&markers)
.markers(interpreter.markers())
.platform(interpreter.platform())
.build();

Expand All @@ -262,7 +279,7 @@ pub(crate) async fn pip_compile(
let flat_index = {
let client = FlatIndexClient::new(&client, &cache);
let entries = client.fetch(index_locations.flat_index()).await?;
FlatIndex::from_entries(entries, Some(&tags), &hasher, &build_options)
FlatIndex::from_entries(entries, tags.as_deref(), &hasher, &build_options)
};

// Track in-flight downloads, builds, etc., across resolutions.
Expand Down Expand Up @@ -319,8 +336,8 @@ pub(crate) async fn pip_compile(
&hasher,
&Reinstall::None,
&upgrade,
Some(&tags),
Some(&markers),
tags.as_deref(),
markers.as_deref(),
python_requirement,
&client,
&flat_index,
Expand Down Expand Up @@ -368,13 +385,15 @@ pub(crate) async fn pip_compile(
}

if include_marker_expression {
let relevant_markers = resolution.marker_tree(&top_level_index, &markers)?;
writeln!(
writer,
"{}",
"# Pinned dependencies known to be valid for:".green()
)?;
writeln!(writer, "{}", format!("# {relevant_markers}").green())?;
if let Some(markers) = markers.as_deref() {
let relevant_markers = resolution.marker_tree(&top_level_index, markers)?;
writeln!(
writer,
"{}",
"# Pinned dependencies known to be valid for:".green()
)?;
writeln!(writer, "{}", format!("# {relevant_markers}").green())?;
}
}

let mut wrote_preamble = false;
Expand Down Expand Up @@ -439,11 +458,11 @@ pub(crate) async fn pip_compile(
"{}",
DisplayResolutionGraph::new(
&resolution,
Some(&markers),
markers.as_deref(),
&no_emit_packages,
generate_hashes,
include_extras,
include_markers,
include_markers || universal,
include_annotations,
include_index_annotation,
annotation_style,
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ async fn run() -> Result<ExitStatus> {
args.settings.build_options,
args.settings.python_version,
args.settings.python_platform,
args.settings.universal,
args.settings.exclude_newer,
args.settings.annotation_style,
args.settings.link_mode,
Expand Down
6 changes: 6 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,8 @@ impl PipCompileSettings {
only_binary,
python_version,
python_platform,
universal,
no_universal,
no_emit_package,
emit_index_url,
no_emit_index_url,
Expand Down Expand Up @@ -583,6 +585,7 @@ impl PipCompileSettings {
legacy_setup_py: flag(legacy_setup_py, no_legacy_setup_py),
python_version,
python_platform,
universal: flag(universal, no_universal),
no_emit_package,
emit_index_url: flag(emit_index_url, no_emit_index_url),
emit_find_links: flag(emit_find_links, no_emit_find_links),
Expand Down Expand Up @@ -1499,6 +1502,7 @@ pub(crate) struct PipSettings {
pub(crate) config_setting: ConfigSettings,
pub(crate) python_version: Option<PythonVersion>,
pub(crate) python_platform: Option<TargetTriple>,
pub(crate) universal: bool,
pub(crate) exclude_newer: Option<ExcludeNewer>,
pub(crate) no_emit_package: Vec<PackageName>,
pub(crate) emit_index_url: bool,
Expand Down Expand Up @@ -1555,6 +1559,7 @@ impl PipSettings {
config_settings,
python_version,
python_platform,
universal,
exclude_newer,
no_emit_package,
emit_index_url,
Expand Down Expand Up @@ -1686,6 +1691,7 @@ impl PipSettings {
.unwrap_or_default(),
python_version: args.python_version.combine(python_version),
python_platform: args.python_platform.combine(python_platform),
universal: args.universal.combine(universal).unwrap_or_default(),
exclude_newer: args.exclude_newer.combine(exclude_newer),
no_emit_package: args
.no_emit_package
Expand Down
45 changes: 45 additions & 0 deletions crates/uv/tests/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6323,6 +6323,51 @@ fn no_strip_markers_transitive_marker() -> Result<()> {
Ok(())
}

/// Perform a universal resolution with a package that has a marker.
#[test]
fn universal() -> Result<()> {
let context = TestContext::new("3.12");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str(indoc::indoc! {r"
trio ; python_version > '3.11'
trio ; sys_platform == 'win32'
"})?;

uv_snapshot!(context.compile()
.arg("requirements.in")
.arg("--universal"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --universal
attrs==23.2.0 ; python_version > '3.11' or sys_platform == 'win32'
# via
# outcome
# trio
cffi==1.16.0 ; implementation_name != 'pypy' and os_name == 'nt' and (python_version > '3.11' or sys_platform == 'win32')
# via trio
idna==3.6 ; python_version > '3.11' or sys_platform == 'win32'
# via trio
outcome==1.3.0.post0 ; python_version > '3.11' or sys_platform == 'win32'
# via trio
pycparser==2.21 ; implementation_name != 'pypy' and os_name == 'nt' and (python_version > '3.11' or sys_platform == 'win32')
# via cffi
sniffio==1.3.1 ; python_version > '3.11' or sys_platform == 'win32'
# via trio
sortedcontainers==2.4.0 ; python_version > '3.11' or sys_platform == 'win32'
# via trio
trio==0.25.0 ; python_version > '3.11' or sys_platform == 'win32'
# via -r requirements.in
----- stderr -----
Resolved 8 packages in [TIME]
"###
);

Ok(())
}

/// Resolve a package from a `requirements.in` file, with a `constraints.txt` file pinning one of
/// its transitive dependencies to a specific version.
#[test]
Expand Down
6 changes: 6 additions & 0 deletions uv.schema.json

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

0 comments on commit 7a1c526

Please sign in to comment.