Skip to content

Commit 9e6d84a

Browse files
committed
When upgrading to lockfile, process manifests one at a time
Multiple manifests in a workspace may request the same dependency at different versions. The version of each dependency should be updated to the corresponding semver compatible version in the lockfile.
1 parent 081f21b commit 9e6d84a

File tree

8 files changed

+280
-90
lines changed

8 files changed

+280
-90
lines changed

src/bin/upgrade/main.rs

+93-88
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,33 @@ struct Args {
9595
/// A collection of manifests.
9696
struct Manifests(Vec<(LocalManifest, cargo_metadata::Package)>);
9797

98+
/// Helper function to check whether a `cargo_metadata::Dependency` is a version dependency.
99+
fn is_version_dep(dependency: &cargo_metadata::Dependency) -> bool {
100+
match dependency.source {
101+
// This is the criterion cargo uses (in `SourceId::from_url`) to decide whether a
102+
// dependency has the 'registry' kind.
103+
Some(ref s) => s.splitn(2, '+').next() == Some("registry"),
104+
_ => false,
105+
}
106+
}
107+
108+
fn dry_run_message() -> Result<()> {
109+
let bufwtr = BufferWriter::stdout(ColorChoice::Always);
110+
let mut buffer = bufwtr.buffer();
111+
buffer
112+
.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))
113+
.chain_err(|| "Failed to set output colour")?;
114+
write!(&mut buffer, "Starting dry run. ").chain_err(|| "Failed to write dry run message")?;
115+
buffer
116+
.set_color(&ColorSpec::new())
117+
.chain_err(|| "Failed to clear output colour")?;
118+
writeln!(&mut buffer, "Changes will not be saved.")
119+
.chain_err(|| "Failed to write dry run message")?;
120+
bufwtr
121+
.print(&buffer)
122+
.chain_err(|| "Failed to print dry run message")
123+
}
124+
98125
impl Manifests {
99126
/// Get all manifests in the workspace.
100127
fn get_all(manifest_path: &Option<PathBuf>) -> Result<Self> {
@@ -148,80 +175,16 @@ impl Manifests {
148175
Ok(Manifests(vec![(manifest, package.to_owned())]))
149176
}
150177

151-
fn get_all_requested_dependencies(&self) -> Vec<cargo_metadata::Dependency> {
152-
/// Helper function to check whether a `cargo_metadata::Dependency` is a version dependency.
153-
fn is_version_dep(dependency: &cargo_metadata::Dependency) -> bool {
154-
match dependency.source {
155-
// This is the criterion cargo uses (in `SourceId::from_url`) to decide whether a
156-
// dependency has the 'registry' kind.
157-
Some(ref s) => s.splitn(2, '+').next() == Some("registry"),
158-
_ => false,
159-
}
160-
}
161-
162-
self.0
163-
.iter()
164-
.flat_map(|&(_, ref package)| package.dependencies.clone())
165-
.filter(is_version_dep)
166-
.collect()
167-
}
168-
169-
fn get_locked_dependencies(&self) -> Result<Vec<cargo_metadata::Package>> {
170-
Ok(self
171-
.0
172-
.iter()
173-
.map(|(manifest, _package)| {
174-
let mut cmd = cargo_metadata::MetadataCommand::new();
175-
cmd.manifest_path(manifest.path.clone());
176-
cmd.other_options(vec!["--locked".to_string()]);
177-
178-
let result = cmd
179-
.exec()
180-
.map_err(|e| Error::from(e.compat()).chain_err(|| "Invalid manifest"))?;
181-
182-
Ok(result
183-
.packages
184-
.into_iter()
185-
.filter(|p| p.source.is_some())
186-
.collect::<Vec<_>>())
187-
})
188-
.collect::<Result<Vec<_>>>()?
189-
.into_iter()
190-
.flatten()
191-
.collect())
192-
}
193-
194178
/// Get the the combined set of dependencies to upgrade. If the user has specified
195179
/// per-dependency desired versions, extract those here.
196-
fn get_dependencies(
197-
&self,
198-
only_update: Vec<String>,
199-
to_lockfile: bool,
200-
) -> Result<DesiredUpgrades> {
201-
if to_lockfile {
202-
let locked = self.get_locked_dependencies()?;
203-
return Ok(DesiredUpgrades(
204-
self.get_all_requested_dependencies()
205-
.into_iter()
206-
.filter_map(|d| {
207-
for p in &locked {
208-
// The requested depenency may be present in the lock file with different versions,
209-
// but only one will be semver-compatible with requested version.
210-
if d.name == p.name && d.req.matches(&p.version) {
211-
return Some((d.name, Some(p.version.to_string())));
212-
}
213-
}
214-
return None;
215-
})
216-
.collect(),
217-
));
218-
}
219-
180+
fn get_dependencies(&self, only_update: Vec<String>) -> Result<DesiredUpgrades> {
220181
Ok(DesiredUpgrades(if only_update.is_empty() {
221182
// User hasn't asked for any specific dependencies to be upgraded, so upgrade all the
222183
// dependencies.
223-
self.get_all_requested_dependencies()
224-
.into_iter()
184+
self.0
185+
.iter()
186+
.flat_map(|&(_, ref package)| package.dependencies.clone())
187+
.filter(is_version_dep)
225188
.map(|dependency| (dependency.name, None))
226189
.collect()
227190
} else {
@@ -244,21 +207,7 @@ impl Manifests {
244207
/// Upgrade the manifests on disk following the previously-determined upgrade schema.
245208
fn upgrade(self, upgraded_deps: &ActualUpgrades, dry_run: bool) -> Result<()> {
246209
if dry_run {
247-
let bufwtr = BufferWriter::stdout(ColorChoice::Always);
248-
let mut buffer = bufwtr.buffer();
249-
buffer
250-
.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))
251-
.chain_err(|| "Failed to set output colour")?;
252-
write!(&mut buffer, "Starting dry run. ")
253-
.chain_err(|| "Failed to write dry run message")?;
254-
buffer
255-
.set_color(&ColorSpec::new())
256-
.chain_err(|| "Failed to clear output colour")?;
257-
writeln!(&mut buffer, "Changes will not be saved.")
258-
.chain_err(|| "Failed to write dry run message")?;
259-
bufwtr
260-
.print(&buffer)
261-
.chain_err(|| "Failed to print dry run message")?;
210+
dry_run_message()?;
262211
}
263212

264213
for (mut manifest, package) in self.0 {
@@ -271,6 +220,58 @@ impl Manifests {
271220

272221
Ok(())
273222
}
223+
224+
/// Update dependencies in Cargo.toml file(s) to match the corresponding
225+
/// version in Cargo.lock.
226+
fn sync_to_lockfile(self, dry_run: bool) -> Result<()> {
227+
// Get locked dependencies. If there may be multiple Cargo.toml
228+
// files in the workspace, then they all share the same lockfile,
229+
// so it suffices to get the query metadata for any one of them.
230+
let (manifest, _package) = self.0.iter().next().expect("should have one");
231+
let mut cmd = cargo_metadata::MetadataCommand::new();
232+
cmd.manifest_path(manifest.path.clone());
233+
cmd.other_options(vec!["--locked".to_string()]);
234+
235+
let result = cmd
236+
.exec()
237+
.map_err(|e| Error::from(e.compat()).chain_err(|| "Invalid manifest"))?;
238+
239+
let locked = result
240+
.packages
241+
.into_iter()
242+
.filter(|p| p.source.is_some()) // Source is none for local packages
243+
.collect::<Vec<_>>();
244+
245+
if dry_run {
246+
dry_run_message()?;
247+
}
248+
249+
for (mut manifest, package) in self.0 {
250+
println!("{}:", package.name);
251+
252+
// Upgrade the manifests one at a time, as multiple manifests may
253+
// request the same dependency at differing versions.
254+
for (name, version) in package
255+
.dependencies
256+
.clone()
257+
.into_iter()
258+
.filter(is_version_dep)
259+
.filter_map(|d| {
260+
for p in &locked {
261+
// The requested depenency may be present in the lock file with different versions,
262+
// but only one will be semver-compatible with requested version.
263+
if d.name == p.name && d.req.matches(&p.version) {
264+
return Some((d.name, p.version.to_string()));
265+
}
266+
}
267+
return None;
268+
})
269+
{
270+
manifest.upgrade(&Dependency::new(&name).set_version(&version), dry_run)?;
271+
}
272+
}
273+
Ok(())
274+
}
274275
}
275276

276277
/// The set of dependencies to be upgraded, alongside desired versions, if specified by the user.
@@ -331,12 +332,16 @@ fn process(args: Args) -> Result<()> {
331332
Manifests::get_local_one(&manifest_path)
332333
}?;
333334

334-
let existing_dependencies = manifests.get_dependencies(dependency, to_lockfile)?;
335+
if to_lockfile {
336+
manifests.sync_to_lockfile(dry_run)
337+
} else {
338+
let existing_dependencies = manifests.get_dependencies(dependency)?;
335339

336-
let upgraded_dependencies =
337-
existing_dependencies.get_upgraded(allow_prerelease, &find(&manifest_path)?)?;
340+
let upgraded_dependencies =
341+
existing_dependencies.get_upgraded(allow_prerelease, &find(&manifest_path)?)?;
338342

339-
manifests.upgrade(&upgraded_dependencies, dry_run)
343+
manifests.upgrade(&upgraded_dependencies, dry_run)
344+
}
340345
}
341346

342347
fn main() {

tests/cargo-upgrade.rs

+18
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub fn copy_workspace_test() -> (tempdir::TempDir, String, Vec<String>) {
3838

3939
let root_manifest_path = copy_in(".", "Cargo.toml");
4040
copy_in(".", "dummy.rs");
41+
copy_in(".", "Cargo.lock");
4142

4243
let workspace_manifest_paths = ["one", "two", "implicit/three", "explicit/four"]
4344
.iter()
@@ -326,6 +327,23 @@ fn upgrade_to_lockfile() {
326327
assert_eq!(target.to_string(), upgraded.to_string());
327328
}
328329

330+
#[test]
331+
fn upgrade_workspace_to_lockfile() {
332+
let (tmpdir, root_manifest, _workspace_manifests) = copy_workspace_test();
333+
334+
execute_command(&["upgrade", "--all", "--to-lockfile"], &root_manifest);
335+
336+
// The members one and two both request different, semver incompatible
337+
// versions of rand. Test that both were upgraded correctly.
338+
let one_upgraded = get_toml(tmpdir.path().join("one/Cargo.toml").to_str().unwrap());
339+
let one_target = get_toml("tests/fixtures/workspace/one/Cargo.toml.lockfile_target");
340+
assert_eq!(one_target.to_string(), one_upgraded.to_string());
341+
342+
let two_upgraded = get_toml(tmpdir.path().join("two/Cargo.toml").to_str().unwrap());
343+
let two_target = get_toml("tests/fixtures/workspace/two/Cargo.toml.lockfile_target");
344+
assert_eq!(two_target.to_string(), two_upgraded.to_string());
345+
}
346+
329347
#[test]
330348
#[cfg(feature = "test-external-apis")]
331349
fn upgrade_prints_messages() {

tests/fixtures/workspace/Cargo.lock

+137
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)