Skip to content

Commit

Permalink
cargo cov report can now work with arbitrary paths.
Browse files Browse the repository at this point in the history
  • Loading branch information
kennytm committed Sep 19, 2017
1 parent 61b71c9 commit c5f13b1
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 53 deletions.
59 changes: 58 additions & 1 deletion cargo-cov/src/argparse.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -155,3 +160,55 @@ pub fn normalize<'a, I: IntoIterator<Item = &'a OsStr>>(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<PathBuf>) -> Result<ReportConfig<'a>> {
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,
})
}
}
8 changes: 4 additions & 4 deletions cargo-cov/src/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -274,7 +274,7 @@ fn locate_project(cargo_path: &OsStr) -> Result<PathBuf> {
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"))?;
Expand Down
22 changes: 6 additions & 16 deletions cargo-cov/src/lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
_ => {
Expand Down
24 changes: 11 additions & 13 deletions cargo-cov/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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)?,
}

Expand Down Expand Up @@ -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 `<src>/target/cov/report/`")
(@arg gcno: --gcno [PATH] "The directory that contains all *.gcno files, default to `<src>/target/cov/build/gcno/`")
(@arg gcda: --gcda [PATH] "The directory that contains all *.gcda files, default to `<src>/target/cov/build/gcda/`")
)
)
).get_matches()
Expand All @@ -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<Cargo>, 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());
Expand Down
21 changes: 11 additions & 10 deletions cargo-cov/src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
//! }
//! ```
use argparse::ReportConfig;
use error::{Result, ResultExt};
use sourcepath::{SourceType, identify_source_path};
use template::new as new_template;
Expand All @@ -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<Option<PathBuf>> {
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<Option<PathBuf>> {
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<Graph> {
fn create_graph(config: &ReportConfig, interner: &mut Interner) -> cov::Result<Graph> {
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);
Expand Down
12 changes: 6 additions & 6 deletions cargo-cov/src/shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -68,9 +68,11 @@ pub fn rustc<'a, I: Iterator<Item = &'a OsStr> + 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");
Expand All @@ -88,7 +90,6 @@ pub fn rustc<'a, I: Iterator<Item = &'a OsStr> + Clone>(args: I) -> Result<()> {
]);
}

cmd.args(args);
debug!("Executing {:?}", cmd);

cmd.ensure_success("rustc")?;
Expand Down Expand Up @@ -182,8 +183,7 @@ pub fn run<'a, I: Iterator<Item = &'a OsStr>>(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)?;

Expand Down Expand Up @@ -254,4 +254,4 @@ impl Drop for LockFile {
fn drop(&mut self) {
let _ = self.unlock();
}
}
}
25 changes: 22 additions & 3 deletions cargo-cov/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<P1: AsRef<Path>, P2: AsRef<Path>>(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<P1: AsRef<Path>, P2: AsRef<Path>, P3: AsRef<Path>>(path: &Path, a: P1, b: P2, c: P3) -> PathBuf {
let mut path = path.join(a);
path.push(b);
path.push(c);
path
}

0 comments on commit c5f13b1

Please sign in to comment.