diff --git a/.travis.yml b/.travis.yml index 012d6045cd2..b204bb0b2f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,9 @@ script: - remark -f README.md > /dev/null - python util/update_lints.py -c - cargo build --features debugging - - rm -rf target/ Cargo.lock - cargo test --features debugging + - SYSROOT=~/rust cargo install + - cargo clippy --lib -- -D clippy after_success: # only test regex_macros if it compiles diff --git a/Cargo.toml b/Cargo.toml index cd6b2834c56..470d2dd7096 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,12 @@ keywords = ["clippy", "lint", "plugin"] [lib] name = "clippy" plugin = true +test = false + +[[bin]] +name = "cargo-clippy" +path = "src/lib.rs" +test = false [dependencies] regex-syntax = "0.3.0" diff --git a/README.md b/README.md index 8f3519c95c4..ee234432c63 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,8 @@ More to come, please [file an issue](https://github.com/Manishearth/rust-clippy/ ## Usage +### As a Compiler Plugin + Compiler plugins are highly unstable and will only work with a nightly Rust for now. Since stable Rust is backwards compatible, you should be able to compile your stable programs with nightly Rust with clippy plugged in to circumvent @@ -217,8 +219,28 @@ src/main.rs:8:5: 11:6 help: Try if let Some(y) = x { println!("{:?}", y) } ``` -An alternate way to use clippy is by compiling and using [`cargo clippy`](https://github.com/arcnmx/cargo-clippy), -a custom cargo subcommand that runs clippy on a given project. +### As a cargo subcommand (`cargo clippy`) + +An alternate way to use clippy is by installing clippy through cargo as a cargo +subcommand. + +```terminal +cargo install clippy +``` + +Now you can run clippy by invoking `cargo clippy`, or +`multirust run nightly cargo clippy` directly from a directory that is usually +compiled with stable. + +In case you are not using multirust, you need to set the environment flag +`SYSROOT` during installation so clippy knows where to find `librustc` and +similar crates. + +```terminal +SYSROOT=/path/to/rustc/sysroot cargo install clippy +``` + +### Configuring clippy You can add options to `allow`/`warn`/`deny`: @@ -234,6 +256,8 @@ You can add options to `allow`/`warn`/`deny`: Note: `deny` produces errors instead of warnings +### Running clippy from the command line without installing + To have cargo compile your crate with clippy without needing `#![plugin(clippy)]` in your code, you can use: @@ -244,6 +268,8 @@ cargo rustc -- -L /path/to/clippy_so -Z extra-plugins=clippy *[Note](https://github.com/Manishearth/rust-clippy/wiki#a-word-of-warning):* Be sure that clippy was compiled with the same version of rustc that cargo invokes here! +### Optional dependency + If you want to make clippy an optional dependency, you can do the following: In your `Cargo.toml`: diff --git a/src/lib.rs b/src/lib.rs index e5d374a560f..5dd473c8d09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,11 +9,145 @@ #![allow(indexing_slicing, shadow_reuse, unknown_lints)] #![allow(float_arithmetic, integer_arithmetic)] -// this only exists to allow the "dogfood" integration test to work -#[allow(dead_code)] -#[allow(print_stdout)] -fn main() { - println!("What are you doing? Don't run clippy as an executable"); +extern crate rustc_driver; +extern crate getopts; + +use rustc_driver::{driver, CompilerCalls, RustcDefaultCalls, Compilation}; +use rustc::session::{config, Session}; +use rustc::session::config::{Input, ErrorOutputType}; +use syntax::diagnostics; +use std::path::PathBuf; + +struct ClippyCompilerCalls(RustcDefaultCalls); + +impl std::default::Default for ClippyCompilerCalls { + fn default() -> Self { + Self::new() + } +} + +impl ClippyCompilerCalls { + fn new() -> Self { + ClippyCompilerCalls(RustcDefaultCalls) + } +} + +impl<'a> CompilerCalls<'a> for ClippyCompilerCalls { + fn early_callback(&mut self, + matches: &getopts::Matches, + sopts: &config::Options, + descriptions: &diagnostics::registry::Registry, + output: ErrorOutputType) + -> Compilation { + self.0.early_callback(matches, sopts, descriptions, output) + } + fn no_input(&mut self, + matches: &getopts::Matches, + sopts: &config::Options, + odir: &Option, + ofile: &Option, + descriptions: &diagnostics::registry::Registry) + -> Option<(Input, Option)> { + self.0.no_input(matches, sopts, odir, ofile, descriptions) + } + fn late_callback(&mut self, + matches: &getopts::Matches, + sess: &Session, + input: &Input, + odir: &Option, + ofile: &Option) + -> Compilation { + self.0.late_callback(matches, sess, input, odir, ofile) + } + fn build_controller(&mut self, sess: &Session, matches: &getopts::Matches) -> driver::CompileController<'a> { + let mut control = self.0.build_controller(sess, matches); + + let old = std::mem::replace(&mut control.after_parse.callback, box |_| {}); + control.after_parse.callback = Box::new(move |state| { + { + let mut registry = rustc_plugin::registry::Registry::new(state.session, state.krate.as_ref().expect("at this compilation stage the krate must be parsed")); + registry.args_hidden = Some(Vec::new()); + plugin_registrar(&mut registry); + + let rustc_plugin::registry::Registry { early_lint_passes, late_lint_passes, lint_groups, llvm_passes, attributes, mir_passes, .. } = registry; + let sess = &state.session; + let mut ls = sess.lint_store.borrow_mut(); + for pass in early_lint_passes { + ls.register_early_pass(Some(sess), true, pass); + } + for pass in late_lint_passes { + ls.register_late_pass(Some(sess), true, pass); + } + + for (name, to) in lint_groups { + ls.register_group(Some(sess), true, name, to); + } + + sess.plugin_llvm_passes.borrow_mut().extend(llvm_passes); + sess.mir_passes.borrow_mut().extend(mir_passes); + sess.plugin_attributes.borrow_mut().extend(attributes); + } + old(state); + }); + + control + } +} + +use std::path::Path; + +pub fn main() { + use std::env; + + if env::var("CLIPPY_DOGFOOD").map(|_| true).unwrap_or(false) { + return; + } + + let dep_path = env::current_dir().expect("current dir is not readable").join("target").join("debug").join("deps"); + let sys_root = match (option_env!("MULTIRUST_HOME"), option_env!("MULTIRUST_TOOLCHAIN")) { + (Some(home), Some(toolchain)) => format!("{}/toolchains/{}", home, toolchain), + _ => option_env!("SYSROOT").expect("need to specify SYSROOT env var during clippy compilation or use multirust").to_owned(), + }; + + if let Some("clippy") = std::env::args().nth(1).as_ref().map(AsRef::as_ref) { + let args = wrap_args(std::env::args().skip(2), dep_path, sys_root); + let path = std::env::current_exe().expect("current executable path invalid"); + let run = std::process::Command::new("cargo") + .args(&args) + .env("RUSTC", path) + .spawn().expect("could not run cargo") + .wait().expect("failed to wait for cargo?") + .success(); + assert!(run, "cargo rustc failed"); + } else { + let args: Vec = if env::args().any(|s| s == "--sysroot") { + env::args().collect() + } else { + env::args().chain(Some("--sysroot".to_owned())).chain(Some(sys_root)).collect() + }; + rustc_driver::run_compiler(&args, &mut ClippyCompilerCalls::new()); + } +} + +fn wrap_args(old_args: I, dep_path: P, sysroot: String) -> Vec + where P: AsRef, I: Iterator { + + let mut args = vec!["rustc".to_owned()]; + + let mut found_dashes = false; + for arg in old_args { + found_dashes |= arg == "--"; + args.push(arg); + } + if !found_dashes { + args.push("--".to_owned()); + } + args.push("-L".to_owned()); + args.push(dep_path.as_ref().to_string_lossy().into_owned()); + args.push(String::from("--sysroot")); + args.push(sysroot); + args.push("-Zno-trans".to_owned()); + args } #[macro_use] diff --git a/tests/dogfood.rs b/tests/dogfood.rs index b5ae813ae51..d050b4fc5ba 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -1,9 +1,11 @@ -#![feature(test)] +#![feature(test, plugin)] +#![plugin(clippy)] +#![deny(clippy, clippy_pedantic)] extern crate compiletest_rs as compiletest; extern crate test; -use std::env::var; +use std::env::{var, set_var}; use std::path::PathBuf; use test::TestPaths; @@ -11,15 +13,14 @@ use test::TestPaths; fn dogfood() { let mut config = compiletest::default_config(); - let cfg_mode = "run-pass".parse().ok().expect("Invalid mode"); + let cfg_mode = "run-pass".parse().expect("Invalid mode"); let mut s = String::new(); s.push_str(" -L target/debug/"); s.push_str(" -L target/debug/deps"); s.push_str(" -Zextra-plugins=clippy -Ltarget_recur/debug -Dclippy_pedantic -Dclippy"); config.target_rustcflags = Some(s); - if let Ok(name) = var::<&str>("TESTNAME") { - let s : String = name.to_owned(); - config.filter = Some(s) + if let Ok(name) = var("TESTNAME") { + config.filter = Some(name.to_owned()) } config.mode = cfg_mode; @@ -29,5 +30,8 @@ fn dogfood() { file: PathBuf::from("src/lib.rs"), relative_dir: PathBuf::new(), }; + + set_var("CLIPPY_DOGFOOD", "tastes like chicken"); + compiletest::runtest::run(config, &paths); }