diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 39dac54ba..056447959 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3439,6 +3439,23 @@ pub struct SyncArgs { )] pub python: 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 959241b4b..f255194de 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -1080,6 +1080,7 @@ async fn lock_and_sync( EditableMode::Editable, InstallOptions::default(), Modifications::Sufficient, + 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 6bc04160e..50615699e 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -357,6 +357,7 @@ pub(crate) async fn remove( EditableMode::Editable, InstallOptions::default(), Modifications::Exact, + 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 a4fd4ae7d..f0a46f16a 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -305,6 +305,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl editable, install_options, modifications, + None, (&settings).into(), &network_settings, &sync_state, @@ -816,6 +817,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl editable, install_options, modifications, + 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 6e057446e..664eb2a94 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -13,7 +13,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::{ @@ -34,8 +34,9 @@ use uv_workspace::pyproject::Source; use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace, WorkspaceCache}; 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::pip::{operations, resolution_tags}; use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::{LockMode, LockOperation, LockResult}; use crate::commands::project::lock_target::LockTarget; @@ -63,6 +64,7 @@ pub(crate) async fn sync( install_options: InstallOptions, modifications: Modifications, python: Option, + python_platform: Option, install_mirrors: PythonInstallMirrors, python_preference: PythonPreference, python_downloads: PythonDownloads, @@ -453,6 +455,7 @@ pub(crate) async fn sync( editable, install_options, modifications, + python_platform.as_ref(), (&settings).into(), &network_settings, &state, @@ -589,6 +592,7 @@ pub(super) async fn do_sync( editable: EditableMode, install_options: InstallOptions, modifications: Modifications, + python_platform: Option<&TargetTriple>, settings: InstallerSettingsRef<'_>, network_settings: &NetworkSettings, state: &PlatformState, @@ -644,7 +648,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(None, python_platform, venv.interpreter()); // Validate that the platform is supported by the lockfile. let environments = target.lock().supported_environments(); @@ -670,13 +674,13 @@ pub(super) async fn do_sync( } } - // Determine the tags to use for resolution. - let tags = venv.interpreter().tags()?; + // Determine the tags to use for the resolution. + let tags = resolution_tags(None, python_platform, venv.interpreter())?; // Read the lockfile. let resolution = target.to_resolution( &marker_env, - tags, + &tags, extras, groups, build_options, @@ -728,7 +732,7 @@ pub(super) async fn do_sync( let entries = client .fetch_all(index_locations.flat_indexes().map(Index::url)) .await?; - FlatIndex::from_entries(entries, Some(tags), &hasher, build_options) + FlatIndex::from_entries(entries, Some(&tags), &hasher, build_options) }; // Create a build dispatch. @@ -768,7 +772,7 @@ pub(super) async fn do_sync( index_locations, config_setting, &hasher, - tags, + &tags, &client, state.in_flight(), concurrency, @@ -847,7 +851,7 @@ fn apply_editable_mode(resolution: Resolution, editable: EditableMode) -> Resolu /// These credentials can come from any of `tool.uv.sources`, `tool.uv.dev-dependencies`, /// `project.dependencies`, and `project.optional-dependencies`. fn store_credentials_from_target(target: InstallTarget<'_>) { - // Iterate over any idnexes in the target. + // Iterate over any indexes in the target. for index in target.indexes() { if let Some(credentials) = index.credentials() { let credentials = Arc::new(credentials); diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index ec278d4b4..ed1e9e246 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -634,6 +634,7 @@ async fn lock_and_sync( EditableMode::Editable, install_options, Modifications::Sufficient, + None, settings.into(), &network_settings, &state, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 28a20f373..261dd8d7c 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1802,6 +1802,7 @@ async fn run_project( args.install_options, args.modifications, args.python, + args.python_platform, args.install_mirrors, globals.python_preference, globals.python_downloads, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index ed86608ed..f89704d45 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1150,6 +1150,7 @@ pub(crate) struct SyncSettings { pub(crate) all_packages: bool, pub(crate) package: Option, pub(crate) python: Option, + pub(crate) python_platform: Option, pub(crate) install_mirrors: PythonInstallMirrors, pub(crate) refresh: Refresh, pub(crate) settings: ResolverInstallerSettings, @@ -1190,6 +1191,7 @@ impl SyncSettings { package, script, python, + python_platform, check, no_check, } = args; @@ -1249,6 +1251,7 @@ impl SyncSettings { all_packages, package, python: python.and_then(Maybe::into_option), + python_platform, refresh: Refresh::from(refresh), settings, install_mirrors, diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 690079abf..d4479296a 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -10026,3 +10026,42 @@ fn read_only() -> 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(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index f20bcf7ec..989cbc54b 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1138,7 +1138,51 @@ 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
  • +
--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