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-platformThe 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-versionThe 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-packageRefresh cached data for a specific package