From 4b82fdc04c897d582b7ff376a9e7a7cf5a79a866 Mon Sep 17 00:00:00 2001 From: Nick Cameron Date: Wed, 16 Nov 2016 14:46:24 +1300 Subject: [PATCH] cargo check Adds a new mode - check - which only checks code, rather than generating machine code. It takes into account that proc macros and build scripts will still require generated code. Implemented by adding a check profile and setting this on each Unit, unless the unit is required to be built (i.e., is a proc macro, build script, or dep of one). --- src/bin/cargo.rs | 2 + src/bin/check.rs | 103 +++++++++++++++++++++ src/cargo/core/manifest.rs | 14 ++- src/cargo/core/workspace.rs | 1 + src/cargo/ops/cargo_clean.rs | 4 +- src/cargo/ops/cargo_compile.rs | 18 ++-- src/cargo/ops/cargo_rustc/context.rs | 68 +++++++++----- src/cargo/ops/cargo_rustc/mod.rs | 28 ++++-- src/cargo/util/toml.rs | 3 + src/etc/_cargo | 18 ++++ src/etc/cargo.bashcomp.sh | 1 + src/etc/man/cargo-check.1 | 132 +++++++++++++++++++++++++++ 12 files changed, 348 insertions(+), 44 deletions(-) create mode 100644 src/bin/check.rs create mode 100644 src/etc/man/cargo-check.1 diff --git a/src/bin/cargo.rs b/src/bin/cargo.rs index 56a03a01011..05cf786dd9c 100644 --- a/src/bin/cargo.rs +++ b/src/bin/cargo.rs @@ -51,6 +51,7 @@ Options: Some common cargo commands are (see all commands with --list): build Compile the current project + check Analyze the current project and report errors, but don't build object files clean Remove the target directory doc Build this project's and its dependencies' documentation new Create a new cargo project @@ -75,6 +76,7 @@ macro_rules! each_subcommand{ ($mac:ident) => { $mac!(bench); $mac!(build); + $mac!(check); $mac!(clean); $mac!(doc); $mac!(fetch); diff --git a/src/bin/check.rs b/src/bin/check.rs new file mode 100644 index 00000000000..4e36b2999d8 --- /dev/null +++ b/src/bin/check.rs @@ -0,0 +1,103 @@ +use std::env; + +use cargo::core::Workspace; +use cargo::ops::{self, CompileOptions, MessageFormat}; +use cargo::util::important_paths::{find_root_manifest_for_wd}; +use cargo::util::{CliResult, Config}; + +#[derive(RustcDecodable)] +pub struct Options { + flag_package: Vec, + flag_jobs: Option, + flag_features: Vec, + flag_all_features: bool, + flag_no_default_features: bool, + flag_target: Option, + flag_manifest_path: Option, + flag_verbose: u32, + flag_quiet: Option, + flag_color: Option, + flag_message_format: MessageFormat, + flag_release: bool, + flag_lib: bool, + flag_bin: Vec, + flag_example: Vec, + flag_test: Vec, + flag_bench: Vec, + flag_locked: bool, + flag_frozen: bool, +} + +pub const USAGE: &'static str = " +Check a local package and all of its dependencies for errors + +Usage: + cargo check [options] + +Options: + -h, --help Print this message + -p SPEC, --package SPEC ... Package to check + -j N, --jobs N Number of parallel jobs, defaults to # of CPUs + --lib Check only this package's library + --bin NAME Check only the specified binary + --example NAME Check only the specified example + --test NAME Check only the specified test target + --bench NAME Check only the specified benchmark target + --release Check artifacts in release mode, with optimizations + --features FEATURES Space-separated list of features to also check + --all-features Check all available features + --no-default-features Do not check the `default` feature + --target TRIPLE Check for the target triple + --manifest-path PATH Path to the manifest to compile + -v, --verbose ... Use verbose output + -q, --quiet No output printed to stdout + --color WHEN Coloring: auto, always, never + --message-format FMT Error format: human, json [default: human] + --frozen Require Cargo.lock and cache are up to date + --locked Require Cargo.lock is up to date + +If the --package argument is given, then SPEC is a package id specification +which indicates which package should be built. If it is not given, then the +current package is built. For more information on SPEC and its format, see the +`cargo help pkgid` command. + +Compilation can be configured via the use of profiles which are configured in +the manifest. The default profile for this command is `dev`, but passing +the --release flag will use the `release` profile instead. +"; + +pub fn execute(options: Options, config: &Config) -> CliResult> { + debug!("executing; cmd=cargo-check; args={:?}", + env::args().collect::>()); + config.configure(options.flag_verbose, + options.flag_quiet, + &options.flag_color, + options.flag_frozen, + options.flag_locked)?; + + let root = find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())?; + + let opts = CompileOptions { + config: config, + jobs: options.flag_jobs, + target: options.flag_target.as_ref().map(|t| &t[..]), + features: &options.flag_features, + all_features: options.flag_all_features, + no_default_features: options.flag_no_default_features, + spec: &options.flag_package, + mode: ops::CompileMode::Check, + release: options.flag_release, + filter: ops::CompileFilter::new(options.flag_lib, + &options.flag_bin, + &options.flag_test, + &options.flag_example, + &options.flag_bench), + message_format: options.flag_message_format, + target_rustdoc_args: None, + target_rustc_args: None, + }; + + let ws = Workspace::new(&root, config)?; + ops::compile(&ws, &opts)?; + Ok(None) +} diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index da40c6a55a0..d5a2f25e0f5 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -71,7 +71,7 @@ impl LibKind { "lib" => LibKind::Lib, "rlib" => LibKind::Rlib, "dylib" => LibKind::Dylib, - "procc-macro" => LibKind::ProcMacro, + "proc-macro" => LibKind::ProcMacro, s => LibKind::Other(s.to_string()), } } @@ -136,6 +136,7 @@ pub struct Profile { pub test: bool, pub doc: bool, pub run_custom_build: bool, + pub check: bool, pub panic: Option, } @@ -168,6 +169,7 @@ pub struct Profiles { pub bench_deps: Profile, pub doc: Profile, pub custom_build: Profile, + pub check: Profile, } /// Information about a binary, a library, an example, etc. that is part of the @@ -531,6 +533,13 @@ impl Profile { ..Profile::default_dev() } } + + pub fn default_check() -> Profile { + Profile { + check: true, + ..Profile::default_dev() + } + } } impl Default for Profile { @@ -547,6 +556,7 @@ impl Default for Profile { test: false, doc: false, run_custom_build: false, + check: false, panic: None, } } @@ -560,6 +570,8 @@ impl fmt::Display for Profile { write!(f, "Profile(doc)") } else if self.run_custom_build { write!(f, "Profile(run)") + } else if self.check { + write!(f, "Profile(check)") } else { write!(f, "Profile(build)") } diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index f35432f6469..d0b0fdeec15 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -450,6 +450,7 @@ impl<'cfg> Workspace<'cfg> { bench_deps: Profile::default_release(), doc: Profile::default_doc(), custom_build: Profile::default_custom_build(), + check: Profile::default_check(), }; for pkg in self.members().filter(|p| p.manifest_path() != root_manifest) { diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index fd270b79aba..330d396dff5 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -54,10 +54,10 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> { for kind in [Kind::Host, Kind::Target].iter() { let Profiles { ref release, ref dev, ref test, ref bench, ref doc, - ref custom_build, ref test_deps, ref bench_deps, + ref custom_build, ref test_deps, ref bench_deps, ref check } = *profiles; let profiles = [release, dev, test, bench, doc, custom_build, - test_deps, bench_deps]; + test_deps, bench_deps, check]; for profile in profiles.iter() { units.push(Unit { pkg: &pkg, diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index e3b22f9d8e3..da6f538ce61 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -69,6 +69,7 @@ pub struct CompileOptions<'a> { pub enum CompileMode { Test, Build, + Check, Bench, Doc { deps: bool }, } @@ -188,13 +189,13 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>, let profiles = ws.profiles(); - let resolve = resolve_dependencies(ws, - source, - features, - all_features, - no_default_features, - &spec)?; - let (spec, packages, resolve_with_overrides) = resolve; + let pair = resolve_dependencies(ws, + source, + features, + all_features, + no_default_features, + &specs)?; + let (packages, resolve_with_overrides) = pair; let mut pkgids = Vec::new(); if spec.len() > 0 { @@ -335,6 +336,7 @@ fn generate_targets<'a>(pkg: &'a Package, CompileMode::Test => test, CompileMode::Bench => &profiles.bench, CompileMode::Build => build, + CompileMode::Check => &profiles.check, CompileMode::Doc { .. } => &profiles.doc, }; match *filter { @@ -366,7 +368,7 @@ fn generate_targets<'a>(pkg: &'a Package, } Ok(base) } - CompileMode::Build => { + CompileMode::Build | CompileMode::Check => { Ok(pkg.targets().iter().filter(|t| { t.is_bin() || t.is_lib() }).map(|t| (t, profile)).collect()) diff --git a/src/cargo/ops/cargo_rustc/context.rs b/src/cargo/ops/cargo_rustc/context.rs index e0759ad8104..bb8176a6f05 100644 --- a/src/cargo/ops/cargo_rustc/context.rs +++ b/src/cargo/ops/cargo_rustc/context.rs @@ -20,7 +20,7 @@ use super::layout::Layout; use super::links::Links; use super::{Kind, Compilation, BuildConfig}; -#[derive(Clone, Copy, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] pub struct Unit<'a> { pub pkg: &'a Package, pub target: &'a Target, @@ -70,9 +70,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { let dest = if build_config.release { "release" } else { "debug" }; let host_layout = Layout::new(ws, None, &dest)?; let target_layout = match build_config.requested_target.as_ref() { - Some(target) => { - Some(Layout::new(ws, Some(&target), &dest)?) - } + Some(target) => Some(Layout::new(ws, Some(&target), dest)?), None => None, }; @@ -148,6 +146,9 @@ impl<'a, 'cfg> Context<'a, 'cfg> { unit: &Unit<'a>, crate_types: &mut BTreeSet) -> CargoResult<()> { + if unit.profile.check { + crate_types.insert("metadata".to_string()); + } for target in unit.pkg.manifest().targets() { crate_types.extend(target.rustc_crate_types().iter().map(|s| { if *s == "lib" { @@ -207,6 +208,10 @@ impl<'a, 'cfg> Context<'a, 'cfg> { line.contains(crate_type) }); if not_supported { + if crate_type == "metadata" { + bail!("compiler does not support `--crate-type metadata`, \ + cannot run `cargo check`."); + } map.insert(crate_type.to_string(), None); continue } @@ -251,8 +256,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> { let mut visited = HashSet::new(); for unit in units { self.walk_used_in_plugin_map(unit, - unit.target.for_host(), - &mut visited)?; + unit.target.for_host(), + &mut visited)?; } Ok(()) } @@ -270,8 +275,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> { } for unit in self.dep_targets(unit)? { self.walk_used_in_plugin_map(&unit, - is_plugin || unit.target.for_host(), - visited)?; + is_plugin || unit.target.for_host(), + visited)?; } Ok(()) } @@ -509,20 +514,25 @@ impl<'a, 'cfg> Context<'a, 'cfg> { } } }; - match *unit.target.kind() { - TargetKind::Example | - TargetKind::Bin | - TargetKind::CustomBuild | - TargetKind::Bench | - TargetKind::Test => { - add("bin", false)?; - } - TargetKind::Lib(..) if unit.profile.test => { - add("bin", false)?; - } - TargetKind::Lib(ref libs) => { - for lib in libs { - add(lib.crate_type(), lib.linkable())?; + + if unit.profile.check { + add("metadata", true)?; + } else { + match *unit.target.kind() { + TargetKind::Example | + TargetKind::Bin | + TargetKind::CustomBuild | + TargetKind::Bench | + TargetKind::Test => { + add("bin", false)?; + } + TargetKind::Lib(..) if unit.profile.test => { + add("bin", false)?; + } + TargetKind::Lib(ref libs) => { + for lib in libs { + add(lib.crate_type(), lib.linkable())?; + } } } } @@ -593,12 +603,20 @@ impl<'a, 'cfg> Context<'a, 'cfg> { match self.get_package(id) { Ok(pkg) => { pkg.targets().iter().find(|t| t.is_lib()).map(|t| { - Ok(Unit { + let profile = if unit.profile.check && + !t.is_custom_build() + && !t.for_host() { + &self.profiles.check + } else { + self.lib_profile() + }; + let unit = Unit { pkg: pkg, target: t, - profile: self.lib_profile(), + profile: profile, kind: unit.kind.for_target(t), - }) + }; + Ok(unit) }) } Err(e) => Some(Err(e)) diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index 809437c644a..6c59910537c 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -52,7 +52,7 @@ pub struct TargetConfig { pub overrides: HashMap, } -pub type PackagesToBuild<'a> = [(&'a Package, Vec<(&'a Target,&'a Profile)>)]; +pub type PackagesToBuild<'a> = [(&'a Package, Vec<(&'a Target, &'a Profile)>)]; // Returns a mapping of the root package plus its immediate dependencies to // where the compiled libraries are all located. @@ -203,6 +203,7 @@ fn compile<'a, 'cfg: 'a>(cx: &mut Context<'a, 'cfg>, for unit in cx.dep_targets(unit)?.iter() { compile(cx, jobs, unit)?; } + Ok(()) } @@ -270,11 +271,20 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult { // FIXME(rust-lang/rust#18913): we probably shouldn't have to do // this manually - for &(ref dst, ref _link_dst, _linkable) in filenames.iter() { - if fs::metadata(&dst).is_ok() { - fs::remove_file(&dst).chain_error(|| { - human(format!("Could not remove file: {}.", dst.display())) - })?; + for &(ref filename, ref _link_dst, _linkable) in filenames.iter() { + let mut dsts = vec![root.join(filename)]; + // If there is both an rmeta and rlib, rustc will prefer to use the + // rlib, even if it is older. Therefore, we must delete the rlib to + // force using the new rmeta. + if dsts[0].extension() == Some(&OsStr::new("rmeta")) { + dsts.push(root.join(filename).with_extension("rlib")); + } + for dst in &dsts { + if fs::metadata(dst).is_ok() { + fs::remove_file(dst).chain_error(|| { + human(format!("Could not remove file: {}.", dst.display())) + })?; + } } } @@ -528,7 +538,7 @@ fn build_base_args(cx: &mut Context, let Profile { ref opt_level, lto, codegen_units, ref rustc_args, debuginfo, debug_assertions, rpath, test, doc: _doc, run_custom_build, - ref panic, rustdoc_args: _, + ref panic, rustdoc_args: _, check, } = *unit.profile; assert!(!run_custom_build); @@ -548,7 +558,9 @@ fn build_base_args(cx: &mut Context, cmd.arg("--error-format").arg("json"); } - if !test { + if check { + cmd.arg("--crate-type").arg("metadata"); + } else if !test { for crate_type in crate_types.iter() { cmd.arg("--crate-type").arg(crate_type); } diff --git a/src/cargo/util/toml.rs b/src/cargo/util/toml.rs index 18b4d4c6cfc..7d47bcf928e 100644 --- a/src/cargo/util/toml.rs +++ b/src/cargo/util/toml.rs @@ -1248,6 +1248,8 @@ fn build_profiles(profiles: &Option) -> Profiles { doc: merge(Profile::default_doc(), profiles.and_then(|p| p.doc.as_ref())), custom_build: Profile::default_custom_build(), + check: merge(Profile::default_check(), + profiles.and_then(|p| p.dev.as_ref())), }; // The test/bench targets cannot have panic=abort because they'll all get // compiled with --test which requires the unwind runtime currently @@ -1277,6 +1279,7 @@ fn build_profiles(profiles: &Option) -> Profiles { test: profile.test, doc: profile.doc, run_custom_build: profile.run_custom_build, + check: profile.check, panic: panic.clone().or(profile.panic), } } diff --git a/src/etc/_cargo b/src/etc/_cargo index 17585920252..4a23bf35280 100644 --- a/src/etc/_cargo +++ b/src/etc/_cargo @@ -51,6 +51,23 @@ case $state in '--color=:colorization option:(auto always never)' \ ;; + check) + _arguments \ + '--features=[space separated feature list]' \ + '--all-features[enable all available features]' \ + '(-h, --help)'{-h,--help}'[show help message]' \ + '(-j, --jobs)'{-j,--jobs}'[number of parallel jobs, defaults to # of CPUs]' \ + "${command_scope_spec[@]}" \ + '--manifest-path=[path to manifest]: :_files -/' \ + '--no-default-features[do not check the default features]' \ + '(-p,--package)'{-p=,--package=}'[package to check]:packages:_get_package_names' \ + '--release=[check in release mode]' \ + '--target=[target triple]' \ + '(-v, --verbose)'{-v,--verbose}'[use verbose output]' \ + '(-q, --quiet)'{-q,--quiet}'[no output printed to stdout]' \ + '--color=:colorization option:(auto always never)' \ + ;; + clean) _arguments \ '(-h, --help)'{-h,--help}'[show help message]' \ @@ -384,6 +401,7 @@ _cargo_cmds(){ local -a commands;commands=( 'bench:execute all benchmarks of a local package' 'build:compile the current project' +'check:check the current project without compiling' 'clean:remove generated artifacts' 'doc:build package documentation' 'fetch:fetch package dependencies' diff --git a/src/etc/cargo.bashcomp.sh b/src/etc/cargo.bashcomp.sh index 5c6675e9069..44b8998839e 100644 --- a/src/etc/cargo.bashcomp.sh +++ b/src/etc/cargo.bashcomp.sh @@ -24,6 +24,7 @@ _cargo() local opt___nocmd="$opt_common -V --version --list" local opt__bench="$opt_common $opt_pkg $opt_feat $opt_mani $opt_jobs --target --lib --bin --test --bench --example --no-run" local opt__build="$opt_common $opt_pkg $opt_feat $opt_mani $opt_jobs --target --lib --bin --test --bench --example --release" + local opt__check="$opt_common $opt_pkg $opt_feat $opt_mani $opt_jobs --target --lib --bin --example" local opt__clean="$opt_common $opt_pkg $opt_mani --target --release" local opt__doc="$opt_common $opt_pkg $opt_feat $opt_mani $opt_jobs --target --open --no-deps --release" local opt__fetch="$opt_common $opt_mani" diff --git a/src/etc/man/cargo-check.1 b/src/etc/man/cargo-check.1 new file mode 100644 index 00000000000..0931bf0e976 --- /dev/null +++ b/src/etc/man/cargo-check.1 @@ -0,0 +1,132 @@ +.TH "CARGO\-CHECK" "1" "May 2016" "The Rust package manager" "Cargo Manual" +.hy +.SH NAME +.PP +cargo\-check \- Check the current project +.SH SYNOPSIS +.PP +\f[I]cargo check\f[] [OPTIONS] +.SH DESCRIPTION +.PP +Check a local package and all of its dependencies. +.PP +If the \f[B]\-\-package\f[] argument is given, then \f[I]SPEC\f[] is a +package id specification which indicates which package should be checked. +If it is not given, then the current package is checked. +For more information on \f[I]SPEC\f[] and its format, see the "cargo +help pkgid" command. +.PP +Compilation can be configured via the use of profiles which are +configured in the manifest. +The default profile for this command is \f[I]dev\f[], but passing the +\f[B]\-\-release\f[] flag will use the \f[I]release\f[] profile instead. +.SH OPTIONS +.TP +.B \-h, \-\-help +Print this message. +.RS +.RE +.TP +.B \-p \f[I]SPEC\f[], \-\-package \f[I]SPEC ...\f[] +Package to check. +.RS +.RE +.TP +.B \-j \f[I]IN\f[], \-\-jobs \f[I]IN\f[] +Number of parallel jobs, defaults to # of CPUs. +.RS +.RE +.TP +.B \-\-lib +Check only this package\[aq]s library. +.RS +.RE +.TP +.B \-\-bin \f[I]NAME\f[] +Check only the specified binary. +.RS +.RE +.TP +.B \-\-example \f[I]NAME\f[] +Check only the specified example. +.RS +.RE +.TP +.B \-\-test \f[I]NAME\f[] +Check only the specified test target. +.RS +.RE +.TP +.B \-\-bench \f[I]NAME\f[] +Check only the specified benchmark target. +.RS +.RE +.TP +.B \-\-release +Check artifacts in release mode. +.RS +.RE +.TP +.B \-\-all\-features +Check with all available features. +.RS +.RE +.TP +.B \-\-features \f[I]FEATURES\f[] +Space\-separated list of features to also check. +.RS +.RE +.TP +.B \-\-no\-default\-features +Do not check the \f[C]default\f[] feature. +.RS +.RE +.TP +.B \-\-target \f[I]TRIPLE\f[] +Check for the target triple. +.RS +.RE +.TP +.B \-\-manifest\-path \f[I]PATH\f[] +Path to the manifest to compile. +.RS +.RE +.TP +.B \-v, \-\-verbose +Use verbose output. +.RS +.RE +.TP +.B \-q, \-\-quiet +No output printed to stdout. +.RS +.RE +.TP +.B \-\-color \f[I]WHEN\f[] +Coloring: auto, always, never. +.RS +.RE +.SH EXAMPLES +.PP +Check a local package and all of its dependencies +.IP +.nf +\f[C] +$\ cargo\ check +\f[] +.fi +.PP +Check a package with optimizations +.IP +.nf +\f[C] +$\ cargo\ check\ \-\-release +\f[] +.fi +.SH SEE ALSO +.PP +cargo(1) +.SH COPYRIGHT +.PP +This work is dual\-licensed under Apache 2.0 and MIT terms. +See \f[I]COPYRIGHT\f[] file in the cargo source distribution.