diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index bf605198f..531091b7b 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3395,6 +3395,31 @@ pub struct SyncArgs { )] pub python: Option>, + /// The minimum Python version that should be supported by the requirements (e.g., `3.7` or + /// `3.7.9`). + /// + /// If a patch version is omitted, the minimum patch version is assumed. For example, `3.7` is + /// mapped to `3.7.0`. + #[arg(long)] + pub python_version: Option, + + /// The platform for which requirements should be installed. + /// + /// Represented as a "target triple", a string that describes the target platform in terms of + /// its CPU, vendor, and operating system name, like `x86_64-unknown-linux-gnu` or + /// `aarch64-apple-darwin`. + /// + /// When targeting macOS (Darwin), the default minimum version is `12.0`. Use + /// `MACOSX_DEPLOYMENT_TARGET` to specify a different minimum version, e.g., `13.0`. + /// + /// WARNING: When specified, uv will select wheels that are compatible with the _target_ + /// platform; as a result, the installed distributions may not be compatible with the _current_ + /// platform. Conversely, any distributions that are built from source may be incompatible with + /// the _target_ platform, as they will be built for the _current_ platform. The + /// `--python-platform` option is intended for advanced use cases. + #[arg(long)] + pub python_platform: Option, + /// Check if the Python environment is synchronized with the project. /// /// If the environment is not up to date, uv will exit with an error. diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index ae20b31d4..eb41a4936 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -1014,6 +1014,8 @@ async fn lock_and_sync( EditableMode::Editable, InstallOptions::default(), Modifications::Sufficient, + None, + None, settings.into(), network_settings, &sync_state, diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index 7fd02277e..98c908d99 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -350,6 +350,8 @@ pub(crate) async fn remove( EditableMode::Editable, InstallOptions::default(), Modifications::Exact, + None, + None, (&settings).into(), &network_settings, &state, diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index ee3caeafa..8160b2373 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -298,6 +298,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl editable, install_options, modifications, + None, + None, (&settings).into(), &network_settings, &sync_state, @@ -796,6 +798,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl editable, install_options, modifications, + None, + None, (&settings).into(), &network_settings, &sync_state, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index f1a73b8c8..26f392996 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -12,7 +12,7 @@ use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode, ExtrasSpecification, ExtrasSpecificationWithDefaults, HashCheckingMode, InstallOptions, - PreviewMode, + PreviewMode, TargetTriple, }; use uv_dispatch::BuildDispatch; use uv_distribution_types::{ @@ -23,7 +23,9 @@ use uv_installer::SitePackages; use uv_normalize::{DefaultExtras, DefaultGroups, PackageName}; use uv_pep508::{MarkerTree, VersionOrUrl}; use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl, ParsedUrl}; -use uv_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest}; +use uv_python::{ + PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest, PythonVersion, +}; use uv_resolver::{FlatIndex, Installable, Lock}; use uv_scripts::{Pep723ItemRef, Pep723Script}; use uv_settings::PythonInstallMirrors; @@ -35,6 +37,7 @@ use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace, use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger}; use crate::commands::pip::operations; use crate::commands::pip::operations::Modifications; +use crate::commands::pip::resolution_markers; use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::{LockMode, LockOperation, LockResult}; use crate::commands::project::lock_target::LockTarget; @@ -62,6 +65,8 @@ pub(crate) async fn sync( install_options: InstallOptions, modifications: Modifications, python: Option, + python_platform: Option, + python_version: Option, install_mirrors: PythonInstallMirrors, python_preference: PythonPreference, python_downloads: PythonDownloads, @@ -445,6 +450,8 @@ pub(crate) async fn sync( editable, install_options, modifications, + python_platform.as_ref(), + python_version.as_ref(), (&settings).into(), &network_settings, &state, @@ -580,6 +587,8 @@ pub(super) async fn do_sync( editable: EditableMode, install_options: InstallOptions, modifications: Modifications, + python_platform: Option<&TargetTriple>, + python_version: Option<&PythonVersion>, settings: InstallerSettingsRef<'_>, network_settings: &NetworkSettings, state: &PlatformState, @@ -634,7 +643,7 @@ pub(super) async fn do_sync( target.validate_groups(groups)?; // Determine the markers to use for resolution. - let marker_env = venv.interpreter().resolver_marker_environment(); + let marker_env = resolution_markers(python_version, python_platform, venv.interpreter()); // Validate that the platform is supported by the lockfile. let environments = target.lock().supported_environments(); diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index fdba41978..93825275a 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -503,6 +503,8 @@ async fn lock_and_sync( EditableMode::Editable, install_options, Modifications::Sufficient, + None, + None, settings.into(), &network_settings, &state, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index ab4aee9e9..daa2d377e 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1802,6 +1802,8 @@ async fn run_project( args.install_options, args.modifications, args.python, + args.python_platform, + args.python_version, args.install_mirrors, globals.python_preference, globals.python_downloads, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 58a012d89..87574355b 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1142,6 +1142,8 @@ pub(crate) struct SyncSettings { pub(crate) all_packages: bool, pub(crate) package: Option, pub(crate) python: Option, + pub(crate) python_platform: Option, + pub(crate) python_version: Option, pub(crate) install_mirrors: PythonInstallMirrors, pub(crate) refresh: Refresh, pub(crate) settings: ResolverInstallerSettings, @@ -1182,6 +1184,8 @@ impl SyncSettings { package, script, python, + python_platform, + python_version, check, no_check, } = args; @@ -1241,6 +1245,8 @@ impl SyncSettings { all_packages, package, python: python.and_then(Maybe::into_option), + python_platform, + python_version, refresh: Refresh::from(refresh), settings, install_mirrors, diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 4187de957..a52bfda5b 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -9989,3 +9989,76 @@ fn sync_url_with_query_parameters() -> Result<()> { Ok(()) } + +#[test] +fn sync_python_platform() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["black"] + "#, + )?; + + // Lock the project + context.lock().assert().success(); + + // Sync with a specific platform should filter packages + uv_snapshot!(context.filters(), context.sync().arg("--python-platform").arg("linux"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 8 packages in [TIME] + Prepared 6 packages in [TIME] + Installed 6 packages in [TIME] + + black==24.3.0 + + click==8.1.7 + + mypy-extensions==1.0.0 + + packaging==24.0 + + pathspec==0.12.1 + + platformdirs==4.2.0 + "###); + + Ok(()) +} + +#[test] +fn sync_with_python_version() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.8" + dependencies = ["typing-extensions"] + "#, + )?; + + // Lock the project + context.lock().assert().success(); + + // Sync with a specific Python version + uv_snapshot!(context.filters(), context.sync().arg("--python-version").arg("3.8"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + typing-extensions==4.10.0 + "###); + + Ok(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 82fe0fa3d..cc073d450 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1129,7 +1129,53 @@ used.

synced to the given environment. The interpreter will be used to create a virtual environment in the project.

See uv python for details on Python discovery and supported request formats.

-

May also be set with the UV_PYTHON environment variable.

--quiet, -q

Use quiet output.

+

May also be set with the UV_PYTHON environment variable.

--python-platform python-platform

The platform for which requirements should be installed.

+

Represented as a "target triple", a string that describes the target platform in terms of its CPU, vendor, and operating system name, like x86_64-unknown-linux-gnu or aarch64-apple-darwin.

+

When targeting macOS (Darwin), the default minimum version is 12.0. Use MACOSX_DEPLOYMENT_TARGET to specify a different minimum version, e.g., 13.0.

+

WARNING: When specified, uv will select wheels that are compatible with the target platform; as a result, the installed distributions may not be compatible with the current platform. Conversely, any distributions that are built from source may be incompatible with the target platform, as they will be built for the current platform. The --python-platform option is intended for advanced use cases.

+

Possible values:

+
    +
  • windows: An alias for x86_64-pc-windows-msvc, the default target for Windows
  • +
  • linux: An alias for x86_64-unknown-linux-gnu, the default target for Linux
  • +
  • macos: An alias for aarch64-apple-darwin, the default target for macOS
  • +
  • x86_64-pc-windows-msvc: A 64-bit x86 Windows target
  • +
  • i686-pc-windows-msvc: A 32-bit x86 Windows target
  • +
  • x86_64-unknown-linux-gnu: An x86 Linux target. Equivalent to x86_64-manylinux_2_17
  • +
  • aarch64-apple-darwin: An ARM-based macOS target, as seen on Apple Silicon devices
  • +
  • x86_64-apple-darwin: An x86 macOS target
  • +
  • aarch64-unknown-linux-gnu: An ARM64 Linux target. Equivalent to aarch64-manylinux_2_17
  • +
  • aarch64-unknown-linux-musl: An ARM64 Linux target
  • +
  • x86_64-unknown-linux-musl: An x86_64 Linux target
  • +
  • x86_64-manylinux2014: An x86_64 target for the manylinux2014 platform. Equivalent to x86_64-manylinux_2_17
  • +
  • x86_64-manylinux_2_17: An x86_64 target for the manylinux_2_17 platform
  • +
  • x86_64-manylinux_2_28: An x86_64 target for the manylinux_2_28 platform
  • +
  • x86_64-manylinux_2_31: An x86_64 target for the manylinux_2_31 platform
  • +
  • x86_64-manylinux_2_32: An x86_64 target for the manylinux_2_32 platform
  • +
  • x86_64-manylinux_2_33: An x86_64 target for the manylinux_2_33 platform
  • +
  • x86_64-manylinux_2_34: An x86_64 target for the manylinux_2_34 platform
  • +
  • x86_64-manylinux_2_35: An x86_64 target for the manylinux_2_35 platform
  • +
  • x86_64-manylinux_2_36: An x86_64 target for the manylinux_2_36 platform
  • +
  • x86_64-manylinux_2_37: An x86_64 target for the manylinux_2_37 platform
  • +
  • x86_64-manylinux_2_38: An x86_64 target for the manylinux_2_38 platform
  • +
  • x86_64-manylinux_2_39: An x86_64 target for the manylinux_2_39 platform
  • +
  • x86_64-manylinux_2_40: An x86_64 target for the manylinux_2_40 platform
  • +
  • aarch64-manylinux2014: An ARM64 target for the manylinux2014 platform. Equivalent to aarch64-manylinux_2_17
  • +
  • aarch64-manylinux_2_17: An ARM64 target for the manylinux_2_17 platform
  • +
  • aarch64-manylinux_2_28: An ARM64 target for the manylinux_2_28 platform
  • +
  • aarch64-manylinux_2_31: An ARM64 target for the manylinux_2_31 platform
  • +
  • aarch64-manylinux_2_32: An ARM64 target for the manylinux_2_32 platform
  • +
  • aarch64-manylinux_2_33: An ARM64 target for the manylinux_2_33 platform
  • +
  • aarch64-manylinux_2_34: An ARM64 target for the manylinux_2_34 platform
  • +
  • aarch64-manylinux_2_35: An ARM64 target for the manylinux_2_35 platform
  • +
  • aarch64-manylinux_2_36: An ARM64 target for the manylinux_2_36 platform
  • +
  • aarch64-manylinux_2_37: An ARM64 target for the manylinux_2_37 platform
  • +
  • aarch64-manylinux_2_38: An ARM64 target for the manylinux_2_38 platform
  • +
  • aarch64-manylinux_2_39: An ARM64 target for the manylinux_2_39 platform
  • +
  • aarch64-manylinux_2_40: An ARM64 target for the manylinux_2_40 platform
  • +
  • wasm32-pyodide2024: A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12
  • +
--python-version python-version

The minimum Python version that should be supported by the requirements (e.g., 3.7 or 3.7.9).

+

If a patch version is omitted, the minimum patch version is assumed. For example, 3.7 is mapped to 3.7.0.

+
--quiet, -q

Use quiet output.

Repeating this option, e.g., -qq, will enable a silent mode in which uv will write no output to stdout.

--refresh

Refresh all cached data

--refresh-package refresh-package

Refresh cached data for a specific package