Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cargo integration #12

Merged
merged 8 commits into from
May 17, 2016
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions src/cargo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use std::env;
use std::fs;
use std::path::PathBuf;
use std::io::prelude::*;
use err::ProfError;
use regex::Regex;
use std::process::{Command, exit};
use std::path::Path;

/// Returns the closest ancestor path containing a `Cargo.toml`.
///
/// Returns `None` if no ancestor path contains a `Cargo.toml`, or if
/// the limit of MAX_ANCESTORS ancestors has been reached.
pub fn find_toml() -> Option<PathBuf> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to not use cargo read-manifest?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agh, was trying to find a command like that! thanks for pointing it out, makes my life easier.

i'll change the get_package_name/find_toml functions to go through cargo read-manifest. i think we'll still need the find_target, unless i'm wrong

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think searching for a target dir is 100% reliable, you'll need to account for things like: rust-lang/cargo#1657

I think using cargo as a library might be the most reliable way to ensure things work reliably, you could inspect how cargo read-manifest is written perhaps as a starting point (or another cargo-foo package?).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair point. i'll dig into this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to say this PR should be merged as it stands, so users can at least have all the improved ergonomics under default output conditions, which I'm sure covers most people's use cases. We can figure out how to deal with custom output directories as a separate issue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NoTargetDirectory error will require the project to have a target/ directory containing the binary to use cargo-profiler w/o --bin. However, the user still has the option of explicitly providing the path to the binary, if they have a non-standard output directory.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you've made a pragmatic decision 👍

/// Checks if the directory contains `Cargo.toml`
fn contains_manifest(path: &PathBuf) -> bool {
fs::read_dir(path)
.map(|entries| {
entries.filter_map(|res| res.ok())
.any(|ent| &ent.file_name() == "Cargo.toml")
})
.unwrap_or(false)
}

// From the current directory we work our way up, looking for `Cargo.toml`
env::current_dir().ok().and_then(|mut wd| {
for _ in 0..10 {
if contains_manifest(&mut wd) {
return Some(wd);
}
if !wd.pop() {
break;
}
}

None
})
}

/// Returns the closest ancestor path containing a `target` directory.
///
/// Returns `None` if no ancestor path contains a `Cargo.toml`, or if
/// the limit of MAX_ANCESTORS ancestors has been reached.
pub fn find_target() -> Option<PathBuf> {
/// Checks if the directory contains `Cargo.toml`
fn contains_manifest(path: &PathBuf) -> bool {
fs::read_dir(path)
.map(|entries| {
entries.filter_map(|res| res.ok())
.any(|ent| ent.path().ends_with("target"))
})
.unwrap_or(false)
}

// From the current directory we work our way up, looking for `Cargo.toml`
env::current_dir().ok().and_then(|mut wd| {
for _ in 0..10 {
if contains_manifest(&mut wd) {
return Some(wd);
}
if !wd.pop() {
break;
}
}

None
})
}


// returns the name of the package parsed from Cargo.toml
// this will only work if the package name is directly underneath [package] tag
pub fn get_package_name(toml_dir: &PathBuf) -> Result<String, ProfError> {
let toml = toml_dir.join("Cargo.toml");
let mut f = try!(fs::File::open(toml));
let mut s = String::new();
try!(f.read_to_string(&mut s));
let mut caps = Vec::new();

lazy_static! {
static ref PACKAGE_REGEX : Regex = Regex::new(r"\[package\]\n+name\s*=\s*.*").unwrap();
static ref REPLACE_REGEX : Regex = Regex::new(r"\[package\]\n+name\s*=\s*").unwrap();
}
let captures_iter = PACKAGE_REGEX.captures_iter(&s);
if captures_iter.collect::<Vec<_>>().len() == 0 {
println!("{}", ProfError::TomlError);
exit(1);
}
for cap in PACKAGE_REGEX.captures_iter(&s) {

let c = cap.at(0).unwrap_or("");
let r = REPLACE_REGEX.replace_all(c, "");
let r = r.replace("\"", "");
caps.push(r)

}
Ok(caps[0].to_string())

}

// build the binary by calling cargo build
// return the path to the built binary
pub fn build_binary(release: bool, package_name: &str) -> Result<String, ProfError> {

match release {
true => {
println!("\n\x1b[1;33mCompiling \x1b[1;0m{} in release mode...",
package_name);
let _ = Command::new("cargo")
.args(&["build", "--release"])
.output();
let target_dir = find_target().unwrap().to_str().unwrap().to_string();
let path = target_dir + "/target/release/" + &package_name;
if !Path::new(&path).exists() {
println!("{}", ProfError::CompilationError(package_name.to_string()));
exit(1);

}
return Ok(path);

}
false => {
println!("\n\x1b[1;33mCompiling \x1b[1;0m{} in debug mode...",
package_name);
let _ = Command::new("cargo")
.arg("build")
.output()
.unwrap_or_else(|e| panic!("failed to execute process: {}", e));;
let target_dir = find_target().unwrap().to_str().unwrap().to_string();
let path = target_dir + "/target/debug/" + &package_name;
if !Path::new(&path).exists() {
println!("{}", ProfError::CompilationError(package_name.to_string()));
exit(1);

}
return Ok(path);
}

}
}
24 changes: 22 additions & 2 deletions src/err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@ pub enum ProfError {
/// Wraps a std::io::Error
IOError(ioError),
MisalignedData,
CompilationError(String),
TomlError,
}

impl fmt::Display for ProfError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ProfError::RegexError => {
write!(f,
"\x1b[1;31merror: \x1b[0mregex error -- please file a bug.")
"\x1b[1;31merror: \x1b[0mregex error -- please file a bug. In bug report, \
please include the original output file from profiler, e.g. from \
valgrind --tool=cachegrind --cachegrind-out-file=cachegrind.txt")
}
ProfError::InvalidProfiler => {
write!(f,
Expand Down Expand Up @@ -53,6 +57,17 @@ impl fmt::Display for ProfError {
"\x1b[1;31merror: \x1b[0mmisaligned data arrays due to regex error -- \
please file a bug.")
}
ProfError::CompilationError(ref err) => {
write!(f,
"\x1b[1;31merror: \x1b[0mfailed to compile {}. Run cargo build to get \
compilation error.",
err)
}
ProfError::TomlError => {
write!(f,
"\x1b[1;31merror: \x1b[0merror in parsing Cargo.toml to derive package \
name. Make sure package name is directly under [package] tag.")
}
}
}
}
Expand All @@ -66,6 +81,10 @@ impl error::Error for ProfError {
ProfError::InvalidNum => "Invalid number.",
ProfError::InvalidSortMetric => "Invalid sort metric.",
ProfError::MisalignedData => "Misaligned Data. File bug.",
ProfError::CompilationError(_) => {
"Failed to compile. Run cargo build to get compilation error."
}
ProfError::TomlError => "Error in parsing Cargo.toml.",
ProfError::IOError(ref err) => err.description(),

}
Expand All @@ -79,8 +98,9 @@ impl error::Error for ProfError {
ProfError::InvalidNum => None,
ProfError::InvalidSortMetric => None,
ProfError::MisalignedData => None,
ProfError::TomlError => None,
ProfError::IOError(ref err) => Some(err),

ProfError::CompilationError(_) => None,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod profiler;
pub mod display;
pub mod err;
pub mod argparse;
pub mod cargo;

pub mod parse {
pub mod callgrind;
Expand Down
38 changes: 28 additions & 10 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod parse;
pub mod display;
pub mod err;
pub mod argparse;
pub mod cargo;

use clap::{Arg, App, SubCommand, AppSettings};
use profiler::Profiler;
Expand All @@ -16,6 +17,7 @@ use parse::cachegrind::CacheGrindParser;
use std::process::Command;
use err::ProfError;
use argparse::{get_profiler, get_binary, get_num, get_sort_metric};
use cargo::{find_toml, get_package_name, build_binary};

fn main() {
let _ = real_main();
Expand All @@ -28,8 +30,14 @@ fn real_main() -> Result<(), ProfError> {
let binary_arg = Arg::with_name("binary")
.long("bin")
.value_name("BINARY")
.required(true)
.required(false)
.help("binary you want to profile");
// create release argument

let release = Arg::with_name("release")
.long("release")
.required(false)
.help("whether binary should be built in release mode");

// create function count argument
let fn_count_arg = Arg::with_name("n")
Expand All @@ -50,6 +58,7 @@ fn real_main() -> Result<(), ProfError> {
.about("gets callgrind features")
.version("1.0")
.author("Suchin Gururangan")
.arg(release.clone())
.arg(binary_arg.clone())
.arg(fn_count_arg.clone());

Expand All @@ -58,6 +67,7 @@ fn real_main() -> Result<(), ProfError> {
.about("gets cachegrind features")
.version("1.0")
.author("Suchin Gururangan")
.arg(release)
.arg(binary_arg)
.arg(fn_count_arg)
.arg(sort_arg);
Expand All @@ -82,23 +92,31 @@ fn real_main() -> Result<(), ProfError> {

// parse arguments from cli call
let (m, profiler) = try!(get_profiler(&matches));
let binary = try!(get_binary(&m));
let toml_dir = find_toml().unwrap();
let package_name = get_package_name(&toml_dir).ok().unwrap();
let binary = {
if m.is_present("binary") {
try!(get_binary(&m)).to_string()
} else {
if m.is_present("release") {
try!(build_binary(true, &package_name[..]))
} else {
try!(build_binary(false, &package_name[..]))
}
}
};

let num = try!(get_num(&m));
let sort_metric = try!(get_sort_metric(&m));

// get the name of the binary from the binary argument
let path = binary.split("/").collect::<Vec<_>>();
let name = path[path.len() - 1];

match profiler {
Profiler::CallGrind { .. } => {
println!("\n\x1b[1;33mProfiling \x1b[1;0m{} \x1b[0mwith callgrind\x1b[0m...",
name)
&package_name[..])
}
Profiler::CacheGrind { .. } => {
println!("\n\x1b[1;33mProfiling \x1b[1;0m{} \x1b[0mwith \
cachegrind\x1b[0m...",
name)
println!("\n\x1b[1;33mProfiling \x1b[1;0m{} \x1b[0mwith cachegrind\x1b[0m...",
&package_name[..])
}
};

Expand Down
Binary file not shown.

This file was deleted.

This file was deleted.

Binary file not shown.

This file was deleted.

This file was deleted.

Binary file not shown.
1 change: 0 additions & 1 deletion target/debug/.fingerprint/libc-22526f8fe5b02580/lib-libc

This file was deleted.

This file was deleted.

Binary file not shown.

This file was deleted.

This file was deleted.

Binary file not shown.

This file was deleted.

This file was deleted.

Binary file not shown.

This file was deleted.

This file was deleted.

Binary file not shown.
Binary file removed target/debug/deps/libbitflags-e87d150db0333415.rlib
Binary file not shown.
Binary file removed target/debug/deps/liblibc-30c6b6751f89189b.rlib
Binary file not shown.
Binary file removed target/debug/deps/libstrsim-0669373314ee49fc.rlib
Binary file not shown.
Binary file not shown.
Binary file removed target/debug/deps/libvec_map-052ff1ed2f091ceb.rlib
Binary file not shown.