Skip to content

Commit

Permalink
"Working with features" updated (#395)
Browse files Browse the repository at this point in the history
* add --features='nose mouth'

Credit to @alanpoon for the original implementation
of this feature. This patch is a rebase of the original
PR (#194) with minor tidying up.

Also contains some unrelated clippy and fmt fixes for
rust 1.41.0.

* mention cargo-feature; update README

* forbid specifying multiple crates with --features

* fixup! forbid specifying multiple crates with --features
  • Loading branch information
alsuren committed Mar 30, 2020
1 parent 48f7089 commit f7c8962
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 26 deletions.
35 changes: 20 additions & 15 deletions README.md
Expand Up @@ -85,25 +85,30 @@ FLAGS:
-V, --version Prints version information
OPTIONS:
--git <uri> Specify a git repository to download the crate from
--manifest-path <path> Path to the manifest to add a dependency to
-p, --package <package> Specify the package in the workspace to add a dependency to (see `cargo help pkgid`)
--path <path> Specify the path the crate should be loaded from
--registry <registry> Registry to use
-r, --rename <rename> Rename a dependency in Cargo.toml, https://doc.rust-
lang.org/cargo/reference/specifying-
dependencies.html#renaming-dependencies-in-cargotoml Only works when
specifying a single dependency
--target <target> Add as dependency to the given target platform
--upgrade <method> Choose method of semantic version upgrade. Must be one of "none" (exact version, `=`
modifier), "patch" (`~` modifier), "minor" (`^` modifier), "all" (`>=`), or "default"
(no modifier) [default: default] [possible values: none, patch, minor, all, default]
--vers <uri> Specify the version to grab from the registry(crates.io). You can also specify version
as part of name, e.g `cargo add bitflags@0.3.2`
--branch <branch> Specify a git branch to download the crate from
--features <features>... Space-separated list of features to add. For an alternative approach to enabling
features, consider installing the `cargo-feature` utility
--git <uri> Specify a git repository to download the crate from
--manifest-path <path> Path to the manifest to add a dependency to
--path <path> Specify the path the crate should be loaded from
-p, --package <pkgid> Package id of the crate to add this dependency to
--registry <registry> Registry to use
-r, --rename <rename> Rename a dependency in Cargo.toml, https://doc.rust-
lang.org/cargo/reference/specifying-
dependencies.html#renaming-dependencies-in-cargotoml Only works
when specifying a single dependency
--target <target> Add as dependency to the given target platform
--upgrade <method> Choose method of semantic version upgrade. Must be one of "none" (exact version,
`=` modifier), "patch" (`~` modifier), "minor" (`^` modifier), "all" (`>=`), or
"default" (no modifier) [default: default] [possible values: none, patch, minor,
all, default]
--vers <uri> Specify the version to grab from the registry(crates.io). You can also specify
version as part of name, e.g `cargo add bitflags@0.3.2`
ARGS:
<crate>... Crates to be added
This command allows you to add a dependency to a Cargo.toml manifest file. If <crate> is a github
or gitlab repository URL, or a local path, `cargo add` will try to automatically get the crate name
and set the appropriate `--git` or `--path` value.
Expand Down
12 changes: 11 additions & 1 deletion src/bin/add/args.rs
Expand Up @@ -115,6 +115,11 @@ pub struct Args {
#[structopt(long = "allow-prerelease")]
pub allow_prerelease: bool,

/// Space-separated list of features to add. For an alternative approach to
/// enabling features, consider installing the `cargo-feature` utility.
#[structopt(long = "features", number_of_values = 1)]
pub features: Option<Vec<String>>,

/// Set `default-features = false` for the added dependency.
#[structopt(long = "no-default-features")]
pub no_default_features: bool,
Expand Down Expand Up @@ -225,7 +230,6 @@ impl Args {
if let Some(registry) = &self.registry {
dependency = dependency.set_registry(registry);
}

Ok(dependency)
}
}
Expand All @@ -242,12 +246,17 @@ impl Args {
return Err(ErrorKind::MultipleCratesWithRename.into());
}

if self.crates.len() > 1 && self.features.is_some() {
return Err(ErrorKind::MultipleCratesWithFeatures.into());
}

self.crates
.iter()
.map(|crate_name| {
self.parse_single_dependency(crate_name).map(|x| {
let mut x = x
.set_optional(self.optional)
.set_features(self.features.clone())
.set_default_features(!self.no_default_features);
if let Some(ref rename) = self.rename {
x = x.set_rename(rename);
Expand Down Expand Up @@ -288,6 +297,7 @@ impl Default for Args {
pkgid: None,
upgrade: "minor".to_string(),
allow_prerelease: false,
features: None,
no_default_features: false,
quiet: false,
offline: true,
Expand Down
13 changes: 12 additions & 1 deletion src/bin/add/main.rs
Expand Up @@ -45,6 +45,11 @@ mod errors {
description("Specified multiple crates with rename")
display("Cannot specify multiple crates with rename")
}
/// Specified multiple crates with features.
MultipleCratesWithFeatures {
description("Specified multiple crates with features")
display("Cannot specify multiple crates with features")
}
}
links {
CargoEditLib(::cargo_edit::Error, ::cargo_edit::ErrorKind);
Expand All @@ -54,6 +59,7 @@ mod errors {
}
}
}

use crate::errors::*;

fn print_msg(dep: &Dependency, section: &[String], optional: bool) -> Result<()> {
Expand Down Expand Up @@ -81,7 +87,12 @@ fn print_msg(dep: &Dependency, section: &[String], optional: bool) -> Result<()>
} else {
format!("{} for target `{}`", &section[2], &section[1])
};
writeln!(output, " {}", section)?;
write!(output, " {}", section)?;
if let Some(f) = &dep.features {
writeln!(output, " with features: {:?}", f)?
} else {
writeln!(output)?
}
Ok(())
}

Expand Down
2 changes: 1 addition & 1 deletion src/bin/upgrade/main.rs
Expand Up @@ -191,7 +191,7 @@ impl Manifests {
fn get_pkgid(pkgid: &str) -> Result<Self> {
let package = manifest_from_pkgid(pkgid)?;
let manifest = LocalManifest::try_new(Path::new(&package.manifest_path))?;
Ok(Manifests(vec![(manifest, package.to_owned())]))
Ok(Manifests(vec![(manifest, package)]))
}

/// Get the manifest specified by the manifest path. Try to make an educated guess if no path is
Expand Down
23 changes: 22 additions & 1 deletion src/dependency.rs
@@ -1,3 +1,4 @@
use std::iter::FromIterator;
use toml_edit;

#[derive(Debug, Hash, PartialEq, Eq, Clone)]
Expand All @@ -19,6 +20,8 @@ pub struct Dependency {
/// The name of the dependency (as it is set in its `Cargo.toml` and known to crates.io)
pub name: String,
optional: bool,
/// List of features to add (or None to keep features unchanged).
pub features: Option<Vec<String>>,
default_features: bool,
source: DependencySource,
/// If the dependency is renamed, this is the new name for the dependency
Expand All @@ -32,6 +35,7 @@ impl Default for Dependency {
name: "".into(),
rename: None,
optional: false,
features: None,
default_features: true,
source: DependencySource::Version {
version: None,
Expand Down Expand Up @@ -97,6 +101,17 @@ impl Dependency {
self.optional = opt;
self
}
/// Set features as an array of string (does some basic parsing)
pub fn set_features(mut self, features: Option<Vec<String>>) -> Dependency {
self.features = features.map(|f| {
f.iter()
.map(|x| x.split(' ').map(String::from))
.flatten()
.filter(|s| !s.is_empty())
.collect::<Vec<String>>()
});
self
}

/// Set the value of default-features for the dependency
pub fn set_default_features(mut self, default_features: bool) -> Dependency {
Expand Down Expand Up @@ -161,13 +176,15 @@ impl Dependency {
pub fn to_toml(&self) -> (String, toml_edit::Item) {
let data: toml_edit::Item = match (
self.optional,
self.features.as_ref(),
self.default_features,
self.source.clone(),
self.rename.as_ref(),
) {
// Extra short when version flag only
(
false,
None,
true,
DependencySource::Version {
version: Some(v),
Expand All @@ -177,7 +194,7 @@ impl Dependency {
None,
) => toml_edit::value(v),
// Other cases are represented as an inline table
(optional, default_features, source, rename) => {
(optional, features, default_features, source, rename) => {
let mut data = toml_edit::InlineTable::default();

match source {
Expand All @@ -204,6 +221,10 @@ impl Dependency {
if self.optional {
data.get_or_insert("optional", optional);
}
if let Some(features) = features {
let features = toml_edit::Value::from_iter(features.iter().cloned());
data.get_or_insert("features", features);
}
if !self.default_features {
data.get_or_insert("default-features", default_features);
}
Expand Down
4 changes: 1 addition & 3 deletions src/fetch.rs
Expand Up @@ -153,9 +153,7 @@ fn fetch_with_cli(repo: &git2::Repository, url: &str, refspec: &str) -> Result<(

let _ = cmd.capture().map_err(|e| match e {
subprocess::PopenError::IoError(io) => ErrorKind::Io(io),
_ => {
unreachable!("expected only io error")
}
_ => unreachable!("expected only io error"),
})?;
Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion src/metadata.rs
Expand Up @@ -12,7 +12,7 @@ pub fn manifest_from_pkgid(pkgid: &str) -> Result<Package> {
let packages = result.packages;
let package = packages
.into_iter()
.find(|pkg| &pkg.name == pkgid)
.find(|pkg| pkg.name == pkgid)
.chain_err(|| {
"Found virtual manifest, but this command requires running against an \
actual package in this workspace. Try adding `--all`."
Expand Down
130 changes: 130 additions & 0 deletions tests/cargo-add.rs
Expand Up @@ -943,6 +943,116 @@ fn adds_dependency_with_target_cfg() {
assert_eq!(val.as_str().unwrap(), "my-package1--CURRENT_VERSION_TEST");
}

#[test]
fn adds_features_dependency() {
let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample");

// dependency not present beforehand
let toml = get_toml(&manifest);
assert!(toml["dependencies"].is_none());

execute_command(
&[
"add",
"https://github.com/killercup/cargo-edit.git",
"--features",
"jui",
],
&manifest,
);

// dependency present afterwards
let toml = get_toml(&manifest);
let val = toml["dependencies"]["cargo-edit"]["features"][0].as_str();
assert_eq!(val, Some("jui"));
}

#[test]
fn overrides_existing_features() {
overwrite_dependency_test(
&["add", "your-face", "--features", "nose"],
&["add", "your-face", "--features", "mouth"],
r#"
[dependencies]
your-face = { version = "your-face--CURRENT_VERSION_TEST", features = ["mouth"] }
"#,
)
}

#[test]
fn keeps_existing_features_by_default() {
overwrite_dependency_test(
&["add", "your-face", "--features", "nose"],
&["add", "your-face"],
r#"
[dependencies]
your-face = { version = "your-face--CURRENT_VERSION_TEST", features = ["nose"] }
"#,
)
}

#[test]
fn handles_specifying_features_option_multiple_times() {
overwrite_dependency_test(
&["add", "your-face"],
&[
"add",
"your-face",
"--features",
"nose",
"--features",
"mouth",
],
r#"
[dependencies]
your-face = { version = "your-face--CURRENT_VERSION_TEST", features = ["nose", "mouth"] }
"#,
)
}

#[test]
fn can_be_forced_to_provide_an_empty_features_list() {
overwrite_dependency_test(
&["add", "your-face"],
&["add", "your-face", "--features", ""],
r#"
[dependencies]
your-face = { version = "your-face--CURRENT_VERSION_TEST", features = [] }
"#,
)
}

#[test]
fn parses_space_separated_argument_to_features() {
overwrite_dependency_test(
&["add", "your-face", "--features", "nose"],
&["add", "your-face", "--features", "mouth ears"],
r#"
[dependencies]
your-face = { version = "your-face--CURRENT_VERSION_TEST", features = ["mouth", "ears"] }
"#,
)
}

#[test]
fn forbids_multiple_crates_with_features_option() {
let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample");

assert_cli::Assert::command(&[
get_command_path("add").as_str(),
"add",
"your-face",
"--features",
"mouth",
"nose",
])
.fails_with(1)
.and()
.stderr()
.contains("Cannot specify multiple crates with features")
.unwrap();
}

#[test]
fn adds_dependency_with_custom_target() {
let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample");
Expand Down Expand Up @@ -1372,3 +1482,23 @@ fn add_dependency_to_workspace_member() {
"toml--CURRENT_VERSION_TEST",
);
}
#[test]
fn add_prints_message_for_features_deps() {
let (_tmpdir, manifest) = clone_out_test("tests/fixtures/add/Cargo.toml.sample");

assert_cli::Assert::command(&[
"target/debug/cargo-add",
"add",
"hello-world",
"--vers",
"0.1.0",
"--features",
"jui",
&format!("--manifest-path={}", manifest),
])
.succeeds()
.and()
.stdout()
.contains(r#"Adding hello-world v0.1.0 to dependencies with features: ["jui"]"#)
.unwrap();
}
4 changes: 1 addition & 3 deletions tests/cargo-upgrade.rs
@@ -1,8 +1,6 @@
#[macro_use]
extern crate pretty_assertions;

use std::fs;

mod utils;
use crate::utils::{
clone_out_test, copy_workspace_test, execute_command, execute_command_for_pkg,
Expand Down Expand Up @@ -501,7 +499,7 @@ For more information try --help ",
#[cfg(feature = "test-external-apis")]
fn upgrade_to_lockfile() {
let (tmpdir, manifest) = clone_out_test("tests/fixtures/upgrade/Cargo.toml.lockfile_source");
fs::copy(
std::fs::copy(
std::path::Path::new("tests/fixtures/upgrade/Cargo.lock"),
tmpdir.path().join("Cargo.lock"),
)
Expand Down

0 comments on commit f7c8962

Please sign in to comment.