From 899ad785bb64a6cae673b8502e45d467008a9fb4 Mon Sep 17 00:00:00 2001 From: Benjamin Gill Date: Mon, 5 Feb 2018 00:10:40 +0000 Subject: [PATCH 01/11] Re-write upgrade API --- src/bin/upgrade/main.rs | 15 +++++++-------- tests/cargo-upgrade.rs | 8 ++++---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/bin/upgrade/main.rs b/src/bin/upgrade/main.rs index 15f269719f..13ea59fbea 100644 --- a/src/bin/upgrade/main.rs +++ b/src/bin/upgrade/main.rs @@ -36,15 +36,14 @@ static USAGE: &'static str = r" Upgrade all dependencies in a manifest file to the latest version. Usage: - cargo upgrade [--all] [--dependency ...] [--manifest-path ] [options] + cargo upgrade [options] + cargo upgrade [options] ... cargo upgrade (-h | --help) cargo upgrade (-V | --version) Options: --all Upgrade all packages in the workspace. - -d --dependency Specific dependency to upgrade. If this option is used, only the - specified dependencies will be upgraded. - --manifest-path Path to the manifest to upgrade. + --manifest-path PATH Path to the manifest to upgrade. --allow-prerelease Include prerelease versions when fetching from crates.io (e.g. '0.6.0-alpha'). Defaults to false. -h --help Show this help page. @@ -61,8 +60,8 @@ be supplied in the presence of a virtual manifest. #[derive(Debug, Deserialize)] struct Args { /// `--dependency -d ` - flag_dependency: Vec, - /// `--manifest-path ` + arg_dependency: Vec, + /// `--manifest-path ` flag_manifest_path: Option, /// `--version` flag_version: bool, @@ -202,13 +201,13 @@ fn main() { let output = if args.flag_all { upgrade_workspace_manifests( &args.flag_manifest_path, - &args.flag_dependency, + &args.arg_dependency, args.flag_allow_prerelease, ) } else { upgrade_manifest( &args.flag_manifest_path, - &args.flag_dependency, + &args.arg_dependency, args.flag_allow_prerelease, ) }; diff --git a/tests/cargo-upgrade.rs b/tests/cargo-upgrade.rs index 49730cdb44..0fafba4940 100644 --- a/tests/cargo-upgrade.rs +++ b/tests/cargo-upgrade.rs @@ -68,7 +68,7 @@ fn upgrade_specified_only() { ); // Update `versioned-package` to the latest version - execute_command(&["upgrade", "-d", "versioned-package"], &manifest); + execute_command(&["upgrade", "versioned-package"], &manifest); // Verify that `versioned-package` was upgraded, but not `versioned-package-2` let dependencies = &get_toml(&manifest)["dependencies"]; @@ -87,7 +87,7 @@ fn fails_to_upgrade_missing_dependency() { let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample"); // Update the non-existent `failure` to the latest version - execute_command(&["upgrade", "-d", "failure"], &manifest); + execute_command(&["upgrade", "failure"], &manifest); // Verify that `failure` has not been added assert!(get_toml(&manifest)["dependencies"]["failure"].is_none()); @@ -236,7 +236,8 @@ fn unknown_flags() { "Unknown flag: '--flag' Usage: - cargo upgrade [--all] [--dependency ...] [--manifest-path ] [options] + cargo upgrade [options] + cargo upgrade [options] ... [--precise ] cargo upgrade (-h | --help) cargo upgrade (-V | --version)", ) @@ -250,7 +251,6 @@ fn upgrade_prints_messages() { assert_cli::Assert::command(&[ "target/debug/cargo-upgrade", "upgrade", - "-d", "docopt", &format!("--manifest-path={}", manifest), ]).succeeds() From b16d3c6bf28c868305de3594fcde23bd85e7122e Mon Sep 17 00:00:00 2001 From: Benjamin Gill Date: Mon, 5 Feb 2018 01:52:01 +0000 Subject: [PATCH 02/11] Add `--precise` flag for upgrade Fixes #177. If this flag is provided, all dependencies upgraded will be blindly upgraded to the version specified. Also, there has been substantial refactoring. The new `Manifests` and `Dependencies` structs now hold the logic for upgrade. This allows us to commonise the cases of a single upgrade and full workspace upgrade quite considerably. This should also make it fairly simple to extend to permit providing a list of packages to confine upgrades to. Also does some tidying up for the new `--precise` option. Most significantly, we now have a test for this functionality. --- Cargo.lock | 6 +- Cargo.toml | 2 +- src/bin/upgrade/main.rs | 250 ++++++++++++----------- src/lib.rs | 3 +- src/manifest.rs | 63 +++++- tests/cargo-add.rs | 3 + tests/cargo-upgrade.rs | 171 +++++++++------- tests/fixtures/add/Cargo.toml.sample | 3 + tests/fixtures/upgrade/Cargo.toml.source | 24 +-- tests/fixtures/upgrade/Cargo.toml.target | 24 +-- 10 files changed, 309 insertions(+), 240 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2072ce4021..ce3c338bfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,7 +113,7 @@ version = "0.3.0-beta.1" dependencies = [ "assert_cli 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "cargo_metadata 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cargo_metadata 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_proxy 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -132,7 +132,7 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.3.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1107,7 +1107,7 @@ dependencies = [ "checksum build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e90dc84f5e62d2ebe7676b83c22d33b6db8bd27340fb6ffbff0a364efa0cb9c9" "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" "checksum bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d828f97b58cc5de3e40c421d0cf2132d6b2da4ee0e11b8632fa838f0f9333ad6" -"checksum cargo_metadata 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1f56ec3e469bca7c276f2eea015aa05c5e381356febdbb0683c2580189604537" +"checksum cargo_metadata 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f410f43295c912ae1328de55e5c050dbef882c17b836f5ed41cc8b96c40d6cc5" "checksum cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b13a57efd6b30ecd6598ebdb302cca617930b5470647570468a65d12ef9719" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" "checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9" diff --git a/Cargo.toml b/Cargo.toml index 4478c28306..2584e41926 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ repository = "killercup/cargo-edit" repository = "killercup/cargo-edit" [dependencies] -cargo_metadata = "0.3.0" +cargo_metadata = "0.4.1" docopt = "0.8" env_proxy = "0.2" error-chain = "0.11.0" diff --git a/src/bin/upgrade/main.rs b/src/bin/upgrade/main.rs index 13ea59fbea..40daf202b2 100644 --- a/src/bin/upgrade/main.rs +++ b/src/bin/upgrade/main.rs @@ -11,12 +11,12 @@ extern crate error_chain; extern crate serde_derive; extern crate toml_edit; -use std::collections::HashMap; -use std::path::Path; +use std::collections::HashSet; +use std::path::{Path, PathBuf}; use std::process; extern crate cargo_edit; -use cargo_edit::{get_latest_dependency, Dependency, Manifest}; +use cargo_edit::{find, get_latest_dependency, Dependency, LocalManifest}; mod errors { error_chain!{ @@ -37,12 +37,13 @@ Upgrade all dependencies in a manifest file to the latest version. Usage: cargo upgrade [options] - cargo upgrade [options] ... + cargo upgrade [options] ... [--precise ] cargo upgrade (-h | --help) cargo upgrade (-V | --version) Options: --all Upgrade all packages in the workspace. + --precise PRECISE Upgrade dependencies to exactly PRECISE. --manifest-path PATH Path to the manifest to upgrade. --allow-prerelease Include prerelease versions when fetching from crates.io (e.g. '0.6.0-alpha'). Defaults to false. @@ -59,9 +60,11 @@ be supplied in the presence of a virtual manifest. /// Docopts input args. #[derive(Debug, Deserialize)] struct Args { - /// `--dependency -d ` + /// `...` arg_dependency: Vec, - /// `--manifest-path ` + /// `--precise PRECISE` + flag_precise: Option, + /// `--manifest-path PATH` flag_manifest_path: Option, /// `--version` flag_version: bool, @@ -71,121 +74,136 @@ struct Args { flag_allow_prerelease: bool, } -fn is_version_dependency(dep: &toml_edit::Item) -> bool { - dep["git"].is_none() && dep["path"].is_none() -} +/// A collection of manifests. +struct Manifests(Vec<(LocalManifest, cargo_metadata::Package)>); -/// Upgrade the specified manifest. Use the closure provided to get the new dependency versions. -fn upgrade_manifest_using_dependencies( - manifest_path: &Option, - only_update: &[String], - new_dependency: F, -) -> Result<()> -where - F: Fn(&String) -> cargo_edit::Result, -{ - let manifest_path = manifest_path.as_ref().map(From::from); - let mut manifest = Manifest::open(&manifest_path)?; - - for (table_path, table) in manifest.get_sections() { - let table_like = table.as_table_like().expect("bug in get_sections"); - for (name, old_value) in table_like.iter() { - let owned = name.to_owned(); - if (only_update.is_empty() || only_update.contains(&owned)) - && is_version_dependency(old_value) - { - let latest_version = new_dependency(&owned)?; - - manifest.update_table_entry(&table_path, &latest_version)?; - } - } - } +impl Manifests { + /// Get all manifests in the workspace. + fn get_all(manifest_path: &Option) -> Result { + let manifest_path = manifest_path.clone().map(PathBuf::from); - let mut file = Manifest::find_file(&manifest_path)?; - manifest - .write_to_file(&mut file) - .chain_err(|| "Failed to write new manifest contents") -} + cargo_metadata::metadata_deps(manifest_path.as_ref().map(Path::new), true) + .chain_err(|| "Failed to get workspace metadata")? + .packages + .into_iter() + .map(|package| { + Ok(( + LocalManifest::try_new(Path::new(&package.manifest_path))?, + package, + )) + }) + .collect::>>() + .map(Manifests) + } -fn upgrade_manifest( - manifest_path: &Option, - only_update: &[String], - allow_prerelease: bool, -) -> Result<()> { - upgrade_manifest_using_dependencies(manifest_path, only_update, |name| { - get_latest_dependency(name, allow_prerelease) - }) -} + /// Get the manifest specified by the manifest path. Try to make an educated guess if no path is + /// provided. + fn get_local_one(manifest_path: &Option) -> Result { + let manifest_path = manifest_path.clone().map(PathBuf::from); + let resolved_manifest_path: String = find(&manifest_path)?.to_string_lossy().into(); -fn upgrade_manifest_from_cache( - manifest_path: &Option, - only_update: &[String], - new_deps: &HashMap, -) -> Result<()> { - upgrade_manifest_using_dependencies( - manifest_path, - only_update, - |name| Ok(new_deps[name].clone()), - ) -} + let manifest = LocalManifest::find(&manifest_path)?; -/// Get a list of the paths of all the (non-virtual) manifests in the workspace. -fn get_workspace_manifests(manifest_path: &Option) -> Result> { - Ok( - cargo_metadata::metadata_deps(manifest_path.as_ref().map(Path::new), true) - .chain_err(|| "Failed to get metadata")? - .packages + let packages = cargo_metadata::metadata_deps(manifest_path.as_ref().map(Path::new), true) + .chain_err(|| "Invalid manifest")? + .packages; + let package = packages .iter() - .map(|p| p.manifest_path.clone()) - .collect(), - ) -} + .find(|p| p.manifest_path == resolved_manifest_path) + // If we have successfully got metadata, but our manifest path does not correspond to a + // package, we must have been called against a virtual manifest. + .chain_err(|| "Found virtual manifest, but this command requires running against an \ + actual package in this workspace. Try adding `--all`.")?; -/// Look up all current direct crates.io dependencies in the workspace. Then get the latest version -/// for each. -fn get_new_workspace_deps( - manifest_path: &Option, - only_update: &[String], - allow_prerelease: bool, -) -> Result> { - let mut new_deps = HashMap::new(); - - cargo_metadata::metadata_deps(manifest_path.as_ref().map(|p| Path::new(p)), true) - .chain_err(|| "Failed to get metadata")? - .packages - .iter() - .flat_map(|package| package.dependencies.to_owned()) - .filter(|dependency| { - only_update.is_empty() || only_update.contains(&dependency.name) - }) - .map(|dependency| { - if !new_deps.contains_key(&dependency.name) { - new_deps.insert( - dependency.name.clone(), - get_latest_dependency(&dependency.name, allow_prerelease)?, - ); - } - Ok(()) - }) - .collect::>>()?; + Ok(Manifests(vec![(manifest, package.to_owned())])) + } - Ok(new_deps) -} + /// Get the combined set of dependencies of the manifests. + fn get_dependencies(&self, only_update: &[String]) -> Dependencies { + /// Helper function to check whether a `cargo_metadata::Dependency` is a version dependency. + fn is_version_dep(dependency: &cargo_metadata::Dependency) -> bool { + match dependency.source { + // This is the criterion cargo uses (in `SourceId::from_url`) to decide whether a + // dependency has the 'registry' kind. + Some(ref s) => s.splitn(2, '+').next() == Some("registry"), + _ => false, + } + } -fn upgrade_workspace_manifests( - manifest_path: &Option, - only_update: &[String], - allow_prerelease: bool, -) -> Result<()> { - let new_deps = get_new_workspace_deps(manifest_path, only_update, allow_prerelease)?; + Dependencies( + self.0 + .iter() + .flat_map(|&(_, ref package)| package.dependencies.clone()) + .filter(|dependency| { + only_update.is_empty() || only_update.contains(&dependency.name) + }) + .filter(is_version_dep) + .map(|dependency| { + // Convert manually from one dependency format to another. Ideally, this would + // be done by implementing `From`. However, that would require pulling in + // `cargo::SourceId::from_url`, which would entail pulling the entirety of + // cargo. + Dependency::new(&dependency.name) + .set_optional(dependency.optional) + .set_version(&dependency.req.to_string()) + }) + .collect(), + ) + } - get_workspace_manifests(manifest_path).and_then(|manifests| { - for manifest in manifests { - upgrade_manifest_from_cache(&Some(manifest), only_update, &new_deps)? + /// Upgrade the manifests on disk. They will upgrade using the new dependencies provided. + fn upgrade(self, upgraded_deps: &Dependencies) -> Result<()> { + for (mut manifest, _) in self.0 { + for dependency in &upgraded_deps.0 { + manifest.upgrade(dependency)?; + } } Ok(()) - }) + } +} + +/// This represents the version dependencies of the manifests that `cargo-upgrade` will upgrade. +struct Dependencies(HashSet); + +impl Dependencies { + /// Transform the dependencies into their upgraded forms. If a version is specified, all + /// dependencies will get that version. + fn get_upgraded( + self, + precise: &Option, + allow_prerelease: bool, + ) -> Result { + self.0 + .into_iter() + .map(|dependency| { + if let Some(ref precise) = *precise { + Ok(dependency.set_version(precise)) + } else { + get_latest_dependency(&dependency.name, allow_prerelease) + .chain_err(|| "Failed to get new version") + } + }) + .collect::>() + .map(Dependencies) + } +} + +/// Main processing function. Allows us to return a `Result` so that `main` can print pretty error +/// messages. +fn process(args: &Args) -> Result<()> { + let manifests = if args.flag_all { + Manifests::get_all(&args.flag_manifest_path) + } else { + Manifests::get_local_one(&args.flag_manifest_path) + }?; + + let existing_dependencies = manifests.get_dependencies(&args.arg_dependency.clone()); + + let upgraded_dependencies = + existing_dependencies.get_upgraded(&args.flag_precise, args.flag_allow_prerelease)?; + + manifests.upgrade(&upgraded_dependencies) } fn main() { @@ -198,21 +216,7 @@ fn main() { process::exit(0); } - let output = if args.flag_all { - upgrade_workspace_manifests( - &args.flag_manifest_path, - &args.arg_dependency, - args.flag_allow_prerelease, - ) - } else { - upgrade_manifest( - &args.flag_manifest_path, - &args.arg_dependency, - args.flag_allow_prerelease, - ) - }; - - if let Err(err) = output { + if let Err(err) = process(&args) { eprintln!("Command failed due to unhandled error: {}\n", err); for e in err.iter().skip(1) { diff --git a/src/lib.rs b/src/lib.rs index 91f50d8e44..ed729f7fa3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces, unused_qualifications)] +extern crate cargo_metadata; extern crate env_proxy; #[macro_use] extern crate error_chain; @@ -26,4 +27,4 @@ pub use dependency::Dependency; pub use errors::*; pub use fetch::{get_crate_name_from_github, get_crate_name_from_gitlab, get_crate_name_from_path, get_latest_dependency}; -pub use manifest::Manifest; +pub use manifest::{find, LocalManifest, Manifest}; diff --git a/src/manifest.rs b/src/manifest.rs index 9cb33aa7d4..c9cd4dadf2 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -1,5 +1,6 @@ use std::fs::{self, File, OpenOptions}; use std::io::{Read, Write}; +use std::ops::Deref; use std::path::{Path, PathBuf}; use std::{env, str}; @@ -11,7 +12,7 @@ use dependency::Dependency; const MANIFEST_FILENAME: &str = "Cargo.toml"; -/// A Cargo Manifest +/// A Cargo manifest #[derive(Debug, Clone)] pub struct Manifest { /// Manifest contents as TOML data @@ -23,7 +24,7 @@ pub struct Manifest { /// If a manifest is specified, return that one. If a path is specified, perform a manifest search /// starting from there. If nothing is specified, start searching from the current directory /// (`cwd`). -fn find(specified: &Option) -> Result { +pub fn find(specified: &Option) -> Result { match *specified { Some(ref path) if fs::metadata(&path) @@ -336,6 +337,64 @@ impl str::FromStr for Manifest { } } +/// A Cargo manifest that is available locally. +#[derive(Debug)] +pub struct LocalManifest { + /// Path to the manifest + path: PathBuf, + /// Manifest contents + manifest: Manifest, +} + +impl Deref for LocalManifest { + type Target = Manifest; + + fn deref(&self) -> &Manifest { + &self.manifest + } +} + +impl LocalManifest { + /// Construct a `LocalManifest`. If no path is provided, make an educated guess as to which one + /// the user means. + pub fn find(path: &Option) -> Result { + let path = find(path)?; + Self::try_new(&path) + } + + /// Construct the `LocalManifest` corresponding to the `Path` provided. + pub fn try_new(path: &Path) -> Result { + let path = path.to_path_buf(); + Ok(LocalManifest { + manifest: Manifest::open(&Some(path.clone()))?, + path: path, + }) + } + + /// Get the `File` corresponding to this manifest. + fn get_file(&self) -> Result { + Manifest::find_file(&Some(self.path.clone())) + } + + /// Instruct this manifest to upgrade a single dependency. If this manifest does not have that + /// dependency, it does nothing. + pub fn upgrade(&mut self, dependency: &Dependency) -> Result<()> { + for (table_path, table) in self.get_sections() { + let table_like = table.as_table_like().expect("Unexpected non-table"); + for (name, _old_value) in table_like.iter() { + if name == dependency.name { + self.manifest + .update_table_entry(&table_path, dependency)?; + } + } + } + + let mut file = self.get_file()?; + self.write_to_file(&mut file) + .chain_err(|| "Failed to write new manifest contents") + } +} + #[cfg(test)] mod tests { use dependency::Dependency; diff --git a/tests/cargo-add.rs b/tests/cargo-add.rs index f06aab7db8..150e5b9adf 100644 --- a/tests/cargo-add.rs +++ b/tests/cargo-add.rs @@ -667,6 +667,9 @@ fn overwrite_dependency_test(first_command: &[&str], second_command: &[&str], ex let expected = r#"[package] name = "cargo-list-test-fixture" version = "0.0.0" + +[lib] +path = "dummy.rs" "#.to_string() + expected; let expected_dep: toml_edit::Document = expected.parse().expect("toml parse error"); assert_eq!(expected_dep.to_string(), toml.to_string()); diff --git a/tests/cargo-upgrade.rs b/tests/cargo-upgrade.rs index 0fafba4940..3541e4c3a4 100644 --- a/tests/cargo-upgrade.rs +++ b/tests/cargo-upgrade.rs @@ -9,6 +9,53 @@ use std::fs; mod utils; use utils::{clone_out_test, execute_command, get_toml}; +/// Helper function that copies the workspace test into a temporary directory. +pub fn copy_workspace_test() -> (tempdir::TempDir, String, Vec) { + // Create a temporary directory and copy in the root manifest, the dummy rust file, and + // workspace member manifests. + let tmpdir = tempdir::TempDir::new("upgrade_workspace") + .expect("failed to construct temporary directory"); + + let (root_manifest_path, workspace_manifest_paths) = { + // Helper to copy in files to the temporary workspace. The standard library doesn't have a + // good equivalent of `cp -r`, hence this oddity. + let copy_in = |dir, file| { + let file_path = tmpdir + .path() + .join(dir) + .join(file) + .to_str() + .unwrap() + .to_string(); + + fs::create_dir_all(tmpdir.path().join(dir)).unwrap(); + + fs::copy( + format!("tests/fixtures/workspace/{}/{}", dir, file), + &file_path, + ).unwrap_or_else(|err| panic!("could not copy test file: {}", err)); + + file_path + }; + + let root_manifest_path = copy_in(".", "Cargo.toml"); + copy_in(".", "dummy.rs"); + + let workspace_manifest_paths = ["one", "two", "implicit/three", "explicit/four"] + .iter() + .map(|member| copy_in(member, "Cargo.toml")) + .collect::>(); + + (root_manifest_path, workspace_manifest_paths) + }; + + ( + tmpdir, + root_manifest_path, + workspace_manifest_paths.to_owned(), + ) +} + // Verify that an upgraded Cargo.toml matches what we expect. #[test] fn upgrade_as_expected() { @@ -26,16 +73,16 @@ fn upgrade_as_expected() { fn upgrade_all() { let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample"); - // Setup manifest with the dependency `versioned-package@0.1.1` - execute_command(&["add", "versioned-package", "--vers", "0.1.1"], &manifest); + // Setup manifest with the dependency `docopt@0.8.0` + execute_command(&["add", "docopt", "--vers", "0.8.0"], &manifest); - // Now, upgrade `versioned-package` to the latest version + // Now, upgrade `docopt` to the latest version execute_command(&["upgrade"], &manifest); - // Verify that `versioned-package` has been updated successfully. + // Verify that `docopt` has been updated successfully. assert_eq!( - get_toml(&manifest)["dependencies"]["versioned-package"].as_str(), - Some("versioned-package--CURRENT_VERSION_TEST") + get_toml(&manifest)["dependencies"]["docopt"].as_str(), + Some("docopt--CURRENT_VERSION_TEST") ); } @@ -43,16 +90,16 @@ fn upgrade_all() { fn upgrade_all_allow_prerelease() { let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample"); - // Setup manifest with the dependency `versioned-package@0.1.1` - execute_command(&["add", "versioned-package", "--vers", "0.1.1"], &manifest); + // Setup manifest with `docopt` + execute_command(&["add", "docopt", "--vers", "0.8"], &manifest); - // Now, upgrade `versioned-package` to the latest version + // Now, upgrade `docopt` to the latest version execute_command(&["upgrade", "--allow-prerelease"], &manifest); - // Verify that `versioned-package` has been updated successfully. + // Verify that `docopt` has been updated successfully. assert_eq!( - get_toml(&manifest)["dependencies"]["versioned-package"].as_str(), - Some("versioned-package--PRERELEASE_VERSION_TEST") + get_toml(&manifest)["dependencies"]["docopt"].as_str(), + Some("docopt--PRERELEASE_VERSION_TEST") ); } @@ -60,33 +107,27 @@ fn upgrade_all_allow_prerelease() { fn upgrade_specified_only() { let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample"); - // Setup manifest with the dependencies `versioned-package` and `versioned-package-2` - execute_command(&["add", "versioned-package", "--vers", "0.1.1"], &manifest); - execute_command( - &["add", "versioned-package-2", "--vers", "0.1.1"], - &manifest, - ); + // Setup manifest with the dependencies `env_proxy` and `docopt` + execute_command(&["add", "docopt", "--vers", "0.8"], &manifest); + execute_command(&["add", "env_proxy", "--vers", "0.1.1"], &manifest); - // Update `versioned-package` to the latest version - execute_command(&["upgrade", "versioned-package"], &manifest); + // Update `docopt` to the latest version + execute_command(&["upgrade", "docopt"], &manifest); - // Verify that `versioned-package` was upgraded, but not `versioned-package-2` + // Verify that `docopt` was upgraded, but not `env_proxy` let dependencies = &get_toml(&manifest)["dependencies"]; assert_eq!( - dependencies["versioned-package"].as_str(), - Some("versioned-package--CURRENT_VERSION_TEST") - ); - assert_eq!( - dependencies["versioned-package-2"].as_str(), - Some("0.1.1") + dependencies["docopt"].as_str(), + Some("docopt--CURRENT_VERSION_TEST") ); + assert_eq!(dependencies["env_proxy"].as_str(), Some("0.1.1")); } #[test] fn fails_to_upgrade_missing_dependency() { let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample"); - // Update the non-existent `failure` to the latest version + // `failure` is not a dependency. Try to upgrade it anyway. execute_command(&["upgrade", "failure"], &manifest); // Verify that `failure` has not been added @@ -99,13 +140,7 @@ fn upgrade_optional_dependency() { // is correct. let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample"); execute_command( - &[ - "add", - "versioned-package", - "--vers", - ">=0.1.1", - "--optional", - ], + &["add", "docopt", "--vers", ">=0.1.1", "--optional"], &manifest, ); @@ -114,56 +149,42 @@ fn upgrade_optional_dependency() { // Dependency present afterwards - correct version, and still optional. let toml = get_toml(&manifest); - let val = &toml["dependencies"]["versioned-package"]; + let val = &toml["dependencies"]["docopt"]; assert_eq!( val["version"].as_str(), - Some("versioned-package--CURRENT_VERSION_TEST") + Some("docopt--CURRENT_VERSION_TEST") ); assert_eq!(val["optional"].as_bool(), Some(true)); } #[test] -fn upgrade_workspace() { - // Create a temporary directory and copy in the root manifest, the dummy rust file, and - // workspace member manifests. - let tmpdir = tempdir::TempDir::new("upgrade_workspace") - .expect("failed to construct temporary directory"); +fn upgrade_precise() { + let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample"); - // Helper to copy in files to the temporary workspace. The standard library doesn't have a good - // equivalent of `cp -r`, hence this oddity. - let copy_in = |dir, file| { - let file_path = tmpdir - .path() - .join(dir) - .join(file) - .to_str() - .unwrap() - .to_string(); - - fs::create_dir_all(tmpdir.path().join(dir)).unwrap(); - - fs::copy( - format!("tests/fixtures/workspace/{}/{}", dir, file), - &file_path, - ).unwrap_or_else(|err| panic!("could not copy test file: {}", err)); - - file_path - }; + // Setup manifest with `docopt` + execute_command(&["add", "docopt", "--vers", "0.8"], &manifest); + + // Now, upgrade `docopt` to the specified version. This version has never (and should never) be + // published. + execute_command(&["upgrade", "--precise", "a spurious version"], &manifest); - let root_manifest = copy_in(".", "Cargo.toml"); - copy_in(".", "dummy.rs"); + // Verify that `docopt` has been updated to the specified version. + assert_eq!( + get_toml(&manifest)["dependencies"]["docopt"].as_str(), + Some("a spurious version") + ); +} - let workspace_manifests = &["one", "two", "implicit/three", "explicit/four"] - .iter() - .map(|member| copy_in(member, "Cargo.toml")) - .collect::>(); +#[test] +fn upgrade_workspace() { + let (_tmpdir, root_manifest, workspace_manifests) = copy_workspace_test(); execute_command(&["upgrade", "--all"], &root_manifest); // All of the workspace members have `libc` as a dependency. for workspace_member in workspace_manifests { assert_eq!( - get_toml(workspace_member)["dependencies"]["libc"].as_str(), + get_toml(&workspace_member)["dependencies"]["libc"].as_str(), Some("libc--CURRENT_VERSION_TEST") ); } @@ -172,19 +193,17 @@ fn upgrade_workspace() { /// Detect if attempting to run against a workspace root and give a helpful warning. #[test] fn detect_workspace() { - let (_tmpdir, manifest) = clone_out_test("tests/fixtures/workspace/Cargo.toml"); + let (_tmpdir, root_manifest, _workspace_manifests) = copy_workspace_test(); assert_cli::Assert::command(&[ "target/debug/cargo-upgrade", "upgrade", "--manifest-path", - &manifest, + &root_manifest, ]).fails_with(1) .prints_error_exactly( - "Command failed due to unhandled error: Failed to write new manifest contents - -Caused by: Found virtual manifest, but this command requires running against an actual package in \ -this workspace.", + "Command failed due to unhandled error: Found virtual manifest, but this command \ + requires running against an actual package in this workspace. Try adding `--all`.", ) .unwrap(); } @@ -224,7 +243,7 @@ fn invalid_root_manifest() { "--manifest-path", &manifest, ]).fails_with(1) - .prints_error("Command failed due to unhandled error: Failed to get metadata") + .prints_error("Command failed due to unhandled error: Failed to get workspace metadata") .unwrap(); } diff --git a/tests/fixtures/add/Cargo.toml.sample b/tests/fixtures/add/Cargo.toml.sample index 5e20016d71..22345929a0 100644 --- a/tests/fixtures/add/Cargo.toml.sample +++ b/tests/fixtures/add/Cargo.toml.sample @@ -1,3 +1,6 @@ [package] name = "cargo-list-test-fixture" version = "0.0.0" + +[lib] +path = "dummy.rs" \ No newline at end of file diff --git a/tests/fixtures/upgrade/Cargo.toml.source b/tests/fixtures/upgrade/Cargo.toml.source index 35c20e79af..728c6c79a3 100644 --- a/tests/fixtures/upgrade/Cargo.toml.source +++ b/tests/fixtures/upgrade/Cargo.toml.source @@ -3,19 +3,15 @@ name = "None" version = "0.1.0" [lib] +path = "dummy.rs" [dependencies] -bar = { git = "https://github.com/foo/bar.git", version = "0.10" } -crates-io = { path = "src/crates-io", version = "0.10" } docopt = "0.8" -foo = { version = "0.7.0", features = ["serde"] } pad = "0.1" -serde_derive = { version = "1.0", optional = true, path = "../serde_derive" } -serde_derive_internals = { version = "=0.15.1", default-features = false, path = "../serde_derive_internals" } serde_json = "1.0" syn = { version = "0.11.10", default-features = false, features = ["parsing"] } tar = { version = "0.4", default-features = false } -winsftp = "0.4.0" +ftp = "2.2.1" [dependencies.semver] features = ["serde"] @@ -23,29 +19,23 @@ version = "0.7" [dev-dependencies] assert_cli = "0.2.0" -cargotest = { path = "tests/cargotest" } -serde_driver = { version = "1.0", path = "../serde_derive" } tempdir = "0.3" [build-dependencies] -serde = { version = "1.0", path = "../serde" } +serde = { version = "1.0", git= "https://github.com/serde-rs/serde.git" } [target.'cfg(unix)'.dependencies] openssl = "0.9" [target."x86_64/windows.json"] # let's make it an inline table -dependencies = { winhttp = "0.4.0" } +dependencies = { rget = "0.3.0" } -[target.'cfg(target_arch = "x86_64")'.dependencies] -native = { path = "native/x86_64" } - -[target.'cfg(unix)'.dev-dependencies] -mio = { version = "0.0.1", path = "../serde_derive" } -geo = { version = "0.2.1", default-features = false, features = ["green"] } +[target.'cfg(target_arch = "x86_64")'.dev-dependencies] +geo = { version = "0.7.0", default-features = false, features = ["postgis-integration"] } [target.foo.build-dependencies] -winsftp = "0.4.0" +ftp = "2.2.1" [features] default = [] diff --git a/tests/fixtures/upgrade/Cargo.toml.target b/tests/fixtures/upgrade/Cargo.toml.target index efd5f6cbc5..c0808d9963 100644 --- a/tests/fixtures/upgrade/Cargo.toml.target +++ b/tests/fixtures/upgrade/Cargo.toml.target @@ -3,19 +3,15 @@ name = "None" version = "0.1.0" [lib] +path = "dummy.rs" [dependencies] -bar = { git = "https://github.com/foo/bar.git", version = "0.10" } -crates-io = { path = "src/crates-io", version = "0.10" } docopt = "docopt--CURRENT_VERSION_TEST" -foo = { version = "foo--CURRENT_VERSION_TEST", features = ["serde"] } pad = "pad--CURRENT_VERSION_TEST" -serde_derive = { version = "1.0", optional = true, path = "../serde_derive" } -serde_derive_internals = { version = "=0.15.1", default-features = false, path = "../serde_derive_internals" } serde_json = "serde_json--CURRENT_VERSION_TEST" syn = { version = "syn--CURRENT_VERSION_TEST", default-features = false, features = ["parsing"] } tar = { version = "tar--CURRENT_VERSION_TEST", default-features = false } -winsftp = "winsftp--CURRENT_VERSION_TEST" +ftp = "ftp--CURRENT_VERSION_TEST" [dependencies.semver] features = ["serde"] @@ -23,29 +19,23 @@ version = "semver--CURRENT_VERSION_TEST" [dev-dependencies] assert_cli = "assert_cli--CURRENT_VERSION_TEST" -cargotest = { path = "tests/cargotest" } -serde_driver = { version = "1.0", path = "../serde_derive" } tempdir = "tempdir--CURRENT_VERSION_TEST" [build-dependencies] -serde = { version = "1.0", path = "../serde" } +serde = { version = "1.0", git= "https://github.com/serde-rs/serde.git" } [target.'cfg(unix)'.dependencies] openssl = "openssl--CURRENT_VERSION_TEST" [target."x86_64/windows.json"] # let's make it an inline table -dependencies = { winhttp = "winhttp--CURRENT_VERSION_TEST" } +dependencies = { rget = "rget--CURRENT_VERSION_TEST" } -[target.'cfg(target_arch = "x86_64")'.dependencies] -native = { path = "native/x86_64" } - -[target.'cfg(unix)'.dev-dependencies] -mio = { version = "0.0.1", path = "../serde_derive" } -geo = { version = "geo--CURRENT_VERSION_TEST", default-features = false, features = ["green"] } +[target.'cfg(target_arch = "x86_64")'.dev-dependencies] +geo = { version = "geo--CURRENT_VERSION_TEST", default-features = false, features = ["postgis-integration"] } [target.foo.build-dependencies] -winsftp = "winsftp--CURRENT_VERSION_TEST" +ftp = "ftp--CURRENT_VERSION_TEST" [features] default = [] From d1265645ab788e4861dcf636232a35f7aa4d8065 Mon Sep 17 00:00:00 2001 From: Benjamin Gill Date: Sun, 11 Feb 2018 20:35:38 +0000 Subject: [PATCH 03/11] Fix formatting/docs And an old bit of oddness that we can now get rid of --- README.md | 8 ++++---- src/bin/upgrade/main.rs | 45 ++++++++++++++++++++--------------------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 3d9b720d58..e4b5227247 100644 --- a/README.md +++ b/README.md @@ -160,15 +160,15 @@ $ cargo upgrade -d regex --all Upgrade dependencies as specified in the local manifest file (i.e. Cargo.toml). Usage: - cargo upgrade [--all] [--dependency ...] [--manifest-path ] [options] + cargo upgrade [options] + cargo upgrade [options] ... [--precise ] cargo upgrade (-h | --help) cargo upgrade (-V | --version) Options: --all Upgrade all packages in the workspace. - -d --dependency Specific dependency to upgrade. If this option is used, only the - specified dependencies will be upgraded. - --manifest-path Path to the manifest to upgrade. + --precise PRECISE Upgrade dependencies to exactly PRECISE. + --manifest-path PATH Path to the manifest to upgrade. --allow-prerelease Include prerelease versions when fetching from crates.io (e.g. '0.6.0-alpha'). Defaults to false. --dry-run Print changes to be made without making them. Defaults to false. diff --git a/src/bin/upgrade/main.rs b/src/bin/upgrade/main.rs index eebdaf308f..6a83f1d1e0 100644 --- a/src/bin/upgrade/main.rs +++ b/src/bin/upgrade/main.rs @@ -26,18 +26,14 @@ mod errors { error_chain!{ links { CargoEditLib(::cargo_edit::Error, ::cargo_edit::ErrorKind); - } - - foreign_links { - // cargo-metadata doesn't (yet) export `ErrorKind` - Metadata(::cargo_metadata::Error); + CargoMetadata(::cargo_metadata::Error, ::cargo_metadata::ErrorKind); } } } use errors::*; static USAGE: &'static str = r" -Upgrade all dependencies in a manifest file to the latest version. +Upgrade dependencies as specified in the local manifest file (i.e. Cargo.toml). Usage: cargo upgrade [options] @@ -55,6 +51,9 @@ Options: -h --help Show this help page. -V --version Show version. +This command differs from `cargo update`, which updates the dependency versions recorded in the +local lock file (Cargo.lock). + Dev, build, and all target dependencies will also be upgraded. Only dependencies from crates.io are supported. Git/path dependencies will be ignored. @@ -160,23 +159,23 @@ impl Manifests { /// Upgrade the manifests on disk. They will upgrade using the new dependencies provided. fn upgrade(self, upgraded_deps: &Dependencies, dry_run: bool) -> Result<()> { - if dry_run { - let bufwtr = BufferWriter::stdout(ColorChoice::Always); - let mut buffer = bufwtr.buffer(); - buffer - .set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true)) - .chain_err(|| "Failed to set output colour")?; - write!(&mut buffer, "Starting dry run. ") - .chain_err(|| "Failed to write dry run message")?; - buffer - .set_color(&ColorSpec::new()) - .chain_err(|| "Failed to clear output colour")?; - writeln!(&mut buffer, "Changes will not be saved.") - .chain_err(|| "Failed to write dry run message")?; - bufwtr - .print(&buffer) - .chain_err(|| "Failed to print dry run message")?; - } + if dry_run { + let bufwtr = BufferWriter::stdout(ColorChoice::Always); + let mut buffer = bufwtr.buffer(); + buffer + .set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true)) + .chain_err(|| "Failed to set output colour")?; + write!(&mut buffer, "Starting dry run. ") + .chain_err(|| "Failed to write dry run message")?; + buffer + .set_color(&ColorSpec::new()) + .chain_err(|| "Failed to clear output colour")?; + writeln!(&mut buffer, "Changes will not be saved.") + .chain_err(|| "Failed to write dry run message")?; + bufwtr + .print(&buffer) + .chain_err(|| "Failed to print dry run message")?; + } for (mut manifest, _) in self.0 { for dependency in &upgraded_deps.0 { From aa22a0891e1e9778576ae8928f6571bd194c608b Mon Sep 17 00:00:00 2001 From: Benjamin Gill Date: Sun, 11 Feb 2018 21:05:03 +0000 Subject: [PATCH 04/11] Formatting --- src/manifest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifest.rs b/src/manifest.rs index ef9ec93735..adf2eceb3b 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -378,7 +378,7 @@ impl LocalManifest { }) } - /// Get the `File` corresponding to this manifest. + /// Get the `File` corresponding to this manifest. fn get_file(&self) -> Result { Manifest::find_file(&Some(self.path.clone())) } From 2ba98209d6ffc35a68004aa098188831b63de247 Mon Sep 17 00:00:00 2001 From: Benjamin Gill Date: Fri, 16 Feb 2018 00:49:12 +0000 Subject: [PATCH 05/11] Support `docopt@0.8`-style crate specifiers --- src/bin/add/args.rs | 92 +++++++---------------------------- src/bin/upgrade/main.rs | 105 +++++++++++++++++++++++++--------------- src/crate_name.rs | 75 ++++++++++++++++++++++++++++ src/lib.rs | 2 + tests/cargo-upgrade.rs | 17 +++++++ tests/utils.rs | 2 +- 6 files changed, 179 insertions(+), 114 deletions(-) create mode 100644 src/crate_name.rs diff --git a/src/bin/add/args.rs b/src/bin/add/args.rs index f236a4fb17..c34ba7c198 100644 --- a/src/bin/add/args.rs +++ b/src/bin/add/args.rs @@ -1,8 +1,7 @@ //! Handle `cargo add` arguments use cargo_edit::Dependency; -use cargo_edit::{get_crate_name_from_github, get_crate_name_from_gitlab, get_crate_name_from_path, - get_latest_dependency}; +use cargo_edit::{get_latest_dependency, CrateName}; use semver; use std::path::PathBuf; @@ -65,27 +64,25 @@ impl Args { /// Build dependencies from arguments pub fn parse_dependencies(&self) -> Result> { if !self.arg_crates.is_empty() { - let mut result = Vec::new(); - for arg_crate in &self.arg_crates { - let le_crate = if crate_name_has_version(arg_crate) { - parse_crate_name_with_version(arg_crate)? - } else { - get_latest_dependency(arg_crate, self.flag_allow_prerelease)? - }.set_optional(self.flag_optional); - - result.push(le_crate); - } - return Ok(result); - } - - if crate_name_has_version(&self.arg_crate) { - return Ok(vec![ - parse_crate_name_with_version(&self.arg_crate)?.set_optional(self.flag_optional), - ]); + return self.arg_crates + .iter() + .map(|crate_name| { + Ok( + if let Some(krate) = CrateName::new(crate_name).parse_as_version()? { + krate + } else { + get_latest_dependency(crate_name, self.flag_allow_prerelease)? + }.set_optional(self.flag_optional), + ) + }) + .collect(); } + let crate_name = CrateName::new(&self.arg_crate); - let dependency = if !crate_name_is_url_or_path(&self.arg_crate) { + let dependency = if let Some(krate) = crate_name.parse_as_version()? { + krate + } else if !crate_name.is_url_or_path() { let dependency = Dependency::new(&self.arg_crate); if let Some(ref version) = self.flag_vers { @@ -108,7 +105,7 @@ impl Args { dep.set_version(&v) } } else { - parse_crate_name_from_uri(&self.arg_crate)? + crate_name.parse_crate_name_from_uri()? }.set_optional(self.flag_optional); Ok(vec![dependency]) @@ -154,58 +151,6 @@ impl Default for Args { } } -fn crate_name_has_version(name: &str) -> bool { - name.contains('@') -} - -fn crate_name_is_url_or_path(name: &str) -> bool { - crate_name_is_github_url(name) || crate_name_is_gitlab_url(name) || crate_name_is_path(name) -} - -fn crate_name_is_github_url(name: &str) -> bool { - name.contains("https://github.com") -} - -fn crate_name_is_gitlab_url(name: &str) -> bool { - name.contains("https://gitlab.com") -} - -fn crate_name_is_path(name: &str) -> bool { - // FIXME: how else can we check if the name is a (possibly invalid) path? - name.contains('.') || name.contains('/') || name.contains('\\') -} - -fn parse_crate_name_with_version(name: &str) -> Result { - assert!(crate_name_has_version(name)); - - let xs: Vec<_> = name.splitn(2, '@').collect(); - let (name, version) = (xs[0], xs[1]); - semver::VersionReq::parse(version).chain_err(|| "Invalid crate version requirement")?; - - Ok(Dependency::new(name).set_version(version)) -} - -fn parse_crate_name_from_uri(name: &str) -> Result { - if crate_name_is_github_url(name) { - if let Ok(ref crate_name) = get_crate_name_from_github(name) { - return Ok(Dependency::new(crate_name).set_git(name)); - } - } else if crate_name_is_gitlab_url(name) { - if let Ok(ref crate_name) = get_crate_name_from_gitlab(name) { - return Ok(Dependency::new(crate_name).set_git(name)); - } - } else if crate_name_is_path(name) { - if let Ok(ref crate_name) = get_crate_name_from_path(name) { - return Ok(Dependency::new(crate_name).set_path(name)); - } - } - - Err(From::from(format!( - "Unable to obtain crate informations from `{}`.\n", - name - ))) -} - #[cfg(test)] mod tests { use cargo_edit::Dependency; @@ -214,7 +159,6 @@ mod tests { #[test] fn test_dependency_parsing() { let args = Args { - arg_crate: "demo".to_owned(), flag_vers: Some("0.4.2".to_owned()), ..Args::default() }; diff --git a/src/bin/upgrade/main.rs b/src/bin/upgrade/main.rs index 6a83f1d1e0..b54d189579 100644 --- a/src/bin/upgrade/main.rs +++ b/src/bin/upgrade/main.rs @@ -11,13 +11,13 @@ extern crate error_chain; extern crate serde_derive; extern crate toml_edit; -use std::collections::HashSet; +use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::io::Write; use std::process; extern crate cargo_edit; -use cargo_edit::{find, get_latest_dependency, Dependency, LocalManifest}; +use cargo_edit::{find, get_latest_dependency, CrateName, Dependency, LocalManifest}; extern crate termcolor; use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; @@ -70,14 +70,14 @@ struct Args { flag_precise: Option, /// `--manifest-path PATH` flag_manifest_path: Option, - /// `--version` - flag_version: bool, /// `--all` flag_all: bool, /// `--allow-prerelease` flag_allow_prerelease: bool, /// `--dry-run` flag_dry_run: bool, + /// `--version` + flag_version: bool, } /// A collection of manifests. @@ -125,7 +125,7 @@ impl Manifests { } /// Get the combined set of dependencies of the manifests. - fn get_dependencies(&self, only_update: &[String]) -> Dependencies { + fn get_dependencies(&self, only_update: Vec) -> Result { /// Helper function to check whether a `cargo_metadata::Dependency` is a version dependency. fn is_version_dep(dependency: &cargo_metadata::Dependency) -> bool { match dependency.source { @@ -136,29 +136,32 @@ impl Manifests { } } - Dependencies( + Ok(InputDependencies(if only_update.is_empty() { self.0 .iter() .flat_map(|&(_, ref package)| package.dependencies.clone()) - .filter(|dependency| { - only_update.is_empty() || only_update.contains(&dependency.name) - }) .filter(is_version_dep) - .map(|dependency| { - // Convert manually from one dependency format to another. Ideally, this would - // be done by implementing `From`. However, that would require pulling in - // `cargo::SourceId::from_url`, which would entail pulling the entirety of - // cargo. - Dependency::new(&dependency.name) - .set_optional(dependency.optional) - .set_version(&dependency.req.to_string()) + .map(|dependency| (dependency.name, None)) + .collect() + } else { + only_update + .into_iter() + .map(|name| { + if let Some(dependency) = CrateName::new(&name.clone()).parse_as_version()? { + Ok(( + dependency.name.clone(), + dependency.version().map(String::from), + )) + } else { + Ok((name, None)) + } }) - .collect(), - ) + .collect::>()? + })) } /// Upgrade the manifests on disk. They will upgrade using the new dependencies provided. - fn upgrade(self, upgraded_deps: &Dependencies, dry_run: bool) -> Result<()> { + fn upgrade(self, upgraded_deps: &ResolvedDependencies, dry_run: bool) -> Result<()> { if dry_run { let bufwtr = BufferWriter::stdout(ColorChoice::Always); let mut buffer = bufwtr.buffer(); @@ -178,8 +181,8 @@ impl Manifests { } for (mut manifest, _) in self.0 { - for dependency in &upgraded_deps.0 { - manifest.upgrade(dependency, dry_run)?; + for (name, version) in &upgraded_deps.0 { + manifest.upgrade(&Dependency::new(name).set_version(version), dry_run)?; } } @@ -187,47 +190,71 @@ impl Manifests { } } -/// This represents the version dependencies of the manifests that `cargo-upgrade` will upgrade. -struct Dependencies(HashSet); +/// The set of dependencies to upgrade. +struct InputDependencies(HashMap>); + +/// The set of dependencies to upgrade alongside the new version of each. +struct ResolvedDependencies(HashMap); -impl Dependencies { +impl InputDependencies { /// Transform the dependencies into their upgraded forms. If a version is specified, all /// dependencies will get that version. fn get_upgraded( self, precise: &Option, allow_prerelease: bool, - ) -> Result { + ) -> Result { self.0 .into_iter() - .map(|dependency| { - if let Some(ref precise) = *precise { - Ok(dependency.set_version(precise)) + .map(|(name, version)| { + if let Some(v) = version { + Ok((name, v)) + } else if let Some(ref v) = *precise { + Ok((name, v.clone())) } else { - get_latest_dependency(&dependency.name, allow_prerelease) + get_latest_dependency(&name, allow_prerelease) + .map(|new_dep| { + ( + name, + new_dep + .version() + .expect("Invalid dependency type") + .to_string(), + ) + }) .chain_err(|| "Failed to get new version") } }) .collect::>() - .map(Dependencies) + .map(ResolvedDependencies) } } /// Main processing function. Allows us to return a `Result` so that `main` can print pretty error /// messages. -fn process(args: &Args) -> Result<()> { - let manifests = if args.flag_all { - Manifests::get_all(&args.flag_manifest_path) +fn process(args: Args) -> Result<()> { + let Args { + arg_dependency, + flag_precise, + flag_manifest_path, + flag_all, + flag_allow_prerelease, + flag_dry_run, + .. + } = args; + + let manifests = if flag_all { + Manifests::get_all(&flag_manifest_path) } else { - Manifests::get_local_one(&args.flag_manifest_path) + Manifests::get_local_one(&flag_manifest_path) }?; - let existing_dependencies = manifests.get_dependencies(&args.arg_dependency.clone()); + let existing_dependencies = manifests.get_dependencies(arg_dependency)?; let upgraded_dependencies = - existing_dependencies.get_upgraded(&args.flag_precise, args.flag_allow_prerelease)?; + existing_dependencies.get_upgraded(&flag_precise, flag_allow_prerelease)?; - manifests.upgrade(&upgraded_dependencies, args.flag_dry_run) + manifests.upgrade(&upgraded_dependencies, flag_dry_run) } fn main() { @@ -240,7 +267,7 @@ fn main() { process::exit(0); } - if let Err(err) = process(&args) { + if let Err(err) = process(args) { eprintln!("Command failed due to unhandled error: {}\n", err); for e in err.iter().skip(1) { diff --git a/src/crate_name.rs b/src/crate_name.rs new file mode 100644 index 0000000000..b9f390dc2e --- /dev/null +++ b/src/crate_name.rs @@ -0,0 +1,75 @@ +//! Crate name parsing. +use semver; + +use {get_crate_name_from_github, get_crate_name_from_gitlab, get_crate_name_from_path}; +use Dependency; +use errors::*; + +/// A crate specifier. This can be a plain name (e.g. `docopt`), a name and a versionreq (e.g. +/// `docopt@^0.8`), a URL, or a path. +#[derive(Debug)] +pub struct CrateName<'a>(&'a str); + +impl<'a> CrateName<'a> { + /// Create a new `CrateName` + pub fn new(name: &'a str) -> Self { + CrateName(name) + } + + /// Does this specify a versionreq? + pub fn has_version(&self) -> bool { + self.0.contains('@') + } + + /// Is this a URI? + pub fn is_url_or_path(&self) -> bool { + self.is_github_url() || self.is_gitlab_url() || self.is_path() + } + + fn is_github_url(&self) -> bool { + self.0.contains("https://github.com") + } + + fn is_gitlab_url(&self) -> bool { + self.0.contains("https://gitlab.com") + } + + fn is_path(&self) -> bool { + // FIXME: how else can we check if the name is a (possibly invalid) path? + self.0.contains('.') || self.0.contains('/') || self.0.contains('\\') + } + + /// If this crate specifier includes a version (e.g. `docopt@0.8`), extract the name and + /// version. + pub fn parse_as_version(&self) -> Result> { + if self.has_version() { + let xs: Vec<_> = self.0.splitn(2, '@').collect(); + let (name, version) = (xs[0], xs[1]); + semver::VersionReq::parse(version).chain_err(|| "Invalid crate version requirement")?; + + Ok(Some(Dependency::new(name).set_version(version))) + // Ok(Some((name, version))) + } else { + Ok(None) + } + } + + /// Will parse this crate name on the assumption that it is a URI. + pub fn parse_crate_name_from_uri(&self) -> Result { + if self.is_github_url() { + if let Ok(ref crate_name) = get_crate_name_from_github(self.0) { + return Ok(Dependency::new(crate_name).set_git(self.0)); + } + } else if self.is_gitlab_url() { + if let Ok(ref crate_name) = get_crate_name_from_gitlab(self.0) { + return Ok(Dependency::new(crate_name).set_git(self.0)); + } + } else if self.is_path() { + if let Ok(ref crate_name) = get_crate_name_from_path(self.0) { + return Ok(Dependency::new(crate_name).set_path(self.0)); + } + } + + bail!("Unable to obtain crate informations from `{}`.\n", self.0) + } +} diff --git a/src/lib.rs b/src/lib.rs index ed729f7fa3..d54fc3cb18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,11 +18,13 @@ extern crate serde_json; extern crate termcolor; extern crate toml_edit; +mod crate_name; mod dependency; mod errors; mod fetch; mod manifest; +pub use crate_name::CrateName; pub use dependency::Dependency; pub use errors::*; pub use fetch::{get_crate_name_from_github, get_crate_name_from_gitlab, get_crate_name_from_path, diff --git a/tests/cargo-upgrade.rs b/tests/cargo-upgrade.rs index fba4b1516e..40537309d2 100644 --- a/tests/cargo-upgrade.rs +++ b/tests/cargo-upgrade.rs @@ -209,6 +209,23 @@ fn upgrade_precise() { ); } +#[test] +fn upgrade_at() { + let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample"); + + // Setup manifest + execute_command(&["add", "docopt", "--vers", "0.8"], &manifest); + + // Now, upgrade `docopt` to a version that seems unlikely to ever get published. + execute_command(&["upgrade", "docopt@1000000.0.0"], &manifest); + + // Verify that `docopt` has been updated to the specified version. + assert_eq!( + get_toml(&manifest)["dependencies"]["docopt"].as_str(), + Some("1000000.0.0") + ); +} + #[test] fn upgrade_workspace() { let (_tmpdir, root_manifest, workspace_manifests) = copy_workspace_test(); diff --git a/tests/utils.rs b/tests/utils.rs index 50ab03bc4d..5054bce85d 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -40,7 +40,7 @@ where println!("Status code: {:?}", call.status); println!("STDOUT: {}", String::from_utf8_lossy(&call.stdout)); println!("STDERR: {}", String::from_utf8_lossy(&call.stderr)); - panic!("cargo-add failed to execute") + panic!("cargo-{} failed to execute", subcommand_name) } } From d844847222cf9957036609e1b2cbbefa6076ff54 Mon Sep 17 00:00:00 2001 From: Benjamin Gill Date: Fri, 16 Feb 2018 01:01:08 +0000 Subject: [PATCH 06/11] Make comments clearer --- src/bin/upgrade/main.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/bin/upgrade/main.rs b/src/bin/upgrade/main.rs index b54d189579..c2ef675662 100644 --- a/src/bin/upgrade/main.rs +++ b/src/bin/upgrade/main.rs @@ -124,8 +124,9 @@ impl Manifests { Ok(Manifests(vec![(manifest, package.to_owned())])) } - /// Get the combined set of dependencies of the manifests. - fn get_dependencies(&self, only_update: Vec) -> Result { + /// Get the the combined set of dependencies to upgrade. If the user has specified + /// per-dependency desired versions, extract those here. + fn get_dependencies(&self, only_update: Vec) -> Result { /// Helper function to check whether a `cargo_metadata::Dependency` is a version dependency. fn is_version_dep(dependency: &cargo_metadata::Dependency) -> bool { match dependency.source { @@ -136,7 +137,9 @@ impl Manifests { } } - Ok(InputDependencies(if only_update.is_empty() { + Ok(DesiredUpgrades(if only_update.is_empty() { + // User hasn't asked for any specific dependencies to be upgraded, so upgrade all the + // dependencies. self.0 .iter() .flat_map(|&(_, ref package)| package.dependencies.clone()) @@ -160,8 +163,8 @@ impl Manifests { })) } - /// Upgrade the manifests on disk. They will upgrade using the new dependencies provided. - fn upgrade(self, upgraded_deps: &ResolvedDependencies, dry_run: bool) -> Result<()> { + /// Upgrade the manifests on disk following the previously-determined upgrade schema. + fn upgrade(self, upgraded_deps: &ActualUpgrades, dry_run: bool) -> Result<()> { if dry_run { let bufwtr = BufferWriter::stdout(ColorChoice::Always); let mut buffer = bufwtr.buffer(); @@ -190,20 +193,21 @@ impl Manifests { } } -/// The set of dependencies to upgrade. -struct InputDependencies(HashMap>); +/// The set of dependencies to be upgraded, alongside desired versions, if specified by the user. +struct DesiredUpgrades(HashMap>); -/// The set of dependencies to upgrade alongside the new version of each. -struct ResolvedDependencies(HashMap); +/// The complete specification of the upgrades that will be performed. Map of the dependency names +/// to the new versions. +struct ActualUpgrades(HashMap); -impl InputDependencies { +impl DesiredUpgrades { /// Transform the dependencies into their upgraded forms. If a version is specified, all /// dependencies will get that version. fn get_upgraded( self, precise: &Option, allow_prerelease: bool, - ) -> Result { + ) -> Result { self.0 .into_iter() .map(|(name, version)| { @@ -226,7 +230,7 @@ impl InputDependencies { } }) .collect::>() - .map(ResolvedDependencies) + .map(ActualUpgrades) } } From 3f58d3c4160a9001d1d92ed7eb0934eda71ef628 Mon Sep 17 00:00:00 2001 From: Benjamin Gill Date: Fri, 16 Feb 2018 01:19:26 +0000 Subject: [PATCH 07/11] Document new `docopt@0.8` feature for cargo-upgrade --- README.md | 27 +++++++++++++++------------ src/bin/upgrade/main.rs | 23 +++++++++++++---------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index e4b5227247..129476424c 100644 --- a/README.md +++ b/README.md @@ -148,10 +148,10 @@ local lock file (Cargo.lock). ```sh # Upgrade all dependencies for the current crate $ cargo upgrade -# Upgrade libc and serde -$ cargo upgrade -d libc --dependency serde +# Upgrade libc (to the latest version) and serde (to v1.0.0) +$ cargo upgrade libc serde@1.0.0 # Upgrade regex across all crates in the workspace -$ cargo upgrade -d regex --all +$ cargo upgrade regex --all ``` #### Usage @@ -163,21 +163,24 @@ Usage: cargo upgrade [options] cargo upgrade [options] ... [--precise ] cargo upgrade (-h | --help) - cargo upgrade (-V | --version) + cargo upgrade (-V | --version) Options: - --all Upgrade all packages in the workspace. - --precise PRECISE Upgrade dependencies to exactly PRECISE. - --manifest-path PATH Path to the manifest to upgrade. - --allow-prerelease Include prerelease versions when fetching from crates.io (e.g. - '0.6.0-alpha'). Defaults to false. - --dry-run Print changes to be made without making them. Defaults to false. - -h --help Show this help page. - -V --version Show version. + --all Upgrade all packages in the workspace. + --precise PRECISE Upgrade the dependencies to exactly PRECISE. + --manifest-path PATH Path to the manifest to upgrade. + --allow-prerelease Include prerelease versions when fetching from crates.io (e.g. + '0.6.0-alpha'). Defaults to false. + --dry-run Print changes to be made without making them. Defaults to false. + -h --help Show this help page. + -V --version Show version. This command differs from `cargo update`, which updates the dependency versions recorded in the local lock file (Cargo.lock). +If ``(s) are provided, only the specified dependencies will be upgraded. The version to +upgrade to for each can be specified with e.g. `docopt@0.8.0`. + Dev, build, and all target dependencies will also be upgraded. Only dependencies from crates.io are supported. Git/path dependencies will be ignored. diff --git a/src/bin/upgrade/main.rs b/src/bin/upgrade/main.rs index c2ef675662..063b077000 100644 --- a/src/bin/upgrade/main.rs +++ b/src/bin/upgrade/main.rs @@ -39,21 +39,24 @@ Usage: cargo upgrade [options] cargo upgrade [options] ... [--precise ] cargo upgrade (-h | --help) - cargo upgrade (-V | --version) + cargo upgrade (-V | --version) Options: - --all Upgrade all packages in the workspace. - --precise PRECISE Upgrade dependencies to exactly PRECISE. - --manifest-path PATH Path to the manifest to upgrade. - --allow-prerelease Include prerelease versions when fetching from crates.io (e.g. - '0.6.0-alpha'). Defaults to false. - --dry-run Print changes to be made without making them. Defaults to false. - -h --help Show this help page. - -V --version Show version. + --all Upgrade all packages in the workspace. + --precise PRECISE Upgrade the dependencies to exactly PRECISE. + --manifest-path PATH Path to the manifest to upgrade. + --allow-prerelease Include prerelease versions when fetching from crates.io (e.g. + '0.6.0-alpha'). Defaults to false. + --dry-run Print changes to be made without making them. Defaults to false. + -h --help Show this help page. + -V --version Show version. This command differs from `cargo update`, which updates the dependency versions recorded in the local lock file (Cargo.lock). +If ``(s) are provided, only the specified dependencies will be upgraded. The version to +upgrade to for each can be specified with e.g. `docopt@0.8.0`. + Dev, build, and all target dependencies will also be upgraded. Only dependencies from crates.io are supported. Git/path dependencies will be ignored. @@ -64,7 +67,7 @@ be supplied in the presence of a virtual manifest. /// Docopts input args. #[derive(Debug, Deserialize)] struct Args { - /// `...` + /// `DEPENDENCY...` arg_dependency: Vec, /// `--precise PRECISE` flag_precise: Option, From c40f0560b0d5a42bcc0e99856122cbc69fa63a2c Mon Sep 17 00:00:00 2001 From: Benjamin Gill Date: Fri, 16 Feb 2018 01:51:43 +0000 Subject: [PATCH 08/11] Remove `--precise` option The `dep@vers' mechanism seems likely to be more generally useful --- README.md | 4 +--- src/bin/upgrade/main.rs | 14 +++----------- tests/cargo-upgrade.rs | 21 +-------------------- 3 files changed, 5 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 129476424c..14b8c66f68 100644 --- a/README.md +++ b/README.md @@ -160,14 +160,12 @@ $ cargo upgrade regex --all Upgrade dependencies as specified in the local manifest file (i.e. Cargo.toml). Usage: - cargo upgrade [options] - cargo upgrade [options] ... [--precise ] + cargo upgrade [options] []... cargo upgrade (-h | --help) cargo upgrade (-V | --version) Options: --all Upgrade all packages in the workspace. - --precise PRECISE Upgrade the dependencies to exactly PRECISE. --manifest-path PATH Path to the manifest to upgrade. --allow-prerelease Include prerelease versions when fetching from crates.io (e.g. '0.6.0-alpha'). Defaults to false. diff --git a/src/bin/upgrade/main.rs b/src/bin/upgrade/main.rs index 063b077000..274daea708 100644 --- a/src/bin/upgrade/main.rs +++ b/src/bin/upgrade/main.rs @@ -36,14 +36,12 @@ static USAGE: &'static str = r" Upgrade dependencies as specified in the local manifest file (i.e. Cargo.toml). Usage: - cargo upgrade [options] - cargo upgrade [options] ... [--precise ] + cargo upgrade [options] []... cargo upgrade (-h | --help) cargo upgrade (-V | --version) Options: --all Upgrade all packages in the workspace. - --precise PRECISE Upgrade the dependencies to exactly PRECISE. --manifest-path PATH Path to the manifest to upgrade. --allow-prerelease Include prerelease versions when fetching from crates.io (e.g. '0.6.0-alpha'). Defaults to false. @@ -67,10 +65,8 @@ be supplied in the presence of a virtual manifest. /// Docopts input args. #[derive(Debug, Deserialize)] struct Args { - /// `DEPENDENCY...` + /// `...` arg_dependency: Vec, - /// `--precise PRECISE` - flag_precise: Option, /// `--manifest-path PATH` flag_manifest_path: Option, /// `--all` @@ -208,7 +204,6 @@ impl DesiredUpgrades { /// dependencies will get that version. fn get_upgraded( self, - precise: &Option, allow_prerelease: bool, ) -> Result { self.0 @@ -216,8 +211,6 @@ impl DesiredUpgrades { .map(|(name, version)| { if let Some(v) = version { Ok((name, v)) - } else if let Some(ref v) = *precise { - Ok((name, v.clone())) } else { get_latest_dependency(&name, allow_prerelease) .map(|new_dep| { @@ -242,7 +235,6 @@ impl DesiredUpgrades { fn process(args: Args) -> Result<()> { let Args { arg_dependency, - flag_precise, flag_manifest_path, flag_all, flag_allow_prerelease, @@ -259,7 +251,7 @@ fn process(args: Args) -> Result<()> { let existing_dependencies = manifests.get_dependencies(arg_dependency)?; let upgraded_dependencies = - existing_dependencies.get_upgraded(&flag_precise, flag_allow_prerelease)?; + existing_dependencies.get_upgraded(flag_allow_prerelease)?; manifests.upgrade(&upgraded_dependencies, flag_dry_run) } diff --git a/tests/cargo-upgrade.rs b/tests/cargo-upgrade.rs index 40537309d2..de565fe808 100644 --- a/tests/cargo-upgrade.rs +++ b/tests/cargo-upgrade.rs @@ -191,24 +191,6 @@ fn upgrade_optional_dependency() { assert_eq!(val["optional"].as_bool(), Some(true)); } -#[test] -fn upgrade_precise() { - let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample"); - - // Setup manifest with `docopt` - execute_command(&["add", "docopt", "--vers", "0.8"], &manifest); - - // Now, upgrade `docopt` to the specified version. This version has never (and should never) be - // published. - execute_command(&["upgrade", "--precise", "a spurious version"], &manifest); - - // Verify that `docopt` has been updated to the specified version. - assert_eq!( - get_toml(&manifest)["dependencies"]["docopt"].as_str(), - Some("a spurious version") - ); -} - #[test] fn upgrade_at() { let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample"); @@ -306,8 +288,7 @@ fn unknown_flags() { "Unknown flag: '--flag' Usage: - cargo upgrade [options] - cargo upgrade [options] ... [--precise ] + cargo upgrade [options] []... cargo upgrade (-h | --help) cargo upgrade (-V | --version)", ) From 24d90a07bebe3227671efa0cc6812a3bc2e13871 Mon Sep 17 00:00:00 2001 From: Benjamin Gill Date: Fri, 16 Feb 2018 09:12:09 +0000 Subject: [PATCH 09/11] Remove old code It was commented out --- src/crate_name.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/crate_name.rs b/src/crate_name.rs index b9f390dc2e..67fbb83b4d 100644 --- a/src/crate_name.rs +++ b/src/crate_name.rs @@ -48,7 +48,6 @@ impl<'a> CrateName<'a> { semver::VersionReq::parse(version).chain_err(|| "Invalid crate version requirement")?; Ok(Some(Dependency::new(name).set_version(version))) - // Ok(Some((name, version))) } else { Ok(None) } From 5c955c74f6b581080d4e58df92ac475b81bd165a Mon Sep 17 00:00:00 2001 From: Benjamin Gill Date: Fri, 16 Feb 2018 09:17:36 +0000 Subject: [PATCH 10/11] Add explicit instructions for the dep@vers form --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 14b8c66f68..1744ee4d8b 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,9 @@ Remove a dependency from a Cargo.toml manifest file. Upgrade dependencies in your `Cargo.toml` to their latest versions. +To specify a version to upgrade to, provide the dependencies in the `@` format, +e.g. `cargo upgrade docopt@~0.9.0 serde@1.0.1`. + This command differs from `cargo update`, which updates the dependency versions recorded in the local lock file (Cargo.lock). @@ -162,7 +165,7 @@ Upgrade dependencies as specified in the local manifest file (i.e. Cargo.toml). Usage: cargo upgrade [options] []... cargo upgrade (-h | --help) - cargo upgrade (-V | --version) + cargo upgrade (-V | --version) Options: --all Upgrade all packages in the workspace. @@ -176,7 +179,7 @@ Options: This command differs from `cargo update`, which updates the dependency versions recorded in the local lock file (Cargo.lock). -If ``(s) are provided, only the specified dependencies will be upgraded. The version to +If ``(s) are provided, only the specified dependencies will be upgraded. The version to upgrade to for each can be specified with e.g. `docopt@0.8.0`. Dev, build, and all target dependencies will also be upgraded. Only dependencies from crates.io are From 3d51789e73ccda72322bae0da10474566d6aeda7 Mon Sep 17 00:00:00 2001 From: Benjamin Gill Date: Fri, 16 Feb 2018 09:21:10 +0000 Subject: [PATCH 11/11] Be clearer than the full versionreq format is OK --- README.md | 8 ++++---- src/bin/upgrade/main.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1744ee4d8b..4c7a19f379 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ Remove a dependency from a Cargo.toml manifest file. Upgrade dependencies in your `Cargo.toml` to their latest versions. To specify a version to upgrade to, provide the dependencies in the `@` format, -e.g. `cargo upgrade docopt@~0.9.0 serde@1.0.1`. +e.g. `cargo upgrade docopt@~0.9.0 serde@>=0.9,<2.0`. This command differs from `cargo update`, which updates the dependency versions recorded in the local lock file (Cargo.lock). @@ -151,9 +151,9 @@ local lock file (Cargo.lock). ```sh # Upgrade all dependencies for the current crate $ cargo upgrade -# Upgrade libc (to the latest version) and serde (to v1.0.0) -$ cargo upgrade libc serde@1.0.0 -# Upgrade regex across all crates in the workspace +# Upgrade docopt (to ~0.9) and serde (to >=0.9,<2.0) +$ cargo upgrade docopt@~0.9 serde@>=0.9,<2.0 +# Upgrade regex (to the latest version) across all crates in the workspace $ cargo upgrade regex --all ``` diff --git a/src/bin/upgrade/main.rs b/src/bin/upgrade/main.rs index 80e09492d8..ae367923aa 100644 --- a/src/bin/upgrade/main.rs +++ b/src/bin/upgrade/main.rs @@ -53,7 +53,7 @@ This command differs from `cargo update`, which updates the dependency versions local lock file (Cargo.lock). If ``(s) are provided, only the specified dependencies will be upgraded. The version to -upgrade to for each can be specified with e.g. `docopt@0.8.0`. +upgrade to for each can be specified with e.g. `docopt@0.8.0` or `serde@>=0.9,<2.0`. Dev, build, and all target dependencies will also be upgraded. Only dependencies from crates.io are supported. Git/path dependencies will be ignored.