From ec2a3a3a6fc24b665eb79364de636e95e7692bdd Mon Sep 17 00:00:00 2001 From: Katharina Fey Date: Mon, 17 Dec 2018 12:46:58 +0100 Subject: [PATCH] Adding feature-flags to release configuration This PR adds `features` and `all_features` as both flags to the CLI as well as the `release.toml` configuration. This change allows you to tell `cargo-release` to verify the package with certain features enabled, in order to avoid needing to use `no-verify` for crates that depend on certain features being enabled (or simply any feature) before they can be uploaded. This change is dependent on rust-lang/cargo#6453, which adds this feature to the base `cargo publish` command. The motivation behind this PR is the same as the above mentioned cargo PR: reducing the amount of packaging errors that can happen due to people not being able to verify their code because it has some feature requirements for compilation, ultimately resulting in a more healthy package ecosystem. --- src/cargo.rs | 12 +++++-- src/config.rs | 4 +++ src/main.rs | 96 +++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 91 insertions(+), 21 deletions(-) diff --git a/src/cargo.rs b/src/cargo.rs index baef36f50..3b44a4e80 100644 --- a/src/cargo.rs +++ b/src/cargo.rs @@ -1,8 +1,16 @@ use cmd::call; use error::FatalError; +use Features; -pub fn publish(dry_run: bool) -> Result { - call(vec![env!("CARGO"), "publish"], dry_run) +pub fn publish(dry_run: bool, features: Features) -> Result { + match features { + Features::None => call(vec![env!("CARGO"), "publish"], dry_run), + Features::Selective(vec) => call( + vec![env!("CARGO"), "publish", "--features", &vec.join(" ")], + dry_run, + ), + Features::All => call(vec![env!("CARGO"), "publish", "--all-features"], dry_run), + } } pub fn update(dry_run: bool) -> Result { diff --git a/src/config.rs b/src/config.rs index ff182d504..daa501676 100644 --- a/src/config.rs +++ b/src/config.rs @@ -28,6 +28,8 @@ pub static TAG_MESSAGE: &'static str = "tag-message"; pub static TAG_PREFIX: &'static str = "tag-prefix"; pub static DOC_COMMIT_MESSAGE: &'static str = "doc-commit-message"; pub static DISABLE_TAG: &'static str = "disable-tag"; +pub static ENABLE_FEATURES: &'static str = "enable-features"; +pub static ENABLE_ALL_FEATURES: &'static str = "all-features"; fn load_from_file(path: &Path) -> io::Result { let mut file = File::open(path)?; @@ -123,6 +125,8 @@ pub fn verify_release_config(config: &Table) -> Option> { TAG_PREFIX, DOC_COMMIT_MESSAGE, DISABLE_TAG, + ENABLE_FEATURES, + ENABLE_ALL_FEATURES ]; let mut invalid_keys = Vec::new(); for i in config.keys() { diff --git a/src/main.rs b/src/main.rs index 35514189c..0c67610fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,8 +40,7 @@ fn get_string_option( config::get_release_config(config_file, config_file_key) .and_then(|f| f.as_str()) .map(|f| f.to_owned()) - }) - .unwrap_or(default_value.to_owned()) + }).unwrap_or(default_value.to_owned()) } fn get_bool_option(cli: bool, config_file: Option<&Table>, config_file_key: &str) -> bool { @@ -50,6 +49,25 @@ fn get_bool_option(cli: bool, config_file: Option<&Table>, config_file_key: &str .unwrap_or(false) } +/// Takes a list in form `"a,b,d,e"` and returns a Vec: `["a", "b", "c", "d"]` +fn get_list_option( + cli: &Option>, + config_file: Option<&Table>, + config_file_key: &str, +) -> Option> { + cli.clone().or_else(|| { + config::get_release_config(config_file, config_file_key) + .and_then(|f| f.as_array()) + .and_then(|a| { + Some( + a.iter() + .map(|i| String::from(i.as_str().unwrap())) + .collect::>(), + ) + }) + }) +} + fn execute(args: &ReleaseOpt) -> Result { let cargo_file = config::parse_cargo_config()?; let custom_config_path_option = args.config.as_ref(); @@ -118,20 +136,20 @@ fn execute(args: &ReleaseOpt) -> Result { .unwrap_or("(cargo-release) start next development iteration {{version}}"); let pre_release_replacements = config::get_release_config(release_config.as_ref(), config::PRE_RELEASE_REPLACEMENTS); - let pre_release_hook = - config::get_release_config(release_config.as_ref(), config::PRE_RELEASE_HOOK).and_then( - |h| match h { - &Value::String(ref s) => Some(vec![s.as_ref()]), - &Value::Array(ref a) => Some( - a.iter() - .map(|v| v.as_str()) - .filter(|o| o.is_some()) - .map(|s| s.unwrap()) - .collect(), - ), - _ => None, - }, - ); + let pre_release_hook = config::get_release_config( + release_config.as_ref(), + config::PRE_RELEASE_HOOK, + ).and_then(|h| match h { + &Value::String(ref s) => Some(vec![s.as_ref()]), + &Value::Array(ref a) => Some( + a.iter() + .map(|v| v.as_str()) + .filter(|o| o.is_some()) + .map(|s| s.unwrap()) + .collect(), + ), + _ => None, + }); let tag_msg = config::get_release_config(release_config.as_ref(), config::TAG_MESSAGE) .and_then(|f| f.as_str()) .unwrap_or("(cargo-release) {{prefix}} version {{version}}"); @@ -148,6 +166,29 @@ fn execute(args: &ReleaseOpt) -> Result { .and_then(|f| f.as_bool()) .unwrap_or(!skip_publish); let metadata = args.metadata.as_ref(); + let feature_list = get_list_option( + &if args.features.is_empty() { + None + } else { + Some(args.features.clone()) + }, + release_config.as_ref(), + config::ENABLE_FEATURES, + ); + let all_features = get_bool_option( + args.all_features, + release_config.as_ref(), + config::ENABLE_FEATURES, + ); + + let features = if all_features { + Features::All + } else { + match feature_list { + Some(vec) => Features::Selective(vec), + None => Features::None, + } + }; // STEP 0: Check if working directory is clean if !git::status()? { @@ -224,7 +265,7 @@ fn execute(args: &ReleaseOpt) -> Result { // STEP 3: cargo publish if publish { shell::log_info("Running cargo publish"); - if !cargo::publish(dry_run)? { + if !cargo::publish(dry_run, features)? { return Ok(103); } } @@ -258,8 +299,7 @@ fn execute(args: &ReleaseOpt) -> Result { config::get_release_config(release_config.as_ref(), config::TAG_PREFIX) .and_then(|f| f.as_str()) .map(|f| f.to_string()) - }) - .or_else(|| rel_path.as_ref().map(|t| format!("{}-", t))); + }).or_else(|| rel_path.as_ref().map(|t| format!("{}-", t))); let current_version = version.to_string(); let tag_name = tag_prefix.as_ref().map_or_else( @@ -313,6 +353,16 @@ fn execute(args: &ReleaseOpt) -> Result { Ok(0) } +/// Expresses what features flags should be used +pub enum Features { + /// None - don't use special features + None, + /// Only use selected features + Selective(Vec), + /// Use all features via `all-features` + All, +} + #[derive(Debug, StructOpt)] struct ReleaseOpt { /// Release level: bumping major|minor|patch|rc|beta|alpha version on release or removing prerelease extensions by default @@ -373,6 +423,14 @@ struct ReleaseOpt { #[structopt(long = "no-confirm")] /// Skip release confirmation and version preview no_confirm: bool, + + #[structopt(long = "features")] + /// Provide a set of features that need to be enabled + features: Vec, + + #[structopt(long = "all-features")] + /// Enable all features via `all-features`. Overrides `features` + all_features: bool, } #[derive(Debug, StructOpt)]