Allow users to provide pre-defined metadata for resolution (#7442)
## Summary This PR enables users to provide pre-defined static metadata for dependencies. It's intended for situations in which the user depends on a package that does _not_ declare static metadata (e.g., a `setup.py`-only sdist), and that is expensive to build or even cannot be built on some architectures. For example, you might have a Linux-only dependency that can't be built on ARM -- but we need to build that package in order to generate the lockfile. By providing static metadata, the user can instruct uv to avoid building that package at all. For example, to override all `anyio` versions: ```toml [project] name = "project" version = "0.1.0" requires-python = ">=3.12" dependencies = ["anyio"] [[tool.uv.dependency-metadata]] name = "anyio" requires-dist = ["iniconfig"] ``` Or, to override a specific version: ```toml [project] name = "project" version = "0.1.0" requires-python = ">=3.12" dependencies = ["anyio"] [[tool.uv.dependency-metadata]] name = "anyio" version = "3.7.0" requires-dist = ["iniconfig"] ``` The current implementation uses `Metadata23` directly, so we adhere to the exact schema expected internally and defined by the standards. Any entries are treated similarly to overrides, in that we won't even look for `anyio@3.7.0` metadata in the above example. (In a way, this also enables #4422, since you could remove a dependency for a specific package, though it's probably too unwieldy to use in practice, since you'd need to redefine the _rest_ of the metadata, and do that for every package that requires the package you want to omit.) This is under-documented, since I want to get feedback on the core ideas and names involved. Closes https://github.com/astral-sh/uv/issues/7393.
This commit is contained in:
@@ -83,7 +83,7 @@ mod resolver {
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use distribution_types::{IndexCapabilities, IndexLocations};
|
||||
use distribution_types::{DependencyMetadata, IndexCapabilities, IndexLocations};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::{MarkerEnvironment, MarkerEnvironmentBuilder};
|
||||
@@ -139,7 +139,7 @@ mod resolver {
|
||||
interpreter: &Interpreter,
|
||||
universal: bool,
|
||||
) -> Result<ResolutionGraph> {
|
||||
let build_isolation = BuildIsolation::Isolated;
|
||||
let build_isolation = BuildIsolation::default();
|
||||
let build_options = BuildOptions::default();
|
||||
let concurrency = Concurrency::default();
|
||||
let config_settings = ConfigSettings::default();
|
||||
@@ -150,17 +150,18 @@ mod resolver {
|
||||
.timestamp()
|
||||
.into(),
|
||||
);
|
||||
let build_constraints = Constraints::default();
|
||||
let capabilities = IndexCapabilities::default();
|
||||
let flat_index = FlatIndex::default();
|
||||
let git = GitResolver::default();
|
||||
let capabilities = IndexCapabilities::default();
|
||||
let hashes = HashStrategy::None;
|
||||
let hashes = HashStrategy::default();
|
||||
let in_flight = InFlight::default();
|
||||
let index = InMemoryIndex::default();
|
||||
let index_locations = IndexLocations::default();
|
||||
let installed_packages = EmptyInstalledPackages;
|
||||
let sources = SourceStrategy::default();
|
||||
let options = OptionsBuilder::new().exclude_newer(exclude_newer).build();
|
||||
let build_constraints = Constraints::default();
|
||||
let sources = SourceStrategy::default();
|
||||
let dependency_metadata = DependencyMetadata::default();
|
||||
|
||||
let python_requirement = if universal {
|
||||
PythonRequirement::from_requires_python(
|
||||
@@ -178,6 +179,7 @@ mod resolver {
|
||||
interpreter,
|
||||
&index_locations,
|
||||
&flat_index,
|
||||
&dependency_metadata,
|
||||
&index,
|
||||
&git,
|
||||
&capabilities,
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use pep508_rs::Requirement;
|
||||
use pypi_types::{Metadata23, VerbatimParsedUrl};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
|
||||
/// Pre-defined [`StaticMetadata`] entries, indexed by [`PackageName`] and [`Version`].
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DependencyMetadata(FxHashMap<PackageName, Vec<StaticMetadata>>);
|
||||
|
||||
impl DependencyMetadata {
|
||||
/// Index a set of [`StaticMetadata`] entries by [`PackageName`] and [`Version`].
|
||||
pub fn from_entries(entries: impl IntoIterator<Item = StaticMetadata>) -> Self {
|
||||
let mut map = Self::default();
|
||||
for entry in entries {
|
||||
map.0.entry(entry.name.clone()).or_default().push(entry);
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
/// Retrieve a [`StaticMetadata`] entry by [`PackageName`] and [`Version`].
|
||||
pub fn get(&self, package: &PackageName, version: &Version) -> Option<Metadata23> {
|
||||
let versions = self.0.get(package)?;
|
||||
|
||||
// Search for an exact, then a global match.
|
||||
let metadata = versions
|
||||
.iter()
|
||||
.find(|v| v.version.as_ref() == Some(version))
|
||||
.or_else(|| versions.iter().find(|v| v.version.is_none()))?;
|
||||
|
||||
Some(Metadata23 {
|
||||
name: metadata.name.clone(),
|
||||
version: version.clone(),
|
||||
requires_dist: metadata.requires_dist.clone(),
|
||||
requires_python: metadata.requires_python.clone(),
|
||||
provides_extras: metadata.provides_extras.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieve all [`StaticMetadata`] entries.
|
||||
pub fn values(&self) -> impl Iterator<Item = &StaticMetadata> {
|
||||
self.0.values().flatten()
|
||||
}
|
||||
}
|
||||
|
||||
/// A subset of the Python Package Metadata 2.3 standard as specified in
|
||||
/// <https://packaging.python.org/specifications/core-metadata/>.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct StaticMetadata {
|
||||
// Mandatory fields
|
||||
pub name: PackageName,
|
||||
#[cfg_attr(
|
||||
feature = "schemars",
|
||||
schemars(
|
||||
with = "String",
|
||||
description = "PEP 440-style package version, e.g., `1.2.3`"
|
||||
)
|
||||
)]
|
||||
pub version: Option<Version>,
|
||||
// Optional fields
|
||||
#[serde(default)]
|
||||
pub requires_dist: Vec<Requirement<VerbatimParsedUrl>>,
|
||||
#[cfg_attr(
|
||||
feature = "schemars",
|
||||
schemars(
|
||||
with = "Option<String>",
|
||||
description = "PEP 508-style Python requirement, e.g., `>=3.10`"
|
||||
)
|
||||
)]
|
||||
pub requires_python: Option<VersionSpecifiers>,
|
||||
#[serde(default)]
|
||||
pub provides_extras: Vec<ExtraName>,
|
||||
}
|
||||
@@ -51,6 +51,7 @@ pub use crate::annotation::*;
|
||||
pub use crate::any::*;
|
||||
pub use crate::buildable::*;
|
||||
pub use crate::cached::*;
|
||||
pub use crate::dependency_metadata::*;
|
||||
pub use crate::diagnostic::*;
|
||||
pub use crate::error::*;
|
||||
pub use crate::file::*;
|
||||
@@ -68,6 +69,7 @@ mod annotation;
|
||||
mod any;
|
||||
mod buildable;
|
||||
mod cached;
|
||||
mod dependency_metadata;
|
||||
mod diagnostic;
|
||||
mod error;
|
||||
mod file;
|
||||
|
||||
@@ -135,7 +135,7 @@ create_exception!(
|
||||
);
|
||||
|
||||
/// A PEP 508 dependency specifier.
|
||||
#[derive(Hash, Debug, Clone, Eq, PartialEq)]
|
||||
#[derive(Hash, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct Requirement<T: Pep508Url = VerbatimUrl> {
|
||||
/// The distribution name such as `requests` in
|
||||
/// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`.
|
||||
@@ -480,7 +480,9 @@ impl<T: Pep508Url> schemars::JsonSchema for Requirement<T> {
|
||||
schemars::schema::SchemaObject {
|
||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||
metadata: Some(Box::new(schemars::schema::Metadata {
|
||||
description: Some("A PEP 508 dependency specifier".to_string()),
|
||||
description: Some(
|
||||
"A PEP 508 dependency specifier, e.g., `ruff >= 0.6.0`".to_string(),
|
||||
),
|
||||
..schemars::schema::Metadata::default()
|
||||
})),
|
||||
..schemars::schema::SchemaObject::default()
|
||||
@@ -535,7 +537,7 @@ impl Extras {
|
||||
}
|
||||
|
||||
/// The actual version specifier or URL to install.
|
||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum VersionOrUrl<T: Pep508Url = VerbatimUrl> {
|
||||
/// A PEP 440 version specifier set
|
||||
VersionSpecifier(VersionSpecifiers),
|
||||
|
||||
@@ -271,6 +271,7 @@ pub fn resolver_options(
|
||||
} else {
|
||||
prerelease
|
||||
},
|
||||
dependency_metadata: None,
|
||||
config_settings: config_setting
|
||||
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
||||
no_build_isolation: flag(no_build_isolation, build_isolation),
|
||||
@@ -364,6 +365,7 @@ pub fn resolver_installer_options(
|
||||
} else {
|
||||
prerelease
|
||||
},
|
||||
dependency_metadata: None,
|
||||
config_settings: config_setting
|
||||
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
||||
no_build_isolation: flag(no_build_isolation, build_isolation),
|
||||
|
||||
@@ -12,7 +12,8 @@ use rustc_hash::FxHashMap;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use distribution_types::{
|
||||
CachedDist, IndexCapabilities, IndexLocations, Name, Resolution, SourceDist, VersionOrUrlRef,
|
||||
CachedDist, DependencyMetadata, IndexCapabilities, IndexLocations, Name, Resolution,
|
||||
SourceDist, VersionOrUrlRef,
|
||||
};
|
||||
use pypi_types::Requirement;
|
||||
use uv_build::{SourceBuild, SourceBuildContext};
|
||||
@@ -45,6 +46,7 @@ pub struct BuildDispatch<'a> {
|
||||
index: &'a InMemoryIndex,
|
||||
git: &'a GitResolver,
|
||||
capabilities: &'a IndexCapabilities,
|
||||
dependency_metadata: &'a DependencyMetadata,
|
||||
in_flight: &'a InFlight,
|
||||
build_isolation: BuildIsolation<'a>,
|
||||
link_mode: install_wheel_rs::linker::LinkMode,
|
||||
@@ -66,6 +68,7 @@ impl<'a> BuildDispatch<'a> {
|
||||
interpreter: &'a Interpreter,
|
||||
index_locations: &'a IndexLocations,
|
||||
flat_index: &'a FlatIndex,
|
||||
dependency_metadata: &'a DependencyMetadata,
|
||||
index: &'a InMemoryIndex,
|
||||
git: &'a GitResolver,
|
||||
capabilities: &'a IndexCapabilities,
|
||||
@@ -90,6 +93,7 @@ impl<'a> BuildDispatch<'a> {
|
||||
index,
|
||||
git,
|
||||
capabilities,
|
||||
dependency_metadata,
|
||||
in_flight,
|
||||
index_strategy,
|
||||
config_settings,
|
||||
@@ -136,6 +140,10 @@ impl<'a> BuildContext for BuildDispatch<'a> {
|
||||
self.capabilities
|
||||
}
|
||||
|
||||
fn dependency_metadata(&self) -> &DependencyMetadata {
|
||||
self.dependency_metadata
|
||||
}
|
||||
|
||||
fn build_options(&self) -> &BuildOptions {
|
||||
self.build_options
|
||||
}
|
||||
|
||||
@@ -360,6 +360,15 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
||||
dist: &BuiltDist,
|
||||
hashes: HashPolicy<'_>,
|
||||
) -> Result<ArchiveMetadata, Error> {
|
||||
// If the metadata was provided by the user directly, prefer it.
|
||||
if let Some(metadata) = self
|
||||
.build_context
|
||||
.dependency_metadata()
|
||||
.get(dist.name(), dist.version())
|
||||
{
|
||||
return Ok(ArchiveMetadata::from_metadata23(metadata.clone()));
|
||||
}
|
||||
|
||||
// If hash generation is enabled, and the distribution isn't hosted on an index, get the
|
||||
// entire wheel to ensure that the hashes are included in the response. If the distribution
|
||||
// is hosted on an index, the hashes will be included in the simple metadata response.
|
||||
@@ -415,6 +424,19 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
||||
source: &BuildableSource<'_>,
|
||||
hashes: HashPolicy<'_>,
|
||||
) -> Result<ArchiveMetadata, Error> {
|
||||
// If the metadata was provided by the user directly, prefer it.
|
||||
if let Some(dist) = source.as_dist() {
|
||||
if let Some(version) = dist.version() {
|
||||
if let Some(metadata) = self
|
||||
.build_context
|
||||
.dependency_metadata()
|
||||
.get(dist.name(), version)
|
||||
{
|
||||
return Ok(ArchiveMetadata::from_metadata23(metadata.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optimization: Skip source dist download when we must not build them anyway.
|
||||
if self
|
||||
.build_context
|
||||
|
||||
@@ -17,11 +17,11 @@ use url::Url;
|
||||
use cache_key::RepositoryUrl;
|
||||
use distribution_filename::{DistExtension, ExtensionError, SourceDistExtension, WheelFilename};
|
||||
use distribution_types::{
|
||||
BuiltDist, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist, Dist,
|
||||
DistributionMetadata, FileLocation, FlatIndexLocation, GitSourceDist, HashPolicy,
|
||||
BuiltDist, DependencyMetadata, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist,
|
||||
Dist, DistributionMetadata, FileLocation, FlatIndexLocation, GitSourceDist, HashPolicy,
|
||||
IndexLocations, IndexUrl, Name, PathBuiltDist, PathSourceDist, RegistryBuiltDist,
|
||||
RegistryBuiltWheel, RegistrySourceDist, RemoteSource, Resolution, ResolvedDist, ToUrlError,
|
||||
UrlString,
|
||||
RegistryBuiltWheel, RegistrySourceDist, RemoteSource, Resolution, ResolvedDist, StaticMetadata,
|
||||
ToUrlError, UrlString,
|
||||
};
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::{split_scheme, MarkerEnvironment, MarkerTree, VerbatimUrl, VerbatimUrlError};
|
||||
@@ -795,6 +795,40 @@ impl Lock {
|
||||
manifest_table.insert("overrides", value(overrides));
|
||||
}
|
||||
|
||||
if !self.manifest.dependency_metadata.is_empty() {
|
||||
let mut tables = ArrayOfTables::new();
|
||||
for metadata in &self.manifest.dependency_metadata {
|
||||
let mut table = Table::new();
|
||||
table.insert("name", value(metadata.name.to_string()));
|
||||
if let Some(version) = metadata.version.as_ref() {
|
||||
table.insert("version", value(version.to_string()));
|
||||
}
|
||||
if !metadata.requires_dist.is_empty() {
|
||||
table.insert(
|
||||
"requires-dist",
|
||||
value(serde::Serialize::serialize(
|
||||
&metadata.requires_dist,
|
||||
toml_edit::ser::ValueSerializer::new(),
|
||||
)?),
|
||||
);
|
||||
}
|
||||
if let Some(requires_python) = metadata.requires_python.as_ref() {
|
||||
table.insert("requires-python", value(requires_python.to_string()));
|
||||
}
|
||||
if !metadata.provides_extras.is_empty() {
|
||||
table.insert(
|
||||
"provides-extras",
|
||||
value(serde::Serialize::serialize(
|
||||
&metadata.provides_extras,
|
||||
toml_edit::ser::ValueSerializer::new(),
|
||||
)?),
|
||||
);
|
||||
}
|
||||
tables.push(table);
|
||||
}
|
||||
manifest_table.insert("dependency-metadata", Item::ArrayOfTables(tables));
|
||||
}
|
||||
|
||||
if !manifest_table.is_empty() {
|
||||
doc.insert("manifest", Item::Table(manifest_table));
|
||||
}
|
||||
@@ -881,6 +915,7 @@ impl Lock {
|
||||
requirements: &[Requirement],
|
||||
constraints: &[Requirement],
|
||||
overrides: &[Requirement],
|
||||
dependency_metadata: &DependencyMetadata,
|
||||
indexes: Option<&IndexLocations>,
|
||||
build_options: &BuildOptions,
|
||||
tags: &Tags,
|
||||
@@ -995,6 +1030,18 @@ impl Lock {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that the lockfile was generated with the same static metadata.
|
||||
{
|
||||
let expected = dependency_metadata
|
||||
.values()
|
||||
.cloned()
|
||||
.collect::<BTreeSet<_>>();
|
||||
let actual = &self.manifest.dependency_metadata;
|
||||
if expected != *actual {
|
||||
return Ok(SatisfiesResult::MismatchedStaticMetadata(expected, actual));
|
||||
}
|
||||
}
|
||||
|
||||
// Collect the set of available indexes (both `--index-url` and `--find-links` entries).
|
||||
let remotes = indexes.map(|locations| {
|
||||
locations
|
||||
@@ -1249,6 +1296,8 @@ pub enum SatisfiesResult<'lock> {
|
||||
MismatchedConstraints(BTreeSet<Requirement>, BTreeSet<Requirement>),
|
||||
/// The lockfile uses a different set of overrides.
|
||||
MismatchedOverrides(BTreeSet<Requirement>, BTreeSet<Requirement>),
|
||||
/// The lockfile uses different static metadata.
|
||||
MismatchedStaticMetadata(BTreeSet<StaticMetadata>, &'lock BTreeSet<StaticMetadata>),
|
||||
/// The lockfile is missing a workspace member.
|
||||
MissingRoot(PackageName),
|
||||
/// The lockfile referenced a remote index that was not provided
|
||||
@@ -1302,6 +1351,9 @@ pub struct ResolverManifest {
|
||||
/// The overrides provided to the resolver.
|
||||
#[serde(default)]
|
||||
overrides: BTreeSet<Requirement>,
|
||||
/// The static metadata provided to the resolver.
|
||||
#[serde(default)]
|
||||
dependency_metadata: BTreeSet<StaticMetadata>,
|
||||
}
|
||||
|
||||
impl ResolverManifest {
|
||||
@@ -1312,12 +1364,14 @@ impl ResolverManifest {
|
||||
requirements: impl IntoIterator<Item = Requirement>,
|
||||
constraints: impl IntoIterator<Item = Requirement>,
|
||||
overrides: impl IntoIterator<Item = Requirement>,
|
||||
dependency_metadata: impl IntoIterator<Item = StaticMetadata>,
|
||||
) -> Self {
|
||||
Self {
|
||||
members: members.into_iter().collect(),
|
||||
requirements: requirements.into_iter().collect(),
|
||||
constraints: constraints.into_iter().collect(),
|
||||
overrides: overrides.into_iter().collect(),
|
||||
dependency_metadata: dependency_metadata.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1340,6 +1394,7 @@ impl ResolverManifest {
|
||||
.into_iter()
|
||||
.map(|requirement| requirement.relative_to(workspace.install_path()))
|
||||
.collect::<Result<BTreeSet<_>, _>>()?,
|
||||
dependency_metadata: self.dependency_metadata,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -105,6 +105,7 @@ Ok(
|
||||
requirements: {},
|
||||
constraints: {},
|
||||
overrides: {},
|
||||
dependency_metadata: {},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
+1
@@ -112,6 +112,7 @@ Ok(
|
||||
requirements: {},
|
||||
constraints: {},
|
||||
overrides: {},
|
||||
dependency_metadata: {},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
+1
@@ -104,6 +104,7 @@ Ok(
|
||||
requirements: {},
|
||||
constraints: {},
|
||||
overrides: {},
|
||||
dependency_metadata: {},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
+1
@@ -176,6 +176,7 @@ Ok(
|
||||
requirements: {},
|
||||
constraints: {},
|
||||
overrides: {},
|
||||
dependency_metadata: {},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
+1
@@ -176,6 +176,7 @@ Ok(
|
||||
requirements: {},
|
||||
constraints: {},
|
||||
overrides: {},
|
||||
dependency_metadata: {},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
+1
@@ -176,6 +176,7 @@ Ok(
|
||||
requirements: {},
|
||||
constraints: {},
|
||||
overrides: {},
|
||||
dependency_metadata: {},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
+1
@@ -85,6 +85,7 @@ Ok(
|
||||
requirements: {},
|
||||
constraints: {},
|
||||
overrides: {},
|
||||
dependency_metadata: {},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
+1
@@ -81,6 +81,7 @@ Ok(
|
||||
requirements: {},
|
||||
constraints: {},
|
||||
overrides: {},
|
||||
dependency_metadata: {},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -71,6 +71,7 @@ Ok(
|
||||
requirements: {},
|
||||
constraints: {},
|
||||
overrides: {},
|
||||
dependency_metadata: {},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -71,6 +71,7 @@ Ok(
|
||||
requirements: {},
|
||||
constraints: {},
|
||||
overrides: {},
|
||||
dependency_metadata: {},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{fmt::Debug, num::NonZeroUsize, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use distribution_types::{FlatIndexLocation, IndexUrl};
|
||||
use distribution_types::{FlatIndexLocation, IndexUrl, StaticMetadata};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pep508_rs::Requirement;
|
||||
use pypi_types::{SupportedEnvironments, VerbatimParsedUrl};
|
||||
@@ -288,6 +288,7 @@ pub struct ResolverOptions {
|
||||
pub allow_insecure_host: Option<Vec<TrustedHost>>,
|
||||
pub resolution: Option<ResolutionMode>,
|
||||
pub prerelease: Option<PrereleaseMode>,
|
||||
pub dependency_metadata: Option<Vec<StaticMetadata>>,
|
||||
pub config_settings: Option<ConfigSettings>,
|
||||
pub exclude_newer: Option<ExcludeNewer>,
|
||||
pub link_mode: Option<LinkMode>,
|
||||
@@ -440,6 +441,29 @@ pub struct ResolverInstallerOptions {
|
||||
possible_values = true
|
||||
)]
|
||||
pub prerelease: Option<PrereleaseMode>,
|
||||
/// Pre-defined static metadata for dependencies of the project (direct or transitive). When
|
||||
/// provided, enables the resolver to use the specified metadata instead of querying the
|
||||
/// registry or building the relevant package from source.
|
||||
///
|
||||
/// Metadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)
|
||||
/// standard, though only the following fields are respected:
|
||||
///
|
||||
/// - `name`: The name of the package.
|
||||
/// - (Optional) `version`: The version of the package. If omitted, the metadata will be applied
|
||||
/// to all versions of the package.
|
||||
/// - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).
|
||||
/// - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).
|
||||
/// - (Optional) `provides-extras`: The extras provided by the package.
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
value_type = "list[dict]",
|
||||
example = r#"
|
||||
dependency-metadata = [
|
||||
{ name = "flask", version = "1.0.0", requires-dist = ["werkzeug"], requires-python = ">=3.6" },
|
||||
]
|
||||
"#
|
||||
)]
|
||||
pub dependency_metadata: Option<Vec<StaticMetadata>>,
|
||||
/// Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend,
|
||||
/// specified as `KEY=VALUE` pairs.
|
||||
#[option(
|
||||
@@ -949,6 +973,29 @@ pub struct PipOptions {
|
||||
possible_values = true
|
||||
)]
|
||||
pub prerelease: Option<PrereleaseMode>,
|
||||
/// Pre-defined static metadata for dependencies of the project (direct or transitive). When
|
||||
/// provided, enables the resolver to use the specified metadata instead of querying the
|
||||
/// registry or building the relevant package from source.
|
||||
///
|
||||
/// Metadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)
|
||||
/// standard, though only the following fields are respected:
|
||||
///
|
||||
/// - `name`: The name of the package.
|
||||
/// - (Optional) `version`: The version of the package. If omitted, the metadata will be applied
|
||||
/// to all versions of the package.
|
||||
/// - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).
|
||||
/// - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).
|
||||
/// - (Optional) `provides-extras`: The extras provided by the package.
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
value_type = "list[dict]",
|
||||
example = r#"
|
||||
dependency-metadata = [
|
||||
{ name = "flask", version = "1.0.0", requires-dist = ["werkzeug"], requires-python = ">=3.6" },
|
||||
]
|
||||
"#
|
||||
)]
|
||||
pub dependency_metadata: Option<Vec<StaticMetadata>>,
|
||||
/// Write the requirements generated by `uv pip compile` to the given `requirements.txt` file.
|
||||
///
|
||||
/// If the file already exists, the existing versions will be preferred when resolving
|
||||
@@ -1290,6 +1337,7 @@ impl From<ResolverInstallerOptions> for ResolverOptions {
|
||||
allow_insecure_host: value.allow_insecure_host,
|
||||
resolution: value.resolution,
|
||||
prerelease: value.prerelease,
|
||||
dependency_metadata: value.dependency_metadata,
|
||||
config_settings: value.config_settings,
|
||||
exclude_newer: value.exclude_newer,
|
||||
link_mode: value.link_mode,
|
||||
@@ -1351,6 +1399,7 @@ pub struct ToolOptions {
|
||||
pub allow_insecure_host: Option<Vec<TrustedHost>>,
|
||||
pub resolution: Option<ResolutionMode>,
|
||||
pub prerelease: Option<PrereleaseMode>,
|
||||
pub dependency_metadata: Option<Vec<StaticMetadata>>,
|
||||
pub config_settings: Option<ConfigSettings>,
|
||||
pub no_build_isolation: Option<bool>,
|
||||
pub no_build_isolation_package: Option<Vec<PackageName>>,
|
||||
@@ -1376,6 +1425,7 @@ impl From<ResolverInstallerOptions> for ToolOptions {
|
||||
allow_insecure_host: value.allow_insecure_host,
|
||||
resolution: value.resolution,
|
||||
prerelease: value.prerelease,
|
||||
dependency_metadata: value.dependency_metadata,
|
||||
config_settings: value.config_settings,
|
||||
no_build_isolation: value.no_build_isolation,
|
||||
no_build_isolation_package: value.no_build_isolation_package,
|
||||
@@ -1403,6 +1453,7 @@ impl From<ToolOptions> for ResolverInstallerOptions {
|
||||
allow_insecure_host: value.allow_insecure_host,
|
||||
resolution: value.resolution,
|
||||
prerelease: value.prerelease,
|
||||
dependency_metadata: value.dependency_metadata,
|
||||
config_settings: value.config_settings,
|
||||
no_build_isolation: value.no_build_isolation,
|
||||
no_build_isolation_package: value.no_build_isolation_package,
|
||||
|
||||
@@ -4,7 +4,8 @@ use std::path::{Path, PathBuf};
|
||||
use anyhow::Result;
|
||||
|
||||
use distribution_types::{
|
||||
CachedDist, IndexCapabilities, IndexLocations, InstalledDist, Resolution, SourceDist,
|
||||
CachedDist, DependencyMetadata, IndexCapabilities, IndexLocations, InstalledDist, Resolution,
|
||||
SourceDist,
|
||||
};
|
||||
use pep508_rs::PackageName;
|
||||
use pypi_types::Requirement;
|
||||
@@ -62,6 +63,9 @@ pub trait BuildContext {
|
||||
/// Return a reference to the discovered registry capabilities.
|
||||
fn capabilities(&self) -> &IndexCapabilities;
|
||||
|
||||
/// Return a reference to any pre-defined static metadata.
|
||||
fn dependency_metadata(&self) -> &DependencyMetadata;
|
||||
|
||||
/// Whether source distribution building or pre-built wheels is disabled.
|
||||
///
|
||||
/// This [`BuildContext::setup_build`] calls will fail if builds are disabled.
|
||||
|
||||
@@ -115,6 +115,7 @@ async fn build_impl(
|
||||
allow_insecure_host,
|
||||
resolution: _,
|
||||
prerelease: _,
|
||||
dependency_metadata,
|
||||
config_setting,
|
||||
no_build_isolation,
|
||||
no_build_isolation_package,
|
||||
@@ -299,6 +300,7 @@ async fn build_impl(
|
||||
&interpreter,
|
||||
index_locations,
|
||||
&flat_index,
|
||||
dependency_metadata,
|
||||
&state.index,
|
||||
&state.git,
|
||||
&state.capabilities,
|
||||
|
||||
@@ -8,7 +8,7 @@ use owo_colors::OwoColorize;
|
||||
use tracing::debug;
|
||||
|
||||
use distribution_types::{
|
||||
IndexCapabilities, IndexLocations, NameRequirementSpecification,
|
||||
DependencyMetadata, IndexCapabilities, IndexLocations, NameRequirementSpecification,
|
||||
UnresolvedRequirementSpecification, Verbatim,
|
||||
};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
@@ -75,6 +75,7 @@ pub(crate) async fn pip_compile(
|
||||
include_index_annotation: bool,
|
||||
index_locations: IndexLocations,
|
||||
index_strategy: IndexStrategy,
|
||||
dependency_metadata: DependencyMetadata,
|
||||
keyring_provider: KeyringProviderType,
|
||||
allow_insecure_host: Vec<TrustedHost>,
|
||||
config_settings: ConfigSettings,
|
||||
@@ -335,6 +336,7 @@ pub(crate) async fn pip_compile(
|
||||
&interpreter,
|
||||
&index_locations,
|
||||
&flat_index,
|
||||
&dependency_metadata,
|
||||
&source_index,
|
||||
&git,
|
||||
&capabilities,
|
||||
|
||||
@@ -6,7 +6,8 @@ use owo_colors::OwoColorize;
|
||||
use tracing::{debug, enabled, Level};
|
||||
|
||||
use distribution_types::{
|
||||
IndexLocations, NameRequirementSpecification, Resolution, UnresolvedRequirementSpecification,
|
||||
DependencyMetadata, IndexLocations, NameRequirementSpecification, Resolution,
|
||||
UnresolvedRequirementSpecification,
|
||||
};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pep508_rs::PackageName;
|
||||
@@ -54,6 +55,7 @@ pub(crate) async fn pip_install(
|
||||
upgrade: Upgrade,
|
||||
index_locations: IndexLocations,
|
||||
index_strategy: IndexStrategy,
|
||||
dependency_metadata: DependencyMetadata,
|
||||
keyring_provider: KeyringProviderType,
|
||||
allow_insecure_host: Vec<TrustedHost>,
|
||||
reinstall: Reinstall,
|
||||
@@ -337,6 +339,7 @@ pub(crate) async fn pip_install(
|
||||
interpreter,
|
||||
&index_locations,
|
||||
&flat_index,
|
||||
&dependency_metadata,
|
||||
&state.index,
|
||||
&state.git,
|
||||
&state.capabilities,
|
||||
|
||||
@@ -5,7 +5,7 @@ use anyhow::Result;
|
||||
use owo_colors::OwoColorize;
|
||||
use tracing::debug;
|
||||
|
||||
use distribution_types::{IndexLocations, Resolution};
|
||||
use distribution_types::{DependencyMetadata, IndexLocations, Resolution};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pep508_rs::PackageName;
|
||||
use uv_auth::store_credentials_from_url;
|
||||
@@ -47,6 +47,7 @@ pub(crate) async fn pip_sync(
|
||||
hash_checking: Option<HashCheckingMode>,
|
||||
index_locations: IndexLocations,
|
||||
index_strategy: IndexStrategy,
|
||||
dependency_metadata: DependencyMetadata,
|
||||
keyring_provider: KeyringProviderType,
|
||||
allow_insecure_host: Vec<TrustedHost>,
|
||||
allow_empty_requirements: bool,
|
||||
@@ -287,6 +288,7 @@ pub(crate) async fn pip_sync(
|
||||
interpreter,
|
||||
&index_locations,
|
||||
&flat_index,
|
||||
&dependency_metadata,
|
||||
&state.index,
|
||||
&state.git,
|
||||
&state.capabilities,
|
||||
|
||||
@@ -300,6 +300,7 @@ pub(crate) async fn add(
|
||||
target.interpreter(),
|
||||
&settings.index_locations,
|
||||
&flat_index,
|
||||
&settings.dependency_metadata,
|
||||
&state.index,
|
||||
&state.git,
|
||||
&state.capabilities,
|
||||
|
||||
@@ -9,7 +9,8 @@ use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||
use tracing::debug;
|
||||
|
||||
use distribution_types::{
|
||||
IndexLocations, NameRequirementSpecification, UnresolvedRequirementSpecification,
|
||||
DependencyMetadata, IndexLocations, NameRequirementSpecification,
|
||||
UnresolvedRequirementSpecification,
|
||||
};
|
||||
use pep440_rs::Version;
|
||||
use pypi_types::{Requirement, SupportedEnvironments};
|
||||
@@ -241,6 +242,7 @@ async fn do_lock(
|
||||
allow_insecure_host,
|
||||
resolution,
|
||||
prerelease,
|
||||
dependency_metadata,
|
||||
config_setting,
|
||||
no_build_isolation,
|
||||
no_build_isolation_package,
|
||||
@@ -409,6 +411,7 @@ async fn do_lock(
|
||||
interpreter,
|
||||
index_locations,
|
||||
&flat_index,
|
||||
dependency_metadata,
|
||||
&state.index,
|
||||
&state.git,
|
||||
&state.capabilities,
|
||||
@@ -436,6 +439,7 @@ async fn do_lock(
|
||||
&constraints,
|
||||
&overrides,
|
||||
environments,
|
||||
dependency_metadata,
|
||||
interpreter,
|
||||
&requires_python,
|
||||
index_locations,
|
||||
@@ -552,8 +556,14 @@ async fn do_lock(
|
||||
// Notify the user of any resolution diagnostics.
|
||||
pip::operations::diagnose_resolution(resolution.diagnostics(), printer)?;
|
||||
|
||||
let manifest = ResolverManifest::new(members, requirements, constraints, overrides)
|
||||
.relative_to(workspace)?;
|
||||
let manifest = ResolverManifest::new(
|
||||
members,
|
||||
requirements,
|
||||
constraints,
|
||||
overrides,
|
||||
dependency_metadata.values().cloned(),
|
||||
)
|
||||
.relative_to(workspace)?;
|
||||
|
||||
let previous = existing_lock.map(ValidatedLock::into_lock);
|
||||
let lock = Lock::from_resolution_graph(&resolution, workspace.install_path())?
|
||||
@@ -591,6 +601,7 @@ impl ValidatedLock {
|
||||
constraints: &[Requirement],
|
||||
overrides: &[Requirement],
|
||||
environments: Option<&SupportedEnvironments>,
|
||||
dependency_metadata: &DependencyMetadata,
|
||||
interpreter: &Interpreter,
|
||||
requires_python: &RequiresPython,
|
||||
index_locations: &IndexLocations,
|
||||
@@ -710,6 +721,7 @@ impl ValidatedLock {
|
||||
requirements,
|
||||
constraints,
|
||||
overrides,
|
||||
dependency_metadata,
|
||||
indexes,
|
||||
build_options,
|
||||
interpreter.tags()?,
|
||||
@@ -773,6 +785,13 @@ impl ValidatedLock {
|
||||
);
|
||||
Ok(Self::Preferable(lock))
|
||||
}
|
||||
SatisfiesResult::MismatchedStaticMetadata(expected, actual) => {
|
||||
debug!(
|
||||
"Ignoring existing lockfile due to mismatched static metadata:\n Expected: {:?}\n Actual: {:?}",
|
||||
expected, actual
|
||||
);
|
||||
Ok(Self::Preferable(lock))
|
||||
}
|
||||
SatisfiesResult::MissingRoot(name) => {
|
||||
debug!("Ignoring existing lockfile due to missing root package: `{name}`");
|
||||
Ok(Self::Preferable(lock))
|
||||
|
||||
@@ -575,6 +575,7 @@ pub(crate) async fn resolve_names(
|
||||
allow_insecure_host,
|
||||
resolution: _,
|
||||
prerelease: _,
|
||||
dependency_metadata,
|
||||
config_setting,
|
||||
no_build_isolation,
|
||||
no_build_isolation_package,
|
||||
@@ -631,6 +632,7 @@ pub(crate) async fn resolve_names(
|
||||
interpreter,
|
||||
index_locations,
|
||||
&flat_index,
|
||||
dependency_metadata,
|
||||
&state.index,
|
||||
&state.git,
|
||||
&state.capabilities,
|
||||
@@ -684,6 +686,7 @@ pub(crate) async fn resolve_environment<'a>(
|
||||
allow_insecure_host,
|
||||
resolution,
|
||||
prerelease,
|
||||
dependency_metadata,
|
||||
config_setting,
|
||||
no_build_isolation,
|
||||
no_build_isolation_package,
|
||||
@@ -774,6 +777,7 @@ pub(crate) async fn resolve_environment<'a>(
|
||||
interpreter,
|
||||
index_locations,
|
||||
&flat_index,
|
||||
dependency_metadata,
|
||||
&state.index,
|
||||
&state.git,
|
||||
&state.capabilities,
|
||||
@@ -837,6 +841,7 @@ pub(crate) async fn sync_environment(
|
||||
index_strategy,
|
||||
keyring_provider,
|
||||
allow_insecure_host,
|
||||
dependency_metadata,
|
||||
config_setting,
|
||||
no_build_isolation,
|
||||
no_build_isolation_package,
|
||||
@@ -903,6 +908,7 @@ pub(crate) async fn sync_environment(
|
||||
interpreter,
|
||||
index_locations,
|
||||
&flat_index,
|
||||
dependency_metadata,
|
||||
&state.index,
|
||||
&state.git,
|
||||
&state.capabilities,
|
||||
@@ -989,6 +995,7 @@ pub(crate) async fn update_environment(
|
||||
allow_insecure_host,
|
||||
resolution,
|
||||
prerelease,
|
||||
dependency_metadata,
|
||||
config_setting,
|
||||
no_build_isolation,
|
||||
no_build_isolation_package,
|
||||
@@ -1104,6 +1111,7 @@ pub(crate) async fn update_environment(
|
||||
interpreter,
|
||||
index_locations,
|
||||
&flat_index,
|
||||
dependency_metadata,
|
||||
&state.index,
|
||||
&state.git,
|
||||
&state.capabilities,
|
||||
|
||||
@@ -180,6 +180,7 @@ pub(super) async fn do_sync(
|
||||
index_strategy,
|
||||
keyring_provider,
|
||||
allow_insecure_host,
|
||||
dependency_metadata,
|
||||
config_setting,
|
||||
no_build_isolation,
|
||||
no_build_isolation_package,
|
||||
@@ -304,6 +305,7 @@ pub(super) async fn do_sync(
|
||||
venv.interpreter(),
|
||||
index_locations,
|
||||
&flat_index,
|
||||
dependency_metadata,
|
||||
&state.index,
|
||||
&state.git,
|
||||
&state.capabilities,
|
||||
|
||||
@@ -9,7 +9,7 @@ use miette::{Diagnostic, IntoDiagnostic};
|
||||
use owo_colors::OwoColorize;
|
||||
use thiserror::Error;
|
||||
|
||||
use distribution_types::IndexLocations;
|
||||
use distribution_types::{DependencyMetadata, IndexLocations};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pypi_types::Requirement;
|
||||
use uv_auth::store_credentials_from_url;
|
||||
@@ -48,6 +48,7 @@ pub(crate) async fn venv(
|
||||
link_mode: LinkMode,
|
||||
index_locations: &IndexLocations,
|
||||
index_strategy: IndexStrategy,
|
||||
dependency_metadata: DependencyMetadata,
|
||||
keyring_provider: KeyringProviderType,
|
||||
allow_insecure_host: Vec<TrustedHost>,
|
||||
prompt: uv_virtualenv::Prompt,
|
||||
@@ -70,6 +71,7 @@ pub(crate) async fn venv(
|
||||
link_mode,
|
||||
index_locations,
|
||||
index_strategy,
|
||||
dependency_metadata,
|
||||
keyring_provider,
|
||||
allow_insecure_host,
|
||||
prompt,
|
||||
@@ -125,6 +127,7 @@ async fn venv_impl(
|
||||
link_mode: LinkMode,
|
||||
index_locations: &IndexLocations,
|
||||
index_strategy: IndexStrategy,
|
||||
dependency_metadata: DependencyMetadata,
|
||||
keyring_provider: KeyringProviderType,
|
||||
allow_insecure_host: Vec<TrustedHost>,
|
||||
prompt: uv_virtualenv::Prompt,
|
||||
@@ -317,6 +320,7 @@ async fn venv_impl(
|
||||
interpreter,
|
||||
index_locations,
|
||||
&flat_index,
|
||||
&dependency_metadata,
|
||||
&state.index,
|
||||
&state.git,
|
||||
&state.capabilities,
|
||||
|
||||
@@ -323,6 +323,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
||||
args.settings.emit_index_annotation,
|
||||
args.settings.index_locations,
|
||||
args.settings.index_strategy,
|
||||
args.settings.dependency_metadata,
|
||||
args.settings.keyring_provider,
|
||||
args.settings.allow_insecure_host,
|
||||
args.settings.config_setting,
|
||||
@@ -390,6 +391,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
||||
args.settings.hash_checking,
|
||||
args.settings.index_locations,
|
||||
args.settings.index_strategy,
|
||||
args.settings.dependency_metadata,
|
||||
args.settings.keyring_provider,
|
||||
args.settings.allow_insecure_host,
|
||||
args.settings.allow_empty_requirements,
|
||||
@@ -453,7 +455,6 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_overrides_txt)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let build_constraints = args
|
||||
.build_constraint
|
||||
.into_iter()
|
||||
@@ -474,6 +475,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
||||
args.settings.upgrade,
|
||||
args.settings.index_locations,
|
||||
args.settings.index_strategy,
|
||||
args.settings.dependency_metadata,
|
||||
args.settings.keyring_provider,
|
||||
args.settings.allow_insecure_host,
|
||||
args.settings.reinstall,
|
||||
@@ -733,6 +735,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
||||
args.settings.link_mode,
|
||||
&args.settings.index_locations,
|
||||
args.settings.index_strategy,
|
||||
args.settings.dependency_metadata,
|
||||
args.settings.keyring_provider,
|
||||
args.settings.allow_insecure_host,
|
||||
uv_virtualenv::Prompt::from_args(prompt),
|
||||
@@ -884,7 +887,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
commands::tool_install(
|
||||
Box::pin(commands::tool_install(
|
||||
args.package,
|
||||
args.editable,
|
||||
args.from,
|
||||
@@ -900,7 +903,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
||||
globals.native_tls,
|
||||
cache,
|
||||
printer,
|
||||
)
|
||||
))
|
||||
.await
|
||||
}
|
||||
Commands::Tool(ToolNamespace {
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::str::FromStr;
|
||||
|
||||
use distribution_types::IndexLocations;
|
||||
use distribution_types::{DependencyMetadata, IndexLocations};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pep508_rs::{ExtraName, RequirementOrigin};
|
||||
use pypi_types::{Requirement, SupportedEnvironments};
|
||||
@@ -1784,6 +1784,7 @@ pub(crate) struct InstallerSettingsRef<'a> {
|
||||
pub(crate) index_strategy: IndexStrategy,
|
||||
pub(crate) keyring_provider: KeyringProviderType,
|
||||
pub(crate) allow_insecure_host: &'a [TrustedHost],
|
||||
pub(crate) dependency_metadata: &'a DependencyMetadata,
|
||||
pub(crate) config_setting: &'a ConfigSettings,
|
||||
pub(crate) no_build_isolation: bool,
|
||||
pub(crate) no_build_isolation_package: &'a [PackageName],
|
||||
@@ -1808,6 +1809,7 @@ pub(crate) struct ResolverSettings {
|
||||
pub(crate) allow_insecure_host: Vec<TrustedHost>,
|
||||
pub(crate) resolution: ResolutionMode,
|
||||
pub(crate) prerelease: PrereleaseMode,
|
||||
pub(crate) dependency_metadata: DependencyMetadata,
|
||||
pub(crate) config_setting: ConfigSettings,
|
||||
pub(crate) no_build_isolation: bool,
|
||||
pub(crate) no_build_isolation_package: Vec<PackageName>,
|
||||
@@ -1826,6 +1828,7 @@ pub(crate) struct ResolverSettingsRef<'a> {
|
||||
pub(crate) allow_insecure_host: &'a [TrustedHost],
|
||||
pub(crate) resolution: ResolutionMode,
|
||||
pub(crate) prerelease: PrereleaseMode,
|
||||
pub(crate) dependency_metadata: &'a DependencyMetadata,
|
||||
pub(crate) config_setting: &'a ConfigSettings,
|
||||
pub(crate) no_build_isolation: bool,
|
||||
pub(crate) no_build_isolation_package: &'a [PackageName],
|
||||
@@ -1857,6 +1860,7 @@ impl ResolverSettings {
|
||||
allow_insecure_host: &self.allow_insecure_host,
|
||||
resolution: self.resolution,
|
||||
prerelease: self.prerelease,
|
||||
dependency_metadata: &self.dependency_metadata,
|
||||
config_setting: &self.config_setting,
|
||||
no_build_isolation: self.no_build_isolation,
|
||||
no_build_isolation_package: &self.no_build_isolation_package,
|
||||
@@ -1880,6 +1884,9 @@ impl From<ResolverOptions> for ResolverSettings {
|
||||
),
|
||||
resolution: value.resolution.unwrap_or_default(),
|
||||
prerelease: value.prerelease.unwrap_or_default(),
|
||||
dependency_metadata: DependencyMetadata::from_entries(
|
||||
value.dependency_metadata.into_iter().flatten(),
|
||||
),
|
||||
index_strategy: value.index_strategy.unwrap_or_default(),
|
||||
keyring_provider: value.keyring_provider.unwrap_or_default(),
|
||||
allow_insecure_host: value.allow_insecure_host.unwrap_or_default(),
|
||||
@@ -1914,6 +1921,7 @@ pub(crate) struct ResolverInstallerSettingsRef<'a> {
|
||||
pub(crate) allow_insecure_host: &'a [TrustedHost],
|
||||
pub(crate) resolution: ResolutionMode,
|
||||
pub(crate) prerelease: PrereleaseMode,
|
||||
pub(crate) dependency_metadata: &'a DependencyMetadata,
|
||||
pub(crate) config_setting: &'a ConfigSettings,
|
||||
pub(crate) no_build_isolation: bool,
|
||||
pub(crate) no_build_isolation_package: &'a [PackageName],
|
||||
@@ -1932,8 +1940,7 @@ pub(crate) struct ResolverInstallerSettingsRef<'a> {
|
||||
/// Represents the shared settings that are used across all uv commands outside the `pip` API.
|
||||
/// Analogous to the settings contained in the `[tool.uv]` table, combined with [`ResolverInstallerArgs`].
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct ResolverInstallerSettings {
|
||||
pub(crate) index_locations: IndexLocations,
|
||||
pub(crate) index_strategy: IndexStrategy,
|
||||
@@ -1941,6 +1948,7 @@ pub(crate) struct ResolverInstallerSettings {
|
||||
pub(crate) allow_insecure_host: Vec<TrustedHost>,
|
||||
pub(crate) resolution: ResolutionMode,
|
||||
pub(crate) prerelease: PrereleaseMode,
|
||||
pub(crate) dependency_metadata: DependencyMetadata,
|
||||
pub(crate) config_setting: ConfigSettings,
|
||||
pub(crate) no_build_isolation: bool,
|
||||
pub(crate) no_build_isolation_package: Vec<PackageName>,
|
||||
@@ -1977,6 +1985,7 @@ impl ResolverInstallerSettings {
|
||||
allow_insecure_host: &self.allow_insecure_host,
|
||||
resolution: self.resolution,
|
||||
prerelease: self.prerelease,
|
||||
dependency_metadata: &self.dependency_metadata,
|
||||
config_setting: &self.config_setting,
|
||||
no_build_isolation: self.no_build_isolation,
|
||||
no_build_isolation_package: &self.no_build_isolation_package,
|
||||
@@ -2002,6 +2011,9 @@ impl From<ResolverInstallerOptions> for ResolverInstallerSettings {
|
||||
),
|
||||
resolution: value.resolution.unwrap_or_default(),
|
||||
prerelease: value.prerelease.unwrap_or_default(),
|
||||
dependency_metadata: DependencyMetadata::from_entries(
|
||||
value.dependency_metadata.into_iter().flatten(),
|
||||
),
|
||||
index_strategy: value.index_strategy.unwrap_or_default(),
|
||||
keyring_provider: value.keyring_provider.unwrap_or_default(),
|
||||
allow_insecure_host: value.allow_insecure_host.unwrap_or_default(),
|
||||
@@ -2058,6 +2070,7 @@ pub(crate) struct PipSettings {
|
||||
pub(crate) dependency_mode: DependencyMode,
|
||||
pub(crate) resolution: ResolutionMode,
|
||||
pub(crate) prerelease: PrereleaseMode,
|
||||
pub(crate) dependency_metadata: DependencyMetadata,
|
||||
pub(crate) output_file: Option<PathBuf>,
|
||||
pub(crate) no_strip_extras: bool,
|
||||
pub(crate) no_strip_markers: bool,
|
||||
@@ -2117,6 +2130,7 @@ impl PipSettings {
|
||||
allow_empty_requirements,
|
||||
resolution,
|
||||
prerelease,
|
||||
dependency_metadata,
|
||||
output_file,
|
||||
no_strip_extras,
|
||||
no_strip_markers,
|
||||
@@ -2157,6 +2171,7 @@ impl PipSettings {
|
||||
allow_insecure_host: top_level_allow_insecure_host,
|
||||
resolution: top_level_resolution,
|
||||
prerelease: top_level_prerelease,
|
||||
dependency_metadata: top_level_dependency_metadata,
|
||||
config_settings: top_level_config_settings,
|
||||
no_build_isolation: top_level_no_build_isolation,
|
||||
no_build_isolation_package: top_level_no_build_isolation_package,
|
||||
@@ -2187,6 +2202,7 @@ impl PipSettings {
|
||||
let allow_insecure_host = allow_insecure_host.combine(top_level_allow_insecure_host);
|
||||
let resolution = resolution.combine(top_level_resolution);
|
||||
let prerelease = prerelease.combine(top_level_prerelease);
|
||||
let dependency_metadata = dependency_metadata.combine(top_level_dependency_metadata);
|
||||
let config_settings = config_settings.combine(top_level_config_settings);
|
||||
let no_build_isolation = no_build_isolation.combine(top_level_no_build_isolation);
|
||||
let no_build_isolation_package =
|
||||
@@ -2220,6 +2236,11 @@ impl PipSettings {
|
||||
},
|
||||
resolution: args.resolution.combine(resolution).unwrap_or_default(),
|
||||
prerelease: args.prerelease.combine(prerelease).unwrap_or_default(),
|
||||
dependency_metadata: DependencyMetadata::from_entries(
|
||||
args.dependency_metadata
|
||||
.combine(dependency_metadata)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
output_file: args.output_file.combine(output_file),
|
||||
no_strip_extras: args
|
||||
.no_strip_extras
|
||||
@@ -2364,6 +2385,7 @@ impl<'a> From<ResolverInstallerSettingsRef<'a>> for ResolverSettingsRef<'a> {
|
||||
allow_insecure_host: settings.allow_insecure_host,
|
||||
resolution: settings.resolution,
|
||||
prerelease: settings.prerelease,
|
||||
dependency_metadata: settings.dependency_metadata,
|
||||
config_setting: settings.config_setting,
|
||||
no_build_isolation: settings.no_build_isolation,
|
||||
no_build_isolation_package: settings.no_build_isolation_package,
|
||||
@@ -2383,6 +2405,7 @@ impl<'a> From<ResolverInstallerSettingsRef<'a>> for InstallerSettingsRef<'a> {
|
||||
index_strategy: settings.index_strategy,
|
||||
keyring_provider: settings.keyring_provider,
|
||||
allow_insecure_host: settings.allow_insecure_host,
|
||||
dependency_metadata: settings.dependency_metadata,
|
||||
config_setting: settings.config_setting,
|
||||
no_build_isolation: settings.no_build_isolation,
|
||||
no_build_isolation_package: settings.no_build_isolation_package,
|
||||
|
||||
@@ -12627,6 +12627,224 @@ fn lock_python_upper_bound() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_dependency_metadata() -> 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 = ["anyio==3.7.0"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "anyio"
|
||||
version = "3.7.0"
|
||||
requires-dist = ["iniconfig"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
"###);
|
||||
|
||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
|
||||
[manifest]
|
||||
|
||||
[[manifest.dependency-metadata]]
|
||||
name = "anyio"
|
||||
version = "3.7.0"
|
||||
requires-dist = ["iniconfig"]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "3.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "iniconfig" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/68/fe/7ce1926952c8a403b35029e194555558514b365ad77d75125f521a2bec62/anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0", size = 80873 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "anyio", specifier = "==3.7.0" }]
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Re-run with `--locked`.
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
"###);
|
||||
|
||||
// Re-run with `--offline`. We shouldn't need a network connection to validate an
|
||||
// already-correct lockfile with immutable metadata.
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--locked").arg("--offline").arg("--no-cache"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
"###);
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Prepared 3 packages in [TIME]
|
||||
Installed 3 packages in [TIME]
|
||||
+ anyio==3.7.0
|
||||
+ iniconfig==2.0.0
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
"###);
|
||||
|
||||
// Update the static metadata.
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["anyio==3.7.0"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "anyio"
|
||||
version = "3.7.0"
|
||||
requires-dist = ["typing-extensions"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// The lockfile should update.
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
Removed iniconfig v2.0.0
|
||||
Added typing-extensions v4.10.0
|
||||
"###);
|
||||
|
||||
// Remove the static metadata.
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["anyio==3.7.0"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// The lockfile should update.
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 4 packages in [TIME]
|
||||
Added idna v3.6
|
||||
Added sniffio v1.3.1
|
||||
Removed typing-extensions v4.10.0
|
||||
"###);
|
||||
|
||||
// Use a blanket match.
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["anyio==3.7.0"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "anyio"
|
||||
requires-dist = ["iniconfig"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// The lockfile should update.
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
Removed idna v3.6
|
||||
Added iniconfig v2.0.0
|
||||
Removed sniffio v1.3.1
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_strip_fragment() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
@@ -140,6 +140,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -279,6 +282,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: Highest,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -419,6 +425,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: Highest,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -591,6 +600,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -709,6 +721,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: Highest,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -859,6 +874,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -1046,6 +1064,9 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: Highest,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -1232,6 +1253,9 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: Highest,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -1396,6 +1420,9 @@ fn resolve_find_links() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: Highest,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -1536,6 +1563,9 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -1714,6 +1744,9 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: Highest,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -1875,6 +1908,9 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -2015,6 +2051,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -2138,6 +2177,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -2261,6 +2303,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: Highest,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -2386,6 +2431,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -2509,6 +2557,7 @@ fn resolve_tool() -> anyhow::Result<()> {
|
||||
LowestDirect,
|
||||
),
|
||||
prerelease: None,
|
||||
dependency_metadata: None,
|
||||
config_settings: None,
|
||||
no_build_isolation: None,
|
||||
no_build_isolation_package: None,
|
||||
@@ -2543,6 +2592,9 @@ fn resolve_tool() -> anyhow::Result<()> {
|
||||
allow_insecure_host: [],
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
config_setting: ConfigSettings(
|
||||
{},
|
||||
),
|
||||
@@ -2683,6 +2735,9 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -2857,6 +2912,9 @@ fn resolve_both() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -3022,6 +3080,9 @@ fn resolve_config_file() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -3240,6 +3301,9 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: LowestDirect,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -3366,6 +3430,9 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: Highest,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
@@ -3511,6 +3578,9 @@ fn allow_insecure_host() -> anyhow::Result<()> {
|
||||
dependency_mode: Transitive,
|
||||
resolution: Highest,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
dependency_metadata: DependencyMetadata(
|
||||
{},
|
||||
),
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
|
||||
@@ -715,8 +715,8 @@ $ uv sync --extra build
|
||||
$ uv sync --extra build --extra compile
|
||||
```
|
||||
|
||||
Some packages, like `cchardet`, only require build dependencies for the _installation_ phase of
|
||||
`uv sync`. Others, like `flash-attn`, require their build dependencies to be present even just to
|
||||
Some packages, like `cchardet` above, only require build dependencies for the _installation_ phase
|
||||
of `uv sync`. Others, like `flash-attn`, require their build dependencies to be present even just to
|
||||
resolve the project's lockfile during the _resolution_ phase.
|
||||
|
||||
In such cases, the build dependencies must be installed prior to running any `uv lock` or `uv sync`
|
||||
@@ -735,10 +735,64 @@ dependencies = ["flash-attn"]
|
||||
no-build-isolation-package = ["flash-attn"]
|
||||
```
|
||||
|
||||
You could run the following sequence of commands:
|
||||
You could run the following sequence of commands to sync `flash-attn`:
|
||||
|
||||
```console
|
||||
$ uv venv
|
||||
$ uv pip install torch
|
||||
$ uv sync
|
||||
```
|
||||
|
||||
Alternatively, you can provide the `flash-attn` metadata upfront via the
|
||||
[`dependency-metadata`](../reference/settings.md#dependency-metadata) setting, thereby forgoing the
|
||||
need to build the package during the dependency resolution phase. For example, to provide the
|
||||
`flash-attn` metadata upfront, include the following in your `pyproject.toml`:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "flash-attn"
|
||||
version = "2.6.3"
|
||||
requires-dist = ["torch", "einops"]
|
||||
```
|
||||
|
||||
!!! tip
|
||||
|
||||
To determine the package metadata for a package like `flash-attn`, navigate to the appropriate Git repository,
|
||||
or look it up on [PyPI](https://pypi.org/project/flash-attn) and download the package's source distribution.
|
||||
The package requirements can typically be found in the `setup.py` or `setup.cfg` file.
|
||||
|
||||
(If the package includes a built distribution, you can unzip it to find the `METADATA` file; however, the presence
|
||||
of a built distribution would negate the need to provide the metadata upfront, since it would already be available
|
||||
to uv.)
|
||||
|
||||
Once included, you can again use the two-step `uv sync` process to install the build dependencies.
|
||||
Given the following `pyproject.toml`:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
description = "..."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[project.optional-dependencies]
|
||||
build = ["torch", "setuptools", "packaging"]
|
||||
compile = ["flash-attn"]
|
||||
|
||||
[tool.uv]
|
||||
no-build-isolation-package = ["flash-attn"]
|
||||
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "flash-attn"
|
||||
version = "2.6.3"
|
||||
requires-dist = ["torch", "einops"]
|
||||
```
|
||||
|
||||
You could run the following sequence of commands to sync `flash-attn`:
|
||||
|
||||
```console
|
||||
$ uv sync --extra build
|
||||
$ uv sync --extra build --extra compile
|
||||
```
|
||||
|
||||
+78
-17
@@ -227,28 +227,35 @@ For more details, see
|
||||
|
||||
## Dependency constraints
|
||||
|
||||
uv supports constraints files (`--constraint constraints.txt`), like pip, which narrow the set of
|
||||
acceptable versions for the given packages. Constraint files are like a regular requirements files,
|
||||
but they do not add packages to the requirements — they only take affect if the package is requested
|
||||
in a direct or transitive dependency. Constraints are often useful for reducing the range of
|
||||
available versions for a transitive dependency without adding a direct requirement on the package.
|
||||
Like pip, uv supports constraint files (`--constraint constraints.txt`) which narrow the set of
|
||||
acceptable versions for the given packages. Constraint files are similar to requirements files, but
|
||||
being listed as a constraint alone will not cause a package to be included to the resolution.
|
||||
Instead, constraints only take effect if a requested package is already pulled in as a direct or
|
||||
transitive dependency. Constraints are useful for reducing the range of available versions for a
|
||||
transitive dependency. They can also be used to keep a resolution in sync with some other set of
|
||||
resolved versions, regardless of which packages are overlapping between the two.
|
||||
|
||||
## Dependency overrides
|
||||
|
||||
Overrides allow bypassing failing or undesirable resolutions by overriding the declared dependencies
|
||||
of a package. Overrides are a useful last resort for cases in which the you know that a dependency
|
||||
is compatible with a newer version of a package than it declares, but the it has not yet been
|
||||
updated to declare that compatibility.
|
||||
Dependency overrides allow bypassing failing or undesirable resolutions by overriding a package's
|
||||
declared dependencies. Overrides are a useful last resort for cases in which you _know_ that a
|
||||
dependency is compatible with a certain version of a package, despite the metadata indicating
|
||||
otherwise.
|
||||
|
||||
For example, if a transitive dependency declares the requirement `pydantic>=1.0,<2.0`, but _works_
|
||||
with `pydantic>=2.0`, the user can override the declared dependency with `pydantic>=1.0,<3` to allow
|
||||
the resolver to installer a newer version of `pydantic`.
|
||||
For example, if a transitive dependency declares the requirement `pydantic>=1.0,<2.0`, but _does_
|
||||
work with `pydantic>=2.0`, the user can override the declared dependency by including
|
||||
`pydantic>=1.0,<3` in the overrides, thereby allowing the resolver to choose a newer version of
|
||||
`pydantic`.
|
||||
|
||||
While constraints and dependencies are purely additive, and thus cannot expand the set of acceptable
|
||||
versions for a package, overrides can expand the set of acceptable versions for a package, providing
|
||||
an escape hatch for erroneous upper version bounds. As with constraints, overrides do not add a
|
||||
dependency on the package and only take affect if the package is requested in a direct or transitive
|
||||
dependency.
|
||||
Concretely, if `pydantic>=1.0,<3` is included as an override, uv will ignore all declared
|
||||
requirements on `pydantic`, replacing them with the override. In the above example, the
|
||||
`pydantic>=1.0,<2.0` requirement would be ignored completely, and would instead be replaced with
|
||||
`pydantic>=1.0,<3`.
|
||||
|
||||
While constraints can only _reduce_ the set of acceptable versions for a package, overrides can
|
||||
_expand_ the set of acceptable versions, providing an escape hatch for erroneous upper version
|
||||
bounds. As with constraints, overrides do not add a dependency on the package and only take effect
|
||||
if the package is requested in a direct or transitive dependency.
|
||||
|
||||
In a `pyproject.toml`, use `tool.uv.override-dependencies` to define a list of overrides. In the
|
||||
pip-compatible interface, the `--override` option can be used to pass files with the same format as
|
||||
@@ -258,6 +265,60 @@ If multiple overrides are provided for the same package, they must be differenti
|
||||
[markers](#platform-markers). If a package has a dependency with a marker, it is replaced
|
||||
unconditionally when using overrides — it does not matter if the marker evaluates to true or false.
|
||||
|
||||
## Dependency metadata
|
||||
|
||||
During resolution, uv needs to resolve the metadata for each package it encounters, in order to
|
||||
determine its dependencies. This metadata is often available as a static file in the package index;
|
||||
however, for packages that only provide source distributions, the metadata may not be available
|
||||
upfront.
|
||||
|
||||
In such cases, uv has to build the package to determine its metadata (e.g., by invoking `setup.py`).
|
||||
This can introduce a performance penalty during resolution. Further, it imposes the requirement that
|
||||
the package can be built on all platforms, which may not be true.
|
||||
|
||||
For example, you may have a package that should only be built and installed on Linux, but doesn't
|
||||
build successfully on macOS or Windows. While uv can construct a perfectly valid lockfile for this
|
||||
scenario, doing so would require building the package, which would fail on non-Linux platforms.
|
||||
|
||||
The `tool.uv.dependency-metadata` table can be used to provide static metadata for such dependencies
|
||||
upfront, thereby allowing uv to skip the build step and use the provided metadata instead.
|
||||
|
||||
For example, to provide metadata for `chumpy` upfront, include its `dependency-metadata` in the
|
||||
`pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "chumpy"
|
||||
version = "0.70"
|
||||
requires-dist = ["numpy>=1.8.1", "scipy>=0.13.0", "six>=1.11.0"]
|
||||
```
|
||||
|
||||
These declarations are intended for cases in which a package does _not_ declare static metadata
|
||||
upfront, though they are also useful for packages that require disabling build isolation. In such
|
||||
cases, it may be easier to declare the package metadata upfront, rather than creating a custom build
|
||||
environment prior to resolving the package.
|
||||
|
||||
For example, you can declare the metadata for `flash-attn`, allowing uv to resolve without building
|
||||
the package from source (which itself requires installing `torch`):
|
||||
|
||||
```toml
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "flash-attn"
|
||||
version = "2.6.3"
|
||||
requires-dist = ["torch", "einops"]
|
||||
```
|
||||
|
||||
Like dependency overrides, `tool.uv.dependency-metadata` can also be used for cases in which a
|
||||
package's metadata is incorrect or incomplete, or when a package is not available in the package
|
||||
index. While dependency overrides allow overriding the allowed versions of a package globally,
|
||||
metadata overrides allow overriding the declared metadata of a _specific package_.
|
||||
|
||||
Entries in the `tool.uv.dependency-metadata` table follow the
|
||||
[Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/) specification,
|
||||
though only `name`, `version`, `requires-dist`, `requires-python`, and `provides-extra` are read by
|
||||
uv. The `version` field is also considered optional. If omitted, the metadata will be used for all
|
||||
versions of the specified package.
|
||||
|
||||
## Lower bounds
|
||||
|
||||
By default, `uv add` adds lower bounds to dependencies and, when using uv to manage projects, uv
|
||||
|
||||
@@ -451,6 +451,47 @@ specified as `KEY=VALUE` pairs.
|
||||
|
||||
---
|
||||
|
||||
### [`dependency-metadata`](#dependency-metadata) {: #dependency-metadata }
|
||||
|
||||
Pre-defined static metadata for dependencies of the project (direct or transitive). When
|
||||
provided, enables the resolver to use the specified metadata instead of querying the
|
||||
registry or building the relevant package from source.
|
||||
|
||||
Metadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)
|
||||
standard, though only the following fields are respected:
|
||||
|
||||
- `name`: The name of the package.
|
||||
- (Optional) `version`: The version of the package. If omitted, the metadata will be applied
|
||||
to all versions of the package.
|
||||
- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).
|
||||
- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).
|
||||
- (Optional) `provides-extras`: The extras provided by the package.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `list[dict]`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.uv]
|
||||
dependency-metadata = [
|
||||
{ name = "flask", version = "1.0.0", requires-dist = ["werkzeug"], requires-python = ">=3.6" },
|
||||
]
|
||||
```
|
||||
=== "uv.toml"
|
||||
|
||||
```toml
|
||||
|
||||
dependency-metadata = [
|
||||
{ name = "flask", version = "1.0.0", requires-dist = ["werkzeug"], requires-python = ">=3.6" },
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### [`exclude-newer`](#exclude-newer) {: #exclude-newer }
|
||||
|
||||
Limit candidate packages to those that were uploaded prior to the given date.
|
||||
@@ -1474,6 +1515,48 @@ Used to reflect custom build scripts and commands that wrap `uv pip compile`.
|
||||
|
||||
---
|
||||
|
||||
#### [`dependency-metadata`](#pip_dependency-metadata) {: #pip_dependency-metadata }
|
||||
<span id="dependency-metadata"></span>
|
||||
|
||||
Pre-defined static metadata for dependencies of the project (direct or transitive). When
|
||||
provided, enables the resolver to use the specified metadata instead of querying the
|
||||
registry or building the relevant package from source.
|
||||
|
||||
Metadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)
|
||||
standard, though only the following fields are respected:
|
||||
|
||||
- `name`: The name of the package.
|
||||
- (Optional) `version`: The version of the package. If omitted, the metadata will be applied
|
||||
to all versions of the package.
|
||||
- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).
|
||||
- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).
|
||||
- (Optional) `provides-extras`: The extras provided by the package.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `list[dict]`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.uv.pip]
|
||||
dependency-metadata = [
|
||||
{ name = "flask", version = "1.0.0", requires-dist = ["werkzeug"], requires-python = ">=3.6" },
|
||||
]
|
||||
```
|
||||
=== "uv.toml"
|
||||
|
||||
```toml
|
||||
[pip]
|
||||
dependency-metadata = [
|
||||
{ name = "flask", version = "1.0.0", requires-dist = ["werkzeug"], requires-python = ">=3.6" },
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`emit-build-options`](#pip_emit-build-options) {: #pip_emit-build-options }
|
||||
<span id="emit-build-options"></span>
|
||||
|
||||
|
||||
Generated
+59
-1
@@ -87,6 +87,16 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"dependency-metadata": {
|
||||
"description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When provided, enables the resolver to use the specified metadata instead of querying the registry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/) standard, though only the following fields are respected:\n\n- `name`: The name of the package. - (Optional) `version`: The version of the package. If omitted, the metadata will be applied to all versions of the package. - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`). - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`). - (Optional) `provides-extras`: The extras provided by the package.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/StaticMetadata"
|
||||
}
|
||||
},
|
||||
"dev-dependencies": {
|
||||
"description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.",
|
||||
"type": [
|
||||
@@ -677,6 +687,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dependency-metadata": {
|
||||
"description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When provided, enables the resolver to use the specified metadata instead of querying the registry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/) standard, though only the following fields are respected:\n\n- `name`: The name of the package. - (Optional) `version`: The version of the package. If omitted, the metadata will be applied to all versions of the package. - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`). - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`). - (Optional) `provides-extras`: The extras provided by the package.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/StaticMetadata"
|
||||
}
|
||||
},
|
||||
"emit-build-options": {
|
||||
"description": "Include `--no-binary` and `--only-binary` entries in the output file generated by `uv pip compile`.",
|
||||
"type": [
|
||||
@@ -1153,7 +1173,7 @@
|
||||
"pattern": "^3\\.\\d+(\\.\\d+)?$"
|
||||
},
|
||||
"Requirement": {
|
||||
"description": "A PEP 508 dependency specifier",
|
||||
"description": "A PEP 508 dependency specifier, e.g., `ruff >= 0.6.0`",
|
||||
"type": "string"
|
||||
},
|
||||
"ResolutionMode": {
|
||||
@@ -1347,6 +1367,44 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"StaticMetadata": {
|
||||
"description": "A subset of the Python Package Metadata 2.3 standard as specified in <https://packaging.python.org/specifications/core-metadata/>.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"version"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"$ref": "#/definitions/PackageName"
|
||||
},
|
||||
"provides-extras": {
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ExtraName"
|
||||
}
|
||||
},
|
||||
"requires-dist": {
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Requirement"
|
||||
}
|
||||
},
|
||||
"requires-python": {
|
||||
"description": "PEP 508-style Python requirement, e.g., `>=3.10`",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"version": {
|
||||
"description": "PEP 440-style package version, e.g., `1.2.3`",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"String": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user