use std::path::PathBuf;
use anyhow::{anyhow, bail, Context, Result};
use serde::{Deserialize, Deserializer};
use crate::common::{workspace_dir, InstallDir, InstallDirLocation};
pub const ESP_IDF_TOOLS_INSTALL_DIR_VAR: &str = "ESP_IDF_TOOLS_INSTALL_DIR";
pub const DEFAULT_TOOLS_INSTALL_DIR: InstallDirLocation = InstallDirLocation::Workspace;
pub const ESP_IDF_GLOB_VAR_PREFIX: &str = "ESP_IDF_GLOB";
pub const DEFAULT_SDKCONFIG_FILE: &str = "sdkconfig";
pub const DEFAULT_SDKCONFIG_DEFAULTS_FILE: &str = "sdkconfig.defaults";
#[derive(Debug, Deserialize, Default, Clone)]
#[serde(default)]
pub struct BuildConfig {
esp_idf_tools_install_dir: Option<InstallDir>,
esp_idf_sdkconfig: Option<PathBuf>,
#[serde(deserialize_with = "parse::list")]
esp_idf_sdkconfig_defaults: Option<Vec<PathBuf>>,
pub mcu: Option<String>,
#[cfg(any(feature = "native", not(feature = "pio")))]
#[serde(skip)]
pub native: crate::native::cargo_driver::config::NativeConfig,
pub esp_idf_sys_root_crate: Option<String>,
}
impl BuildConfig {
pub fn try_from_env() -> Result<BuildConfig> {
let cfg: BuildConfig = utils::parse_from_env(&[])?;
#[cfg(any(feature = "native", not(feature = "pio")))]
let cfg = {
use crate::native::cargo_driver::config::NativeConfig;
BuildConfig {
native: NativeConfig::try_from_env()?,
..cfg
}
};
Ok(cfg)
}
pub fn print(&self) {
eprintln!("Build configuration: {self:#?}");
}
pub fn esp_idf_tools_install_dir(&self) -> Result<(InstallDir, bool)> {
match self.esp_idf_tools_install_dir.clone() {
Some(val) => Ok((val, false)),
None => InstallDir::try_from(None).map(|v| (v, true)),
}
}
pub fn esp_idf_sdkconfig(&self) -> PathBuf {
self.esp_idf_sdkconfig
.clone()
.unwrap_or_else(|| DEFAULT_SDKCONFIG_FILE.into())
}
pub fn esp_idf_sdkconfig_defaults(&self) -> Vec<PathBuf> {
self.esp_idf_sdkconfig_defaults
.clone()
.unwrap_or_else(|| vec![DEFAULT_SDKCONFIG_DEFAULTS_FILE.into()])
}
pub fn with_cargo_metadata(&mut self) -> Result<()> {
let current_target = std::env::var("TARGET")?;
let filter_string = format!("--filter-platform={current_target}");
let metadata = cargo_metadata::MetadataCommand::new()
.current_dir(workspace_dir()?)
.other_options(vec!["--locked".into(), filter_string])
.exec()?;
let root_package = match (metadata.root_package(), &self.esp_idf_sys_root_crate) {
(_, Some(pkg_name)) => {
metadata.workspace_packages()
.into_iter().find(|p| &p.name == pkg_name)
.ok_or_else(|| anyhow!("the crate given by `ESP_IDF_SYS_ROOT_CRATE` does not exist in this workspace"))?
},
(Some(pkg), _) => pkg,
(None, None) => bail!("could not identify the root crate and `ESP_IDF_SYS_ROOT_CRATE` not specified")
};
let EspIdfSys {
v:
BuildConfig {
esp_idf_tools_install_dir,
esp_idf_sdkconfig,
esp_idf_sdkconfig_defaults,
mcu,
#[cfg(any(feature = "native", not(feature = "pio")))]
native: _,
esp_idf_sys_root_crate: _,
},
} = EspIdfSys::deserialize(&root_package.metadata)?;
utils::set_when_none(&mut self.esp_idf_sdkconfig, esp_idf_sdkconfig);
utils::set_when_none(
&mut self.esp_idf_sdkconfig_defaults,
esp_idf_sdkconfig_defaults,
);
utils::set_when_none(
&mut self.esp_idf_tools_install_dir,
esp_idf_tools_install_dir,
);
utils::set_when_none(&mut self.mcu, mcu);
#[cfg(any(feature = "native", not(feature = "pio")))]
self.native.with_cargo_metadata(root_package, &metadata)?;
Ok(())
}
}
#[derive(Deserialize, Default)]
pub struct EspIdfSys<T: Default> {
#[serde(default, rename = "esp-idf-sys")]
pub v: T,
}
impl<'a, T> EspIdfSys<T>
where
T: Default,
T: Deserialize<'a>,
{
pub fn deserialize<D>(de: D) -> Result<Self>
where
D: Deserializer<'a>,
D::Error: Send + Sync + std::error::Error + 'static,
{
let result = Option::<Self>::deserialize(de)
.with_context(|| anyhow!("could not read build config from manifest metadata"))?;
Ok(result.unwrap_or_default())
}
}
pub mod parse {
use serde::{Deserialize, Deserializer};
use super::utils::ValueOrVec;
pub fn list<'d, T, D>(de: D) -> Result<Option<Vec<T>>, D::Error>
where
D: Deserializer<'d>,
T: for<'s> From<&'s str> + Deserialize<'d>,
{
Option::<ValueOrVec<String, T>>::deserialize(de).map(|val| match val {
Some(ValueOrVec::Val(s)) => Some(
s.split(';')
.filter(|s| !s.is_empty())
.map(Into::into)
.collect(),
),
Some(ValueOrVec::Vec(v)) => Some(v),
None => None,
})
}
}
pub mod utils {
use embuild::cargo;
use serde::de::{self, Deserializer, Visitor};
use serde::Deserialize;
#[derive(Deserialize, Debug)]
#[serde(untagged)]
pub enum ValueOrVec<V, E = V> {
Val(V),
Vec(Vec<E>),
}
pub fn serde_introspect<'de, T>() -> &'static [&'static str]
where
T: Deserialize<'de>,
{
struct StructFieldsDeserializer<'a> {
fields: &'a mut Option<&'static [&'static str]>,
}
impl<'de> Deserializer<'de> for StructFieldsDeserializer<'_> {
type Error = serde::de::value::Error;
fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(de::Error::custom("I'm just here for the fields"))
}
fn deserialize_struct<V>(
self,
_name: &'static str,
fields: &'static [&'static str],
_visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
*self.fields = Some(fields); Err(de::Error::custom("I'm just here for the fields"))
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
byte_buf option unit unit_struct newtype_struct seq tuple
tuple_struct map enum identifier ignored_any
}
}
let mut serialized_names = None;
let _ = T::deserialize(StructFieldsDeserializer {
fields: &mut serialized_names,
});
serialized_names.unwrap_or_default()
}
pub fn set_when_none<T>(val: &mut Option<T>, new: Option<T>) {
if val.is_none() {
*val = new;
}
}
pub fn parse_from_env<T>(exclude_list: &[&str]) -> envy::Result<T>
where
T: for<'d> Deserialize<'d>,
{
let var_filter = |k: &str| !exclude_list.contains(&k);
for var in serde_introspect::<T>().iter().filter(|s| var_filter(s)) {
cargo::track_env_var(var.to_uppercase());
}
let vars = std::env::vars().filter(|(key, _)| var_filter(&key.to_lowercase()));
envy::from_iter(vars)
}
}