Skip to content

Commit a769f8a

Browse files
committed
Add '--to-lockfile' flag to cargo-upgrade
When this flag is present, all dependencies in 'Cargo.toml' are upgraded to the locked version as recorded in 'Cargo.lock'. Multiple manifests in a workspace may request the same dependency at different versions. The version of each dependency is updated to the corresponding semver compatible version in the lockfile.
1 parent 8f8028e commit a769f8a

13 files changed

+2003
-37
lines changed

src/bin/add/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ use cargo_edit::{find, update_registry_index, Dependency, Manifest};
1919
use std::io::Write;
2020
use std::process;
2121
use structopt::StructOpt;
22-
use toml_edit::Item as TomlItem;
2322
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
23+
use toml_edit::Item as TomlItem;
2424

2525
mod args;
2626

src/bin/upgrade/main.rs

+101-30
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ Dev, build, and all target dependencies will also be upgraded. Only dependencies
5353
supported. Git/path dependencies will be ignored.
5454
5555
All packages in the workspace will be upgraded if the `--all` flag is supplied. The `--all` flag may
56-
be supplied in the presence of a virtual manifest."
56+
be supplied in the presence of a virtual manifest.
57+
58+
If the '--to-lockfile' flag is supplied, all dependencies will be upraged to the currently locked
59+
version as recorded in the local lock file. The local lock file must be present and up to date if
60+
this flag is passed."
5761
)]
5862
Upgrade(Args),
5963
}
@@ -82,11 +86,42 @@ struct Args {
8286
/// Run without accessing the network
8387
#[structopt(long = "offline")]
8488
pub offline: bool,
89+
90+
/// Upgrade all packages to the version in the lockfile.
91+
#[structopt(long = "to-lockfile", conflicts_with = "dependency")]
92+
pub to_lockfile: bool,
8593
}
8694

8795
/// A collection of manifests.
8896
struct Manifests(Vec<(LocalManifest, cargo_metadata::Package)>);
8997

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+
90125
impl Manifests {
91126
/// Get all manifests in the workspace.
92127
fn get_all(manifest_path: &Option<PathBuf>) -> Result<Self> {
@@ -143,16 +178,6 @@ impl Manifests {
143178
/// Get the the combined set of dependencies to upgrade. If the user has specified
144179
/// per-dependency desired versions, extract those here.
145180
fn get_dependencies(&self, only_update: Vec<String>) -> Result<DesiredUpgrades> {
146-
/// Helper function to check whether a `cargo_metadata::Dependency` is a version dependency.
147-
fn is_version_dep(dependency: &cargo_metadata::Dependency) -> bool {
148-
match dependency.source {
149-
// This is the criterion cargo uses (in `SourceId::from_url`) to decide whether a
150-
// dependency has the 'registry' kind.
151-
Some(ref s) => s.splitn(2, '+').next() == Some("registry"),
152-
_ => false,
153-
}
154-
}
155-
156181
Ok(DesiredUpgrades(if only_update.is_empty() {
157182
// User hasn't asked for any specific dependencies to be upgraded, so upgrade all the
158183
// dependencies.
@@ -182,21 +207,7 @@ impl Manifests {
182207
/// Upgrade the manifests on disk following the previously-determined upgrade schema.
183208
fn upgrade(self, upgraded_deps: &ActualUpgrades, dry_run: bool) -> Result<()> {
184209
if dry_run {
185-
let bufwtr = BufferWriter::stdout(ColorChoice::Always);
186-
let mut buffer = bufwtr.buffer();
187-
buffer
188-
.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))
189-
.chain_err(|| "Failed to set output colour")?;
190-
write!(&mut buffer, "Starting dry run. ")
191-
.chain_err(|| "Failed to write dry run message")?;
192-
buffer
193-
.set_color(&ColorSpec::new())
194-
.chain_err(|| "Failed to clear output colour")?;
195-
writeln!(&mut buffer, "Changes will not be saved.")
196-
.chain_err(|| "Failed to write dry run message")?;
197-
bufwtr
198-
.print(&buffer)
199-
.chain_err(|| "Failed to print dry run message")?;
210+
dry_run_message()?;
200211
}
201212

202213
for (mut manifest, package) in self.0 {
@@ -209,6 +220,61 @@ impl Manifests {
209220

210221
Ok(())
211222
}
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. For workspaces with multiple Cargo.toml
228+
// files, there is only a single lockfile, so it suffices to get
229+
// metadata for any one of Cargo.toml files.
230+
let (manifest, _package) =
231+
self.0.iter().next().ok_or_else(|| {
232+
ErrorKind::CargoEditLib(::cargo_edit::ErrorKind::InvalidCargoConfig)
233+
})?;
234+
let mut cmd = cargo_metadata::MetadataCommand::new();
235+
cmd.manifest_path(manifest.path.clone());
236+
cmd.other_options(vec!["--locked".to_string()]);
237+
238+
let result = cmd
239+
.exec()
240+
.map_err(|e| Error::from(e.compat()).chain_err(|| "Invalid manifest"))?;
241+
242+
let locked = result
243+
.packages
244+
.into_iter()
245+
.filter(|p| p.source.is_some()) // Source is none for local packages
246+
.collect::<Vec<_>>();
247+
248+
if dry_run {
249+
dry_run_message()?;
250+
}
251+
252+
for (mut manifest, package) in self.0 {
253+
println!("{}:", package.name);
254+
255+
// Upgrade the manifests one at a time, as multiple manifests may
256+
// request the same dependency at differing versions.
257+
for (name, version) in package
258+
.dependencies
259+
.clone()
260+
.into_iter()
261+
.filter(is_version_dep)
262+
.filter_map(|d| {
263+
for p in &locked {
264+
// The requested depenency may be present in the lock file with different versions,
265+
// but only one will be semver-compatible with requested version.
266+
if d.name == p.name && d.req.matches(&p.version) {
267+
return Some((d.name, p.version.to_string()));
268+
}
269+
}
270+
None
271+
})
272+
{
273+
manifest.upgrade(&Dependency::new(&name).set_version(&version), dry_run)?;
274+
}
275+
}
276+
Ok(())
277+
}
212278
}
213279

214280
/// The set of dependencies to be upgraded, alongside desired versions, if specified by the user.
@@ -255,6 +321,7 @@ fn process(args: Args) -> Result<()> {
255321
all,
256322
allow_prerelease,
257323
dry_run,
324+
to_lockfile,
258325
..
259326
} = args;
260327

@@ -268,12 +335,16 @@ fn process(args: Args) -> Result<()> {
268335
Manifests::get_local_one(&manifest_path)
269336
}?;
270337

271-
let existing_dependencies = manifests.get_dependencies(dependency)?;
338+
if to_lockfile {
339+
manifests.sync_to_lockfile(dry_run)
340+
} else {
341+
let existing_dependencies = manifests.get_dependencies(dependency)?;
272342

273-
let upgraded_dependencies =
274-
existing_dependencies.get_upgraded(allow_prerelease, &find(&manifest_path)?)?;
343+
let upgraded_dependencies =
344+
existing_dependencies.get_upgraded(allow_prerelease, &find(&manifest_path)?)?;
275345

276-
manifests.upgrade(&upgraded_dependencies, dry_run)
346+
manifests.upgrade(&upgraded_dependencies, dry_run)
347+
}
277348
}
278349

279350
fn main() {

src/manifest.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ impl str::FromStr for Manifest {
355355
#[derive(Debug)]
356356
pub struct LocalManifest {
357357
/// Path to the manifest
358-
path: PathBuf,
358+
pub path: PathBuf,
359359
/// Manifest contents
360360
manifest: Manifest,
361361
}

tests/cargo-add.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1101,7 +1101,9 @@ fn adds_sorted_dependencies() {
11011101

11021102
// and all the dependencies in the output get sorted
11031103
let toml = get_toml(&manifest);
1104-
assert_eq!(toml.to_string(), r#"[package]
1104+
assert_eq!(
1105+
toml.to_string(),
1106+
r#"[package]
11051107
name = "cargo-list-test-fixture"
11061108
version = "0.0.0"
11071109
@@ -1112,4 +1114,3 @@ toml_edit = "0.1.5"
11121114
"#
11131115
);
11141116
}
1115-

tests/cargo-upgrade.rs

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#[macro_use]
22
extern crate pretty_assertions;
33

4-
use std::fs;
4+
use std::{fs, path::Path};
55

66
mod utils;
77
use crate::utils::{clone_out_test, execute_command, get_command_path, get_toml};
@@ -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()
@@ -309,6 +310,40 @@ For more information try --help ",
309310
.unwrap();
310311
}
311312

313+
// Verify that an upgraded Cargo.toml matches what we expect.
314+
#[test]
315+
fn upgrade_to_lockfile() {
316+
let (tmpdir, manifest) = clone_out_test("tests/fixtures/upgrade/Cargo.toml.source");
317+
fs::copy(
318+
Path::new("tests/fixtures/upgrade/Cargo.lock"),
319+
tmpdir.path().join("Cargo.lock"),
320+
)
321+
.unwrap_or_else(|err| panic!("could not copy test lock file: {}", err));;
322+
execute_command(&["upgrade", "--to-lockfile"], &manifest);
323+
324+
let upgraded = get_toml(&manifest);
325+
let target = get_toml("tests/fixtures/upgrade/Cargo.toml.lockfile_target");
326+
327+
assert_eq!(target.to_string(), upgraded.to_string());
328+
}
329+
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+
312347
#[test]
313348
#[cfg(feature = "test-external-apis")]
314349
fn upgrade_prints_messages() {

0 commit comments

Comments
 (0)