#[path = "gen/de.rs"]
mod gen;
use std::{
borrow::Cow,
collections::BTreeMap,
ffi::OsStr,
fs,
path::{Path, PathBuf},
slice,
str::FromStr,
};
use serde::{Deserialize, Serialize};
pub use crate::value::{Definition, Value};
use crate::{
easy,
error::{Context as _, Error, Result},
resolve::{ResolveContext, TargetTripleRef},
};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct Config {
#[serde(default)]
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub alias: BTreeMap<String, StringList>,
#[serde(default)]
#[serde(skip_serializing_if = "BuildConfig::is_none")]
pub build: BuildConfig,
#[serde(default)]
#[serde(skip_serializing_if = "DocConfig::is_none")]
pub doc: DocConfig,
#[serde(default)]
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub env: BTreeMap<String, EnvConfigValue>,
#[serde(default)]
#[serde(skip_serializing_if = "FutureIncompatReportConfig::is_none")]
pub future_incompat_report: FutureIncompatReportConfig,
#[serde(default)]
#[serde(skip_serializing_if = "NetConfig::is_none")]
pub net: NetConfig,
#[serde(default)]
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub target: BTreeMap<String, TargetConfig>,
#[serde(default)]
#[serde(skip_serializing_if = "TermConfig::is_none")]
pub term: TermConfig,
}
impl Config {
pub fn load() -> Result<Self> {
Self::load_with_cwd(std::env::current_dir().context("failed to get current directory")?)
}
pub fn load_with_cwd(cwd: impl AsRef<Path>) -> Result<Self> {
let cwd = cwd.as_ref();
Self::_load_with_options(cwd, home::cargo_home_with_cwd(cwd).ok())
}
pub fn load_with_options(
cwd: impl AsRef<Path>,
cargo_home: impl Into<Option<PathBuf>>,
) -> Result<Self> {
Self::_load_with_options(cwd.as_ref(), cargo_home.into())
}
pub(crate) fn _load_with_options(
current_dir: &Path,
cargo_home: Option<PathBuf>,
) -> Result<Config> {
let mut base = None;
for path in crate::Walk::with_cargo_home(current_dir, cargo_home) {
let config = Self::_load_file(&path)?;
match &mut base {
None => base = Some((path, config)),
Some((base_path, base)) => base.merge(config, false).with_context(|| {
format!(
"failed to merge config from `{}` into `{}`",
path.display(),
base_path.display()
)
})?,
}
}
Ok(base.map(|(_, c)| c).unwrap_or_default())
}
pub fn load_file(path: impl AsRef<Path>) -> Result<Self> {
Self::_load_file(path.as_ref())
}
fn _load_file(path: &Path) -> Result<Self> {
let buf = fs::read_to_string(path)
.with_context(|| format!("failed to read `{}`", path.display()))?;
let mut config: Config = toml::from_str(&buf).with_context(|| {
format!("failed to parse `{}` as cargo configuration", path.display())
})?;
config.set_path(path);
Ok(config)
}
pub(crate) fn merge(&mut self, from: Self, force: bool) -> Result<()> {
crate::merge::Merge::merge(self, from, force)
}
pub(crate) fn set_path(&mut self, path: &Path) {
crate::value::SetPath::set_path(self, path);
}
pub(crate) fn resolve_target(
cx: &ResolveContext,
target_configs: &BTreeMap<String, TargetConfig>,
override_target_rustflags: bool,
build_rustflags: &Option<Flags>,
target_triple: &TargetTripleRef<'_>,
build_config: &easy::BuildConfig,
) -> Result<Option<TargetConfig>> {
let target = target_triple.triple();
if target.starts_with("cfg(") {
bail!("'{target}' is not valid target triple");
}
let mut target_config = target_configs.get(target).cloned();
let target_u_upper = target_u_upper(target);
let mut target_linker = target_config.as_mut().and_then(|c| c.linker.take());
let mut target_runner = target_config.as_mut().and_then(|c| c.runner.take());
let mut target_rustflags: Option<Flags> =
target_config.as_mut().and_then(|c| c.rustflags.take());
if let Some(linker) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_LINKER"))? {
target_linker = Some(linker);
}
if let Some(runner) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_RUNNER"))? {
target_runner = Some(
PathAndArgs::from_string(&runner.val, runner.definition)
.context("invalid length 0, expected at least one element")?,
);
}
if let Some(rustflags) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_RUSTFLAGS"))? {
let mut rustflags =
Flags::from_space_separated(&rustflags.val, rustflags.definition.as_ref());
match &mut target_rustflags {
Some(target_rustflags) => {
target_rustflags.flags.append(&mut rustflags.flags);
}
target_rustflags @ None => *target_rustflags = Some(rustflags),
}
}
for (k, v) in target_configs {
if !k.starts_with("cfg(") {
continue;
}
if cx.eval_cfg(k, target_triple, build_config)? {
if target_runner.is_none() {
if let Some(runner) = v.runner.as_ref() {
target_runner = Some(runner.clone());
}
}
if let Some(rustflags) = v.rustflags.as_ref() {
match &mut target_rustflags {
Some(target_rustflags) => {
target_rustflags.flags.extend_from_slice(&rustflags.flags);
}
target_rustflags @ None => *target_rustflags = Some(rustflags.clone()),
}
}
}
}
if let Some(linker) = target_linker {
target_config.get_or_insert_with(TargetConfig::default).linker = Some(linker);
}
if let Some(runner) = target_runner {
target_config.get_or_insert_with(TargetConfig::default).runner = Some(runner);
}
if override_target_rustflags {
target_config.get_or_insert_with(TargetConfig::default).rustflags =
build_rustflags.clone();
} else if let Some(rustflags) = target_rustflags {
target_config.get_or_insert_with(TargetConfig::default).rustflags = Some(rustflags);
} else {
target_config.get_or_insert_with(TargetConfig::default).rustflags =
build_rustflags.clone();
}
Ok(target_config)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct BuildConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub jobs: Option<Value<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rustc: Option<Value<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rustc_wrapper: Option<Value<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rustc_workspace_wrapper: Option<Value<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rustdoc: Option<Value<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<StringOrArray>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_dir: Option<Value<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rustflags: Option<Flags>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rustdocflags: Option<Flags>,
#[serde(skip_serializing_if = "Option::is_none")]
pub incremental: Option<Value<bool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dep_info_basedir: Option<Value<String>>,
#[serde(skip)]
pub(crate) override_target_rustflags: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct TargetConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub linker: Option<Value<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub runner: Option<PathAndArgs>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rustflags: Option<Flags>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct DocConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub browser: Option<PathAndArgs>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
#[non_exhaustive]
pub enum EnvConfigValue {
Value(Value<String>),
Table {
value: Value<String>,
#[serde(skip_serializing_if = "Option::is_none")]
force: Option<Value<bool>>,
#[serde(skip_serializing_if = "Option::is_none")]
relative: Option<Value<bool>>,
},
}
impl EnvConfigValue {
pub(crate) const fn kind(&self) -> &'static str {
match self {
Self::Value(..) => "string",
Self::Table { .. } => "table",
}
}
pub(crate) fn resolve(&self, current_dir: &Path) -> Cow<'_, OsStr> {
match self {
Self::Value(v) => OsStr::new(&v.val).into(),
Self::Table { value, relative, .. } => {
if relative.as_ref().map_or(false, |v| v.val) {
if let Some(def) = &value.definition {
return def.root(current_dir).join(&value.val).into_os_string().into();
}
}
OsStr::new(&value.val).into()
}
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct FutureIncompatReportConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub frequency: Option<Value<Frequency>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct NetConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub retry: Option<Value<u32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub git_fetch_with_cli: Option<Value<bool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub offline: Option<Value<bool>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct TermConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub quiet: Option<Value<bool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub verbose: Option<Value<bool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<Value<Color>>,
#[serde(default)]
#[serde(skip_serializing_if = "TermProgress::is_none")]
pub progress: TermProgress,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct TermProgress {
#[serde(skip_serializing_if = "Option::is_none")]
pub when: Option<Value<When>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub width: Option<Value<u32>>,
}
#[allow(clippy::exhaustive_enums)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Color {
Auto,
Always,
Never,
}
impl Color {
pub const fn as_str(self) -> &'static str {
match self {
Self::Auto => "auto",
Self::Always => "always",
Self::Never => "never",
}
}
}
impl Default for Color {
fn default() -> Self {
Self::Auto
}
}
impl FromStr for Color {
type Err = Error;
fn from_str(color: &str) -> Result<Self, Self::Err> {
match color {
"auto" => Ok(Self::Auto),
"always" => Ok(Self::Always),
"never" => Ok(Self::Never),
other => bail!("must be auto, always, or never, but found `{other}`"),
}
}
}
#[allow(clippy::exhaustive_enums)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum When {
Auto,
Always,
Never,
}
impl When {
pub const fn as_str(self) -> &'static str {
match self {
Self::Auto => "auto",
Self::Always => "always",
Self::Never => "never",
}
}
}
impl Default for When {
fn default() -> Self {
Self::Auto
}
}
impl FromStr for When {
type Err = Error;
fn from_str(color: &str) -> Result<Self, Self::Err> {
match color {
"auto" => Ok(Self::Auto),
"always" => Ok(Self::Always),
"never" => Ok(Self::Never),
other => bail!("must be auto, always, or never, but found `{other}`"),
}
}
}
#[allow(clippy::exhaustive_enums)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Frequency {
Always,
Never,
}
impl Frequency {
pub const fn as_str(self) -> &'static str {
match self {
Self::Always => "always",
Self::Never => "never",
}
}
}
impl Default for Frequency {
fn default() -> Self {
Self::Always
}
}
impl FromStr for Frequency {
type Err = Error;
fn from_str(color: &str) -> Result<Self, Self::Err> {
match color {
"always" => Ok(Self::Always),
"never" => Ok(Self::Never),
other => bail!("must be always or never, but found `{other}`"),
}
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(transparent)]
pub struct Flags {
pub flags: Vec<Value<String>>,
#[serde(skip)]
pub(crate) deserialized_repr: StringListDeserializedRepr,
}
impl Flags {
pub(crate) fn from_encoded(s: &Value<String>) -> Self {
Self {
flags: split_encoded(&s.val)
.map(|v| Value { val: v.to_owned(), definition: s.definition.clone() })
.collect(),
deserialized_repr: StringListDeserializedRepr::Array,
}
}
pub(crate) fn from_space_separated(s: &str, def: Option<&Definition>) -> Self {
Self {
flags: split_space_separated(s)
.map(|v| Value { val: v.to_owned(), definition: def.cloned() })
.collect(),
deserialized_repr: StringListDeserializedRepr::String,
}
}
pub(crate) fn from_array(flags: Vec<Value<String>>) -> Self {
Self { flags, deserialized_repr: StringListDeserializedRepr::Array }
}
}
impl<'de> Deserialize<'de> for Flags {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let v: StringOrArray = Deserialize::deserialize(deserializer)?;
match v {
StringOrArray::String(s) => {
Ok(Self::from_space_separated(&s.val, s.definition.as_ref()))
}
StringOrArray::Array(v) => Ok(Self::from_array(v)),
}
}
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
#[serde(transparent)]
pub struct ConfigRelativePath(pub(crate) Value<String>);
impl ConfigRelativePath {
pub fn value(&self) -> &Value<String> {
&self.0
}
pub fn raw_value(&self) -> &str {
&self.0.val
}
pub(crate) fn resolve_program(&self, current_dir: &Path) -> Cow<'_, Path> {
self.0.resolve_as_program_path(current_dir)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PathAndArgs {
pub path: ConfigRelativePath,
pub args: Vec<Value<String>>,
pub(crate) deserialized_repr: StringListDeserializedRepr,
}
impl PathAndArgs {
pub(crate) fn from_string(value: &str, definition: Option<Definition>) -> Option<Self> {
let mut s = split_space_separated(value);
let path = s.next()?;
Some(Self {
args: s.map(|v| Value { val: v.to_owned(), definition: definition.clone() }).collect(),
path: ConfigRelativePath(Value { val: path.to_owned(), definition }),
deserialized_repr: StringListDeserializedRepr::String,
})
}
pub(crate) fn from_array(mut list: Vec<Value<String>>) -> Option<Self> {
if list.is_empty() {
return None;
}
let path = list.remove(0);
Some(Self {
path: ConfigRelativePath(path),
args: list,
deserialized_repr: StringListDeserializedRepr::Array,
})
}
}
impl Serialize for PathAndArgs {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self.deserialized_repr {
StringListDeserializedRepr::String => {
let mut s = self.path.raw_value().to_owned();
for arg in &self.args {
s.push(' ');
s.push_str(&arg.val);
}
s.serialize(serializer)
}
StringListDeserializedRepr::Array => {
let mut v = Vec::with_capacity(1 + self.args.len());
v.push(&self.path.0.val);
for arg in &self.args {
v.push(&arg.val);
}
v.serialize(serializer)
}
}
}
}
impl<'de> Deserialize<'de> for PathAndArgs {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrArray {
String(String),
Array(Vec<Value<String>>),
}
let v: StringOrArray = Deserialize::deserialize(deserializer)?;
let res = match v {
StringOrArray::String(s) => Self::from_string(&s, None),
StringOrArray::Array(v) => Self::from_array(v),
};
match res {
Some(path) => Ok(path),
None => Err(D::Error::invalid_length(0, &"at least one element")),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct StringList {
pub list: Vec<Value<String>>,
pub(crate) deserialized_repr: StringListDeserializedRepr,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum StringListDeserializedRepr {
String,
Array,
}
impl StringListDeserializedRepr {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::String => "string",
Self::Array => "array",
}
}
}
impl StringList {
pub(crate) fn from_string(value: &str, definition: Option<&Definition>) -> Self {
Self {
list: split_space_separated(value)
.map(|v| Value { val: v.to_owned(), definition: definition.cloned() })
.collect(),
deserialized_repr: StringListDeserializedRepr::String,
}
}
pub(crate) fn from_array(list: Vec<Value<String>>) -> Self {
Self { list, deserialized_repr: StringListDeserializedRepr::Array }
}
}
impl Serialize for StringList {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self.deserialized_repr {
StringListDeserializedRepr::String => {
let mut s = String::with_capacity(
self.list.len().saturating_sub(1)
+ self.list.iter().map(|v| v.val.len()).sum::<usize>(),
);
for arg in &self.list {
if !s.is_empty() {
s.push(' ');
}
s.push_str(&arg.val);
}
s.serialize(serializer)
}
StringListDeserializedRepr::Array => self.list.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for StringList {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let v: StringOrArray = Deserialize::deserialize(deserializer)?;
match v {
StringOrArray::String(s) => Ok(Self::from_string(&s.val, s.definition.as_ref())),
StringOrArray::Array(v) => Ok(Self::from_array(v)),
}
}
}
#[allow(clippy::exhaustive_enums)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum StringOrArray {
String(Value<String>),
Array(Vec<Value<String>>),
}
impl StringOrArray {
pub(crate) const fn kind(&self) -> &'static str {
match self {
Self::String(..) => "string",
Self::Array(..) => "array",
}
}
pub(crate) fn as_array_no_split(&self) -> &[Value<String>] {
match self {
Self::String(s) => slice::from_ref(s),
Self::Array(v) => v,
}
}
}
fn target_u_lower(target: &str) -> String {
target.replace(['-', '.'], "_")
}
pub(crate) fn target_u_upper(target: &str) -> String {
let mut target = target_u_lower(target);
target.make_ascii_uppercase();
target
}
pub(crate) fn split_encoded(s: &str) -> impl Iterator<Item = &str> {
s.split('\x1f')
}
pub(crate) fn split_space_separated(s: &str) -> impl Iterator<Item = &str> {
s.split(' ').map(str::trim).filter(|s| !s.is_empty())
}