use crate::cli;
use serde::Deserialize;
use serde_json::Error as JsonError;
use std::env;
use std::io;
use std::process::{Command, Output, Stdio};
use std::string::FromUtf8Error;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum SubcommandError {
#[error("Command failed with I/O error")]
Io(#[from] io::Error),
#[error("Command failed with exit code: {0:?}")]
Command(Option<i32>),
#[error("Command failed with UTF-8 error")]
Utf8(#[from] FromUtf8Error),
#[error("Command failed with environment error")]
Var(#[from] env::VarError),
#[error("JSON error: {1}")]
Json(#[source] JsonError, String),
}
trait Runner {
fn run(&mut self, verbose: usize) -> io::Result<Output>;
}
impl Runner for Command {
fn run(&mut self, verbose: usize) -> io::Result<Output> {
if verbose > 0 {
self.arg(format!("-{}", str::repeat("v", verbose)));
eprintln!("+ {:?}", self);
}
self.output()
}
}
#[derive(Deserialize, Debug)]
pub(crate) struct CargoArtifact {
pub(crate) executable: String,
pub(crate) target: CargoArtifactTarget,
}
#[derive(Deserialize, Debug)]
pub(crate) struct CargoArtifactTarget {
pub(crate) name: String,
}
#[derive(Deserialize, Debug)]
struct CargoMessage {
message: Option<CargoMessageMessage>,
}
#[derive(Deserialize, Debug)]
struct CargoMessageMessage {
rendered: String,
}
pub(crate) fn run(args: &cli::BuildArgs, verbose: usize) -> Result<CargoArtifact, SubcommandError> {
let rustflags = env::var("RUSTFLAGS")
.map(|mut var| {
var.push_str(" -Clinker-plugin-lto");
var
})
.or_else(|e| match e {
env::VarError::NotPresent => Ok(String::from("-Clinker-plugin-lto")),
e => Err(e),
})?;
env::set_var("RUSTFLAGS", rustflags);
let build_args = {
let release_flag = "--release".to_owned();
let mut args = args.rest.clone();
if !args.contains(&release_flag) {
args.push(release_flag);
}
args
};
let output = Command::new("cargo")
.arg(format!("+{}", include_str!("../rust-toolchain").trim()))
.arg("build")
.arg("-Z=build-std=core,alloc")
.arg("--message-format=json-render-diagnostics")
.arg(format!("--target={}", args.target.as_ref().unwrap()))
.args(build_args)
.stderr(Stdio::inherit())
.run(verbose)?;
let json = String::from_utf8(output.stdout)?;
if output.status.success() {
parse_artifact(&json)
} else {
let (_artifacts, errors) = split_output(&json);
print_messages(errors)?;
Err(SubcommandError::Command(output.status.code()))
}
}
fn split_output(json: &str) -> (Vec<&str>, Vec<&str>) {
json.trim()
.split('\n')
.filter(|x| {
!x.is_empty()
&& !x.starts_with('#')
&& !x.contains("] cargo:")
&& !x.contains(r#""reason":"build-script-executed""#)
})
.partition(|x| x.contains(r#""reason":"compiler-artifact""#))
}
fn parse_artifact(json: &str) -> Result<CargoArtifact, SubcommandError> {
let (artifacts, warnings) = split_output(json);
print_messages(warnings)?;
let json = *artifacts.last().expect("Expected artifact JSON");
serde_json::from_str(json).map_err(|e| SubcommandError::Json(e, json.into()))
}
fn print_messages<'a, T>(messages: T) -> Result<(), SubcommandError>
where
T: IntoIterator<Item = &'a str>,
{
for s in messages {
let message: CargoMessage =
serde_json::from_str(s).map_err(|e| SubcommandError::Json(e, s.into()))?;
if let Some(message) = message.message {
eprintln!("{}", message.rendered);
}
}
Ok(())
}