use std::cell::Cell;
use std::env;
use std::fmt;
use std::str::FromStr;
use failure::{bail, Error};
use serde::{Deserialize, Serialize};
use crate::util::errors::CargoResult;
pub const SEE_CHANNELS: &str =
"See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information \
about Rust release channels.";
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize)]
pub enum Edition {
Edition2015,
Edition2018,
}
impl fmt::Display for Edition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Edition::Edition2015 => f.write_str("2015"),
Edition::Edition2018 => f.write_str("2018"),
}
}
}
impl FromStr for Edition {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s {
"2015" => Ok(Edition::Edition2015),
"2018" => Ok(Edition::Edition2018),
s => bail!(
"supported edition values are `2015` or `2018`, but `{}` \
is unknown",
s
),
}
}
}
#[derive(PartialEq)]
enum Status {
Stable,
Unstable,
}
macro_rules! features {
(
pub struct Features {
$([$stab:ident] $feature:ident: bool,)*
}
) => (
#[derive(Default, Clone, Debug)]
pub struct Features {
$($feature: bool,)*
activated: Vec<String>,
}
impl Feature {
$(
pub fn $feature() -> &'static Feature {
fn get(features: &Features) -> bool {
stab!($stab) == Status::Stable || features.$feature
}
static FEAT: Feature = Feature {
name: stringify!($feature),
get,
};
&FEAT
}
)*
fn is_enabled(&self, features: &Features) -> bool {
(self.get)(features)
}
}
impl Features {
fn status(&mut self, feature: &str) -> Option<(&mut bool, Status)> {
if feature.contains("_") {
return None
}
let feature = feature.replace("-", "_");
$(
if feature == stringify!($feature) {
return Some((&mut self.$feature, stab!($stab)))
}
)*
None
}
}
)
}
macro_rules! stab {
(stable) => {
Status::Stable
};
(unstable) => {
Status::Unstable
};
}
features! {
pub struct Features {
[stable] test_dummy_stable: bool,
[unstable] test_dummy_unstable: bool,
[stable] alternative_registries: bool,
[stable] edition: bool,
[stable] rename_dependency: bool,
[unstable] publish_lockfile: bool,
[stable] profile_overrides: bool,
[unstable] namespaced_features: bool,
[stable] default_run: bool,
[unstable] metabuild: bool,
[unstable] public_dependency: bool,
[unstable] named_profiles: bool,
}
}
pub struct Feature {
name: &'static str,
get: fn(&Features) -> bool,
}
impl Features {
pub fn new(features: &[String], warnings: &mut Vec<String>) -> CargoResult<Features> {
let mut ret = Features::default();
for feature in features {
ret.add(feature, warnings)?;
ret.activated.push(feature.to_string());
}
Ok(ret)
}
fn add(&mut self, feature: &str, warnings: &mut Vec<String>) -> CargoResult<()> {
let (slot, status) = match self.status(feature) {
Some(p) => p,
None => bail!("unknown cargo feature `{}`", feature),
};
if *slot {
bail!("the cargo feature `{}` has already been activated", feature);
}
match status {
Status::Stable => {
let warning = format!(
"the cargo feature `{}` is now stable \
and is no longer necessary to be listed \
in the manifest",
feature
);
warnings.push(warning);
}
Status::Unstable if !nightly_features_allowed() => bail!(
"the cargo feature `{}` requires a nightly version of \
Cargo, but this is the `{}` channel\n\
{}",
feature,
channel(),
SEE_CHANNELS
),
Status::Unstable => {}
}
*slot = true;
Ok(())
}
pub fn activated(&self) -> &[String] {
&self.activated
}
pub fn require(&self, feature: &Feature) -> CargoResult<()> {
if feature.is_enabled(self) {
Ok(())
} else {
let feature = feature.name.replace("_", "-");
let mut msg = format!("feature `{}` is required", feature);
if nightly_features_allowed() {
let s = format!(
"\n\nconsider adding `cargo-features = [\"{0}\"]` \
to the manifest",
feature
);
msg.push_str(&s);
} else {
let s = format!(
"\n\n\
this Cargo does not support nightly features, but if you\n\
switch to nightly channel you can add\n\
`cargo-features = [\"{}\"]` to enable this feature",
feature
);
msg.push_str(&s);
}
bail!("{}", msg);
}
}
pub fn is_enabled(&self, feature: &Feature) -> bool {
feature.is_enabled(self)
}
}
#[derive(Default, Debug)]
pub struct CliUnstable {
pub print_im_a_teapot: bool,
pub unstable_options: bool,
pub no_index_update: bool,
pub avoid_dev_deps: bool,
pub minimal_versions: bool,
pub package_features: bool,
pub advanced_env: bool,
pub config_profile: bool,
pub dual_proc_macros: bool,
pub mtime_on_use: bool,
pub named_profiles: bool,
pub binary_dep_depinfo: bool,
pub build_std: Option<Vec<String>>,
pub timings: Option<Vec<String>>,
pub doctest_xcompile: bool,
pub panic_abort_tests: bool,
}
impl CliUnstable {
pub fn parse(&mut self, flags: &[String]) -> CargoResult<()> {
if !flags.is_empty() && !nightly_features_allowed() {
bail!(
"the `-Z` flag is only accepted on the nightly channel of Cargo, \
but this is the `{}` channel\n\
{}",
channel(),
SEE_CHANNELS
);
}
for flag in flags {
self.add(flag)?;
}
Ok(())
}
fn add(&mut self, flag: &str) -> CargoResult<()> {
let mut parts = flag.splitn(2, '=');
let k = parts.next().unwrap();
let v = parts.next();
fn parse_bool(key: &str, value: Option<&str>) -> CargoResult<bool> {
match value {
None | Some("yes") => Ok(true),
Some("no") => Ok(false),
Some(s) => bail!("flag -Z{} expected `no` or `yes`, found: `{}`", key, s),
}
}
fn parse_timings(value: Option<&str>) -> Vec<String> {
match value {
None => vec!["html".to_string(), "info".to_string()],
Some(v) => v.split(',').map(|s| s.to_string()).collect(),
}
}
fn parse_empty(key: &str, value: Option<&str>) -> CargoResult<bool> {
if let Some(v) = value {
bail!("flag -Z{} does not take a value, found: `{}`", key, v);
}
Ok(true)
};
match k {
"print-im-a-teapot" => self.print_im_a_teapot = parse_bool(k, v)?,
"unstable-options" => self.unstable_options = parse_empty(k, v)?,
"no-index-update" => self.no_index_update = parse_empty(k, v)?,
"avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?,
"minimal-versions" => self.minimal_versions = parse_empty(k, v)?,
"package-features" => self.package_features = parse_empty(k, v)?,
"advanced-env" => self.advanced_env = parse_empty(k, v)?,
"config-profile" => self.config_profile = parse_empty(k, v)?,
"dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?,
"mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?,
"named-profiles" => self.named_profiles = parse_empty(k, v)?,
"binary-dep-depinfo" => self.binary_dep_depinfo = parse_empty(k, v)?,
"build-std" => {
self.build_std = Some(crate::core::compiler::standard_lib::parse_unstable_flag(v))
}
"timings" => self.timings = Some(parse_timings(v)),
"doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?,
"panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
_ => bail!("unknown `-Z` flag specified: {}", k),
}
Ok(())
}
pub fn fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()> {
if !self.unstable_options {
let see = format!(
"See https://github.com/rust-lang/cargo/issues/{} for more \
information about the `{}` flag.",
issue, flag
);
if nightly_features_allowed() {
bail!(
"the `{}` flag is unstable, pass `-Z unstable-options` to enable it\n\
{}",
flag,
see
);
} else {
bail!(
"the `{}` flag is unstable, and only available on the nightly channel \
of Cargo, but this is the `{}` channel\n\
{}\n\
{}",
flag,
channel(),
SEE_CHANNELS,
see
);
}
}
Ok(())
}
}
pub fn channel() -> String {
if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") {
return override_channel;
}
if let Ok(staging) = env::var("RUSTC_BOOTSTRAP") {
if staging == "1" {
return "dev".to_string();
}
}
crate::version()
.cfg_info
.map(|c| c.release_channel)
.unwrap_or_else(|| String::from("dev"))
}
thread_local!(
static NIGHTLY_FEATURES_ALLOWED: Cell<bool> = Cell::new(false);
static ENABLE_NIGHTLY_FEATURES: Cell<bool> = Cell::new(false);
);
pub fn nightly_features_allowed() -> bool {
if ENABLE_NIGHTLY_FEATURES.with(|c| c.get()) {
return true;
}
match &channel()[..] {
"nightly" | "dev" => NIGHTLY_FEATURES_ALLOWED.with(|c| c.get()),
_ => false,
}
}
pub fn maybe_allow_nightly_features() {
NIGHTLY_FEATURES_ALLOWED.with(|c| c.set(true));
}
pub fn enable_nightly_features() {
ENABLE_NIGHTLY_FEATURES.with(|c| c.set(true));
}