Skip to content

Commit

Permalink
Support for CMake-first build
Browse files Browse the repository at this point in the history
  • Loading branch information
imarkov committed Oct 6, 2021
1 parent ac6522b commit 002262a
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 135 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "esp-idf-sys"
version = "0.20.2"
version = "0.20.3"
authors = ["Alexey Arbuzov <[email protected]>", "sapir <[email protected]>", "Ivan Markov <[email protected]>"]
edition = "2018"
categories = ["embedded", "hardware-support"]
Expand All @@ -10,6 +10,7 @@ repository = "https://github.com/ivmarkov/esp-idf-sys"
license = "MIT OR Apache-2.0"
readme = "README.md"
links = "esp_idf"
build = "build/build.rs"

# No xtensa in regular compiler yet
[package.metadata.docs.rs]
Expand All @@ -31,7 +32,7 @@ embedded-svc = "0.8.3"
paste = "1"

[build-dependencies]
embuild = "0.24"
embuild = "0.24.5"
anyhow = "1"
strum = { version = "0.21", optional = true, features = ["derive"] }
regex = "1.5"
104 changes: 13 additions & 91 deletions build.rs → build/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,107 +2,27 @@
compile_error!("One of the features `pio` or `native` must be selected.");

use anyhow::*;
use regex::{self};
use std::{
env, error, fs,
iter::once,
path::{Path, PathBuf},
str::FromStr,
};
use regex;
use std::{env, iter::once, path::PathBuf};

use embuild::{bindgen, build, cargo, kconfig, path_buf, utils::OsStrExt};

use common::*;

mod common;

// Note that the feature `native` must come before `pio`. These features are really
// mutually exclusive but that would require that all dependencies specify the same
// feature so instead we prefer the `native` feature over `pio` so that if one package
// specifies it, this overrides the `pio` feature for all other dependencies too.
// See https://doc.rust-lang.org/cargo/reference/features.html#mutually-exclusive-features.
#[cfg(any(feature = "pio", feature = "native"))]
#[cfg_attr(feature = "native", path = "build_native.rs")]
#[cfg_attr(all(feature = "pio", not(feature = "native")), path = "build_pio.rs")]
mod build_impl;

pub(crate) const STABLE_PATCHES: &[&str] = &[
"patches/missing_xtensa_atomics_fix.diff",
"patches/pthread_destructor_fix.diff",
"patches/ping_setsockopt_fix.diff",
];

#[allow(unused)]
pub(crate) const MASTER_PATCHES: &[&str] = &["patches/master_missing_xtensa_atomics_fix.diff"];

pub(crate) struct EspIdfBuildOutput<I>
where
I: Iterator<Item = (String, kconfig::Value)>,
{
pub(crate) cincl_args: build::CInclArgs,
pub(crate) link_args: Option<build::LinkArgs>,
pub(crate) kconfig_args: I,
pub(crate) bindgen: bindgen::Factory,
}

struct EspIdfVersion {
major: u32,
minor: u32,
patch: u32,
}

impl EspIdfVersion {
fn parse(bindings_file: impl AsRef<Path>) -> Result<Self> {
let bindings_content = fs::read_to_string(bindings_file.as_ref())?;

Ok(Self {
major: Self::grab_const(&bindings_content, "ESP_IDF_VERSION_MAJOR", "u32")?,
minor: Self::grab_const(&bindings_content, "ESP_IDF_VERSION_MINOR", "u32")?,
patch: Self::grab_const(bindings_content, "ESP_IDF_VERSION_PATCH", "u32")?,
})
}

fn get_cfg(&self) -> impl Iterator<Item = String> {
once(format!(
"esp_idf_full_version=\"{}.{}.{}\"",
self.major, self.minor, self.patch
))
.chain(once(format!(
"esp_idf_version=\"{}.{}\"",
self.major, self.minor
)))
.chain(once(format!("esp_idf_major_version=\"{}\"", self.major)))
.chain(once(format!("esp_idf_minor_version=\"{}\"", self.minor)))
.chain(once(format!("esp_idf_patch_version=\"{}\"", self.patch)))
}

fn grab_const<T>(
text: impl AsRef<str>,
const_name: impl AsRef<str>,
const_type: impl AsRef<str>,
) -> Result<T>
where
T: FromStr,
T::Err: error::Error + Send + Sync + 'static,
{
// Future: Consider using bindgen::callbacks::ParseCallbacks for grabbing macro-based constants. Should be more reliable compared to grepping

let const_name = const_name.as_ref();

let value = regex::Regex::new(&format!(
r"\s+const\s+{}\s*:\s*{}\s*=\s*(\S+)\s*;",
const_name,
const_type.as_ref()
))?
.captures(text.as_ref())
.ok_or_else(|| anyhow!("Failed to capture constant {}", const_name))?
.get(1)
.ok_or_else(|| anyhow!("Failed to capture the value of constant {}", const_name))?
.as_str()
.parse::<T>()?;

Ok(value)
}
}
#[cfg_attr(feature = "native", path = "native.rs")]
#[cfg_attr(all(feature = "pio", not(feature = "native")), path = "pio.rs")]
mod build_driver;

fn main() -> anyhow::Result<()> {
let build_output = build_impl::main()?;
let build_output = build_driver::build()?;

// We need to restrict the kconfig parameters which are turned into rustc cfg items
// because otherwise we would be hitting rustc command line restrictions on Windows
Expand Down Expand Up @@ -156,6 +76,7 @@ fn main() -> anyhow::Result<()> {
.blacklist_function("strtold")
.blacklist_function("_strtold_r")
.blacklist_function("esp_eth_mac_new_esp32")
.clang_args(build_output.components.clang_args())
.clang_args(vec![
"-target",
if mcu == "esp32c3" {
Expand All @@ -172,7 +93,8 @@ fn main() -> anyhow::Result<()> {
args: cfg_args
.args
.into_iter()
.chain(EspIdfVersion::parse(bindings_file)?.get_cfg())
.chain(EspIdfVersion::parse(bindings_file)?.cfg_args())
.chain(build_output.components.cfg_args())
.chain(once(mcu))
.collect(),
};
Expand Down
130 changes: 130 additions & 0 deletions build/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use anyhow::*;
use regex::{self};
use std::{collections::HashSet, error, fs, iter::once, path::Path, str::FromStr};

use embuild::{bindgen, build, kconfig};

pub const STABLE_PATCHES: &[&str] = &[
"patches/missing_xtensa_atomics_fix.diff",
"patches/pthread_destructor_fix.diff",
"patches/ping_setsockopt_fix.diff",
];

#[allow(unused)]
pub const MASTER_PATCHES: &[&str] = &["patches/master_missing_xtensa_atomics_fix.diff"];

const ALL_COMPONENTS: &[&str] = &[
// TODO: Put all IDF components here
"comp_pthread_enabled",
"comp_nvs_flash_enabled",
"comp_esp_http_client_enabled",
"comp_esp_http_server_enabled",
"comp_espcoredump_enabled",
"comp_app_update_enabled",
"comp_esp_serial_slave_link_enabled",
"comp_spi_flash_enabled",
"comp_esp_adc_cal_enabled",
];

pub struct EspIdfBuildOutput {
pub cincl_args: build::CInclArgs,
pub link_args: Option<build::LinkArgs>,
pub kconfig_args: Box<dyn Iterator<Item = (String, kconfig::Value)>>,
pub components: EspIdfComponents,
pub bindgen: bindgen::Factory,
}

pub struct EspIdfComponents(Vec<&'static str>);

impl EspIdfComponents {
pub fn new() -> Self {
Self(ALL_COMPONENTS.iter().map(|s| *s).collect::<Vec<_>>())
}

#[allow(dead_code)]
pub fn from<I, S>(enabled: I) -> Self
where
I: Iterator<Item = S>,
S: Into<String>,
{
let enabled = enabled.map(Into::into).collect::<HashSet<_>>();

Self(
ALL_COMPONENTS
.iter()
.map(|s| *s)
.filter(|s| enabled.contains(*s))
.collect::<Vec<_>>(),
)
}

pub fn clang_args<'a>(&'a self) -> impl Iterator<Item = String> + 'a {
self.0
.iter()
.map(|s| format!("-DESP_IDF_{}", s.to_uppercase()))
}

pub fn cfg_args<'a>(&'a self) -> impl Iterator<Item = String> + 'a {
self.0.iter().map(|c| format!("esp_idf_{}", c))
}
}

pub struct EspIdfVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}

impl EspIdfVersion {
pub fn parse(bindings_file: impl AsRef<Path>) -> Result<Self> {
let bindings_content = fs::read_to_string(bindings_file.as_ref())?;

Ok(Self {
major: Self::grab_const(&bindings_content, "ESP_IDF_VERSION_MAJOR", "u32")?,
minor: Self::grab_const(&bindings_content, "ESP_IDF_VERSION_MINOR", "u32")?,
patch: Self::grab_const(bindings_content, "ESP_IDF_VERSION_PATCH", "u32")?,
})
}

pub fn cfg_args(&self) -> impl Iterator<Item = String> {
once(format!(
"esp_idf_full_version=\"{}.{}.{}\"",
self.major, self.minor, self.patch
))
.chain(once(format!(
"esp_idf_version=\"{}.{}\"",
self.major, self.minor
)))
.chain(once(format!("esp_idf_major_version=\"{}\"", self.major)))
.chain(once(format!("esp_idf_minor_version=\"{}\"", self.minor)))
.chain(once(format!("esp_idf_patch_version=\"{}\"", self.patch)))
}

fn grab_const<T>(
text: impl AsRef<str>,
const_name: impl AsRef<str>,
const_type: impl AsRef<str>,
) -> Result<T>
where
T: FromStr,
T::Err: error::Error + Send + Sync + 'static,
{
// Future: Consider using bindgen::callbacks::ParseCallbacks for grabbing macro-based constants. Should be more reliable compared to grepping

let const_name = const_name.as_ref();

let value = regex::Regex::new(&format!(
r"\s+const\s+{}\s*:\s*{}\s*=\s*(\S+)\s*;",
const_name,
const_type.as_ref()
))?
.captures(text.as_ref())
.ok_or_else(|| anyhow!("Failed to capture constant {}", const_name))?
.get(1)
.ok_or_else(|| anyhow!("Failed to capture the value of constant {}", const_name))?
.as_str()
.parse::<T>()?;

Ok(value)
}
}
Loading

0 comments on commit 002262a

Please sign in to comment.