Skip to content

Commit 6341aaa

Browse files
committed
Add PEP 751 support to uv pip compile
1 parent ffcd5eb commit 6341aaa

File tree

14 files changed

+1553
-118
lines changed

14 files changed

+1553
-118
lines changed

crates/uv-cli/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1075,13 +1075,22 @@ pub struct PipCompileArgs {
10751075
#[arg(long, group = "sources")]
10761076
pub group: Vec<PipGroupName>,
10771077

1078-
/// Write the compiled requirements to the given `requirements.txt` file.
1078+
/// Write the compiled requirements to the given `requirements.txt` or `pylock.toml` file.
10791079
///
10801080
/// If the file already exists, the existing versions will be preferred when resolving
10811081
/// dependencies, unless `--upgrade` is also specified.
10821082
#[arg(long, short)]
10831083
pub output_file: Option<PathBuf>,
10841084

1085+
/// The format in which the resolution should be output.
1086+
///
1087+
/// Supports both `requirements.txt` and `pylock.toml` (PEP 751) output formats.
1088+
///
1089+
/// uv will infer the output format from the file extension of the output file, if
1090+
/// provided. Otherwise, defaults to `requirements.txt`.
1091+
#[arg(long, value_enum)]
1092+
pub format: Option<ExportFormat>,
1093+
10851094
/// Include extras in the output file.
10861095
///
10871096
/// By default, uv strips extras, as any packages pulled in by the extras are already included

crates/uv-requirements/src/upgrade.rs

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use uv_configuration::Upgrade;
77
use uv_fs::CWD;
88
use uv_git::ResolvedRepositoryReference;
99
use uv_requirements_txt::RequirementsTxt;
10-
use uv_resolver::{Lock, LockError, Preference, PreferenceError};
10+
use uv_resolver::{Lock, LockError, Preference, PreferenceError, PylockToml, PylockTomlError};
1111

1212
#[derive(Debug, Default)]
1313
pub struct LockedRequirements {
@@ -17,21 +17,26 @@ pub struct LockedRequirements {
1717
pub git: Vec<ResolvedRepositoryReference>,
1818
}
1919

20+
impl LockedRequirements {
21+
/// Create a [`LockedRequirements`] from a list of preferences.
22+
pub fn from_preferences(preferences: Vec<Preference>) -> Self {
23+
Self {
24+
preferences,
25+
..LockedRequirements::default()
26+
}
27+
}
28+
}
29+
2030
/// Load the preferred requirements from an existing `requirements.txt`, applying the upgrade strategy.
2131
pub async fn read_requirements_txt(
22-
output_file: Option<&Path>,
32+
output_file: &Path,
2333
upgrade: &Upgrade,
2434
) -> Result<Vec<Preference>> {
2535
// As an optimization, skip reading the lockfile is we're upgrading all packages anyway.
2636
if upgrade.is_all() {
2737
return Ok(Vec::new());
2838
}
2939

30-
// If the lockfile doesn't exist, don't respect any pinned versions.
31-
let Some(output_file) = output_file.filter(|path| path.exists()) else {
32-
return Ok(Vec::new());
33-
};
34-
3540
// Parse the requirements from the lockfile.
3641
let requirements_txt = RequirementsTxt::parse(
3742
output_file,
@@ -95,3 +100,40 @@ pub fn read_lock_requirements(
95100

96101
Ok(LockedRequirements { preferences, git })
97102
}
103+
104+
/// Load the preferred requirements from an existing `pylock.toml` file, applying the upgrade strategy.
105+
pub async fn read_pylock_toml_requirements(
106+
output_file: &Path,
107+
upgrade: &Upgrade,
108+
) -> Result<LockedRequirements, PylockTomlError> {
109+
// As an optimization, skip iterating over the lockfile is we're upgrading all packages anyway.
110+
if upgrade.is_all() {
111+
return Ok(LockedRequirements::default());
112+
}
113+
114+
// Read the `pylock.toml` from disk, and deserialize it from TOML.
115+
let content = fs_err::tokio::read_to_string(&output_file).await?;
116+
let lock = toml::from_str::<PylockToml>(&content)?;
117+
118+
let mut preferences = Vec::new();
119+
let mut git = Vec::new();
120+
121+
for package in &lock.packages {
122+
// Skip the distribution if it's not included in the upgrade strategy.
123+
if upgrade.contains(&package.name) {
124+
continue;
125+
}
126+
127+
// Map each entry in the lockfile to a preference.
128+
if let Some(preference) = Preference::from_pylock_toml(package)? {
129+
preferences.push(preference);
130+
}
131+
132+
// Map each entry in the lockfile to a Git SHA.
133+
if let Some(git_ref) = package.as_git_ref() {
134+
git.push(git_ref);
135+
}
136+
}
137+
138+
Ok(LockedRequirements { preferences, git })
139+
}

crates/uv-resolver/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub use exclusions::Exclusions;
55
pub use flat_index::{FlatDistributions, FlatIndex};
66
pub use fork_strategy::ForkStrategy;
77
pub use lock::{
8-
Installable, Lock, LockError, LockVersion, Package, PackageMap, PylockToml,
8+
Installable, Lock, LockError, LockVersion, Package, PackageMap, PylockToml, PylockTomlError,
99
RequirementsTxtExport, ResolverManifest, SatisfiesResult, TreeDisplay, VERSION,
1010
};
1111
pub use manifest::Manifest;

crates/uv-resolver/src/lock/export/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use uv_pep508::MarkerTree;
1414
use uv_pypi_types::ConflictItem;
1515

1616
use crate::graph_ops::{marker_reachability, Reachable};
17-
pub use crate::lock::export::pylock_toml::PylockToml;
17+
pub(crate) use crate::lock::export::pylock_toml::PylockTomlPackage;
18+
pub use crate::lock::export::pylock_toml::{PylockToml, PylockTomlError};
1819
pub use crate::lock::export::requirements_txt::RequirementsTxtExport;
1920
use crate::universal_marker::resolve_conflicts;
2021
use crate::{Installable, Package};

0 commit comments

Comments
 (0)