use std::env;
use std::fmt;
use std::fs::{read_to_string, write, File};
use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};
use failure::ResultExt;
use lazy_static::*;
use regex::Regex;
use crate::error::*;
use crate::executable::{Cargo, ExecutableRunner, Linker};
use crate::source::Crate;
const LAST_BUILD_CMD: &str = ".last-build-command";
const TARGET_NAME: &str = "nvptx64-nvidia-cuda";
#[derive(Debug)]
pub struct Builder {
source_crate: Crate,
profile: Profile,
colors: bool,
crate_type: Option<CrateType>,
}
#[derive(Debug)]
pub struct BuildOutput<'a> {
builder: &'a Builder,
output_path: PathBuf,
file_suffix: String,
}
#[derive(Debug)]
pub enum BuildStatus<'a> {
Success(BuildOutput<'a>),
NotNeeded,
}
#[derive(PartialEq, Clone, Debug)]
pub enum Profile {
Debug,
Release,
}
#[derive(Clone, Copy, Debug)]
pub enum CrateType {
Library,
Binary,
}
impl Builder {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
Ok(Builder {
source_crate: Crate::analyse(path).context("Unable to analyse source crate")?,
profile: Profile::Release, colors: true,
crate_type: None,
})
}
pub fn is_build_needed() -> bool {
let cargo_env = env::var("CARGO");
let recursive_env = env::var("PTX_CRATE_BUILDING");
let is_rls_build = cargo_env.is_ok() && cargo_env.unwrap().ends_with("rls");
let is_recursive_build = recursive_env.is_ok() && recursive_env.unwrap() == "1";
!is_rls_build && !is_recursive_build
}
pub fn disable_colors(mut self) -> Self {
self.colors = false;
self
}
pub fn set_profile(mut self, profile: Profile) -> Self {
self.profile = profile;
self
}
pub fn set_crate_type(mut self, crate_type: CrateType) -> Self {
self.crate_type = Some(crate_type);
self
}
pub fn build(&self) -> Result<BuildStatus> {
if !Self::is_build_needed() {
return Ok(BuildStatus::NotNeeded);
}
ExecutableRunner::new(Linker).with_args(vec!["-V"]).run()?;
let mut cargo = ExecutableRunner::new(Cargo);
let mut args = Vec::new();
args.push("rustc");
if self.profile == Profile::Release {
args.push("--release");
}
args.push("--color");
args.push(if self.colors { "always" } else { "never" });
args.push("--target");
args.push(TARGET_NAME);
match self.crate_type {
Some(CrateType::Binary) => {
args.push("--bin");
args.push(self.source_crate.get_name());
}
Some(CrateType::Library) => {
args.push("--lib");
}
_ => {}
}
args.push("-v");
args.push("--");
args.push("--crate-type");
args.push("cdylib");
args.push("-Zcrate-attr=no_main");
let output_path = {
self.source_crate
.get_output_path()
.context("Unable to create output path")?
};
cargo
.with_args(&args)
.with_cwd(self.source_crate.get_path())
.with_env("PTX_CRATE_BUILDING", "1")
.with_env("CARGO_TARGET_DIR", output_path.clone());
let cargo_output = cargo.run().map_err(|error| match error.kind() {
BuildErrorKind::CommandFailed { stderr, .. } => {
let lines = stderr
.trim_matches('\n')
.split('\n')
.filter(Self::output_is_not_verbose)
.map(String::from)
.collect();
Error::from(BuildErrorKind::BuildFailed(lines))
}
_ => error,
})?;
Ok(BuildStatus::Success(
self.prepare_output(output_path, &cargo_output.stderr)?,
))
}
fn prepare_output(&self, output_path: PathBuf, cargo_stderr: &str) -> Result<BuildOutput> {
lazy_static! {
static ref SUFFIX_REGEX: Regex =
Regex::new(r"-C extra-filename=([\S]+)").expect("Unable to parse regex...");
}
let crate_name = self.source_crate.get_output_file_prefix();
let build_command = {
cargo_stderr
.trim_matches('\n')
.split('\n')
.find(|line| {
line.contains(&format!("--crate-name {}", crate_name))
&& line.contains("--crate-type cdylib")
})
.map(|line| BuildCommand::Realtime(line.to_string()))
.or_else(|| Self::load_cached_build_command(&output_path))
.ok_or_else(|| {
Error::from(BuildErrorKind::InternalError(String::from(
"Unable to find build command of the device crate",
)))
})?
};
if let BuildCommand::Realtime(ref command) = build_command {
Self::store_cached_build_command(&output_path, &command)?;
}
let file_suffix = match SUFFIX_REGEX.captures(&build_command) {
Some(caps) => caps[1].to_string(),
None => {
bail!(BuildErrorKind::InternalError(String::from(
"Unable to find `extra-filename` rustc flag",
)));
}
};
Ok(BuildOutput::new(self, output_path, file_suffix))
}
fn output_is_not_verbose(line: &&str) -> bool {
!line.starts_with("+ ")
&& !line.contains("Running")
&& !line.contains("Fresh")
&& !line.starts_with("Caused by:")
&& !line.starts_with(" process didn\'t exit successfully: ")
}
fn load_cached_build_command(output_path: &Path) -> Option<BuildCommand> {
match read_to_string(output_path.join(LAST_BUILD_CMD)) {
Ok(contents) => Some(BuildCommand::Cached(contents)),
Err(_) => None,
}
}
fn store_cached_build_command(output_path: &Path, command: &str) -> Result<()> {
write(output_path.join(LAST_BUILD_CMD), command.as_bytes())
.context(BuildErrorKind::OtherError)?;
Ok(())
}
}
impl<'a> BuildOutput<'a> {
fn new(builder: &'a Builder, output_path: PathBuf, file_suffix: String) -> Self {
BuildOutput {
builder,
output_path,
file_suffix,
}
}
pub fn get_assembly_path(&self) -> PathBuf {
self.output_path
.join(TARGET_NAME)
.join(self.builder.profile.to_string())
.join("deps")
.join(format!(
"{}{}.ptx",
self.builder.source_crate.get_output_file_prefix(),
self.file_suffix,
))
}
pub fn dependencies(&self) -> Result<Vec<PathBuf>> {
let mut deps_contents = {
self.get_deps_file_contents()
.context("Unable to get crate deps")?
};
if deps_contents.is_empty() {
bail!(BuildErrorKind::InternalError(String::from(
"Empty deps file",
)));
}
deps_contents = deps_contents
.chars()
.skip(3) .skip_while(|c| *c != ':')
.skip(1)
.collect::<String>();
let cargo_deps = vec![
self.builder.source_crate.get_path().join("Cargo.toml"),
self.builder.source_crate.get_path().join("Cargo.lock"),
];
Ok(deps_contents
.trim()
.split(' ')
.map(|item| PathBuf::from(item.trim()))
.chain(cargo_deps.into_iter())
.collect())
}
fn get_deps_file_contents(&self) -> Result<String> {
let crate_deps_path = self
.output_path
.join(TARGET_NAME)
.join(self.builder.profile.to_string())
.join(format!(
"{}.d",
self.builder
.source_crate
.get_deps_file_prefix(self.builder.crate_type)?
));
let mut crate_deps_reader =
BufReader::new(File::open(crate_deps_path).context(BuildErrorKind::OtherError)?);
let mut crate_deps_contents = String::new();
crate_deps_reader
.read_to_string(&mut crate_deps_contents)
.context(BuildErrorKind::OtherError)?;
Ok(crate_deps_contents)
}
}
impl fmt::Display for Profile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Profile::Debug => write!(f, "debug"),
Profile::Release => write!(f, "release"),
}
}
}
enum BuildCommand {
Realtime(String),
Cached(String),
}
impl std::ops::Deref for BuildCommand {
type Target = str;
fn deref(&self) -> &str {
match self {
BuildCommand::Realtime(line) => &line,
BuildCommand::Cached(line) => &line,
}
}
}