use std::{collections::BTreeMap, ffi::OsStr};
use anyhow::{format_err, Context as _, Result};
use camino::Utf8Path;
use serde::Deserialize;
use crate::{cargo::Cargo, cli::Coloring, env};
#[derive(Debug, Default, Deserialize)]
pub(crate) struct Config {
#[serde(default)]
build: Build,
#[serde(default)]
target: BTreeMap<String, Target>,
#[serde(default)]
pub(crate) doc: Doc,
#[serde(default)]
pub(crate) term: Term,
}
impl Config {
pub(crate) fn new(
cargo: &Cargo,
workspace_root: &Utf8Path,
target: Option<&str>,
host: Option<&str>,
) -> Result<Self> {
let mut cmd = cargo.process();
cmd.args(["-Z", "unstable-options", "config", "get", "--format", "json"])
.dir(workspace_root);
let mut config = match cmd.read() {
Ok(s) => serde_json::from_str(&s)
.with_context(|| format!("failed to parse output from {}", cmd))?,
Err(e) => {
warn!("{:#}", e);
Self::default()
}
};
config.apply_env(target, host)?;
Ok(config)
}
fn apply_env(&mut self, target: Option<&str>, host: Option<&str>) -> Result<()> {
if let Some(target) = target {
self.build.target = Some(target.to_owned());
} else if let Some(target) = env::var("CARGO_BUILD_TARGET")? {
self.build.target = Some(target);
}
let target = self.build.target.as_deref().or(host);
if let Some(rustflags) = env::var("RUSTFLAGS")? {
self.build.rustflags = Some(StringOrArray::String(rustflags));
} else if let Some(target) = target {
if let Some(rustflags) = env::var(&format!(
"CARGO_TARGET_{}_RUSTFLAGS",
target.to_uppercase().replace('-', "_")
))? {
self.build.rustflags = Some(StringOrArray::String(rustflags));
} else if let Some(Target { rustflags: Some(rustflags) }) = self.target.get(target) {
self.build.rustflags = Some(rustflags.clone());
} else if let Some(rustflags) = env::var("CARGO_BUILD_RUSTFLAGS")? {
self.build.rustflags = Some(StringOrArray::String(rustflags));
}
} else if let Some(rustflags) = env::var("CARGO_BUILD_RUSTFLAGS")? {
self.build.rustflags = Some(StringOrArray::String(rustflags));
}
if let Some(rustdocflags) = env::var("RUSTDOCFLAGS")? {
self.build.rustdocflags = Some(StringOrArray::String(rustdocflags));
} else if let Some(rustdocflags) = env::var("CARGO_BUILD_RUSTDOCFLAGS")? {
self.build.rustdocflags = Some(StringOrArray::String(rustdocflags));
}
if self.doc.browser.is_none() {
if let Some(browser) = env::var("BROWSER")? {
self.doc.browser = Some(StringOrArray::String(browser));
}
}
if let Some(verbose) = env::var("CARGO_TERM_VERBOSE")? {
self.term.verbose = Some(verbose.parse()?);
}
if let Some(color) = env::var("CARGO_TERM_COLOR")? {
self.term.color =
Some(clap::ArgEnum::from_str(&color, false).map_err(|e| format_err!("{}", e))?);
}
Ok(())
}
pub(crate) fn merge_to_args(
&self,
target: &mut Option<String>,
verbose: &mut u8,
color: &mut Option<Coloring>,
) {
if target.is_none() {
*target = self.build.target.clone();
}
if *verbose == 0 && self.term.verbose.unwrap_or(false) {
*verbose = 1;
}
if color.is_none() {
*color = self.term.color;
}
}
pub(crate) fn rustflags(&self) -> Option<String> {
self.build.rustflags.as_ref().map(ToString::to_string)
}
pub(crate) fn rustdocflags(&self) -> Option<String> {
self.build.rustdocflags.as_ref().map(ToString::to_string)
}
}
#[derive(Debug, Default, Deserialize)]
pub(crate) struct Build {
rustflags: Option<StringOrArray>,
rustdocflags: Option<StringOrArray>,
target: Option<String>,
}
#[derive(Debug, Deserialize)]
struct Target {
rustflags: Option<StringOrArray>,
}
#[derive(Debug, Default, Deserialize)]
pub(crate) struct Doc {
pub(crate) browser: Option<StringOrArray>,
}
#[derive(Debug, Default, Deserialize)]
pub(crate) struct Term {
pub(crate) verbose: Option<bool>,
pub(crate) color: Option<Coloring>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub(crate) enum StringOrArray {
String(String),
Array(Vec<String>),
}
impl StringOrArray {
pub(crate) fn path_and_args(&self) -> Option<(&OsStr, Vec<&str>)> {
match self {
Self::String(s) => {
let mut s = s.split(' ');
let path = s.next()?;
Some((OsStr::new(path), s.collect()))
}
Self::Array(v) => {
let path = v.get(0)?;
Some((OsStr::new(path), v.iter().skip(1).map(String::as_str).collect()))
}
}
}
}
impl ToString for StringOrArray {
fn to_string(&self) -> String {
match self {
Self::String(s) => s.clone(),
Self::Array(v) => v.join(" "),
}
}
}