use crate::DEFAULT_TARGET;
use core::hash::{Hash, Hasher};
use lazy_static::lazy_static;
use std::{collections::hash_map::DefaultHasher, process::Command};
use structopt::StructOpt;
lazy_static! {
static ref RUST_VERSION: rustc_version::VersionMeta = rustc_version::version_meta().unwrap();
}
#[derive(Debug, StructOpt)]
pub struct Project {
#[structopt(short, long, default_value = "address")]
sanitizer: Vec<String>,
#[structopt(long, default_value = DEFAULT_TARGET)]
target: String,
#[structopt(long)]
all_features: bool,
#[structopt(long)]
release: Option<Option<bool>>,
#[structopt(long)]
no_default_features: bool,
#[structopt(long)]
features: Option<String>,
#[structopt(short, long)]
package: Option<String>,
#[structopt(long)]
manifest_path: Option<String>,
#[structopt(long)]
toolchain: Option<String>,
#[structopt(long)]
target_dir: Option<String>,
#[structopt(long)]
build_std: bool,
#[structopt(long)]
rustc_bootstrap: bool,
}
impl Project {
fn cargo(&self) -> Command {
let mut cmd = match self.toolchain() {
"default" => Command::new("cargo"),
toolchain => {
let mut cmd = Command::new("rustup");
cmd.arg("run").arg(toolchain).arg("cargo");
cmd
}
};
if self.rustc_bootstrap {
cmd.env("RUSTC_BOOTSTRAP", "1");
}
cmd
}
fn toolchain(&self) -> &str {
if let Some(toolchain) = self.toolchain.as_ref() {
toolchain
} else if self.requires_nightly() && RUST_VERSION.channel == rustc_version::Channel::Stable
{
"nightly"
} else {
"default"
}
}
pub fn cmd(&self, call: &str, flags: &[&str], fuzzer: Option<&str>) -> Command {
let mut cmd = self.cargo();
cmd.arg(call).arg("--target").arg(&self.target);
if self.release() {
cmd.arg("--release");
}
if self.no_default_features {
cmd.arg("--no-default-features");
}
if self.all_features {
cmd.arg("--all-features");
}
if let Some(value) = self.features.as_ref() {
cmd.arg("--features").arg(value);
}
if let Some(value) = self.package.as_ref() {
cmd.arg("--package").arg(value);
}
if let Some(value) = self.manifest_path.as_ref() {
cmd.arg("--manifest-path").arg(value);
}
if let Some(fuzzer) = fuzzer {
let rustflags = self.rustflags("RUSTFLAGS", flags);
if let Some(value) = self.target_dir.as_ref() {
cmd.arg("--target-dir").arg(value);
} else {
let mut hasher = DefaultHasher::new();
rustflags.hash(&mut hasher);
cmd.arg("--target-dir")
.arg(format!("target/fuzz/build_{:x}", hasher.finish()));
}
if self.build_std {
cmd.arg("-Zbuild-std");
}
cmd.env("RUSTFLAGS", rustflags)
.env("RUSTDOCFLAGS", self.rustflags("RUSTDOCFLAGS", flags))
.env("BOLERO_FUZZER", fuzzer);
}
cmd
}
fn rustflags(&self, inherits: &str, flags: &[&str]) -> String {
[
"--cfg fuzzing",
"-Cdebug-assertions",
"-Ctarget-cpu=native",
"-Cdebuginfo=2",
"-Coverflow_checks",
"-Clink-dead-code",
]
.iter()
.chain({
let toolchain = self.toolchain();
let version_meta = if toolchain == "default" {
RUST_VERSION.clone()
} else {
let mut cmd = Command::new("rustup");
let stdout = cmd
.arg("run")
.arg(toolchain)
.arg("rustc")
.arg("-vV")
.output()
.unwrap()
.stdout;
let stdout = core::str::from_utf8(&stdout).unwrap();
rustc_version::version_meta_for(stdout).unwrap()
};
let is_rust_159 = version_meta.semver.major == 1 && version_meta.semver.minor >= 59;
let is_llvm_13 = version_meta.llvm_version.map_or(true, |v| v.major >= 13);
Some(if is_rust_159 && is_llvm_13 {
&"-Cpasses=sancov-module"
} else {
&"-Cpasses=sancov"
})
})
.chain(flags.iter())
.cloned()
.chain(if cfg!(target_os = "linux") {
Some("-Clink-arg=-fuse-ld=gold")
} else {
None
})
.chain(if self.release() {
Some("-Ccodegen-units=1")
} else {
None
})
.map(String::from)
.chain(self.sanitizer_flags())
.chain(std::env::var(inherits).ok())
.collect::<Vec<_>>()
.join(" ")
}
pub fn requires_nightly(&self) -> bool {
!self.rustc_bootstrap && (self.sanitizers().next().is_some() || self.build_std)
}
fn sanitizers(&self) -> impl Iterator<Item = &str> {
self.sanitizer
.iter()
.map(String::as_str)
.filter(|s| s != &"NONE")
}
fn sanitizer_flags(&self) -> impl Iterator<Item = String> + '_ {
self.sanitizers()
.map(|sanitizer| format!("-Zsanitizer={}", sanitizer))
}
fn release(&self) -> bool {
match self.release {
None => true,
Some(None) => true,
Some(Some(v)) => v,
}
}
}