diff --git a/cargo-cov/src/argparse.rs b/cargo-cov/src/argparse.rs index 288db2f..ae8a0dc 100644 --- a/cargo-cov/src/argparse.rs +++ b/cargo-cov/src/argparse.rs @@ -1,10 +1,15 @@ //! Extra functions for command line argument parsing. +use error::Result; +use sourcepath::{SOURCE_TYPE_DEFAULT, SourceType}; +use utils::{join_3, parent_3}; + use clap::ArgMatches; +use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; -use std::path::Path; +use std::path::{Path, PathBuf}; lazy_static! { /// The list of special arguments. See [`update_from_clap()`] for detail. @@ -155,3 +160,55 @@ pub fn normalize<'a, I: IntoIterator>(args: I, specialized: &m normalized } + + +/// Parsed command-line configuration for the `report` subcommand. +pub struct ReportConfig<'a> { + pub workspace_path: Cow<'a, Path>, + pub gcno_path: Cow<'a, Path>, + pub gcda_path: Cow<'a, Path>, + pub output_path: Cow<'a, Path>, + pub template_name: &'a OsStr, + pub allowed_source_types: SourceType, +} + +impl<'a> ReportConfig<'a> { + /// Parses the command-line arguments for the `report` subcommand. + /// + /// Returns None if + pub fn parse(matches: &'a ArgMatches<'a>, cov_build_path: Result) -> Result> { + fn match_or_else<'a, F: FnOnce() -> PathBuf>(matches: &'a ArgMatches<'a>, name: &str, default: F) -> Cow<'a, Path> { + match matches.value_of_os(name) { + Some(path) => Cow::Borrowed(Path::new(path)), + None => Cow::Owned(default()), + } + } + + + let (workspace_path, cov_build_path) = match (matches.value_of_os("workspace"), cov_build_path) { + (Some(workspace_path), _) => { + let workspace_path = Path::new(workspace_path); + let cov_build_path = join_3(workspace_path, "target", "cov", "build"); + (Cow::Borrowed(workspace_path), cov_build_path) + }, + (_, Ok(cov_build_path)) => (Cow::Owned(parent_3(&cov_build_path).to_owned()), cov_build_path), + (None, Err(e)) => return Err(e), + }; + + let gcno_path = match_or_else(matches, "gcno", || cov_build_path.join("gcno")); + let gcda_path = match_or_else(matches, "gcda", || cov_build_path.join("gcda")); + let output_path = match_or_else(matches, "output", || join_3(&workspace_path, "target", "cov", "report")); + + let template_name = matches.value_of_os("template").unwrap_or_else(|| OsStr::new("html")); + let allowed_source_types = matches.values_of("include").map_or(SOURCE_TYPE_DEFAULT, |it| SourceType::from_multi_str(it).expect("SourceType")); + + Ok(ReportConfig { + workspace_path, + gcno_path, + gcda_path, + output_path, + template_name, + allowed_source_types, + }) + } +} diff --git a/cargo-cov/src/cargo.rs b/cargo-cov/src/cargo.rs index 12cb779..cf1539d 100644 --- a/cargo-cov/src/cargo.rs +++ b/cargo-cov/src/cargo.rs @@ -108,9 +108,9 @@ impl<'a> Cargo<'a> { }) } - /// Obtains the `target/cov/build` path. - pub fn cov_build_path(&self) -> &Path { - &self.cov_build_path + /// Obtains the `target/cov/build` path and transfers ownership. + pub fn into_cov_build_path(self) -> PathBuf { + self.cov_build_path } /// Prepares the coverage folder for building. @@ -274,7 +274,7 @@ fn locate_project(cargo_path: &OsStr) -> Result { let child = Command::new(cargo_path) // @rustfmt-force-break .stdin(Stdio::null()) .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) + .stderr(Stdio::null()) .arg("locate-project") .spawn()?; let project_location: ProjectLocation = from_reader(child.stdout.expect("stdout"))?; diff --git a/cargo-cov/src/lookup.rs b/cargo-cov/src/lookup.rs index 662bd83..8e24f9d 100644 --- a/cargo-cov/src/lookup.rs +++ b/cargo-cov/src/lookup.rs @@ -52,33 +52,23 @@ const PROFILER_GLOB_PATTERNS: &[&str] = &[ fn profiler_name_part(target: &str) -> Result<&str> { Ok(match target { // iOS and macOS - "aarch64-apple-ios" | - "armv7-apple-ios" | - "armv7s-apple-ios" => "_ios", - "i386-apple-ios" | - "x86_64-apple-ios" => "_iossim", - "i686-apple-darwin" | - "x86_64-apple-darwin" => "_osx", + "aarch64-apple-ios" | "armv7-apple-ios" | "armv7s-apple-ios" => "_ios", + "i386-apple-ios" | "x86_64-apple-ios" => "_iossim", + "i686-apple-darwin" | "x86_64-apple-darwin" => "_osx", // Android "aarch64-linux-android" => "-aarch64-android", - "arm-linux-androideabi" | - "armv7-linux-androideabi" => "-arm-android", + "arm-linux-androideabi" | "armv7-linux-androideabi" => "-arm-android", "i686-linux-android" => "-i686-android", "x86_64-linux-android" => "-x86_64-android", "mipsel-linux-android" => "-mipsel-android", "mips64el-linux-android" => "-mips64el-android", // Windows -- LLVM's installer provides -i386 packages. - "i586-pc-windows-msvc" | - "i686-pc-windows-msvc" => "-i386", + "i586-pc-windows-msvc" | "i686-pc-windows-msvc" => "-i386", // ARM with hard-float support - "arm-unknown-linux-gnueabihf" | - "arm-unknown-linux-musleabihf" | - "armv7-unknown-linux-gnueabihf" | - "armv7-unknown-linux-musleabihf" | - "thumbv7em-none-eabihf" => "-armhf", + "arm-unknown-linux-gnueabihf" | "arm-unknown-linux-musleabihf" | "armv7-unknown-linux-gnueabihf" | "armv7-unknown-linux-musleabihf" | "thumbv7em-none-eabihf" => "-armhf", // Everything else _ => { diff --git a/cargo-cov/src/main.rs b/cargo-cov/src/main.rs index 0e91569..e2fe6ea 100644 --- a/cargo-cov/src/main.rs +++ b/cargo-cov/src/main.rs @@ -57,10 +57,7 @@ use cargo::Cargo; use clap::ArgMatches; use either::Either; use error::Result; -use sourcepath::*; -use std::ffi::OsStr; -use std::iter::empty; use std::process::exit; /// Program entry. Calls [`run()`] and prints any error returned to `stderr`. @@ -118,13 +115,13 @@ fn run() -> Result<()> { Some(args) => normalize(args, &mut special_args), None => Vec::new(), }; - let cargo = Cargo::new(special_args, forward_args)?; + let cargo = Cargo::new(special_args, forward_args); // Actually run the subcommands. Please do not pass ArgMatches as a whole to the receiver functions. match subcommand { - "build" | "test" | "run" => cargo.forward(subcommand)?, - "clean" => clean(&cargo, matches)?, - "report" => generate_reports(&cargo, matches)?, + "build" | "test" | "run" => cargo?.forward(subcommand)?, + "clean" => clean(&cargo?, matches)?, + "report" => generate_reports(cargo, matches)?, _ => ui::print_unknown_subcommand(subcommand)?, } @@ -187,6 +184,10 @@ Subcommands: "unknown", "all", ]) "Generate reports for some specific sources") + (@arg workspace: --workspace [PATH] "The directory to find the source code, default to the current Cargo workspace") + (@arg output: --output -o [PATH] "The directory to store the generated report, default to `/target/cov/report/`") + (@arg gcno: --gcno [PATH] "The directory that contains all *.gcno files, default to `/target/cov/build/gcno/`") + (@arg gcda: --gcda [PATH] "The directory that contains all *.gcda files, default to `/target/cov/build/gcda/`") ) ) ).get_matches() @@ -195,12 +196,9 @@ Subcommands: /// Parses the command line arguments and forwards to [`report::generate()`]. /// /// [`report::generate()`]: report/fn.generate.html -fn generate_reports(cargo: &Cargo, matches: &ArgMatches) -> Result<()> { - let allowed_source_types = matches.values_of("include").map_or(SOURCE_TYPE_DEFAULT, |it| SourceType::from_multi_str(it).expect("SourceType")); - - let template = matches.value_of_os("template").unwrap_or_else(|| OsStr::new("html")); - let open_path = report::generate(cargo.cov_build_path(), template, allowed_source_types)?; - +fn generate_reports(cargo: Result, matches: &ArgMatches) -> Result<()> { + let report_config = ReportConfig::parse(matches, cargo.map(Cargo::into_cov_build_path))?; + let open_path = report::generate(&report_config)?; if matches.is_present("open") { if let Some(path) = open_path { progress!("Opening", "{}", path.display()); diff --git a/cargo-cov/src/report.rs b/cargo-cov/src/report.rs index d1551d5..41ed00a 100644 --- a/cargo-cov/src/report.rs +++ b/cargo-cov/src/report.rs @@ -137,6 +137,7 @@ //! } //! ``` +use argparse::ReportConfig; use error::{Result, ResultExt}; use sourcepath::{SourceType, identify_source_path}; use template::new as new_template; @@ -153,27 +154,27 @@ use std::io::{BufRead, BufReader, Read, Write}; use std::path::{Path, PathBuf}; /// Entry point of `cargo cov report` subcommand. Renders the coverage report using a template. -pub fn generate(cov_build_path: &Path, template_name: &OsStr, allowed_source_types: SourceType) -> Result> { - let report_path = cov_build_path.with_file_name("report"); - clean_dir(&report_path).chain_err(|| "Cannot clean report directory")?; - create_dir_all(&report_path)?; +pub fn generate(config: &ReportConfig) -> Result> { + let report_path = &config.output_path; + clean_dir(report_path).chain_err(|| "Cannot clean report directory")?; + create_dir_all(report_path)?; let mut interner = Interner::new(); - let graph = create_graph(cov_build_path, &mut interner).chain_err(|| "Cannot create graph")?; + let graph = create_graph(config, &mut interner).chain_err(|| "Cannot create graph")?; let report = graph.report(); - render(&report_path, template_name, allowed_source_types, &report, &interner).chain_err(|| "Cannot render report") + render(report_path, config.template_name, config.allowed_source_types, &report, &interner).chain_err(|| "Cannot render report") } /// Creates an analyzed [`Graph`] from all GCNO and GCDA inside the `target/cov/build` folder. /// /// [`Graph`]: ../../cov/graph/struct.Graph.html -fn create_graph(cov_build_path: &Path, interner: &mut Interner) -> cov::Result { +fn create_graph(config: &ReportConfig, interner: &mut Interner) -> cov::Result { let mut graph = Graph::default(); - for extension in &["gcno", "gcda"] { - progress!("Parsing", "*.{} files", extension); - for entry in read_dir(cov_build_path.join(extension))? { + for &(extension, dir_path) in &[("gcno", &config.gcno_path), ("gcda", &config.gcda_path)] { + progress!("Parsing", "{}/*.{}", dir_path.display(), extension); + for entry in read_dir(dir_path)? { let path = entry?.path(); if path.extension() == Some(OsStr::new(extension)) { trace!("merging {} {:?}", extension, path); diff --git a/cargo-cov/src/shim.rs b/cargo-cov/src/shim.rs index a772a23..180f371 100644 --- a/cargo-cov/src/shim.rs +++ b/cargo-cov/src/shim.rs @@ -25,7 +25,7 @@ use argparse::is_rustc_compiling_local_crate; use error::{Result, ResultExt}; -use utils::{CommandExt, parent_3}; +use utils::{CommandExt, join_2, parent_3}; use fs2::FileExt; use rand::{Rng, thread_rng}; @@ -68,9 +68,11 @@ pub fn rustc<'a, I: Iterator + Clone>(args: I) -> Result<()> { let cov_build_path_os = env::var_os("COV_BUILD_PATH").expect("COV_BUILD_PATH"); let cov_build_path = Path::new(&cov_build_path_os); let workspace_path = parent_3(cov_build_path); + let is_local = is_rustc_compiling_local_crate(args.clone(), workspace_path); let mut cmd = Command::new(rustc_path); - let is_local = is_rustc_compiling_local_crate(args.clone(), workspace_path); + cmd.args(args); + if is_local { let profiler_lib_path = env::var_os("COV_PROFILER_LIB_PATH").expect("COV_PROFILER_LIB_PATH"); let profiler_lib_name = env::var_os("COV_PROFILER_LIB_NAME").expect("COV_PROFILER_LIB_NAME"); @@ -88,7 +90,6 @@ pub fn rustc<'a, I: Iterator + Clone>(args: I) -> Result<()> { ]); } - cmd.args(args); debug!("Executing {:?}", cmd); cmd.ensure_success("rustc")?; @@ -182,8 +183,7 @@ pub fn run<'a, I: Iterator>(mut args: I) -> Result<()> { /// ``` pub fn move_gcov_files(cov_build_path: &Path, extension: &OsStr) -> Result<()> { let mut rng = thread_rng(); - let mut dest_path = cov_build_path.join(extension); - dest_path.push("*"); + let mut dest_path = join_2(cov_build_path, extension, "*"); let mut lock_file = LockFile::new(cov_build_path)?; @@ -254,4 +254,4 @@ impl Drop for LockFile { fn drop(&mut self) { let _ = self.unlock(); } -} \ No newline at end of file +} diff --git a/cargo-cov/src/utils.rs b/cargo-cov/src/utils.rs index 15c860e..f7ed638 100644 --- a/cargo-cov/src/utils.rs +++ b/cargo-cov/src/utils.rs @@ -16,7 +16,7 @@ use std::os::unix::ffi::OsStrExt; use std::os::unix::fs::PermissionsExt; #[cfg(windows)] use std::os::windows::ffi::{OsStrExt, OsStringExt}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::Command; pub trait OptionExt { @@ -109,7 +109,26 @@ impl CommandExt for Command { } } - +/// Short circuit of `path.parent().parent().parent()`. +/// +/// # Panics +/// +/// Panics when any of the parent returns `None`. pub fn parent_3(path: &Path) -> &Path { - path.parent().expect("..").parent().expect("../..").parent().expect("../../..") + path.parent().and_then(Path::parent).and_then(Path::parent).expect("../../..") +} + +/// Short circuit of `path.join(a).join(b)` without creating intermediate `PathBuf`s. +pub fn join_2, P2: AsRef>(path: &Path, a: P1, b: P2) -> PathBuf { + let mut path = path.join(a); + path.push(b); + path +} + +/// Short circuit of `path.join(a).join(b).join(c)` without creating intermediate `PathBuf`s. +pub fn join_3, P2: AsRef, P3: AsRef>(path: &Path, a: P1, b: P2, c: P3) -> PathBuf { + let mut path = path.join(a); + path.push(b); + path.push(c); + path }