diff --git a/crates/pypi-types/src/lib.rs b/crates/pypi-types/src/lib.rs index a38dc248797a..ec9abbb444dd 100644 --- a/crates/pypi-types/src/lib.rs +++ b/crates/pypi-types/src/lib.rs @@ -7,6 +7,7 @@ pub use parsed_url::*; pub use requirement::*; pub use scheme::*; pub use simple_json::*; +pub use supported_environments::*; mod base_url; mod direct_url; @@ -17,3 +18,4 @@ mod parsed_url; mod requirement; mod scheme; mod simple_json; +mod supported_environments; diff --git a/crates/uv-workspace/src/environments.rs b/crates/pypi-types/src/supported_environments.rs similarity index 98% rename from crates/uv-workspace/src/environments.rs rename to crates/pypi-types/src/supported_environments.rs index 9c640dab200b..226e506fff80 100644 --- a/crates/uv-workspace/src/environments.rs +++ b/crates/pypi-types/src/supported_environments.rs @@ -4,6 +4,7 @@ use serde::ser::SerializeSeq; use pep508_rs::MarkerTree; +/// A list of supported marker environments. #[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct SupportedEnvironments(Vec); diff --git a/crates/uv-settings/src/combine.rs b/crates/uv-settings/src/combine.rs index 7f51370fa049..f7c1e8af6581 100644 --- a/crates/uv-settings/src/combine.rs +++ b/crates/uv-settings/src/combine.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use distribution_types::IndexUrl; use install_wheel_rs::linker::LinkMode; +use pypi_types::SupportedEnvironments; use uv_configuration::{ConfigSettings, IndexStrategy, KeyringProviderType, TargetTriple}; use uv_python::{PythonDownloads, PythonPreference, PythonVersion}; use uv_resolver::{AnnotationStyle, ExcludeNewer, PrereleaseMode, ResolutionMode}; @@ -75,12 +76,13 @@ impl_combine_or!(LinkMode); impl_combine_or!(NonZeroUsize); impl_combine_or!(PathBuf); impl_combine_or!(PrereleaseMode); +impl_combine_or!(PythonDownloads); +impl_combine_or!(PythonPreference); impl_combine_or!(PythonVersion); impl_combine_or!(ResolutionMode); impl_combine_or!(String); +impl_combine_or!(SupportedEnvironments); impl_combine_or!(TargetTriple); -impl_combine_or!(PythonPreference); -impl_combine_or!(PythonDownloads); impl_combine_or!(bool); impl Combine for Option> { diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index 0557fb146228..73ef373c98d2 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use distribution_types::{FlatIndexLocation, IndexUrl}; use install_wheel_rs::linker::LinkMode; use pep508_rs::Requirement; -use pypi_types::VerbatimParsedUrl; +use pypi_types::{SupportedEnvironments, VerbatimParsedUrl}; use uv_configuration::{ ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple, }; @@ -43,9 +43,15 @@ pub struct Options { // NOTE(charlie): These fields are shared with `ToolUv` in // `crates/uv-workspace/src/pyproject.rs`, and the documentation lives on that struct. + #[cfg_attr(feature = "schemars", schemars(skip))] pub override_dependencies: Option>>, + + #[cfg_attr(feature = "schemars", schemars(skip))] pub constraint_dependencies: Option>>, + #[cfg_attr(feature = "schemars", schemars(skip))] + pub environments: Option, + // NOTE(charlie): These fields should be kept in-sync with `ToolUv` in // `crates/uv-workspace/src/pyproject.rs`. #[serde(default, skip_serializing)] @@ -60,10 +66,6 @@ pub struct Options { #[cfg_attr(feature = "schemars", schemars(skip))] dev_dependencies: serde::de::IgnoredAny, - #[serde(default, skip_serializing)] - #[cfg_attr(feature = "schemars", schemars(skip))] - environments: serde::de::IgnoredAny, - #[serde(default, skip_serializing)] #[cfg_attr(feature = "schemars", schemars(skip))] managed: serde::de::IgnoredAny, diff --git a/crates/uv-workspace/src/lib.rs b/crates/uv-workspace/src/lib.rs index 17113d54c34a..f9bc90906388 100644 --- a/crates/uv-workspace/src/lib.rs +++ b/crates/uv-workspace/src/lib.rs @@ -1,10 +1,8 @@ -pub use environments::SupportedEnvironments; pub use workspace::{ check_nested_workspaces, DiscoveryOptions, ProjectWorkspace, VirtualProject, Workspace, WorkspaceError, WorkspaceMember, }; -mod environments; pub mod pyproject; pub mod pyproject_mut; mod workspace; diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 559a6c931fcd..2e4a00cfe418 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -14,9 +14,8 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use url::Url; -use crate::environments::SupportedEnvironments; use pep440_rs::VersionSpecifiers; -use pypi_types::{RequirementSource, VerbatimParsedUrl}; +use pypi_types::{RequirementSource, SupportedEnvironments, VerbatimParsedUrl}; use uv_git::GitReference; use uv_macros::OptionsMetadata; use uv_normalize::{ExtraName, PackageName}; @@ -121,11 +120,14 @@ pub struct ToolUv { /// By default, uv will resolve for all possible environments during a `uv lock` operation. /// However, you can restrict the set of supported environments to improve performance and avoid /// unsatisfiable branches in the solution space. + /// + /// These environments will also respected when `uv pip compile` is invoked with the + /// `--universal` flag. #[cfg_attr( feature = "schemars", schemars( with = "Option>", - description = "A list of environment markers, e.g. `python_version >= '3.6'`." + description = "A list of environment markers, e.g., `python_version >= '3.6'`." ) )] #[option( diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 9e5c5604401b..4e8f3d34aa09 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -9,12 +9,11 @@ use rustc_hash::FxHashSet; use tracing::{debug, trace, warn}; use pep508_rs::{MarkerTree, RequirementOrigin, VerbatimUrl}; -use pypi_types::{Requirement, RequirementSource, VerbatimParsedUrl}; +use pypi_types::{Requirement, RequirementSource, SupportedEnvironments, VerbatimParsedUrl}; use uv_fs::Simplified; use uv_normalize::{GroupName, PackageName, DEV_DEPENDENCIES}; use uv_warnings::warn_user; -use crate::environments::SupportedEnvironments; use crate::pyproject::{Project, PyProjectToml, Source, ToolUvWorkspace}; #[derive(thiserror::Error, Debug)] diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 89613564131b..aedff28a2ea5 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -11,7 +11,7 @@ use tracing::debug; use distribution_types::{IndexLocations, UnresolvedRequirementSpecification, Verbatim}; use install_wheel_rs::linker::LinkMode; -use pypi_types::Requirement; +use pypi_types::{Requirement, SupportedEnvironments}; use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; @@ -53,6 +53,7 @@ pub(crate) async fn pip_compile( build_constraints: &[RequirementsSource], constraints_from_workspace: Vec, overrides_from_workspace: Vec, + environments: SupportedEnvironments, extras: ExtrasSpecification, output_file: Option<&Path>, resolution_mode: ResolutionMode, @@ -171,10 +172,10 @@ pub(crate) async fn pip_compile( } // Find an interpreter to use for building distributions - let environments = EnvironmentPreference::from_system_flag(system, false); + let environment_preference = EnvironmentPreference::from_system_flag(system, false); let interpreter = if let Some(python) = python.as_ref() { let request = PythonRequest::parse(python); - PythonInstallation::find(&request, environments, python_preference, &cache) + PythonInstallation::find(&request, environment_preference, python_preference, &cache) } else { // TODO(zanieb): The split here hints at a problem with the abstraction; we should be able to use // `PythonInstallation::find(...)` here. @@ -184,7 +185,7 @@ pub(crate) async fn pip_compile( } else { PythonRequest::default() }; - PythonInstallation::find_best(&request, environments, python_preference, &cache) + PythonInstallation::find_best(&request, environment_preference, python_preference, &cache) }? .into_interpreter(); @@ -244,7 +245,10 @@ pub(crate) async fn pip_compile( // Determine the environment for the resolution. let (tags, markers) = if universal { - (None, ResolverMarkers::universal(vec![])) + ( + None, + ResolverMarkers::universal(environments.into_markers()), + ) } else { let (tags, markers) = resolution_environment(python_version, python_platform, &interpreter)?; diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 81e78834a6ec..ea20a927c932 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -10,7 +10,7 @@ use tracing::debug; use distribution_types::{IndexLocations, UnresolvedRequirementSpecification}; use pep440_rs::Version; -use pypi_types::Requirement; +use pypi_types::{Requirement, SupportedEnvironments}; use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder}; @@ -28,7 +28,7 @@ use uv_resolver::{ }; use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy}; use uv_warnings::warn_user; -use uv_workspace::{DiscoveryOptions, SupportedEnvironments, Workspace}; +use uv_workspace::{DiscoveryOptions, Workspace}; use crate::commands::pip::loggers::{DefaultResolveLogger, ResolveLogger, SummaryResolveLogger}; use crate::commands::project::{find_requires_python, FoundInterpreter, ProjectError, SharedState}; diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 606e621d06fe..ca4091015f4e 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -303,6 +303,7 @@ async fn run(cli: Cli) -> Result { &build_constraints, args.constraints_from_workspace, args.overrides_from_workspace, + args.environments, args.settings.extras, args.settings.output_file.as_deref(), args.settings.resolution, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 48115c1e4890..c0378b1a485b 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -7,7 +7,7 @@ use std::str::FromStr; use distribution_types::IndexLocations; use install_wheel_rs::linker::LinkMode; use pep508_rs::{ExtraName, RequirementOrigin}; -use pypi_types::Requirement; +use pypi_types::{Requirement, SupportedEnvironments}; use uv_cache::{CacheArgs, Refresh}; use uv_cli::{ options::{flag, resolver_installer_options, resolver_options}, @@ -927,9 +927,10 @@ pub(crate) struct PipCompileSettings { pub(crate) src_file: Vec, pub(crate) constraint: Vec, pub(crate) r#override: Vec, + pub(crate) build_constraint: Vec, pub(crate) constraints_from_workspace: Vec, pub(crate) overrides_from_workspace: Vec, - pub(crate) build_constraint: Vec, + pub(crate) environments: SupportedEnvironments, pub(crate) refresh: Refresh, pub(crate) settings: PipSettings, } @@ -1015,6 +1016,12 @@ impl PipCompileSettings { Vec::new() }; + let environments = if let Some(configuration) = &filesystem { + configuration.environments.clone().unwrap_or_default() + } else { + SupportedEnvironments::default() + }; + Self { src_file, constraint: constraint @@ -1031,6 +1038,7 @@ impl PipCompileSettings { .collect(), constraints_from_workspace, overrides_from_workspace, + environments, refresh: Refresh::from(refresh), settings: PipSettings::combine( PipOptions { diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 2c14662f70fd..4713b8e424c0 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -11946,3 +11946,52 @@ fn symlink() -> Result<()> { Ok(()) } + +/// Resolve with `--universal`, applying user-provided constraints to the space of supported +/// environments. +#[test] +fn universal_constrained_environment() -> 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"] + + [tool.uv] + environments = "platform_system != 'Windows'" + "#, + )?; + + uv_snapshot!(context.filters(), context.pip_compile() + .arg("pyproject.toml") + .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] pyproject.toml --universal + black==24.3.0 ; platform_system != 'Windows' + # via project (pyproject.toml) + click==8.1.7 ; platform_system != 'Windows' + # via black + mypy-extensions==1.0.0 ; platform_system != 'Windows' + # via black + packaging==24.0 ; platform_system != 'Windows' + # via black + pathspec==0.12.1 ; platform_system != 'Windows' + # via black + platformdirs==4.2.0 ; platform_system != 'Windows' + # via black + + ----- stderr ----- + Resolved 6 packages in [TIME] + "### + ); + + Ok(()) +} diff --git a/crates/uv/tests/show_settings.rs b/crates/uv/tests/show_settings.rs index 5862c7046b6e..ec73a186af6a 100644 --- a/crates/uv/tests/show_settings.rs +++ b/crates/uv/tests/show_settings.rs @@ -76,9 +76,12 @@ fn resolve_uv_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -211,9 +214,12 @@ fn resolve_uv_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -347,9 +353,12 @@ fn resolve_uv_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -515,9 +524,12 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -652,9 +664,12 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -775,9 +790,12 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -935,9 +953,12 @@ fn resolve_index_url() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -1095,9 +1116,12 @@ fn resolve_index_url() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -1300,9 +1324,12 @@ fn resolve_find_links() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -1459,9 +1486,12 @@ fn resolve_top_level() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -1588,9 +1618,12 @@ fn resolve_top_level() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -1745,9 +1778,12 @@ fn resolve_top_level() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -1926,9 +1962,12 @@ fn resolve_user_configuration() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -2045,9 +2084,12 @@ fn resolve_user_configuration() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -2164,9 +2206,12 @@ fn resolve_user_configuration() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -2285,9 +2330,12 @@ fn resolve_user_configuration() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -2576,9 +2624,12 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -2723,9 +2774,12 @@ fn resolve_both() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -2885,9 +2939,12 @@ fn resolve_config_file() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -3122,9 +3179,12 @@ fn resolve_skip_empty() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { @@ -3244,9 +3304,12 @@ fn resolve_skip_empty() -> anyhow::Result<()> { ], constraint: [], override: [], + build_constraint: [], constraints_from_workspace: [], overrides_from_workspace: [], - build_constraint: [], + environments: SupportedEnvironments( + [], + ), refresh: None( Timestamp( SystemTime { diff --git a/docs/reference/settings.md b/docs/reference/settings.md index f316ebf4d4a9..d2141ef448a1 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -243,6 +243,9 @@ By default, uv will resolve for all possible environments during a `uv lock` ope However, you can restrict the set of supported environments to improve performance and avoid unsatisfiable branches in the solution space. +These environments will also respected when `uv pip compile` is invoked with the +`--universal` flag. + **Default value**: `[]` **Type**: `str | list[str]` diff --git a/uv.schema.json b/uv.schema.json index 871fa0e12eb3..de3a900473bc 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -77,7 +77,7 @@ } }, "environments": { - "description": "A list of environment markers, e.g. `python_version >= '3.6'`.", + "description": "A list of environment markers, e.g., `python_version >= '3.6'`.", "type": [ "array", "null"