#![forbid(unsafe_code)]
#![forbid(warnings)]
extern crate cargo;
extern crate colored;
extern crate petgraph;
extern crate structopt;
mod cli;
mod format;
use crate::cli::build_graph;
use crate::cli::find_unsafe_in_packages;
use crate::cli::get_cfgs;
use crate::cli::print_tree;
use crate::cli::registry;
use crate::cli::resolve;
use crate::cli::resolve_rs_file_deps;
use crate::cli::workspace;
use crate::cli::Charset;
use crate::cli::ExtraDeps;
use crate::cli::Prefix;
use crate::cli::PrintConfig;
use crate::cli::ASCII_SYMBOLS;
use crate::cli::UNSAFE_COUNTERS_HEADER;
use crate::cli::UTF8_SYMBOLS;
use crate::format::Pattern;
use cargo::core::compiler::CompileMode;
use cargo::core::resolver::Method;
use cargo::core::shell::Shell;
use cargo::core::shell::Verbosity;
use cargo::ops::CompileOptions;
use cargo::CliResult;
use cargo::Config;
use colored::Colorize;
use geiger::IncludeTests;
use petgraph::EdgeDirection;
use std::path::PathBuf;
use structopt::clap::AppSettings;
use structopt::StructOpt;
#[derive(StructOpt)]
#[structopt(bin_name = "cargo")]
pub enum Opts {
#[structopt(
name = "geiger",
raw(
setting = "AppSettings::UnifiedHelpMessage",
setting = "AppSettings::DeriveDisplayOrder",
setting = "AppSettings::DontCollapseArgsInUsage"
)
)]
Geiger(Args),
}
#[derive(StructOpt)]
pub struct Args {
#[structopt(long = "package", short = "p", value_name = "SPEC")]
pub package: Option<String>,
#[structopt(long = "features", value_name = "FEATURES")]
pub features: Option<String>,
#[structopt(long = "all-features")]
pub all_features: bool,
#[structopt(long = "no-default-features")]
pub no_default_features: bool,
#[structopt(long = "target", value_name = "TARGET")]
pub target: Option<String>,
#[structopt(long = "all-targets")]
pub all_targets: bool,
#[structopt(
long = "manifest-path",
value_name = "PATH",
parse(from_os_str)
)]
pub manifest_path: Option<PathBuf>,
#[structopt(long = "invert", short = "i")]
pub invert: bool,
#[structopt(long = "no-indent")]
pub no_indent: bool,
#[structopt(long = "prefix-depth")]
pub prefix_depth: bool,
#[structopt(long = "all", short = "a")]
pub all: bool,
#[structopt(
long = "charset",
value_name = "CHARSET",
default_value = "utf8"
)]
pub charset: Charset,
#[structopt(
long = "format",
short = "f",
value_name = "FORMAT",
default_value = "{p}"
)]
pub format: String,
#[structopt(long = "verbose", short = "v", parse(from_occurrences))]
pub verbose: u32,
#[structopt(long = "quiet", short = "q")]
pub quiet: Option<bool>,
#[structopt(long = "color", value_name = "WHEN")]
pub color: Option<String>,
#[structopt(long = "frozen")]
pub frozen: bool,
#[structopt(long = "locked")]
pub locked: bool,
#[structopt(short = "Z", value_name = "FLAG")]
pub unstable_flags: Vec<String>,
#[structopt(long = "include-tests")]
pub include_tests: bool,
#[structopt(long = "build-dependencies", alias = "build-deps")]
pub build_deps: bool,
#[structopt(long = "dev-dependencies", alias = "dev-deps")]
pub dev_deps: bool,
#[structopt(long = "all-dependencies", alias = "all-deps")]
pub all_deps: bool,
}
pub fn build_compile_options<'a>(
args: &'a Args,
config: &'a Config,
) -> CompileOptions<'a> {
let features = Method::split_features(
&args.features.clone().into_iter().collect::<Vec<_>>(),
)
.into_iter()
.map(|s| s.to_string());
let mut opt =
CompileOptions::new(&config, CompileMode::Check { test: false })
.unwrap();
opt.features = features.collect::<_>();
opt.all_features = args.all_features;
opt.no_default_features = args.no_default_features;
opt
}
fn real_main(args: &Args, config: &mut Config) -> CliResult {
let target_dir = None; config.configure(
args.verbose,
args.quiet,
&args.color,
args.frozen,
args.locked,
&target_dir,
&args.unstable_flags,
)?;
let verbosity = if args.verbose == 0 {
Verbosity::Normal
} else {
Verbosity::Verbose
};
let ws = workspace(config, args.manifest_path.clone())?;
let package = ws.current()?;
let mut registry = registry(config, &package)?;
let (packages, resolve) = resolve(
&mut registry,
&ws,
args.features.clone(),
args.all_features,
args.no_default_features,
)?;
let ids = packages.package_ids().collect::<Vec<_>>();
let packages = registry.get(&ids)?;
let root_pack_id = match args.package {
Some(ref pkg) => resolve.query(pkg)?,
None => package.package_id(),
};
let config_host = config.rustc(Some(&ws))?.host;
let target = if args.all_targets {
None
} else {
Some(args.target.as_ref().unwrap_or(&config_host).as_str())
};
let format = Pattern::try_build(&args.format)
.map_err(|e| failure::err_msg(e.to_string()))?;
let extra_deps = if args.all_deps {
ExtraDeps::All
} else if args.build_deps {
ExtraDeps::Build
} else if args.dev_deps {
ExtraDeps::Dev
} else {
ExtraDeps::NoMore
};
let cfgs = get_cfgs(config, &args.target, &ws)?;
let graph = build_graph(
&resolve,
&packages,
package.package_id(),
target,
cfgs.as_ref().map(|r| &**r),
extra_deps,
)?;
let direction = if args.invert {
EdgeDirection::Incoming
} else {
EdgeDirection::Outgoing
};
let symbols = match args.charset {
Charset::Ascii => &ASCII_SYMBOLS,
Charset::Utf8 => &UTF8_SYMBOLS,
};
let prefix = if args.prefix_depth {
Prefix::Depth
} else if args.no_indent {
Prefix::None
} else {
Prefix::Indent
};
let copt = build_compile_options(args, config);
let rs_files_used = resolve_rs_file_deps(&copt, &ws).unwrap();
if verbosity == Verbosity::Verbose {
let mut paths = rs_files_used
.keys()
.map(|k| k.to_owned())
.collect::<Vec<PathBuf>>();
paths.sort();
paths
.iter()
.for_each(|p| println!("Used by build (sorted): {}", p.display()));
}
let allow_partial_results = true;
let include_tests = if args.include_tests {
IncludeTests::Yes
} else {
IncludeTests::No
};
let pc = PrintConfig {
all: args.all,
verbosity,
direction,
prefix,
format: &format,
symbols,
allow_partial_results,
include_tests,
};
println!(" {}...", "Scanning".green().bold());
let geiger_ctx = find_unsafe_in_packages(
&packages,
rs_files_used,
pc.allow_partial_results,
pc.include_tests,
pc.verbosity,
);
println!(
" {}...{}",
"Scanning".green().bold(),
"Done".green().bold()
);
println!();
println!("Metric output format: x/y");
println!(" x = unsafe code used by the build");
println!(" y = total unsafe code found in the crate");
println!();
println!("Symbols: ");
let forbids = "No `unsafe` usage found, declares #![forbid(unsafe_code)]";
let unknown = "No `unsafe` usage found, missing #![forbid(unsafe_code)]";
let guilty = "`unsafe` usage found";
#[cfg(not(target_os = "windows"))]
{
println!(" {} = {}", cli::LOCK, forbids);
println!(" {} = {}", cli::QUESTION_MARK, unknown);
println!(" {}\r\x1B[6C = {}", cli::RADS, guilty);
}
#[cfg(target_os = "windows")]
{
println!(" {} = {}", ":)".green(), forbids);
println!(" {} = {}", "? ", unknown);
println!(" {} = {}", "! ".red().bold(), guilty);
}
println!();
println!(
"{}",
UNSAFE_COUNTERS_HEADER
.iter()
.map(|s| s.to_owned())
.collect::<Vec<_>>()
.join(" ")
.bold()
);
println!();
print_tree(root_pack_id, &graph, &geiger_ctx, &pc);
geiger_ctx
.rs_files_used
.iter()
.filter(|(_k, v)| **v == 0)
.for_each(|(k, _v)| {
eprintln!(
"WARNING: Dependency file was never scanned: {}",
k.display()
)
});
Ok(())
}
fn main() {
env_logger::init();
let mut config = match Config::default() {
Ok(cfg) => cfg,
Err(e) => {
let mut shell = Shell::new();
cargo::exit_with_error(e.into(), &mut shell)
}
};
let Opts::Geiger(args) = Opts::from_args();
if let Err(e) = real_main(&args, &mut config) {
let mut shell = Shell::new();
cargo::exit_with_error(e, &mut shell)
}
}