diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 10c362e2a16..d4ff0de6c12 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -809,6 +809,19 @@ fn build_base_args<'a, 'cfg>( cmd.args(args); } + let unit_cfg = bcx.cfg(unit.kind); + let features = bcx.resolve.features(unit.pkg.package_id()); + if let Some(ref lints) = unit.pkg.manifest().lints() { + for ref lint_section in lints.iter() { + lint_section.set_lint_flags(unit_cfg, features, cmd); + } + } + if let Some(ref virtual_lints) = bcx.ws.virtual_lints() { + for ref lint_section in virtual_lints.iter() { + lint_section.set_lint_flags(unit_cfg, features, cmd); + } + } + // -C overflow-checks is implied by the setting of -C debug-assertions, // so we only need to provide -C overflow-checks if it differs from // the value of -C debug-assertions we would provide. diff --git a/src/cargo/core/lints.rs b/src/cargo/core/lints.rs new file mode 100644 index 00000000000..45649e061a3 --- /dev/null +++ b/src/cargo/core/lints.rs @@ -0,0 +1,88 @@ +use std::collections::BTreeMap; +use std::collections::HashSet; +use std::str::FromStr; + +use util::{Cfg, CfgExpr, ProcessBuilder}; +use util::errors::CargoResult; + +#[derive(Clone, PartialEq, Debug)] +enum LintKind { + Allow, + Warn, + Deny, + Forbid, +} + +impl LintKind { + pub fn try_from_string(lint_state: &str) -> Option { + match lint_state.as_ref() { + "allow" => Some(LintKind::Allow), + "warn" => Some(LintKind::Warn), + "deny" => Some(LintKind::Deny), + "forbid" => Some(LintKind::Forbid), + _ => None, + } + } + + pub fn flag(&self) -> char { + match self { + LintKind::Allow => 'A', + LintKind::Warn => 'W', + LintKind::Deny => 'D', + LintKind::Forbid => 'F', + } + } +} + +#[derive(Clone, Debug)] +pub struct Lints { + lints: Vec<(String, LintKind)>, + cfg: Option, +} + +impl Lints { + pub fn new( + cfg: Option<&String>, + manifest_lints: &BTreeMap, + warnings: &mut Vec, + ) -> CargoResult { + let cfg = if let Some(t) = cfg { + if t.starts_with("cfg(") && t.ends_with(')') { + Some(CfgExpr::from_str(&t[4..t.len() - 1])?) + } else { + bail!("expected `cfg(...)`, found {}", t) + } + } else { + None + }; + + let mut lints = vec![]; + for (lint_name, lint_state) in manifest_lints.iter() { + if let Some(state) = LintKind::try_from_string(lint_state) { + lints.push((lint_name.to_string(), state)); + } else { + warnings.push(format!( + "invalid lint state for `{}` (expected `warn`, `allow`, `deny` or `forbid`)", + lint_name + )); + } + } + Ok(Lints { lints, cfg }) + } + + pub fn set_lint_flags(&self, unit_cfg: &[Cfg], features: &HashSet, cmd: &mut ProcessBuilder) { + match self.cfg { + None => self.set_flags(cmd), + Some(CfgExpr::Value(Cfg::KeyPair(ref key, ref value))) + if key == "feature" && features.contains(value) => self.set_flags(cmd), + Some(ref cfg) if cfg.matches(unit_cfg) => self.set_flags(cmd), + _ => (), + } + } + + fn set_flags(&self, cmd: &mut ProcessBuilder) { + for (lint_name, state) in self.lints.iter() { + cmd.arg(format!("-{}", state.flag())).arg(lint_name); + } + } +} diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index 88ede658c3e..801c25feeb3 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -12,6 +12,7 @@ use toml; use url::Url; use core::interning::InternedString; +use core::lints::Lints; use core::profiles::Profiles; use core::{Dependency, PackageId, PackageIdSpec, SourceId, Summary}; use core::{Edition, Feature, Features, WorkspaceConfig}; @@ -44,6 +45,7 @@ pub struct Manifest { original: Rc, features: Features, edition: Edition, + lints: Option>, im_a_teapot: Option, default_run: Option, metabuild: Option>, @@ -68,6 +70,7 @@ pub struct VirtualManifest { workspace: WorkspaceConfig, profiles: Profiles, warnings: Warnings, + lints: Option>, } /// General metadata about a package which is just blindly uploaded to the @@ -361,6 +364,7 @@ impl Manifest { workspace: WorkspaceConfig, features: Features, edition: Edition, + lints: Option>, im_a_teapot: Option, default_run: Option, original: Rc, @@ -383,6 +387,7 @@ impl Manifest { features, edition, original, + lints, im_a_teapot, default_run, publish_lockfile, @@ -423,6 +428,9 @@ impl Manifest { pub fn warnings(&self) -> &Warnings { &self.warnings } + pub fn lints(&self) -> &Option> { + &self.lints + } pub fn profiles(&self) -> &Profiles { &self.profiles } @@ -531,6 +539,7 @@ impl VirtualManifest { patch: HashMap>, workspace: WorkspaceConfig, profiles: Profiles, + lints: Option>, ) -> VirtualManifest { VirtualManifest { replace, @@ -538,6 +547,7 @@ impl VirtualManifest { workspace, profiles, warnings: Warnings::new(), + lints, } } @@ -553,6 +563,10 @@ impl VirtualManifest { &self.workspace } + pub fn lints(&self) -> &Option> { + &self.lints + } + pub fn profiles(&self) -> &Profiles { &self.profiles } diff --git a/src/cargo/core/mod.rs b/src/cargo/core/mod.rs index 3312ba3458c..3017abcdb35 100644 --- a/src/cargo/core/mod.rs +++ b/src/cargo/core/mod.rs @@ -21,6 +21,7 @@ pub mod compiler; pub mod dependency; mod features; mod interning; +pub mod lints; pub mod manifest; pub mod package; pub mod package_id; diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index d603516e00a..0cbefcc6cd3 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -7,6 +7,7 @@ use std::slice; use glob::glob; use url::Url; +use core::lints::Lints; use core::profiles::Profiles; use core::registry::PackageRegistry; use core::{Dependency, PackageIdSpec}; @@ -245,6 +246,16 @@ impl<'cfg> Workspace<'cfg> { } } + pub fn virtual_lints(&self) -> &Option> { + let root = self.root_manifest + .as_ref() + .unwrap_or(&self.current_manifest); + match *self.packages.get(root) { + MaybePackage::Virtual(ref vm) => vm.lints(), + _ => &None, + } + } + /// Returns the root path of this workspace. /// /// That is, this returns the path of the directory containing the diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 79b68189a2d..899d4bc54f6 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -14,6 +14,7 @@ use url::Url; use core::dependency::{Kind, Platform}; use core::manifest::{LibKind, ManifestMetadata, TargetSourcePath, Warnings}; +use core::lints::Lints; use core::profiles::Profiles; use core::{Dependency, Manifest, PackageId, Summary, Target}; use core::{Edition, EitherManifest, Feature, Features, VirtualManifest}; @@ -240,6 +241,9 @@ pub struct TomlManifest { patch: Option>>, workspace: Option, badges: Option>>, + lints: Option>, + #[serde(rename = "lints2")] + feature_lints: Option>>, } #[derive(Deserialize, Serialize, Clone, Debug, Default)] @@ -748,6 +752,8 @@ impl TomlManifest { workspace: None, badges: self.badges.clone(), cargo_features: self.cargo_features.clone(), + lints: self.lints.clone(), + feature_lints: self.feature_lints.clone(), }); fn map_deps( @@ -997,6 +1003,7 @@ impl TomlManifest { ), }; let profiles = Profiles::new(me.profile.as_ref(), config, &features, &mut warnings)?; + let lints = lints(me.lints.as_ref(), me.feature_lints.as_ref(), &mut warnings)?; let publish = match project.publish { Some(VecStringOrBool::VecString(ref vecstring)) => { features @@ -1035,6 +1042,7 @@ impl TomlManifest { workspace_config, features, edition, + lints, project.im_a_teapot, project.default_run.clone(), Rc::clone(me), @@ -1109,6 +1117,7 @@ impl TomlManifest { (me.replace(&mut cx)?, me.patch(&mut cx)?) }; let profiles = Profiles::new(me.profile.as_ref(), config, &features, &mut warnings)?; + let lints = lints(me.lints.as_ref(), me.feature_lints.as_ref(), &mut warnings)?; let workspace_config = match me.workspace { Some(ref config) => WorkspaceConfig::Root(WorkspaceRootConfig::new( &root, @@ -1121,7 +1130,7 @@ impl TomlManifest { } }; Ok(( - VirtualManifest::new(replace, patch, workspace_config, profiles), + VirtualManifest::new(replace, patch, workspace_config, profiles, lints), nested_paths, )) } @@ -1489,3 +1498,24 @@ impl fmt::Debug for PathValue { self.0.fmt(f) } } + +fn lints( + toml_lints: Option<&BTreeMap>, + toml_feature_lints: Option<&BTreeMap>>, + warnings: &mut Vec +) -> CargoResult>> { + let mut lints = vec![]; + if let Some(toml_lints) = toml_lints { + lints.push(Lints::new(None, toml_lints, warnings)?); + } + if let Some(toml_feature_lints) = toml_feature_lints { + for (ref cfg, ref feature_lints) in toml_feature_lints.iter() { + lints.push(Lints::new(Some(cfg), feature_lints, warnings)?); + } + } + Ok(if !lints.is_empty() { + Some(lints) + } else { + None + }) +} diff --git a/tests/testsuite/lints.rs b/tests/testsuite/lints.rs new file mode 100644 index 00000000000..3da4644551b --- /dev/null +++ b/tests/testsuite/lints.rs @@ -0,0 +1,190 @@ +use support::project; + +#[test] +fn deny() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [lints] + dead_code = "deny" + "#, + ) + .file("src/lib.rs", "fn foo() {}") + .build(); + + p.cargo("build") + .with_status(101) + .with_stderr_contains("[..]error: function is never used: `foo`[..]") + .run(); +} + +#[test] +fn empty_lints_block() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [lints] + "#, + ) + .file("src/lib.rs", "fn foo() {}") + .build(); + + p.cargo("build").with_status(0).run(); +} + +#[test] +fn invalid_state() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [lints] + non_snake_case = "something something" + "#, + ) + .file("src/lib.rs", "fn foo() {}") + .build(); + + p.cargo("build").with_status(0).run(); +} + +#[test] +fn virtual_workspace() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar"] + + [lints] + dead_code = "deny" + "#, + ) + .file( + "bar/Cargo.toml", + r#" + [project] + name = "bar" + version = "0.1.0" + authors = [] + "#, + ) + .file("bar/src/lib.rs", "fn baz() {}") + .build(); + + p.cargo("build") + .with_status(101) + .with_stderr_contains("[..]error: function is never used: `baz`[..]") + .run(); +} + +#[test] +fn member_workspace() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar"] + "#, + ) + .file( + "bar/Cargo.toml", + r#" + [project] + name = "bar" + version = "0.1.0" + authors = [] + + [lints] + dead_code = "deny" + "#, + ) + .file("bar/src/lib.rs", "fn baz() {}") + .build(); + + p.cargo("build") + .with_status(101) + .with_stderr_contains("[..]error: function is never used: `baz`[..]") + .run(); +} + +#[test] +fn virtual_workspace_overrides() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar"] + + [lints] + dead_code = "deny" + "#, + ) + .file( + "bar/Cargo.toml", + r#" + [project] + name = "bar" + version = "0.1.0" + authors = [] + + [lints] + dead_code = "allow" + "#, + ) + .file("bar/src/lib.rs", "fn baz() {}") + .build(); + + p.cargo("build") + .with_status(101) + .with_stderr_contains("[..]error: function is never used: `baz`[..]") + .run(); +} + +#[test] +fn feature_flag() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + bar = [] + + [lints2.'cfg(feature = "bar")'] + dead_code = "deny" + "#, + ) + .file("src/lib.rs", "fn foo() {}") + .build(); + + p.cargo("build").with_status(0).run(); + p.cargo("build --features bar") + .with_status(101) + .with_stderr_contains("[..]error: function is never used: `foo`[..]") + .run(); +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index 825615bfb90..6df01671f52 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -59,6 +59,7 @@ mod git; mod init; mod install; mod jobserver; +mod lints; mod local_registry; mod lockfile_compat; mod login;