Skip to content

Commit 2ba7df8

Browse files
bors[bot]tofay
andauthored
Merge #337
337: Add '--to-lockfile' flag to cargo-upgrade r=ordian a=tofay When this flag is present, all dependencies in 'Cargo.toml' are upgraded to the locked version as recorded in 'Cargo.lock'. Fixes #297 Co-authored-by: Tom Fay <[email protected]> Co-authored-by: Tom Fay <[email protected]>
2 parents e420ccf + 6fed4c2 commit 2ba7df8

12 files changed

+421
-49
lines changed

src/bin/upgrade/main.rs

+117-45
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ Dev, build, and all target dependencies will also be upgraded. Only dependencies
5555
supported. Git/path dependencies will be ignored.
5656
5757
All packages in the workspace will be upgraded if the `--all` flag is supplied. The `--all` flag may
58-
be supplied in the presence of a virtual manifest."
58+
be supplied in the presence of a virtual manifest.
59+
60+
If the '--to-lockfile' flag is supplied, all dependencies will be upgraded to the currently locked
61+
version as recorded in the Cargo.lock file. This flag requires that the Cargo.lock file is
62+
up-to-date. If the lock file is missing, or it needs to be updated, cargo-upgrade will exit with an
63+
error. If the '--to-lockfile' flag is supplied then the network won't be accessed."
5964
)]
6065
Upgrade(Args),
6166
}
@@ -84,11 +89,42 @@ struct Args {
8489
/// Run without accessing the network
8590
#[structopt(long = "offline")]
8691
pub offline: bool,
92+
93+
/// Upgrade all packages to the version in the lockfile.
94+
#[structopt(long = "to-lockfile", conflicts_with = "dependency")]
95+
pub to_lockfile: bool,
8796
}
8897

8998
/// A collection of manifests.
9099
struct Manifests(Vec<(LocalManifest, cargo_metadata::Package)>);
91100

101+
/// Helper function to check whether a `cargo_metadata::Dependency` is a version dependency.
102+
fn is_version_dep(dependency: &cargo_metadata::Dependency) -> bool {
103+
match dependency.source {
104+
// This is the criterion cargo uses (in `SourceId::from_url`) to decide whether a
105+
// dependency has the 'registry' kind.
106+
Some(ref s) => s.splitn(2, '+').next() == Some("registry"),
107+
_ => false,
108+
}
109+
}
110+
111+
fn dry_run_message() -> Result<()> {
112+
let bufwtr = BufferWriter::stdout(ColorChoice::Always);
113+
let mut buffer = bufwtr.buffer();
114+
buffer
115+
.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))
116+
.chain_err(|| "Failed to set output colour")?;
117+
write!(&mut buffer, "Starting dry run. ").chain_err(|| "Failed to write dry run message")?;
118+
buffer
119+
.set_color(&ColorSpec::new())
120+
.chain_err(|| "Failed to clear output colour")?;
121+
writeln!(&mut buffer, "Changes will not be saved.")
122+
.chain_err(|| "Failed to write dry run message")?;
123+
bufwtr
124+
.print(&buffer)
125+
.chain_err(|| "Failed to print dry run message")
126+
}
127+
92128
impl Manifests {
93129
/// Get all manifests in the workspace.
94130
fn get_all(manifest_path: &Option<PathBuf>) -> Result<Self> {
@@ -145,16 +181,6 @@ impl Manifests {
145181
/// Get the the combined set of dependencies to upgrade. If the user has specified
146182
/// per-dependency desired versions, extract those here.
147183
fn get_dependencies(&self, only_update: Vec<String>) -> Result<DesiredUpgrades> {
148-
/// Helper function to check whether a `cargo_metadata::Dependency` is a version dependency.
149-
fn is_version_dep(dependency: &cargo_metadata::Dependency) -> bool {
150-
match dependency.source {
151-
// This is the criterion cargo uses (in `SourceId::from_url`) to decide whether a
152-
// dependency has the 'registry' kind.
153-
Some(ref s) => s.splitn(2, '+').next() == Some("registry"),
154-
_ => false,
155-
}
156-
}
157-
158184
// Map the names of user-specified dependencies to the (optionally) requested version.
159185
let selected_dependencies = only_update
160186
.into_iter()
@@ -203,21 +229,7 @@ impl Manifests {
203229
/// Upgrade the manifests on disk following the previously-determined upgrade schema.
204230
fn upgrade(self, upgraded_deps: &ActualUpgrades, dry_run: bool) -> Result<()> {
205231
if dry_run {
206-
let bufwtr = BufferWriter::stdout(ColorChoice::Always);
207-
let mut buffer = bufwtr.buffer();
208-
buffer
209-
.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))
210-
.chain_err(|| "Failed to set output colour")?;
211-
write!(&mut buffer, "Starting dry run. ")
212-
.chain_err(|| "Failed to write dry run message")?;
213-
buffer
214-
.set_color(&ColorSpec::new())
215-
.chain_err(|| "Failed to clear output colour")?;
216-
writeln!(&mut buffer, "Changes will not be saved.")
217-
.chain_err(|| "Failed to write dry run message")?;
218-
bufwtr
219-
.print(&buffer)
220-
.chain_err(|| "Failed to print dry run message")?;
232+
dry_run_message()?;
221233
}
222234

223235
for (mut manifest, package) in self.0 {
@@ -234,6 +246,61 @@ impl Manifests {
234246

235247
Ok(())
236248
}
249+
250+
/// Update dependencies in Cargo.toml file(s) to match the corresponding
251+
/// version in Cargo.lock.
252+
fn sync_to_lockfile(self, dry_run: bool) -> Result<()> {
253+
// Get locked dependencies. For workspaces with multiple Cargo.toml
254+
// files, there is only a single lockfile, so it suffices to get
255+
// metadata for any one of Cargo.toml files.
256+
let (manifest, _package) =
257+
self.0.iter().next().ok_or_else(|| {
258+
ErrorKind::CargoEditLib(::cargo_edit::ErrorKind::InvalidCargoConfig)
259+
})?;
260+
let mut cmd = cargo_metadata::MetadataCommand::new();
261+
cmd.manifest_path(manifest.path.clone());
262+
cmd.other_options(vec!["--locked".to_string()]);
263+
264+
let result = cmd
265+
.exec()
266+
.map_err(|e| Error::from(e.compat()).chain_err(|| "Invalid manifest"))?;
267+
268+
let locked = result
269+
.packages
270+
.into_iter()
271+
.filter(|p| p.source.is_some()) // Source is none for local packages
272+
.collect::<Vec<_>>();
273+
274+
if dry_run {
275+
dry_run_message()?;
276+
}
277+
278+
for (mut manifest, package) in self.0 {
279+
println!("{}:", package.name);
280+
281+
// Upgrade the manifests one at a time, as multiple manifests may
282+
// request the same dependency at differing versions.
283+
for (name, version) in package
284+
.dependencies
285+
.clone()
286+
.into_iter()
287+
.filter(is_version_dep)
288+
.filter_map(|d| {
289+
for p in &locked {
290+
// The requested dependency may be present in the lock file with different versions,
291+
// but only one will be semver-compatible with the requested version.
292+
if d.name == p.name && d.req.matches(&p.version) {
293+
return Some((d.name, p.version.to_string()));
294+
}
295+
}
296+
None
297+
})
298+
{
299+
manifest.upgrade(&Dependency::new(&name).set_version(&version), dry_run)?;
300+
}
301+
}
302+
Ok(())
303+
}
237304
}
238305

239306
/// The set of dependencies to be upgraded, alongside the registries returned from cargo metadata, and
@@ -287,10 +354,11 @@ fn process(args: Args) -> Result<()> {
287354
all,
288355
allow_prerelease,
289356
dry_run,
357+
to_lockfile,
290358
..
291359
} = args;
292360

293-
if !args.offline {
361+
if !args.offline && !to_lockfile {
294362
let url = registry_url(&find(&manifest_path)?, None)?;
295363
update_registry_index(&url)?;
296364
}
@@ -301,27 +369,31 @@ fn process(args: Args) -> Result<()> {
301369
Manifests::get_local_one(&manifest_path)
302370
}?;
303371

304-
let existing_dependencies = manifests.get_dependencies(dependency)?;
305-
306-
// Update indices for any alternative registries, unless
307-
// we're offline.
308-
if !args.offline {
309-
for registry_url in existing_dependencies
310-
.0
311-
.values()
312-
.filter_map(|(registry, _)| registry.as_ref())
313-
.collect::<HashSet<_>>()
314-
{
315-
update_registry_index(&Url::parse(registry_url).map_err(|_| {
316-
ErrorKind::CargoEditLib(::cargo_edit::ErrorKind::InvalidCargoConfig)
317-
})?)?;
372+
if to_lockfile {
373+
manifests.sync_to_lockfile(dry_run)
374+
} else {
375+
let existing_dependencies = manifests.get_dependencies(dependency)?;
376+
377+
// Update indices for any alternative registries, unless
378+
// we're offline.
379+
if !args.offline {
380+
for registry_url in existing_dependencies
381+
.0
382+
.values()
383+
.filter_map(|(registry, _)| registry.as_ref())
384+
.collect::<HashSet<_>>()
385+
{
386+
update_registry_index(&Url::parse(registry_url).map_err(|_| {
387+
ErrorKind::CargoEditLib(::cargo_edit::ErrorKind::InvalidCargoConfig)
388+
})?)?;
389+
}
318390
}
319-
}
320391

321-
let upgraded_dependencies =
322-
existing_dependencies.get_upgraded(allow_prerelease, &find(&manifest_path)?)?;
392+
let upgraded_dependencies =
393+
existing_dependencies.get_upgraded(allow_prerelease, &find(&manifest_path)?)?;
323394

324-
manifests.upgrade(&upgraded_dependencies, dry_run)
395+
manifests.upgrade(&upgraded_dependencies, dry_run)
396+
}
325397
}
326398

327399
fn main() {

src/manifest.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ impl str::FromStr for Manifest {
366366
#[derive(Debug)]
367367
pub struct LocalManifest {
368368
/// Path to the manifest
369-
path: PathBuf,
369+
pub path: PathBuf,
370370
/// Manifest contents
371371
manifest: Manifest,
372372
}

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::{
@@ -41,6 +41,7 @@ pub fn copy_workspace_test() -> (tempdir::TempDir, String, Vec<String>) {
4141

4242
let root_manifest_path = copy_in(".", "Cargo.toml");
4343
copy_in(".", "dummy.rs");
44+
copy_in(".", "Cargo.lock");
4445

4546
let workspace_manifest_paths = ["one", "two", "implicit/three", "explicit/four"]
4647
.iter()
@@ -415,6 +416,40 @@ For more information try --help ",
415416
.unwrap();
416417
}
417418

419+
// Verify that an upgraded Cargo.toml matches what we expect.
420+
#[test]
421+
fn upgrade_to_lockfile() {
422+
let (tmpdir, manifest) = clone_out_test("tests/fixtures/upgrade/Cargo.toml.lockfile_source");
423+
fs::copy(
424+
Path::new("tests/fixtures/upgrade/Cargo.lock"),
425+
tmpdir.path().join("Cargo.lock"),
426+
)
427+
.unwrap_or_else(|err| panic!("could not copy test lock file: {}", err));;
428+
execute_command(&["upgrade", "--to-lockfile"], &manifest);
429+
430+
let upgraded = get_toml(&manifest);
431+
let target = get_toml("tests/fixtures/upgrade/Cargo.toml.lockfile_target");
432+
433+
assert_eq!(target.to_string(), upgraded.to_string());
434+
}
435+
436+
#[test]
437+
fn upgrade_workspace_to_lockfile() {
438+
let (tmpdir, root_manifest, _workspace_manifests) = copy_workspace_test();
439+
440+
execute_command(&["upgrade", "--all", "--to-lockfile"], &root_manifest);
441+
442+
// The members one and two both request different, semver incompatible
443+
// versions of rand. Test that both were upgraded correctly.
444+
let one_upgraded = get_toml(tmpdir.path().join("one/Cargo.toml").to_str().unwrap());
445+
let one_target = get_toml("tests/fixtures/workspace/one/Cargo.toml.lockfile_target");
446+
assert_eq!(one_target.to_string(), one_upgraded.to_string());
447+
448+
let two_upgraded = get_toml(tmpdir.path().join("two/Cargo.toml").to_str().unwrap());
449+
let two_target = get_toml("tests/fixtures/workspace/two/Cargo.toml.lockfile_target");
450+
assert_eq!(two_target.to_string(), two_upgraded.to_string());
451+
}
452+
418453
#[test]
419454
#[cfg(feature = "test-external-apis")]
420455
fn upgrade_prints_messages() {

tests/fixtures/upgrade/Cargo.lock

+78
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "one"
3+
version = "0.1.0"
4+
5+
[lib]
6+
path = "../dummy.rs"
7+
8+
[dependencies]
9+
libc = "0.2.28"
10+
rand = "0.3"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "one"
3+
version = "0.1.0"
4+
5+
[lib]
6+
path = "../dummy.rs"
7+
8+
[dependencies]
9+
libc = "0.2.65"
10+
rand = "0.3.10"

0 commit comments

Comments
 (0)