diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 1aafe96ea..7c3fe2001 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1075,13 +1075,22 @@ pub struct PipCompileArgs { #[arg(long, group = "sources")] pub group: Vec, - /// Write the compiled requirements to the given `requirements.txt` file. + /// Write the compiled requirements to the given `requirements.txt` or `pylock.toml` file. /// /// If the file already exists, the existing versions will be preferred when resolving /// dependencies, unless `--upgrade` is also specified. #[arg(long, short)] pub output_file: Option, + /// The format in which the resolution should be output. + /// + /// Supports both `requirements.txt` and `pylock.toml` (PEP 751) output formats. + /// + /// uv will infer the output format from the file extension of the output file, if + /// provided. Otherwise, defaults to `requirements.txt`. + #[arg(long, value_enum)] + pub format: Option, + /// Include extras in the output file. /// /// By default, uv strips extras, as any packages pulled in by the extras are already included diff --git a/crates/uv-requirements/src/upgrade.rs b/crates/uv-requirements/src/upgrade.rs index 7e31facc3..01273ef59 100644 --- a/crates/uv-requirements/src/upgrade.rs +++ b/crates/uv-requirements/src/upgrade.rs @@ -7,7 +7,7 @@ use uv_configuration::Upgrade; use uv_fs::CWD; use uv_git::ResolvedRepositoryReference; use uv_requirements_txt::RequirementsTxt; -use uv_resolver::{Lock, LockError, Preference, PreferenceError}; +use uv_resolver::{Lock, LockError, Preference, PreferenceError, PylockToml, PylockTomlError}; #[derive(Debug, Default)] pub struct LockedRequirements { @@ -17,9 +17,19 @@ pub struct LockedRequirements { pub git: Vec, } +impl LockedRequirements { + /// Create a [`LockedRequirements`] from a list of preferences. + pub fn from_preferences(preferences: Vec) -> Self { + Self { + preferences, + ..LockedRequirements::default() + } + } +} + /// Load the preferred requirements from an existing `requirements.txt`, applying the upgrade strategy. pub async fn read_requirements_txt( - output_file: Option<&Path>, + output_file: &Path, upgrade: &Upgrade, ) -> Result> { // As an optimization, skip reading the lockfile is we're upgrading all packages anyway. @@ -27,11 +37,6 @@ pub async fn read_requirements_txt( return Ok(Vec::new()); } - // If the lockfile doesn't exist, don't respect any pinned versions. - let Some(output_file) = output_file.filter(|path| path.exists()) else { - return Ok(Vec::new()); - }; - // Parse the requirements from the lockfile. let requirements_txt = RequirementsTxt::parse( output_file, @@ -95,3 +100,40 @@ pub fn read_lock_requirements( Ok(LockedRequirements { preferences, git }) } + +/// Load the preferred requirements from an existing `pylock.toml` file, applying the upgrade strategy. +pub async fn read_pylock_toml_requirements( + output_file: &Path, + upgrade: &Upgrade, +) -> Result { + // As an optimization, skip iterating over the lockfile is we're upgrading all packages anyway. + if upgrade.is_all() { + return Ok(LockedRequirements::default()); + } + + // Read the `pylock.toml` from disk, and deserialize it from TOML. + let content = fs_err::tokio::read_to_string(&output_file).await?; + let lock = toml::from_str::(&content)?; + + let mut preferences = Vec::new(); + let mut git = Vec::new(); + + for package in &lock.packages { + // Skip the distribution if it's not included in the upgrade strategy. + if upgrade.contains(&package.name) { + continue; + } + + // Map each entry in the lockfile to a preference. + if let Some(preference) = Preference::from_pylock_toml(package)? { + preferences.push(preference); + } + + // Map each entry in the lockfile to a Git SHA. + if let Some(git_ref) = package.as_git_ref() { + git.push(git_ref); + } + } + + Ok(LockedRequirements { preferences, git }) +} diff --git a/crates/uv-resolver/src/lib.rs b/crates/uv-resolver/src/lib.rs index 8c6fc97e8..ac1519545 100644 --- a/crates/uv-resolver/src/lib.rs +++ b/crates/uv-resolver/src/lib.rs @@ -5,7 +5,7 @@ pub use exclusions::Exclusions; pub use flat_index::{FlatDistributions, FlatIndex}; pub use fork_strategy::ForkStrategy; pub use lock::{ - Installable, Lock, LockError, LockVersion, Package, PackageMap, PylockToml, + Installable, Lock, LockError, LockVersion, Package, PackageMap, PylockToml, PylockTomlError, RequirementsTxtExport, ResolverManifest, SatisfiesResult, TreeDisplay, VERSION, }; pub use manifest::Manifest; diff --git a/crates/uv-resolver/src/lock/export/mod.rs b/crates/uv-resolver/src/lock/export/mod.rs index 197fe678f..e9af87736 100644 --- a/crates/uv-resolver/src/lock/export/mod.rs +++ b/crates/uv-resolver/src/lock/export/mod.rs @@ -14,7 +14,8 @@ use uv_pep508::MarkerTree; use uv_pypi_types::ConflictItem; use crate::graph_ops::{marker_reachability, Reachable}; -pub use crate::lock::export::pylock_toml::PylockToml; +pub(crate) use crate::lock::export::pylock_toml::PylockTomlPackage; +pub use crate::lock::export::pylock_toml::{PylockToml, PylockTomlError}; pub use crate::lock::export::requirements_txt::RequirementsTxtExport; use crate::universal_marker::resolve_conflicts; use crate::{Installable, Package}; diff --git a/crates/uv-resolver/src/lock/export/pylock_toml.rs b/crates/uv-resolver/src/lock/export/pylock_toml.rs index 70d87c9d1..998f08401 100644 --- a/crates/uv-resolver/src/lock/export/pylock_toml.rs +++ b/crates/uv-resolver/src/lock/export/pylock_toml.rs @@ -11,6 +11,7 @@ use serde::Deserialize; use toml_edit::{value, Array, ArrayOfTables, Item, Table}; use url::Url; +use uv_cache_key::RepositoryUrl; use uv_configuration::{DependencyGroupsWithDefaults, ExtrasSpecification, InstallOptions}; use uv_distribution_filename::{ BuildTag, DistExtension, ExtensionError, SourceDistExtension, SourceDistFilename, @@ -18,11 +19,12 @@ use uv_distribution_filename::{ }; use uv_distribution_types::{ BuiltDist, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist, Dist, Edge, - FileLocation, GitSourceDist, IndexUrl, Node, PathBuiltDist, PathSourceDist, RegistryBuiltDist, - RegistryBuiltWheel, RegistrySourceDist, RemoteSource, Resolution, ResolvedDist, SourceDist, - ToUrlError, UrlString, + FileLocation, GitSourceDist, IndexUrl, Name, Node, PathBuiltDist, PathSourceDist, + RegistryBuiltDist, RegistryBuiltWheel, RegistrySourceDist, RemoteSource, Resolution, + ResolvedDist, SourceDist, ToUrlError, UrlString, }; use uv_fs::{relative_to, PortablePathBuf}; +use uv_git::{RepositoryReference, ResolvedRepositoryReference}; use uv_git_types::{GitOid, GitReference, GitUrl, GitUrlParseError}; use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep440::Version; @@ -33,7 +35,8 @@ use uv_small_str::SmallString; use crate::lock::export::ExportableRequirements; use crate::lock::{each_element_on_its_line_array, Source}; -use crate::{Installable, LockError, RequiresPython}; +use crate::resolution::ResolutionGraphNode; +use crate::{Installable, LockError, RequiresPython, ResolverOutput}; #[derive(Debug, thiserror::Error)] pub enum PylockTomlError { @@ -89,6 +92,10 @@ pub enum PylockTomlError { Extension(#[from] ExtensionError), #[error(transparent)] Jiff(#[from] jiff::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Deserialize(#[from] toml::de::Error), } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -105,17 +112,19 @@ pub struct PylockToml { #[serde(skip_serializing_if = "Vec::is_empty", default)] default_groups: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] - packages: Vec, + pub packages: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] attestation_identities: Vec, } #[derive(Debug, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "kebab-case")] -struct PylockTomlPackage { - name: PackageName, +pub struct PylockTomlPackage { + pub name: PackageName, #[serde(skip_serializing_if = "Option::is_none")] - version: Option, + pub version: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub index: Option, #[serde( skip_serializing_if = "uv_pep508::marker::ser::is_empty", serialize_with = "uv_pep508::marker::ser::serialize", @@ -127,8 +136,6 @@ struct PylockTomlPackage { #[serde(skip_serializing_if = "Vec::is_empty", default)] dependencies: Vec, #[serde(skip_serializing_if = "Option::is_none")] - index: Option, - #[serde(skip_serializing_if = "Option::is_none")] vcs: Option, #[serde(skip_serializing_if = "Option::is_none")] directory: Option, @@ -239,6 +246,267 @@ struct PylockTomlAttestationIdentity { } impl<'lock> PylockToml { + /// Construct a [`PylockToml`] from a [`ResolverOutput`]. + pub fn from_resolution( + resolution: &ResolverOutput, + omit: &[PackageName], + install_path: &Path, + ) -> Result { + // The lock version is always `1.0` at time of writing. + let lock_version = Version::new([1, 0]); + + // The created by field is always `uv` at time of writing. + let created_by = "uv".to_string(); + + // Use the `requires-python` from the target lockfile. + let requires_python = resolution.requires_python.clone(); + + // We don't support locking for multiple extras at time of writing. + let extras = vec![]; + + // We don't support locking for multiple dependency groups at time of writing. + let dependency_groups = vec![]; + + // We don't support locking for multiple dependency groups at time of writing. + let default_groups = vec![]; + + // We don't support attestation identities at time of writing. + let attestation_identities = vec![]; + + // Convert each node to a `pylock.toml`-style package. + let mut packages = Vec::with_capacity(resolution.graph.node_count()); + for node_index in resolution.graph.node_indices() { + let ResolutionGraphNode::Dist(node) = &resolution.graph[node_index] else { + continue; + }; + if !node.is_base() { + continue; + } + let ResolvedDist::Installable { dist, version } = &node.dist else { + continue; + }; + if omit.contains(dist.name()) { + continue; + } + + // Create a `pylock.toml`-style package. + let mut package = PylockTomlPackage { + name: dist.name().clone(), + version: version.clone(), + marker: node.marker.pep508(), + requires_python: None, + dependencies: vec![], + index: None, + vcs: None, + directory: None, + archive: None, + sdist: None, + wheels: None, + }; + + match &**dist { + Dist::Built(BuiltDist::DirectUrl(dist)) => { + package.archive = Some(PylockTomlArchive { + url: Some((*dist.location).clone()), + path: None, + size: dist.size(), + upload_time: None, + subdirectory: None, + hashes: Hashes::from(node.hashes.clone()), + }); + } + Dist::Built(BuiltDist::Path(dist)) => { + let path = relative_to(&dist.install_path, install_path) + .map(Box::::from) + .unwrap_or_else(|_| dist.install_path.clone()); + package.archive = Some(PylockTomlArchive { + url: None, + path: Some(PortablePathBuf::from(path)), + size: dist.size(), + upload_time: None, + subdirectory: None, + hashes: Hashes::from(node.hashes.clone()), + }); + } + Dist::Built(BuiltDist::Registry(dist)) => { + package.wheels = Some( + dist.wheels + .iter() + .map(|wheel| { + let url = + wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?; + Ok(PylockTomlWheel { + // Optional "when the last component of path/ url would be the same value". + name: if url + .filename() + .is_ok_and(|filename| filename == *wheel.file.filename) + { + None + } else { + Some(wheel.filename.clone()) + }, + upload_time: wheel + .file + .upload_time_utc_ms + .map(Timestamp::from_millisecond) + .transpose()?, + url: Some( + wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?, + ), + path: None, + size: wheel.file.size, + hashes: Hashes::from(wheel.file.hashes.clone()), + }) + }) + .collect::, PylockTomlError>>()?, + ); + + if let Some(sdist) = dist.sdist.as_ref() { + let url = sdist.file.url.to_url().map_err(PylockTomlError::ToUrl)?; + package.sdist = Some(PylockTomlSdist { + // Optional "when the last component of path/ url would be the same value". + name: if url + .filename() + .is_ok_and(|filename| filename == *sdist.file.filename) + { + None + } else { + Some(sdist.file.filename.clone()) + }, + upload_time: sdist + .file + .upload_time_utc_ms + .map(Timestamp::from_millisecond) + .transpose()?, + url: Some(url), + path: None, + size: sdist.file.size, + hashes: Hashes::from(sdist.file.hashes.clone()), + }); + } + } + Dist::Source(SourceDist::DirectUrl(dist)) => { + package.archive = Some(PylockTomlArchive { + url: Some((*dist.location).clone()), + path: None, + size: dist.size(), + upload_time: None, + subdirectory: dist.subdirectory.clone().map(PortablePathBuf::from), + hashes: Hashes::from(node.hashes.clone()), + }); + } + Dist::Source(SourceDist::Directory(dist)) => { + let path = relative_to(&dist.install_path, install_path) + .map(Box::::from) + .unwrap_or_else(|_| dist.install_path.clone()); + package.directory = Some(PylockTomlDirectory { + path: PortablePathBuf::from(path), + editable: if dist.editable { Some(true) } else { None }, + subdirectory: None, + }); + } + Dist::Source(SourceDist::Git(dist)) => { + package.vcs = Some(PylockTomlVcs { + r#type: VcsKind::Git, + url: Some(dist.git.repository().clone()), + path: None, + requested_revision: dist.git.reference().as_str().map(ToString::to_string), + commit_id: dist.git.precise().unwrap_or_else(|| { + panic!("Git distribution is missing a precise hash: {dist}") + }), + subdirectory: dist.subdirectory.clone().map(PortablePathBuf::from), + }); + } + Dist::Source(SourceDist::Path(dist)) => { + let path = relative_to(&dist.install_path, install_path) + .map(Box::::from) + .unwrap_or_else(|_| dist.install_path.clone()); + package.archive = Some(PylockTomlArchive { + url: None, + path: Some(PortablePathBuf::from(path)), + size: dist.size(), + upload_time: None, + subdirectory: None, + hashes: Hashes::from(node.hashes.clone()), + }); + } + Dist::Source(SourceDist::Registry(dist)) => { + package.wheels = Some( + dist.wheels + .iter() + .map(|wheel| { + let url = + wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?; + Ok(PylockTomlWheel { + // Optional "when the last component of path/ url would be the same value". + name: if url + .filename() + .is_ok_and(|filename| filename == *wheel.file.filename) + { + None + } else { + Some(wheel.filename.clone()) + }, + upload_time: wheel + .file + .upload_time_utc_ms + .map(Timestamp::from_millisecond) + .transpose()?, + url: Some( + wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?, + ), + path: None, + size: wheel.file.size, + hashes: Hashes::from(wheel.file.hashes.clone()), + }) + }) + .collect::, PylockTomlError>>()?, + ); + + let url = dist.file.url.to_url().map_err(PylockTomlError::ToUrl)?; + package.sdist = Some(PylockTomlSdist { + // Optional "when the last component of path/ url would be the same value". + name: if url + .filename() + .is_ok_and(|filename| filename == *dist.file.filename) + { + None + } else { + Some(dist.file.filename.clone()) + }, + upload_time: dist + .file + .upload_time_utc_ms + .map(Timestamp::from_millisecond) + .transpose()?, + url: Some(url), + path: None, + size: dist.file.size, + hashes: Hashes::from(dist.file.hashes.clone()), + }); + } + } + + // Add the package to the list of packages. + packages.push(package); + } + + // Sort the packages by name, then version. + packages.sort_by(|a, b| a.name.cmp(&b.name).then(a.version.cmp(&b.version))); + + // Return the constructed `pylock.toml`. + Ok(PylockToml { + lock_version, + created_by, + requires_python: Some(requires_python), + extras, + dependency_groups, + default_groups, + packages, + attestation_identities, + }) + } + /// Construct a [`PylockToml`] from a uv lockfile. pub fn from_lock( target: &impl Installable<'lock>, @@ -258,7 +526,7 @@ impl<'lock> PylockToml { install_options, ); - // Sort the nodes, such that unnamed URLs (editables) appear at the top. + // Sort the nodes. nodes.sort_unstable_by_key(|node| &node.package.id); // The lock version is always `1.0` at time of writing. @@ -826,6 +1094,20 @@ impl PylockTomlPackage { best.map(|(_, i)| i) } + + /// Returns the [`ResolvedRepositoryReference`] for the package, if it is a Git source. + pub fn as_git_ref(&self) -> Option { + let vcs = self.vcs.as_ref()?; + let url = vcs.url.as_ref()?; + let requested_revision = vcs.requested_revision.as_ref()?; + Some(ResolvedRepositoryReference { + reference: RepositoryReference { + url: RepositoryUrl::new(url), + reference: GitReference::from_rev(requested_revision.clone()), + }, + sha: vcs.commit_id, + }) + } } impl PylockTomlWheel { diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 1710e38ed..3b3b5f5d7 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -50,8 +50,9 @@ use uv_types::{BuildContext, HashStrategy}; use uv_workspace::WorkspaceMember; use crate::fork_strategy::ForkStrategy; -pub use crate::lock::export::PylockToml; +pub(crate) use crate::lock::export::PylockTomlPackage; pub use crate::lock::export::RequirementsTxtExport; +pub use crate::lock::export::{PylockToml, PylockTomlError}; pub use crate::lock::installable::Installable; pub use crate::lock::map::PackageMap; pub use crate::lock::tree::TreeDisplay; diff --git a/crates/uv-resolver/src/preferences.rs b/crates/uv-resolver/src/preferences.rs index adc40d6b9..7658b1730 100644 --- a/crates/uv-resolver/src/preferences.rs +++ b/crates/uv-resolver/src/preferences.rs @@ -7,10 +7,11 @@ use tracing::trace; use uv_distribution_types::IndexUrl; use uv_normalize::PackageName; use uv_pep440::{Operator, Version}; -use uv_pep508::{MarkerTree, VersionOrUrl}; +use uv_pep508::{MarkerTree, VerbatimUrl, VersionOrUrl}; use uv_pypi_types::{HashDigest, HashDigests, HashError}; use uv_requirements_txt::{RequirementEntry, RequirementsTxtRequirement}; +use crate::lock::PylockTomlPackage; use crate::universal_marker::UniversalMarker; use crate::{LockError, ResolverEnvironment}; @@ -93,6 +94,27 @@ impl Preference { })) } + /// Create a [`Preference`] from a locked distribution. + pub fn from_pylock_toml(package: &PylockTomlPackage) -> Result, LockError> { + let Some(version) = package.version.as_ref() else { + return Ok(None); + }; + Ok(Some(Self { + name: package.name.clone(), + version: version.clone(), + marker: MarkerTree::TRUE, + index: PreferenceIndex::from( + package + .index + .as_ref() + .map(|index| IndexUrl::from(VerbatimUrl::from(index.clone()))), + ), + // `pylock.toml` doesn't have fork annotations. + fork_markers: vec![], + hashes: HashDigests::empty(), + })) + } + /// Return the [`PackageName`] of the package for this [`Preference`]. pub fn name(&self) -> &PackageName { &self.name diff --git a/crates/uv-settings/src/combine.rs b/crates/uv-settings/src/combine.rs index f669d3a6a..3469cedce 100644 --- a/crates/uv-settings/src/combine.rs +++ b/crates/uv-settings/src/combine.rs @@ -4,8 +4,8 @@ use std::path::PathBuf; use url::Url; use uv_configuration::{ - ConfigSettings, IndexStrategy, KeyringProviderType, RequiredVersion, TargetTriple, - TrustedPublishing, + ConfigSettings, ExportFormat, IndexStrategy, KeyringProviderType, RequiredVersion, + TargetTriple, TrustedPublishing, }; use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex}; use uv_install_wheel::LinkMode; @@ -75,6 +75,7 @@ macro_rules! impl_combine_or { impl_combine_or!(AnnotationStyle); impl_combine_or!(ExcludeNewer); +impl_combine_or!(ExportFormat); impl_combine_or!(ForkStrategy); impl_combine_or!(Index); impl_combine_or!(IndexStrategy); diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index fa6ac8855..f98c045a7 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -14,8 +14,8 @@ use uv_auth::UrlAuthPolicies; use uv_cache::Cache; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ - BuildOptions, Concurrency, ConfigSettings, Constraints, ExtrasSpecification, IndexStrategy, - NoBinary, NoBuild, PreviewMode, Reinstall, SourceStrategy, Upgrade, + BuildOptions, Concurrency, ConfigSettings, Constraints, ExportFormat, ExtrasSpecification, + IndexStrategy, NoBinary, NoBuild, PreviewMode, Reinstall, SourceStrategy, Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::{BuildDispatch, SharedState}; @@ -23,7 +23,8 @@ use uv_distribution_types::{ DependencyMetadata, HashGeneration, Index, IndexLocations, NameRequirementSpecification, Origin, Requirement, UnresolvedRequirementSpecification, Verbatim, }; -use uv_fs::Simplified; +use uv_fs::{Simplified, CWD}; +use uv_git::ResolvedRepositoryReference; use uv_install_wheel::LinkMode; use uv_normalize::{GroupName, PackageName}; use uv_pypi_types::{Conflicts, SupportedEnvironments}; @@ -31,12 +32,13 @@ use uv_python::{ EnvironmentPreference, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, PythonVersion, VersionRequest, }; +use uv_requirements::upgrade::{read_pylock_toml_requirements, LockedRequirements}; use uv_requirements::{ upgrade::read_requirements_txt, RequirementsSource, RequirementsSpecification, }; use uv_resolver::{ AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, FlatIndex, ForkStrategy, - InMemoryIndex, OptionsBuilder, PrereleaseMode, PythonRequirement, RequiresPython, + InMemoryIndex, OptionsBuilder, PrereleaseMode, PylockToml, PythonRequirement, RequiresPython, ResolutionMode, ResolverEnvironment, }; use uv_torch::{TorchMode, TorchStrategy}; @@ -64,6 +66,7 @@ pub(crate) async fn pip_compile( extras: ExtrasSpecification, groups: BTreeMap>, output_file: Option<&Path>, + format: Option, resolution_mode: ResolutionMode, prerelease_mode: PrereleaseMode, fork_strategy: ForkStrategy, @@ -118,15 +121,18 @@ pub(crate) async fn pip_compile( "uv pip compile".green() )); } - if output_file - .and_then(Path::extension) - .is_some_and(|name| name.eq_ignore_ascii_case("toml")) - { - return Err(anyhow!( - "TOML is not a supported output format for `{}` (only `requirements.txt`-style output is supported)", - "uv pip compile".green() - )); - } + + // Determine the output format. + let format = format.unwrap_or_else(|| { + let extension = output_file.and_then(Path::extension); + if extension.is_some_and(|ext| ext.eq_ignore_ascii_case("txt")) { + ExportFormat::RequirementsTxt + } else if extension.is_some_and(|ext| ext.eq_ignore_ascii_case("toml")) { + ExportFormat::PylockToml + } else { + ExportFormat::RequirementsTxt + } + }); // Respect `UV_PYTHON` if python.is_none() && python_version.is_none() { @@ -334,8 +340,9 @@ pub(crate) async fn pip_compile( (Some(tags), ResolverEnvironment::specific(marker_env)) }; - // Generate, but don't enforce hashes for the requirements. - let hasher = if generate_hashes { + // Generate, but don't enforce hashes for the requirements. PEP 751 _requires_ a hash to be + // present, but otherwise, we omit them by default. + let hasher = if generate_hashes || matches!(format, ExportFormat::PylockToml) { HashStrategy::Generate(HashGeneration::All) } else { HashStrategy::None @@ -396,7 +403,25 @@ pub(crate) async fn pip_compile( .build(); // Read the lockfile, if present. - let preferences = read_requirements_txt(output_file, &upgrade).await?; + let LockedRequirements { preferences, git } = + if let Some(output_file) = output_file.filter(|output_file| output_file.exists()) { + match format { + ExportFormat::RequirementsTxt => LockedRequirements::from_preferences( + read_requirements_txt(output_file, &upgrade).await?, + ), + ExportFormat::PylockToml => { + read_pylock_toml_requirements(output_file, &upgrade).await? + } + } + } else { + LockedRequirements::default() + }; + + // Populate the Git resolver. + for ResolvedRepositoryReference { reference, sha } in git { + debug!("Inserting Git reference into resolver: `{reference:?}` at `{sha}`"); + state.git().insert(reference, sha); + } // Combine the `--no-binary` and `--no-build` flags from the requirements files. let build_options = build_options.combine(no_binary, no_build); @@ -524,96 +549,135 @@ pub(crate) async fn pip_compile( )?; } - if include_marker_expression { - if let Some(marker_env) = resolver_env.marker_environment() { - let relevant_markers = resolution.marker_tree(&top_level_index, marker_env)?; - if let Some(relevant_markers) = relevant_markers.contents() { - writeln!( - writer, - "{}", - "# Pinned dependencies known to be valid for:".green() - )?; - writeln!(writer, "{}", format!("# {relevant_markers}").green())?; + match format { + ExportFormat::RequirementsTxt => { + if include_marker_expression { + if let Some(marker_env) = resolver_env.marker_environment() { + let relevant_markers = resolution.marker_tree(&top_level_index, marker_env)?; + if let Some(relevant_markers) = relevant_markers.contents() { + writeln!( + writer, + "{}", + "# Pinned dependencies known to be valid for:".green() + )?; + writeln!(writer, "{}", format!("# {relevant_markers}").green())?; + } + } } - } - } - let mut wrote_preamble = false; + let mut wrote_preamble = false; - // If necessary, include the `--index-url` and `--extra-index-url` locations. - if include_index_url { - if let Some(index) = index_locations.default_index() { - writeln!(writer, "--index-url {}", index.url().verbatim())?; - wrote_preamble = true; - } - let mut seen = FxHashSet::default(); - for extra_index in index_locations.implicit_indexes() { - if seen.insert(extra_index.url()) { - writeln!(writer, "--extra-index-url {}", extra_index.url().verbatim())?; - wrote_preamble = true; + // If necessary, include the `--index-url` and `--extra-index-url` locations. + if include_index_url { + if let Some(index) = index_locations.default_index() { + writeln!(writer, "--index-url {}", index.url().verbatim())?; + wrote_preamble = true; + } + let mut seen = FxHashSet::default(); + for extra_index in index_locations.implicit_indexes() { + if seen.insert(extra_index.url()) { + writeln!(writer, "--extra-index-url {}", extra_index.url().verbatim())?; + wrote_preamble = true; + } + } } - } - } - // If necessary, include the `--find-links` locations. - if include_find_links { - for flat_index in index_locations.flat_indexes() { - writeln!(writer, "--find-links {}", flat_index.url().verbatim())?; - wrote_preamble = true; - } - } - - // If necessary, include the `--no-binary` and `--only-binary` options. - if include_build_options { - match build_options.no_binary() { - NoBinary::None => {} - NoBinary::All => { - writeln!(writer, "--no-binary :all:")?; - wrote_preamble = true; - } - NoBinary::Packages(packages) => { - for package in packages { - writeln!(writer, "--no-binary {package}")?; + // If necessary, include the `--find-links` locations. + if include_find_links { + for flat_index in index_locations.flat_indexes() { + writeln!(writer, "--find-links {}", flat_index.url().verbatim())?; wrote_preamble = true; } } - } - match build_options.no_build() { - NoBuild::None => {} - NoBuild::All => { - writeln!(writer, "--only-binary :all:")?; - wrote_preamble = true; - } - NoBuild::Packages(packages) => { - for package in packages { - writeln!(writer, "--only-binary {package}")?; - wrote_preamble = true; + + // If necessary, include the `--no-binary` and `--only-binary` options. + if include_build_options { + match build_options.no_binary() { + NoBinary::None => {} + NoBinary::All => { + writeln!(writer, "--no-binary :all:")?; + wrote_preamble = true; + } + NoBinary::Packages(packages) => { + for package in packages { + writeln!(writer, "--no-binary {package}")?; + wrote_preamble = true; + } + } + } + match build_options.no_build() { + NoBuild::None => {} + NoBuild::All => { + writeln!(writer, "--only-binary :all:")?; + wrote_preamble = true; + } + NoBuild::Packages(packages) => { + for package in packages { + writeln!(writer, "--only-binary {package}")?; + wrote_preamble = true; + } + } } } + + // If we wrote an index, add a newline to separate it from the requirements + if wrote_preamble { + writeln!(writer)?; + } + + write!( + writer, + "{}", + DisplayResolutionGraph::new( + &resolution, + &resolver_env, + &no_emit_packages, + generate_hashes, + include_extras, + include_markers || universal, + include_annotations, + include_index_annotation, + annotation_style, + ) + )?; + } + ExportFormat::PylockToml => { + if include_marker_expression { + warn_user!("The `--emit-marker-expression` option is not supported for `pylock.toml` output"); + } + if include_index_url { + warn_user!( + "The `--emit-index-url` option is not supported for `pylock.toml` output" + ); + } + if include_find_links { + warn_user!( + "The `--emit-find-links` option is not supported for `pylock.toml` output" + ); + } + if include_build_options { + warn_user!( + "The `--emit-build-options` option is not supported for `pylock.toml` output" + ); + } + if include_index_annotation { + warn_user!("The `--emit-index-annotation` option is not supported for `pylock.toml` output"); + } + + // Determine the directory relative to which the output file should be written. + let output_file = output_file.map(std::path::absolute).transpose()?; + let install_path = if let Some(output_file) = output_file.as_deref() { + output_file.parent().unwrap() + } else { + &*CWD + }; + + // Convert the resolution to a `pylock.toml` file. + let export = PylockToml::from_resolution(&resolution, &no_emit_packages, install_path)?; + write!(writer, "{}", export.to_toml()?)?; } } - // If we wrote an index, add a newline to separate it from the requirements - if wrote_preamble { - writeln!(writer)?; - } - - write!( - writer, - "{}", - DisplayResolutionGraph::new( - &resolution, - &resolver_env, - &no_emit_packages, - generate_hashes, - include_extras, - include_markers || universal, - include_annotations, - include_index_annotation, - annotation_style, - ) - )?; - // If any "unsafe" packages were excluded, notify the user. let excluded = no_emit_packages .into_iter() diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index b9c01b002..333bb7526 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -450,6 +450,7 @@ async fn run(mut cli: Cli) -> Result { args.settings.extras, groups, args.settings.output_file.as_deref(), + args.format, args.settings.resolution, args.settings.prerelease, args.settings.fork_strategy, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index b944b879f..a45d08f88 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1638,6 +1638,7 @@ impl ExportSettings { #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PipCompileSettings { + pub(crate) format: Option, pub(crate) src_file: Vec, pub(crate) constraints: Vec, pub(crate) overrides: Vec, @@ -1666,6 +1667,7 @@ impl PipCompileSettings { deps, group, output_file, + format, no_strip_extras, strip_extras, no_strip_markers, @@ -1754,6 +1756,7 @@ impl PipCompileSettings { }; Self { + format, src_file, constraints: constraints .into_iter() diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index b1fe62c17..593dd5d24 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -16254,18 +16254,973 @@ fn compile_invalid_output_file() -> Result<()> { error: `pyproject.toml` is not a supported output format for `uv pip compile` (only `requirements.txt`-style output is supported) "); + Ok(()) +} + +#[test] +fn pep_751_compile_registry_wheel() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("iniconfig")?; + uv_snapshot!(context .pip_compile() - .arg("requirements.in") + .arg("requirements.txt") + .arg("--universal") .arg("-o") - .arg("uv.toml"), @r" - success: false - exit_code: 2 + .arg("pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "iniconfig" + version = "2.0.0" + sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", upload-time = 2023-01-07T11:08:11Z, size = 4646, hashes = { sha256 = "2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", upload-time = 2023-01-07T11:08:09Z, size = 5892, hashes = { sha256 = "b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" } }] + + ----- stderr ----- + Resolved 1 package in [TIME] + "#); + + uv_snapshot!(context.filters(), context.pip_sync() + .arg("--preview") + .arg("pylock.toml"), @r" + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- - error: TOML is not a supported output format for `uv pip compile` (only `requirements.txt`-style output is supported) - "); + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + " + ); + + Ok(()) +} + +#[test] +fn pep_751_compile_registry_sdist() -> Result<()> { + let context = TestContext::new("3.12").with_exclude_newer("2025-01-29T00:00:00Z"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("source-distribution")?; + + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "source-distribution" + version = "0.0.3" + sdist = { url = "https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz", upload-time = 2024-11-03T02:35:36Z, size = 2166, hashes = { sha256 = "be5895c175dbca2d91709a6ab7d5f28e1794272db551ae9a5faf3ae2ed74c3d8" } } + + ----- stderr ----- + Resolved 1 package in [TIME] + "#); + + uv_snapshot!(context.filters(), context.pip_sync() + .arg("--preview") + .arg("pylock.toml"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + source-distribution==0.0.3 + " + ); + + Ok(()) +} + +#[test] +fn pep_751_compile_directory() -> Result<()> { + let context = TestContext::new("3.12"); + + // Create a local dependency in a subdirectory. + let pyproject_toml = context.temp_dir.child("foo").child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "foo" + version = "1.0.0" + dependencies = ["anyio"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#, + )?; + context + .temp_dir + .child("foo") + .child("src") + .child("foo") + .child("__init__.py") + .touch()?; + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("./foo")?; + + 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 = ["foo"] + + [tool.uv.sources] + foo = { path = "foo" } + "#, + )?; + + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "anyio" + version = "4.3.0" + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", upload-time = 2024-02-19T08:36:28Z, size = 159642, hashes = { sha256 = "f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", upload-time = 2024-02-19T08:36:26Z, size = 85584, hashes = { sha256 = "048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8" } }] + + [[packages]] + name = "foo" + version = "1.0.0" + directory = { path = "foo" } + + [[packages]] + name = "idna" + version = "3.6" + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", upload-time = 2023-11-25T15:40:54Z, size = 175426, hashes = { sha256 = "9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", upload-time = 2023-11-25T15:40:52Z, size = 61567, hashes = { sha256 = "c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" } }] + + [[packages]] + name = "sniffio" + version = "1.3.1" + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", upload-time = 2024-02-25T23:20:04Z, size = 20372, hashes = { sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", upload-time = 2024-02-25T23:20:01Z, size = 10235, hashes = { sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" } }] + + ----- stderr ----- + Resolved 4 packages in [TIME] + "#); + + uv_snapshot!(context.filters(), context.pip_sync() + .arg("--preview") + .arg("pylock.toml"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + + anyio==4.3.0 + + foo==1.0.0 (from file://[TEMP_DIR]/foo) + + idna==3.6 + + sniffio==1.3.1 + " + ); + + Ok(()) +} + +#[test] +#[cfg(feature = "git")] +fn pep_751_compile_git() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str( + "uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage.git@0.0.1", + )?; + + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "uv-public-pypackage" + version = "0.1.0" + vcs = { type = "git", url = "https://github.com/astral-test/uv-public-pypackage.git", requested-revision = "0.0.1", commit-id = "0dacfd662c64cb4ceb16e6cf65a157a8b715b979" } + + ----- stderr ----- + Resolved 1 package in [TIME] + "#); + + uv_snapshot!(context.filters(), context.pip_sync() + .arg("--preview") + .arg("pylock.toml"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage.git@0dacfd662c64cb4ceb16e6cf65a157a8b715b979) + " + ); + + Ok(()) +} + +#[test] +fn pep_751_compile_url_wheel() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str( + "anyio @ https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", + )?; + + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "anyio" + version = "4.3.0" + archive = { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hashes = { sha256 = "048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8" } } + + [[packages]] + name = "idna" + version = "3.6" + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", upload-time = 2023-11-25T15:40:54Z, size = 175426, hashes = { sha256 = "9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", upload-time = 2023-11-25T15:40:52Z, size = 61567, hashes = { sha256 = "c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" } }] + + [[packages]] + name = "sniffio" + version = "1.3.1" + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", upload-time = 2024-02-25T23:20:04Z, size = 20372, hashes = { sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", upload-time = 2024-02-25T23:20:01Z, size = 10235, hashes = { sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" } }] + + ----- stderr ----- + Resolved 3 packages in [TIME] + "#); + + uv_snapshot!(context.filters(), context.pip_sync() + .arg("--preview") + .arg("pylock.toml"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 2 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 (from https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl) + + idna==3.6 + + sniffio==1.3.1 + " + ); + + Ok(()) +} + +#[test] +fn pep_751_compile_url_sdist() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str( + "anyio @ https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", + )?; + + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "anyio" + version = "4.3.0" + archive = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hashes = { sha256 = "f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6" } } + + [[packages]] + name = "idna" + version = "3.6" + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", upload-time = 2023-11-25T15:40:54Z, size = 175426, hashes = { sha256 = "9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", upload-time = 2023-11-25T15:40:52Z, size = 61567, hashes = { sha256 = "c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" } }] + + [[packages]] + name = "sniffio" + version = "1.3.1" + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", upload-time = 2024-02-25T23:20:04Z, size = 20372, hashes = { sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", upload-time = 2024-02-25T23:20:01Z, size = 10235, hashes = { sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" } }] + + ----- stderr ----- + Resolved 3 packages in [TIME] + "#); + + uv_snapshot!(context.filters(), context.pip_sync() + .arg("--preview") + .arg("pylock.toml"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 (from https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz) + + idna==3.6 + + sniffio==1.3.1 + " + ); + + Ok(()) +} + +#[test] +fn pep_751_compile_path_wheel() -> Result<()> { + let context = TestContext::new("3.12"); + + // Download the source. + let archive = context.temp_dir.child("iniconfig-2.0.0-py3-none-any.whl"); + download_to_disk( + "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", + &archive, + ); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("./iniconfig-2.0.0-py3-none-any.whl")?; + + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "iniconfig" + version = "2.0.0" + archive = { path = "iniconfig-2.0.0-py3-none-any.whl", hashes = { sha256 = "b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" } } + + ----- stderr ----- + Resolved 1 package in [TIME] + "#); + + uv_snapshot!(context.filters(), context.pip_sync() + .arg("--preview") + .arg("pylock.toml"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed 1 package in [TIME] + + iniconfig==2.0.0 (from file://[TEMP_DIR]/iniconfig-2.0.0-py3-none-any.whl) + " + ); + + // Ensure that the path is relative to the output `pylock.toml` file. + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("nested/pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o nested/pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "iniconfig" + version = "2.0.0" + archive = { path = "../iniconfig-2.0.0-py3-none-any.whl", hashes = { sha256 = "b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" } } + + ----- stderr ----- + Resolved 1 package in [TIME] + "#); + + Ok(()) +} + +#[test] +fn pep_751_compile_path_sdist() -> Result<()> { + let context = TestContext::new("3.12"); + + // Download the source. + let archive = context.temp_dir.child("iniconfig-2.0.0.tar.gz"); + download_to_disk( + "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", + &archive, + ); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("./iniconfig-2.0.0.tar.gz")?; + + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "iniconfig" + version = "2.0.0" + archive = { path = "iniconfig-2.0.0.tar.gz", hashes = { sha256 = "2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3" } } + + ----- stderr ----- + Resolved 1 package in [TIME] + "#); + + uv_snapshot!(context.filters(), context.pip_sync() + .arg("--preview") + .arg("pylock.toml"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 (from file://[TEMP_DIR]/iniconfig-2.0.0.tar.gz) + " + ); + + // Ensure that the path is relative to the output `pylock.toml` file. + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("nested/pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o nested/pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "iniconfig" + version = "2.0.0" + archive = { path = "../iniconfig-2.0.0.tar.gz", hashes = { sha256 = "2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3" } } + + ----- stderr ----- + Resolved 1 package in [TIME] + "#); + + Ok(()) +} + +#[test] +fn pep_751_compile_preferences() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(indoc::indoc! {r" + anyio==3.0.0 + idna==3.0.0 + "})?; + + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "anyio" + version = "3.0.0" + sdist = { url = "https://files.pythonhosted.org/packages/99/0d/65165f99e5f4f3b4c43a5ed9db0fb7aa655f5a58f290727a30528a87eb45/anyio-3.0.0.tar.gz", upload-time = 2021-04-20T14:02:14Z, size = 116952, hashes = { sha256 = "b553598332c050af19f7d41f73a7790142f5bc3d5eb8bd82f5e515ec22019bd9" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/3b/49/ebee263b69fe243bd1fd0a88bc6bb0f7732bf1794ba3273cb446351f9482/anyio-3.0.0-py3-none-any.whl", upload-time = 2021-04-20T14:02:13Z, size = 72182, hashes = { sha256 = "e71c3d9d72291d12056c0265d07c6bbedf92332f78573e278aeb116f24f30395" } }] + + [[packages]] + name = "idna" + version = "3.0" + sdist = { url = "https://files.pythonhosted.org/packages/2f/2e/bfe821bd26194fb474e0932df8ed82e24bd312ba628a8644d93c5a28b5d4/idna-3.0.tar.gz", upload-time = 2021-01-01T05:58:25Z, size = 180786, hashes = { sha256 = "c9a26e10e5558412384fac891eefb41957831d31be55f1e2c98ed97a70abb969" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/0f/6b/3a878f15ef3324754bf4780f8f047d692d9860be894ff8fb3135cef8bed8/idna-3.0-py2.py3-none-any.whl", upload-time = 2021-01-01T05:58:22Z, size = 58618, hashes = { sha256 = "320229aadbdfc597bc28876748cc0c9d04d476e0fe6caacaaddea146365d9f63" } }] + + [[packages]] + name = "sniffio" + version = "1.3.1" + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", upload-time = 2024-02-25T23:20:04Z, size = 20372, hashes = { sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", upload-time = 2024-02-25T23:20:01Z, size = 10235, hashes = { sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" } }] + + ----- stderr ----- + Resolved 3 packages in [TIME] + "#); + + // Modify the requirements to loosen the `anyio` version. + requirements_txt.write_str("anyio")?; + + // The `anyio` version should be retained, since we respect the existing preferences. + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "anyio" + version = "3.0.0" + sdist = { url = "https://files.pythonhosted.org/packages/99/0d/65165f99e5f4f3b4c43a5ed9db0fb7aa655f5a58f290727a30528a87eb45/anyio-3.0.0.tar.gz", upload-time = 2021-04-20T14:02:14Z, size = 116952, hashes = { sha256 = "b553598332c050af19f7d41f73a7790142f5bc3d5eb8bd82f5e515ec22019bd9" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/3b/49/ebee263b69fe243bd1fd0a88bc6bb0f7732bf1794ba3273cb446351f9482/anyio-3.0.0-py3-none-any.whl", upload-time = 2021-04-20T14:02:13Z, size = 72182, hashes = { sha256 = "e71c3d9d72291d12056c0265d07c6bbedf92332f78573e278aeb116f24f30395" } }] + + [[packages]] + name = "idna" + version = "3.0" + sdist = { url = "https://files.pythonhosted.org/packages/2f/2e/bfe821bd26194fb474e0932df8ed82e24bd312ba628a8644d93c5a28b5d4/idna-3.0.tar.gz", upload-time = 2021-01-01T05:58:25Z, size = 180786, hashes = { sha256 = "c9a26e10e5558412384fac891eefb41957831d31be55f1e2c98ed97a70abb969" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/0f/6b/3a878f15ef3324754bf4780f8f047d692d9860be894ff8fb3135cef8bed8/idna-3.0-py2.py3-none-any.whl", upload-time = 2021-01-01T05:58:22Z, size = 58618, hashes = { sha256 = "320229aadbdfc597bc28876748cc0c9d04d476e0fe6caacaaddea146365d9f63" } }] + + [[packages]] + name = "sniffio" + version = "1.3.1" + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", upload-time = 2024-02-25T23:20:04Z, size = 20372, hashes = { sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", upload-time = 2024-02-25T23:20:01Z, size = 10235, hashes = { sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" } }] + + ----- stderr ----- + Resolved 3 packages in [TIME] + "#); + + // Unless we pass `--upgrade-package`. + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml") + .arg("--upgrade-package") + .arg("idna"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "anyio" + version = "3.0.0" + sdist = { url = "https://files.pythonhosted.org/packages/99/0d/65165f99e5f4f3b4c43a5ed9db0fb7aa655f5a58f290727a30528a87eb45/anyio-3.0.0.tar.gz", upload-time = 2021-04-20T14:02:14Z, size = 116952, hashes = { sha256 = "b553598332c050af19f7d41f73a7790142f5bc3d5eb8bd82f5e515ec22019bd9" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/3b/49/ebee263b69fe243bd1fd0a88bc6bb0f7732bf1794ba3273cb446351f9482/anyio-3.0.0-py3-none-any.whl", upload-time = 2021-04-20T14:02:13Z, size = 72182, hashes = { sha256 = "e71c3d9d72291d12056c0265d07c6bbedf92332f78573e278aeb116f24f30395" } }] + + [[packages]] + name = "idna" + version = "3.6" + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", upload-time = 2023-11-25T15:40:54Z, size = 175426, hashes = { sha256 = "9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", upload-time = 2023-11-25T15:40:52Z, size = 61567, hashes = { sha256 = "c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" } }] + + [[packages]] + name = "sniffio" + version = "1.3.1" + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", upload-time = 2024-02-25T23:20:04Z, size = 20372, hashes = { sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", upload-time = 2024-02-25T23:20:01Z, size = 10235, hashes = { sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" } }] + + ----- stderr ----- + Resolved 3 packages in [TIME] + "#); + + // Or `--upgrade`. + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml") + .arg("--upgrade"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "anyio" + version = "4.3.0" + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", upload-time = 2024-02-19T08:36:28Z, size = 159642, hashes = { sha256 = "f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", upload-time = 2024-02-19T08:36:26Z, size = 85584, hashes = { sha256 = "048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8" } }] + + [[packages]] + name = "idna" + version = "3.6" + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", upload-time = 2023-11-25T15:40:54Z, size = 175426, hashes = { sha256 = "9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", upload-time = 2023-11-25T15:40:52Z, size = 61567, hashes = { sha256 = "c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" } }] + + [[packages]] + name = "sniffio" + version = "1.3.1" + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", upload-time = 2024-02-25T23:20:04Z, size = 20372, hashes = { sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", upload-time = 2024-02-25T23:20:01Z, size = 10235, hashes = { sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" } }] + + ----- stderr ----- + Resolved 3 packages in [TIME] + "#); + + Ok(()) +} + +#[test] +fn pep_751_compile_warn() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("iniconfig")?; + + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml") + .arg("--emit-index-url"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml --emit-index-url + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "iniconfig" + version = "2.0.0" + sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", upload-time = 2023-01-07T11:08:11Z, size = 4646, hashes = { sha256 = "2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", upload-time = 2023-01-07T11:08:09Z, size = 5892, hashes = { sha256 = "b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" } }] + + ----- stderr ----- + Resolved 1 package in [TIME] + warning: The `--emit-index-url` option is not supported for `pylock.toml` output + "#); + + Ok(()) +} + +#[test] +fn pep_751_compile_non_universal() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("black")?; + + // `colorama` should be excluded, since we're on Linux. + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--python-platform") + .arg("linux") + .arg("-o") + .arg("pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --python-platform linux -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "black" + version = "24.3.0" + sdist = { url = "https://files.pythonhosted.org/packages/8f/5f/bac24a952668c7482cfdb4ebf91ba57a796c9da8829363a772040c1a3312/black-24.3.0.tar.gz", upload-time = 2024-03-15T19:35:43Z, size = 634292, hashes = { sha256 = "a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f" } } + wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/32/1a25d1b83147ca128797a627f429f9dc390eb066805c6aa319bea3ffffa5/black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", upload-time = 2024-03-15T19:43:32Z, size = 1587891, hashes = { sha256 = "7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395" } }, + { url = "https://files.pythonhosted.org/packages/c4/91/6cb204786acc693edc4bf1b9230ffdc3cbfaeb7cd04d3a12fb4b13882a53/black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2024-03-15T19:41:59Z, size = 1434886, hashes = { sha256 = "9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995" } }, + { url = "https://files.pythonhosted.org/packages/ef/e4/53b5d07117381f7d5e946a54dd4c62617faad90713649619bbc683769dfe/black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-03-15T19:38:22Z, size = 1747400, hashes = { sha256 = "e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7" } }, + { url = "https://files.pythonhosted.org/packages/13/9c/f2e7532d11b05add5ab383a9f90be1a49954bf510803f98064b45b42f98e/black-24.3.0-cp310-cp310-win_amd64.whl", upload-time = 2024-03-15T19:39:43Z, size = 1363816, hashes = { sha256 = "4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0" } }, + { url = "https://files.pythonhosted.org/packages/68/df/ceea5828be9c4931cb5a75b7e8fb02971f57524da7a16dfec0d4d575327f/black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", upload-time = 2024-03-15T19:45:27Z, size = 1571235, hashes = { sha256 = "4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9" } }, + { url = "https://files.pythonhosted.org/packages/46/5f/30398c5056cb72f883b32b6520ad00042a9d0454b693f70509867db03a80/black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2024-03-15T19:43:52Z, size = 1414926, hashes = { sha256 = "aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597" } }, + { url = "https://files.pythonhosted.org/packages/6b/59/498885b279e890f656ea4300a2671c964acb6d97994ea626479c2e5501b4/black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-03-15T19:38:13Z, size = 1725920, hashes = { sha256 = "65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d" } }, + { url = "https://files.pythonhosted.org/packages/8f/b0/4bef40c808cc615187db983b75bacdca1c110a229d41ba9887549fac529c/black-24.3.0-cp311-cp311-win_amd64.whl", upload-time = 2024-03-15T19:39:34Z, size = 1372608, hashes = { sha256 = "bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5" } }, + { url = "https://files.pythonhosted.org/packages/b6/c6/1d174efa9ff02b22d0124c73fc5f4d4fb006d0d9a081aadc354d05754a13/black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", upload-time = 2024-03-15T19:45:20Z, size = 1600822, hashes = { sha256 = "2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f" } }, + { url = "https://files.pythonhosted.org/packages/d9/ed/704731afffe460b8ff0672623b40fce9fe569f2ee617c15857e4d4440a3a/black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2024-03-15T19:45:00Z, size = 1429987, hashes = { sha256 = "4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11" } }, + { url = "https://files.pythonhosted.org/packages/a8/05/8dd038e30caadab7120176d4bc109b7ca2f4457f12eef746b0560a583458/black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-03-15T19:38:24Z, size = 1755319, hashes = { sha256 = "c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4" } }, + { url = "https://files.pythonhosted.org/packages/71/9d/e5fa1ff4ef1940be15a64883c0bb8d2fcf626efec996eab4ae5a8c691d2c/black-24.3.0-cp312-cp312-win_amd64.whl", upload-time = 2024-03-15T19:39:37Z, size = 1385180, hashes = { sha256 = "56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5" } }, + { url = "https://files.pythonhosted.org/packages/37/76/1f85c4349d6b3424c7672dbc6c4b39ab89372b575801ffdc23d34b023c6f/black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", upload-time = 2024-03-15T19:47:26Z, size = 1579568, hashes = { sha256 = "79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837" } }, + { url = "https://files.pythonhosted.org/packages/ba/24/6d82cde63c1340ea55cb74fd697f62b94b6d6fa7069a1aa216475dfd2a30/black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", upload-time = 2024-03-15T19:46:18Z, size = 1423188, hashes = { sha256 = "e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd" } }, + { url = "https://files.pythonhosted.org/packages/71/61/48664319cee4f8e22633e075ff101ec6253195b056cb23e0c5f8a5086e87/black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-03-15T19:38:15Z, size = 1730623, hashes = { sha256 = "65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213" } }, + { url = "https://files.pythonhosted.org/packages/3b/95/ed26a160d7a13d6afb3e94448ec079fb4e37bbedeaf408b6b6dbf67d6cd2/black-24.3.0-cp38-cp38-win_amd64.whl", upload-time = 2024-03-15T19:39:43Z, size = 1370465, hashes = { sha256 = "b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959" } }, + { url = "https://files.pythonhosted.org/packages/62/f5/78881e9b1c340ccc02d5d4ebe61cfb9140452b3d11272a896b405033511b/black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", upload-time = 2024-03-15T19:48:33Z, size = 1587504, hashes = { sha256 = "c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb" } }, + { url = "https://files.pythonhosted.org/packages/17/cc/67ba827fe23b39d55e8408937763b2ad21d904d63ca1c60b47d608ee7fb2/black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2024-03-15T19:47:39Z, size = 1434037, hashes = { sha256 = "6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7" } }, + { url = "https://files.pythonhosted.org/packages/fa/aa/6a2493c7d3506e9b64edbd0782e21637c376da005eecc546904e47b5cdbf/black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-03-15T19:38:16Z, size = 1745481, hashes = { sha256 = "d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7" } }, + { url = "https://files.pythonhosted.org/packages/18/68/9e86e73b58819624af6797ffe68dd7d09ed90fa1f9eb8d4d675f8c5e6ab0/black-24.3.0-cp39-cp39-win_amd64.whl", upload-time = 2024-03-15T19:39:15Z, size = 1363531, hashes = { sha256 = "7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f" } }, + { url = "https://files.pythonhosted.org/packages/4d/ea/31770a7e49f3eedfd8cd7b35e78b3a3aaad860400f8673994bc988318135/black-24.3.0-py3-none-any.whl", upload-time = 2024-03-15T19:35:41Z, size = 201493, hashes = { sha256 = "41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93" } }, + ] + + [[packages]] + name = "click" + version = "8.1.7" + sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", upload-time = 2023-08-17T17:29:11Z, size = 336121, hashes = { sha256 = "ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", upload-time = 2023-08-17T17:29:10Z, size = 97941, hashes = { sha256 = "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28" } }] + + [[packages]] + name = "mypy-extensions" + version = "1.0.0" + sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", upload-time = 2023-02-04T12:11:27Z, size = 4433, hashes = { sha256 = "75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", upload-time = 2023-02-04T12:11:25Z, size = 4695, hashes = { sha256 = "4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d" } }] + + [[packages]] + name = "packaging" + version = "24.0" + sdist = { url = "https://files.pythonhosted.org/packages/ee/b5/b43a27ac7472e1818c4bafd44430e69605baefe1f34440593e0332ec8b4d/packaging-24.0.tar.gz", upload-time = 2024-03-10T09:39:28Z, size = 147882, hashes = { sha256 = "eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", upload-time = 2024-03-10T09:39:25Z, size = 53488, hashes = { sha256 = "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5" } }] + + [[packages]] + name = "pathspec" + version = "0.12.1" + sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", upload-time = 2023-12-10T22:30:45Z, size = 51043, hashes = { sha256 = "a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", upload-time = 2023-12-10T22:30:43Z, size = 31191, hashes = { sha256 = "a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08" } }] + + [[packages]] + name = "platformdirs" + version = "4.2.0" + sdist = { url = "https://files.pythonhosted.org/packages/96/dc/c1d911bf5bb0fdc58cc05010e9f3efe3b67970cef779ba7fbc3183b987a8/platformdirs-4.2.0.tar.gz", upload-time = 2024-01-31T01:00:36Z, size = 20055, hashes = { sha256 = "ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", upload-time = 2024-01-31T01:00:34Z, size = 17717, hashes = { sha256 = "0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068" } }] + + ----- stderr ----- + Resolved 6 packages in [TIME] + "#); + + // `colorama` should be included, since we're on Windows. + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--python-platform") + .arg("windows") + .arg("-o") + .arg("pylock.toml"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --python-platform windows -o pylock.toml + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "black" + version = "24.3.0" + sdist = { url = "https://files.pythonhosted.org/packages/8f/5f/bac24a952668c7482cfdb4ebf91ba57a796c9da8829363a772040c1a3312/black-24.3.0.tar.gz", upload-time = 2024-03-15T19:35:43Z, size = 634292, hashes = { sha256 = "a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f" } } + wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/32/1a25d1b83147ca128797a627f429f9dc390eb066805c6aa319bea3ffffa5/black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", upload-time = 2024-03-15T19:43:32Z, size = 1587891, hashes = { sha256 = "7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395" } }, + { url = "https://files.pythonhosted.org/packages/c4/91/6cb204786acc693edc4bf1b9230ffdc3cbfaeb7cd04d3a12fb4b13882a53/black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2024-03-15T19:41:59Z, size = 1434886, hashes = { sha256 = "9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995" } }, + { url = "https://files.pythonhosted.org/packages/ef/e4/53b5d07117381f7d5e946a54dd4c62617faad90713649619bbc683769dfe/black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-03-15T19:38:22Z, size = 1747400, hashes = { sha256 = "e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7" } }, + { url = "https://files.pythonhosted.org/packages/13/9c/f2e7532d11b05add5ab383a9f90be1a49954bf510803f98064b45b42f98e/black-24.3.0-cp310-cp310-win_amd64.whl", upload-time = 2024-03-15T19:39:43Z, size = 1363816, hashes = { sha256 = "4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0" } }, + { url = "https://files.pythonhosted.org/packages/68/df/ceea5828be9c4931cb5a75b7e8fb02971f57524da7a16dfec0d4d575327f/black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", upload-time = 2024-03-15T19:45:27Z, size = 1571235, hashes = { sha256 = "4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9" } }, + { url = "https://files.pythonhosted.org/packages/46/5f/30398c5056cb72f883b32b6520ad00042a9d0454b693f70509867db03a80/black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2024-03-15T19:43:52Z, size = 1414926, hashes = { sha256 = "aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597" } }, + { url = "https://files.pythonhosted.org/packages/6b/59/498885b279e890f656ea4300a2671c964acb6d97994ea626479c2e5501b4/black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-03-15T19:38:13Z, size = 1725920, hashes = { sha256 = "65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d" } }, + { url = "https://files.pythonhosted.org/packages/8f/b0/4bef40c808cc615187db983b75bacdca1c110a229d41ba9887549fac529c/black-24.3.0-cp311-cp311-win_amd64.whl", upload-time = 2024-03-15T19:39:34Z, size = 1372608, hashes = { sha256 = "bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5" } }, + { url = "https://files.pythonhosted.org/packages/b6/c6/1d174efa9ff02b22d0124c73fc5f4d4fb006d0d9a081aadc354d05754a13/black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", upload-time = 2024-03-15T19:45:20Z, size = 1600822, hashes = { sha256 = "2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f" } }, + { url = "https://files.pythonhosted.org/packages/d9/ed/704731afffe460b8ff0672623b40fce9fe569f2ee617c15857e4d4440a3a/black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2024-03-15T19:45:00Z, size = 1429987, hashes = { sha256 = "4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11" } }, + { url = "https://files.pythonhosted.org/packages/a8/05/8dd038e30caadab7120176d4bc109b7ca2f4457f12eef746b0560a583458/black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-03-15T19:38:24Z, size = 1755319, hashes = { sha256 = "c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4" } }, + { url = "https://files.pythonhosted.org/packages/71/9d/e5fa1ff4ef1940be15a64883c0bb8d2fcf626efec996eab4ae5a8c691d2c/black-24.3.0-cp312-cp312-win_amd64.whl", upload-time = 2024-03-15T19:39:37Z, size = 1385180, hashes = { sha256 = "56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5" } }, + { url = "https://files.pythonhosted.org/packages/37/76/1f85c4349d6b3424c7672dbc6c4b39ab89372b575801ffdc23d34b023c6f/black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", upload-time = 2024-03-15T19:47:26Z, size = 1579568, hashes = { sha256 = "79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837" } }, + { url = "https://files.pythonhosted.org/packages/ba/24/6d82cde63c1340ea55cb74fd697f62b94b6d6fa7069a1aa216475dfd2a30/black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", upload-time = 2024-03-15T19:46:18Z, size = 1423188, hashes = { sha256 = "e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd" } }, + { url = "https://files.pythonhosted.org/packages/71/61/48664319cee4f8e22633e075ff101ec6253195b056cb23e0c5f8a5086e87/black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-03-15T19:38:15Z, size = 1730623, hashes = { sha256 = "65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213" } }, + { url = "https://files.pythonhosted.org/packages/3b/95/ed26a160d7a13d6afb3e94448ec079fb4e37bbedeaf408b6b6dbf67d6cd2/black-24.3.0-cp38-cp38-win_amd64.whl", upload-time = 2024-03-15T19:39:43Z, size = 1370465, hashes = { sha256 = "b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959" } }, + { url = "https://files.pythonhosted.org/packages/62/f5/78881e9b1c340ccc02d5d4ebe61cfb9140452b3d11272a896b405033511b/black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", upload-time = 2024-03-15T19:48:33Z, size = 1587504, hashes = { sha256 = "c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb" } }, + { url = "https://files.pythonhosted.org/packages/17/cc/67ba827fe23b39d55e8408937763b2ad21d904d63ca1c60b47d608ee7fb2/black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2024-03-15T19:47:39Z, size = 1434037, hashes = { sha256 = "6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7" } }, + { url = "https://files.pythonhosted.org/packages/fa/aa/6a2493c7d3506e9b64edbd0782e21637c376da005eecc546904e47b5cdbf/black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-03-15T19:38:16Z, size = 1745481, hashes = { sha256 = "d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7" } }, + { url = "https://files.pythonhosted.org/packages/18/68/9e86e73b58819624af6797ffe68dd7d09ed90fa1f9eb8d4d675f8c5e6ab0/black-24.3.0-cp39-cp39-win_amd64.whl", upload-time = 2024-03-15T19:39:15Z, size = 1363531, hashes = { sha256 = "7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f" } }, + { url = "https://files.pythonhosted.org/packages/4d/ea/31770a7e49f3eedfd8cd7b35e78b3a3aaad860400f8673994bc988318135/black-24.3.0-py3-none-any.whl", upload-time = 2024-03-15T19:35:41Z, size = 201493, hashes = { sha256 = "41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93" } }, + ] + + [[packages]] + name = "click" + version = "8.1.7" + sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", upload-time = 2023-08-17T17:29:11Z, size = 336121, hashes = { sha256 = "ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", upload-time = 2023-08-17T17:29:10Z, size = 97941, hashes = { sha256 = "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28" } }] + + [[packages]] + name = "colorama" + version = "0.4.6" + marker = "sys_platform == 'win32'" + sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", upload-time = 2022-10-25T02:36:22Z, size = 27697, hashes = { sha256 = "08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", upload-time = 2022-10-25T02:36:20Z, size = 25335, hashes = { sha256 = "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" } }] + + [[packages]] + name = "mypy-extensions" + version = "1.0.0" + sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", upload-time = 2023-02-04T12:11:27Z, size = 4433, hashes = { sha256 = "75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", upload-time = 2023-02-04T12:11:25Z, size = 4695, hashes = { sha256 = "4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d" } }] + + [[packages]] + name = "packaging" + version = "24.0" + sdist = { url = "https://files.pythonhosted.org/packages/ee/b5/b43a27ac7472e1818c4bafd44430e69605baefe1f34440593e0332ec8b4d/packaging-24.0.tar.gz", upload-time = 2024-03-10T09:39:28Z, size = 147882, hashes = { sha256 = "eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", upload-time = 2024-03-10T09:39:25Z, size = 53488, hashes = { sha256 = "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5" } }] + + [[packages]] + name = "pathspec" + version = "0.12.1" + sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", upload-time = 2023-12-10T22:30:45Z, size = 51043, hashes = { sha256 = "a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", upload-time = 2023-12-10T22:30:43Z, size = 31191, hashes = { sha256 = "a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08" } }] + + [[packages]] + name = "platformdirs" + version = "4.2.0" + sdist = { url = "https://files.pythonhosted.org/packages/96/dc/c1d911bf5bb0fdc58cc05010e9f3efe3b67970cef779ba7fbc3183b987a8/platformdirs-4.2.0.tar.gz", upload-time = 2024-01-31T01:00:36Z, size = 20055, hashes = { sha256 = "ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", upload-time = 2024-01-31T01:00:34Z, size = 17717, hashes = { sha256 = "0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068" } }] + + ----- stderr ----- + Resolved 7 packages in [TIME] + "#); + + Ok(()) +} + +#[test] +fn pep_751_compile_no_emit_package() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("anyio")?; + + uv_snapshot!(context + .pip_compile() + .arg("requirements.txt") + .arg("--universal") + .arg("-o") + .arg("pylock.toml") + .arg("--no-emit-package") + .arg("idna"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml --no-emit-package idna + lock-version = "1.0" + created-by = "uv" + requires-python = ">=3.12.9" + + [[packages]] + name = "anyio" + version = "4.3.0" + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", upload-time = 2024-02-19T08:36:28Z, size = 159642, hashes = { sha256 = "f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", upload-time = 2024-02-19T08:36:26Z, size = 85584, hashes = { sha256 = "048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8" } }] + + [[packages]] + name = "sniffio" + version = "1.3.1" + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", upload-time = 2024-02-25T23:20:04Z, size = 20372, hashes = { sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } } + wheels = [{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", upload-time = 2024-02-25T23:20:01Z, size = 10235, hashes = { sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" } }] + + # The following packages were excluded from the output: + # idna + + ----- stderr ----- + Resolved 3 packages in [TIME] + "#); Ok(()) } diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index f382e6d3c..55da48df5 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -84,6 +84,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -244,6 +245,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -405,6 +407,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -598,6 +601,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -760,6 +764,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -900,6 +905,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -1087,6 +1093,7 @@ fn resolve_index_url() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -1280,6 +1287,7 @@ fn resolve_index_url() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -1529,6 +1537,7 @@ fn resolve_find_links() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -1713,6 +1722,7 @@ fn resolve_top_level() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -1858,6 +1868,7 @@ fn resolve_top_level() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -2049,6 +2060,7 @@ fn resolve_top_level() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -2263,6 +2275,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -2398,6 +2411,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -2533,6 +2547,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -2670,6 +2685,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -2990,6 +3006,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -3153,6 +3170,7 @@ fn resolve_both() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -3438,6 +3456,7 @@ fn resolve_config_file() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -3699,6 +3718,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -3837,6 +3857,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -3994,6 +4015,7 @@ fn allow_insecure_host() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -4143,6 +4165,7 @@ fn index_priority() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -4336,6 +4359,7 @@ fn index_priority() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -4535,6 +4559,7 @@ fn index_priority() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -4729,6 +4754,7 @@ fn index_priority() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -4930,6 +4956,7 @@ fn index_priority() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], @@ -5124,6 +5151,7 @@ fn index_priority() -> anyhow::Result<()> { ), } PipCompileSettings { + format: None, src_file: [ "requirements.in", ], diff --git a/docs/reference/cli.md b/docs/reference/cli.md index a35e152ac..338fa0ff9 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -5723,6 +5723,19 @@ uv pip compile [OPTIONS] >
  • requires-python: Optimize for selecting latest supported version of each package, for each supported Python version
  • +
    --format format

    The format in which the resolution should be output.

    + +

    Supports both requirements.txt and pylock.toml (PEP 751) output formats.

    + +

    uv will infer the output format from the file extension of the output file, if provided. Otherwise, defaults to requirements.txt.

    + +

    Possible values:

    + +
      +
    • requirements.txt: Export in requirements.txt format
    • + +
    • pylock.toml: Export in pylock.toml format
    • +
    --generate-hashes

    Include distribution hashes in the output file

    --group group

    Install the specified dependency group from a pyproject.toml.

    @@ -5872,7 +5885,7 @@ uv pip compile [OPTIONS] >

    Multiple packages may be provided. Disable binaries for all packages with :all:. Clear previously specified packages with :none:.

    -
    --output-file, -o output-file

    Write the compiled requirements to the given requirements.txt file.

    +
    --output-file, -o output-file

    Write the compiled requirements to the given requirements.txt or pylock.toml file.

    If the file already exists, the existing versions will be preferred when resolving dependencies, unless --upgrade is also specified.