-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Expand file tree
/
Copy pathupgrade.rs
More file actions
173 lines (146 loc) · 5.79 KB
/
upgrade.rs
File metadata and controls
173 lines (146 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
use std::path::Path;
use anyhow::Result;
use rustc_hash::FxHashSet;
use uv_configuration::Upgrade;
use uv_fs::CWD;
use uv_git::ResolvedRepositoryReference;
use uv_normalize::PackageName;
use uv_requirements_txt::RequirementsTxt;
use uv_resolver::{Lock, LockError, Preference, PreferenceError, PylockToml, PylockTomlErrorKind};
#[derive(Debug, Default)]
pub struct LockedRequirements {
/// The pinned versions from the lockfile.
pub preferences: Vec<Preference>,
/// The pinned Git SHAs from the lockfile.
pub git: Vec<ResolvedRepositoryReference>,
}
impl LockedRequirements {
/// Create a [`LockedRequirements`] from a list of preferences.
pub fn from_preferences(preferences: Vec<Preference>) -> Self {
Self {
preferences,
..Self::default()
}
}
}
/// Load the preferred requirements from an existing `requirements.txt`, applying the upgrade strategy.
pub async fn read_requirements_txt(
output_file: &Path,
upgrade: &Upgrade,
) -> Result<Vec<Preference>> {
// As an optimization, skip reading the lockfile is we're upgrading all packages anyway.
if upgrade.is_all() {
return Ok(Vec::new());
}
// Parse the requirements from the lockfile.
let requirements_txt = RequirementsTxt::parse(output_file, &*CWD).await?;
// Map each entry in the lockfile to a preference.
let preferences = requirements_txt
.requirements
.into_iter()
.map(Preference::from_entry)
.filter_map(Result::transpose)
.collect::<Result<Vec<_>, PreferenceError>>()?;
// Apply the upgrade strategy to the requirements.
Ok(if upgrade.is_none() {
// Respect all pinned versions from the existing lockfile.
preferences
} else {
// Ignore all pinned versions for packages that should be upgraded.
preferences
.into_iter()
.filter(|preference| !upgrade.contains(preference.name()))
.collect()
})
}
/// Load the preferred requirements from an existing lockfile, applying the upgrade strategy.
pub fn read_lock_requirements(
lock: &Lock,
install_path: &Path,
upgrade: &Upgrade,
) -> Result<LockedRequirements, LockError> {
// As an optimization, skip iterating over the lockfile is we're upgrading all packages anyway.
if upgrade.is_all() {
return Ok(LockedRequirements::default());
}
// Resolve `--upgrade-group` to a set of package names by collecting all dependencies from
// the specified groups across all workspace member packages in the lock.
let group_packages = resolve_group_packages(lock, upgrade);
let mut preferences = Vec::new();
let mut git = Vec::new();
for package in lock.packages() {
// Skip the distribution if it's included in the upgrade strategy or belongs to an
// upgraded group.
if upgrade.contains(package.name()) || group_packages.contains(package.name()) {
continue;
}
// Map each entry in the lockfile to a preference.
if let Some(preference) = Preference::from_lock(package, install_path)? {
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 })
}
/// Resolve the `--upgrade-group` group names to a set of package names by looking at the
/// dependency groups defined on packages in the lockfile.
fn resolve_group_packages(lock: &Lock, upgrade: &Upgrade) -> FxHashSet<PackageName> {
let Some(groups) = upgrade.groups() else {
return FxHashSet::default();
};
let mut packages = FxHashSet::default();
// Check package-level dependency groups (the standard case for projects with a `[project]`
// table).
for package in lock.packages() {
for (group_name, dependencies) in package.resolved_dependency_groups() {
if groups.contains(group_name) {
for dependency in dependencies {
packages.insert(dependency.package_name().clone());
}
}
}
}
// Check manifest-level dependency groups, which cover projects without a `[project]` table
// (e.g., virtual workspace roots or PEP 723 scripts).
for (group_name, requirements) in lock.dependency_groups() {
if groups.contains(group_name) {
for requirement in requirements {
packages.insert(requirement.name.clone());
}
}
}
packages
}
/// 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<LockedRequirements, PylockTomlErrorKind> {
// 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::<PylockToml>(&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 })
}