From 50853c2239de0498db74d045269fdde14d48cd1f Mon Sep 17 00:00:00 2001 From: Dominik Gschwind Date: Mon, 6 Sep 2021 18:01:57 +0200 Subject: [PATCH 1/8] Implement native esp-idf cmake build Move `build.rs` contents to `build_pio.rs` Add feature `native` Add optional dependency `strum` --- Cargo.toml | 10 +- build.rs | 144 +-------- build_native.rs | 399 +++++++++++++++++++++++++ build_pio.rs | 136 +++++++++ resources/cmake_project/CMakeLists.txt | 14 + resources/cmake_project/main.c | 1 + 6 files changed, 569 insertions(+), 135 deletions(-) create mode 100644 build_native.rs create mode 100644 build_pio.rs create mode 100644 resources/cmake_project/CMakeLists.txt create mode 100644 resources/cmake_project/main.c diff --git a/Cargo.toml b/Cargo.toml index 41b46ac937c..82751e88d6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,15 @@ links = "esp_idf" default-target = "x86_64-unknown-linux-gnu" [features] -default = ["std", "embedded-svc/std"] +default = ["std", "embedded-svc/std", "native"] std = [] +# Use `platformio` to build the `esp-idf` +platformio = [] +# Use native `esp-idf` tooling to build it +native = ["strum"] + [dependencies] mutex-trait = "0.2" embedded-svc = "0.8.3" @@ -27,4 +32,5 @@ paste = "1" [build-dependencies] embuild = "0.23" -anyhow = "1" +anyhow = {version = "1", features = ["backtrace"]} +strum = { version = "0.21", optional = true, features = ["derive"] } diff --git a/build.rs b/build.rs index 31cdc2d55ae..325cd7a3174 100644 --- a/build.rs +++ b/build.rs @@ -1,136 +1,14 @@ -use std::convert::TryFrom; -use std::{env, path::PathBuf}; +#[cfg(not(any(feature = "pio", feature = "native")))] +compile_error!("One of the features `platformio` or `native` must be selected."); -use anyhow::*; +#[cfg(all(feature = "pio", feature = "native"))] +compile_error!("The features `platformio` and `native` are mutually exclusive. Only one of them can be selected at a time."); -use embuild::bindgen; -use embuild::build; -use embuild::cargo; -use embuild::kconfig; -use embuild::pio; -use embuild::pio::project; +#[cfg(any(feature = "platformio", feature = "native"))] +#[cfg_attr(feature = "platformio", path = "build_pio.rs")] +#[cfg_attr(feature = "native", path = "build_native.rs")] +mod build_impl; -fn main() -> Result<()> { - let pio_scons_vars = if let Some(pio_scons_vars) = project::SconsVariables::from_piofirst() { - println!("cargo:info=PIO->Cargo build detected: generating bindings only"); - - pio_scons_vars - } else { - let pio = pio::Pio::install_default()?; - - let resolution = pio::Resolver::new(pio.clone()) - .params(pio::ResolutionParams { - platform: Some("espressif32".into()), - frameworks: vec!["espidf".into()], - target: Some(env::var("TARGET")?), - ..Default::default() - }) - .resolve(true)?; - - let mut builder = - project::Builder::new(PathBuf::from(env::var("OUT_DIR")?).join("esp-idf")); - - builder - .enable_scons_dump() - .enable_c_entry_points() - .options(build::env_options_iter("ESP_IDF_SYS_PIO_CONF")?) - .files(build::tracked_globs_iter( - PathBuf::from("."), - &["patches/**"], - )?) - .files(build::tracked_env_globs_iter("ESP_IDF_SYS_GLOB")?); - - #[cfg(feature = "espidf_master")] - builder - .platform_package( - "framework-espidf", - "https://github.com/ivmarkov/esp-idf.git#master", - ) - .platform_package_patch( - PathBuf::from("patches").join("master_missing_xtensa_atomics_fix.diff"), - PathBuf::from("framework-espidf"), - ) - .platform_package_patch( - PathBuf::from("patches").join("ping_setsockopt_fix.diff"), - PathBuf::from("framework-espidf"), - ); - - #[cfg(feature = "espidf_master")] - env::set_var("IDF_MAINTAINER", "1"); - - #[cfg(not(feature = "espidf_master"))] - builder - .platform_package_patch( - PathBuf::from("patches").join("pthread_destructor_fix.diff"), - PathBuf::from("framework-espidf"), - ) - .platform_package_patch( - PathBuf::from("patches").join("ping_setsockopt_fix.diff"), - PathBuf::from("framework-espidf"), - ) - .platform_package_patch( - PathBuf::from("patches").join("missing_xtensa_atomics_fix.diff"), - PathBuf::from("framework-espidf"), - ); - - let project_path = builder.generate(&resolution)?; - - pio.build(&project_path, env::var("PROFILE")? == "release")?; - - let pio_scons_vars = project::SconsVariables::from_dump(&project_path)?; - - build::LinkArgsBuilder::try_from(&pio_scons_vars)? - .build() - .propagate(); - - pio_scons_vars - }; - - // In case other SYS crates need to have access to the ESP-IDF C headers - build::CInclArgs::try_from(&pio_scons_vars)?.propagate(); - - let cfg_args = kconfig::CfgArgs::try_from( - pio_scons_vars - .project_dir - .join(if pio_scons_vars.release_build { - "sdkconfig.release" - } else { - "sdkconfig.debug" - }) - .as_path(), - )?; - - cfg_args.propagate("ESP_IDF"); - cfg_args.output("ESP_IDF"); - - let mcu = pio_scons_vars.mcu.as_str(); - - // Output the exact ESP32 MCU, so that we and crates depending directly on us can branch using e.g. #[cfg(esp32xxx)] - println!("cargo:rustc-cfg={}", mcu); - println!("cargo:MCU={}", mcu); - - let header = PathBuf::from("src") - .join("include") - .join(if mcu == "esp8266" { - "esp-8266-rtos-sdk" - } else { - "esp-idf" - }) - .join("bindings.h"); - - cargo::track_file(&header); - - bindgen::run( - bindgen::Factory::from_scons_vars(&pio_scons_vars)? - .builder()? - .ctypes_prefix("c_types") - .header(header.to_string_lossy()) - .blacklist_function("strtold") - .blacklist_function("_strtold_r") - .clang_args(if mcu == "esp32c3" { - vec!["-target", "riscv32"] - } else { - vec![] - }), - ) -} +fn main() -> anyhow::Result<()> { + build_impl::main() +} \ No newline at end of file diff --git a/build_native.rs b/build_native.rs new file mode 100644 index 00000000000..c6b26029c67 --- /dev/null +++ b/build_native.rs @@ -0,0 +1,399 @@ +use std::convert::TryFrom; +use std::env; +use std::ffi::OsString; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +use anyhow::*; +use embuild::cargo::IntoWarning; +use embuild::cmake::codemodel::Language; +use embuild::cmake::ObjKind; +use embuild::fs::copy_file_if_different; +use embuild::git::{CloneOptions, Repository}; +use embuild::python::{check_python_at_least, PYTHON}; +use embuild::utils::{OsStrExt, PathExt}; +use embuild::{bindgen, build, cargo, cmake, cmd, cmd_output, git, kconfig, path_buf}; +use strum::{Display, EnumString}; + +const SDK_DIR_VAR: &str = "SDK_DIR"; +const ESP_IDF_VERSION_VAR: &str = "ESP_IDF_VERSION"; +const ESP_IDF_REPOSITORY_VAR: &str = "ESP_IDF_REPOSITORY"; +const ESP_IDF_SDKCONFIG_DEFAULTS_VAR: &str = "ESP_IDF_SDKCONFIG_DEFAULTS"; +const ESP_IDF_SDKCONFIG_VAR: &str = "ESP_IDF_SDKCONFIG"; +const ESP_IDF_EXTRA_TOOLS_VAR: &str = "ESP_IDF_EXTRA_TOOLS"; +const MCU_VAR: &str = "MCU"; + +const DEFAULT_SDK_DIR: &str = ".sdk"; +const DEFAULT_ESP_IDF_REPOSITORY: &str = "https://github.com/espressif/esp-idf.git"; +const DEFAULT_ESP_IDF_VERSION: &str = "v4.3"; + +const STABLE_PATCHES: &[&str] = &[ + "master_missing_xtensa_atomics_fix.diff", + "ping_setsockopt_fix.diff", +]; +const MASTER_PATCHES: &[&str] = &[ + "pthread_destructor_fix.diff", + "ping_setsockopt_fix.diff", + "missing_xtensa_atomics_fix.diff", +]; + +fn esp_idf_version() -> git::Ref { + let version = env::var(ESP_IDF_VERSION_VAR).unwrap_or(DEFAULT_ESP_IDF_VERSION.to_owned()); + let version = version.trim(); + assert!( + !version.is_empty(), + "${} (='{}') must contain a valid version", + ESP_IDF_VERSION_VAR, + version + ); + + match version.split_once(':') { + Some(("commit", c)) => git::Ref::Commit(c.to_owned()), + Some(("tag", t)) => git::Ref::Tag(t.to_owned()), + Some(("branch", b)) => git::Ref::Branch(b.to_owned()), + _ => match version.chars().next() { + Some(c) if c.is_ascii_digit() => git::Ref::Tag("v".to_owned() + version), + Some('v') if version.len() > 1 && version.chars().nth(1).unwrap().is_ascii_digit() => { + git::Ref::Tag(version.to_owned()) + } + Some(_) => git::Ref::Branch(version.to_owned()), + _ => unreachable!(), + }, + } +} + +pub fn main() -> Result<()> { + if let Err(err) = run() { + println!("{:#}", err); + assert!(false); + } + Ok(()) +} + +pub fn run() -> Result<()> { + let out_dir = path_buf![env::var("OUT_DIR")?]; + let target = env::var("TARGET")?; + let workspace_dir = out_dir.pop_times(6); + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?); + + let chip = if let Some(mcu) = env::var_os(MCU_VAR) { + Chip::from_str(&mcu.to_string_lossy())? + } else { + Chip::detect(&target)? + }; + + cargo::track_env_var(SDK_DIR_VAR); + cargo::track_env_var(ESP_IDF_VERSION_VAR); + cargo::track_env_var(ESP_IDF_REPOSITORY_VAR); + cargo::track_env_var(ESP_IDF_SDKCONFIG_DEFAULTS_VAR); + cargo::track_env_var(ESP_IDF_SDKCONFIG_VAR); + cargo::track_env_var(ESP_IDF_EXTRA_TOOLS_VAR); + cargo::track_env_var(MCU_VAR); + + let sdk_dir = path_buf![env::var(SDK_DIR_VAR).unwrap_or(DEFAULT_SDK_DIR.to_owned())] + .abspath_relative_to(&workspace_dir); + + // Clone esp-idf. + let esp_idf_dir = sdk_dir.join("esp-idf"); + let esp_idf_version = esp_idf_version(); + let esp_idf_repo = + env::var(ESP_IDF_REPOSITORY_VAR).unwrap_or(DEFAULT_ESP_IDF_REPOSITORY.to_owned()); + let mut esp_idf = Repository::new(&esp_idf_dir); + + let esp_idf_modified = esp_idf.clone_ext( + &esp_idf_repo, + CloneOptions::new() + .force_ref(esp_idf_version.clone()) + .depth(1), + )?; + + // Apply patches, only if `clone_ext` changed the repo + if esp_idf_modified { + match esp_idf_version { + git::Ref::Branch(b) if esp_idf.get_default_branch()?.as_ref() == Some(&b) => { + esp_idf.apply(MASTER_PATCHES)? + } + git::Ref::Tag(t) if t == DEFAULT_ESP_IDF_VERSION => esp_idf.apply(STABLE_PATCHES)?, + _ => (), + }; + } + + // This is a workaround for msys or even git bash. + // When using them `idf_tools.py` prints unix paths (ex. `/c/user/` instead of + // `C:\user\`), so we correct this with an invocation of `cygpath` which converts the + // path to the windows representation. + let to_win_path = if cfg!(windows) && cmd_output!("cygpath", "--version").is_ok() { + |p: String| cmd_output!("cygpath", "-w", p).unwrap().to_string() + } else { + |p: String| p + }; + + // Create python virtualenv. + check_python_at_least(3, 0)?; + let idf_tools_py = path_buf![&esp_idf_dir, "tools", "idf_tools.py"]; + + let get_python_env_dir = || -> Result { + Ok(cmd_output!(PYTHON, &idf_tools_py, "--idf-path", &esp_idf_dir, "--quiet", "export", "--format=key-value"; ignore_exitcode, env=("IDF_TOOLS_PATH", &sdk_dir)) + .lines() + .find(|s| s.trim_start().starts_with("IDF_PYTHON_ENV_PATH=")) + .ok_or(anyhow!("`idf_tools.py export` result contains no `IDF_PYTHON_ENV_PATH` item."))? + .trim() + .strip_prefix("IDF_PYTHON_ENV_PATH=").unwrap() + .to_string()) + }; + + let python_env_dir = get_python_env_dir().map(&to_win_path); + let python_env_dir: PathBuf = if python_env_dir.is_err() + || !Path::new(&python_env_dir.as_ref().unwrap()).exists() + { + cmd!(PYTHON, &idf_tools_py, "--idf-path", &esp_idf_dir, "--quiet", "--non-interactive", "install-python-env"; env=("IDF_TOOLS_PATH", &sdk_dir))?; + to_win_path(get_python_env_dir()?) + } else { + python_env_dir.unwrap() + }.into(); + let python = embuild::which::which_in( + "python", + Some(&python_env_dir.join("Scripts")), + env::current_dir()?, + )?; + + // Install tools. + let chip_target_triple = chip.target_triple(); + let mut tools = vec!["ninja", chip_target_triple]; + tools.extend(chip.ulp_tool().iter()); + cmd!(python, &idf_tools_py, "--idf-path", &esp_idf_dir, "--non-interactive", "install"; env=("IDF_TOOLS_PATH", &sdk_dir), args=(tools))?; + + // Intall extra tools if requested, but don't fail compilation if this errors + if let Some(extra_tools) = env::var_os(ESP_IDF_EXTRA_TOOLS_VAR) { + cmd!( + python, &idf_tools_py, "--idf-path", &esp_idf_dir, "--non-interactive", "install"; + args=(extra_tools.to_string_lossy().split(';').filter(|s| !s.is_empty()).map(str::trim)), + env=("IDF_TOOLS_PATH", &sdk_dir) + ) + .into_warning(); + } + + // Get the paths to the tools. + let mut bin_paths: Vec<_> = cmd_output!(python, &idf_tools_py, "--idf-path", &esp_idf_dir, "--quiet", "export", "--format=key-value"; ignore_exitcode, env=("IDF_TOOLS_PATH", &sdk_dir)) + .lines() + .find(|s| s.trim_start().starts_with("PATH=")) + .expect("`idf_tools.py export` result contains no `PATH` item.").trim() + .strip_prefix("PATH=").unwrap() + .split(if cfg!(windows) { &[';', ':'][..] } else { &[':'][..] }) + .map(|s| s.to_owned()) + .collect(); + bin_paths.pop(); + let bin_paths: Vec<_> = bin_paths + .into_iter() + .map(|s| PathBuf::from(to_win_path(s))) + .chain(env::split_paths(&env::var("PATH")?)) + .collect(); + let paths = env::join_paths(bin_paths.iter())?; + + // Create cmake project. + copy_file_if_different( + manifest_dir.join(path_buf!("resources", "cmake_project", "CMakeLists.txt")), + &out_dir, + )?; + copy_file_if_different( + manifest_dir.join(path_buf!("resources", "cmake_project", "main.c")), + &out_dir, + )?; + + // The `kconfig.cmake` script looks at this variable if it should compile `mconf` on windows. + // But this variable is also present when using git-bash which doesn't have gcc. + env::remove_var("MSYSTEM"); + + // Resolve `ESP_IDF_SDKCONFIG` and `ESP_IDF_SDKCONFIG_DEFAULTS` to an absolute path + // relative to the workspace directory if not empty. + let sdkconfig = env::var_os(ESP_IDF_SDKCONFIG_VAR) + .filter(|v| !v.is_empty()) + .map(|v| { + Path::new(&v) + .abspath_relative_to(&workspace_dir) + .into_os_string() + }) + .unwrap_or_else(|| OsString::new()); + + let sdkconfig_defaults = env::var_os(ESP_IDF_SDKCONFIG_DEFAULTS_VAR) + .filter(|v| !v.is_empty()) + .map(|v| -> Result { + let mut result = OsString::new(); + for s in v + .try_to_str()? + .split(';') + .filter(|v| !v.is_empty()) + .map(|v| Path::new(v).abspath_relative_to(&workspace_dir)) + { + if !result.is_empty() { + result.push(";"); + } + result.push(s); + } + + Ok(result) + }) + .unwrap_or_else(|| Ok(OsString::new()))?; + + let cmake_toolchain_file = + path_buf![&esp_idf_dir, "tools", "cmake", chip.cmake_toolchain_file()]; + + // Get the asm, C and C++ flags from the toolchain file, these would otherwise get + // overwritten because `cmake::Config` also sets these (see + // https://github.com/espressif/esp-idf/issues/7507). + let (asm_flags, c_flags, cxx_flags) = { + let mut vars = cmake::get_script_variables(&cmake_toolchain_file)?; + ( + vars.remove("CMAKE_ASM_FLAGS").unwrap_or_default(), + vars.remove("CMAKE_C_FLAGS").unwrap_or_default(), + vars.remove("CMAKE_CXX_FLAGS").unwrap_or_default(), + ) + }; + + // `cmake::Config` automatically uses `/build` and there is no way to query + // what build directory it sets, so we hard-code it. + let cmake_build_dir = out_dir.join("build"); + + let query = cmake::Query::new( + &cmake_build_dir, + "cargo", + &[ObjKind::Codemodel, ObjKind::Toolchains], + )?; + + // Build the esp-idf. + cmake::Config::new(&out_dir) + .generator("Ninja") + .out_dir(&out_dir) + .no_build_target(true) + .define("CMAKE_TOOLCHAIN_FILE", &cmake_toolchain_file) + .always_configure(true) + .pic(false) + .asmflag(asm_flags) + .cflag(c_flags) + .cxxflag(cxx_flags) + .env("IDF_PATH", &esp_idf_dir) + .env("PATH", &paths) + .env("SDKCONFIG", sdkconfig) + .env("SDKCONFIG_DEFAULTS", sdkconfig_defaults) + .env("IDF_TARGET", &chip.to_string()) + .build(); + + let replies = query.get_replies()?; + let target = replies + .get_codemodel()? + .into_first_conf() + .get_target("libespidf.elf") + .unwrap_or_else(|| { + bail!("Could not read build information from cmake: Target 'libespidf.elf' not found",) + })?; + + let compiler = replies + .get_toolchains() + .and_then(|mut t| { + t.take(Language::C) + .ok_or_else(|| Error::msg("No C toolchain")) + }) + .and_then(|t| { + t.compiler + .path + .ok_or_else(|| Error::msg("No compiler path set")) + }) + .context("Could not determine the compiler from cmake")?; + + let header_file = path_buf![&manifest_dir, "src", "include", "esp-idf", "bindings.h"]; + + bindgen::run( + bindgen::Factory::from_cmake(&target.compile_groups[0])? + .with_linker(&compiler) + .builder()? + .ctypes_prefix("c_types") + .header(header_file.try_to_str()?) + .blacklist_function("strtold") + .blacklist_function("_strtold_r") + .clang_args(["-target", chip.clang_target()]), + )?; + + // Output the exact ESP32 MCU, so that we and crates depending directly on us can branch using e.g. #[cfg(esp32xxx)] + cargo::set_rustc_cfg(chip, ""); + cargo::set_metadata("MCU", chip); + + build::LinkArgsBuilder::try_from(&target.link)? + .linker(&compiler) + .working_directory(&cmake_build_dir) + .force_ldproxy(true) + .build() + .propagate(); + + // In case other SYS crates need to have access to the ESP-IDF C headers + build::CInclArgs::try_from(&target.compile_groups[0])?.propagate(); + + let sdkconfig_json = path_buf![&cmake_build_dir, "config", "sdkconfig.json"]; + let cfgs = kconfig::CfgArgs::try_from_json(&sdkconfig_json) + .with_context(|| anyhow!("Failed to read '{:?}'", sdkconfig_json))?; + cfgs.propagate("ESP_IDF"); + cfgs.output("ESP_IDF"); + + Ok(()) +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, EnumString)] +#[repr(u32)] +pub enum Chip { + /// Xtensa LX7 base dual core + #[strum(serialize = "esp32")] + ESP32 = 0, + /// Xtensa LX7 based single core + #[strum(serialize = "esp32s2")] + ESP32S2, + /// Xtensa LX7 based single core + #[strum(serialize = "esp32s3")] + ESP32S3, + /// RISC-V based single core + #[strum(serialize = "esp32c3")] + ESP32C3, +} + +impl Chip { + pub fn detect(target_triple: &str) -> Result { + if target_triple.starts_with("xtensa-esp") { + if target_triple.contains("esp32s3") { + return Ok(Chip::ESP32S3); + } else if target_triple.contains("esp32s2") { + return Ok(Chip::ESP32S2); + } else { + return Ok(Chip::ESP32); + } + } else if target_triple.starts_with("riscv32imac-esp") { + return Ok(Chip::ESP32C3); + } + bail!("Unsupported target '{}'", target_triple) + } + + pub fn target_triple(self) -> &'static str { + match self { + Self::ESP32 => "xtensa-esp32-elf", + Self::ESP32S2 => "xtensa-esp32s2-elf", + Self::ESP32S3 => "xtensa-esp32s3-elf", + Self::ESP32C3 => "riscv32-esp-elf", + } + } + + pub fn ulp_tool(self) -> Option<&'static str> { + match self { + Self::ESP32 => Some("esp32ulp-elf"), + Self::ESP32S2 => Some("esp32s2ulp-elf"), + _ => None, + } + } + + pub fn cmake_toolchain_file(self) -> String { + format!("toolchain-{}.cmake", self) + } + + pub fn clang_target(self) -> &'static str { + match self { + Self::ESP32 | Self::ESP32S2 | Self::ESP32S3 => "xtensa", + Self::ESP32C3 => "riscv32", + } + } +} diff --git a/build_pio.rs b/build_pio.rs new file mode 100644 index 00000000000..47caa8c115e --- /dev/null +++ b/build_pio.rs @@ -0,0 +1,136 @@ +use std::convert::TryFrom; +use std::{env, path::PathBuf}; + +use anyhow::*; + +use embuild::bindgen; +use embuild::build; +use embuild::cargo; +use embuild::kconfig; +use embuild::pio; +use embuild::pio::project; + +pub fn main() -> Result<()> { + let pio_scons_vars = if let Some(pio_scons_vars) = project::SconsVariables::from_piofirst() { + println!("cargo:info=PIO->Cargo build detected: generating bindings only"); + + pio_scons_vars + } else { + let pio = pio::Pio::install_default()?; + + let resolution = pio::Resolver::new(pio.clone()) + .params(pio::ResolutionParams { + platform: Some("espressif32".into()), + frameworks: vec!["espidf".into()], + target: Some(env::var("TARGET")?), + ..Default::default() + }) + .resolve(true)?; + + let mut builder = + project::Builder::new(PathBuf::from(env::var("OUT_DIR")?).join("esp-idf")); + + builder + .enable_scons_dump() + .enable_c_entry_points() + .options(build::env_options_iter("ESP_IDF_SYS_PIO_CONF")?) + .files(build::tracked_globs_iter( + PathBuf::from("."), + &["patches/**"], + )?) + .files(build::tracked_env_globs_iter("ESP_IDF_SYS_GLOB")?); + + #[cfg(feature = "espidf_master")] + builder + .platform_package( + "framework-espidf", + "https://github.com/ivmarkov/esp-idf.git#master", + ) + .platform_package_patch( + PathBuf::from("patches").join("master_missing_xtensa_atomics_fix.diff"), + PathBuf::from("framework-espidf"), + ) + .platform_package_patch( + PathBuf::from("patches").join("ping_setsockopt_fix.diff"), + PathBuf::from("framework-espidf"), + ); + + #[cfg(feature = "espidf_master")] + env::set_var("IDF_MAINTAINER", "1"); + + #[cfg(not(feature = "espidf_master"))] + builder + .platform_package_patch( + PathBuf::from("patches").join("pthread_destructor_fix.diff"), + PathBuf::from("framework-espidf"), + ) + .platform_package_patch( + PathBuf::from("patches").join("ping_setsockopt_fix.diff"), + PathBuf::from("framework-espidf"), + ) + .platform_package_patch( + PathBuf::from("patches").join("missing_xtensa_atomics_fix.diff"), + PathBuf::from("framework-espidf"), + ); + + let project_path = builder.generate(&resolution)?; + + pio.build(&project_path, env::var("PROFILE")? == "release")?; + + let pio_scons_vars = project::SconsVariables::from_dump(&project_path)?; + + build::LinkArgsBuilder::try_from(&pio_scons_vars)? + .build() + .propagate(); + + pio_scons_vars + }; + + // In case other SYS crates need to have access to the ESP-IDF C headers + build::CInclArgs::try_from(&pio_scons_vars)?.propagate(); + + let cfg_args = kconfig::CfgArgs::try_from( + pio_scons_vars + .project_dir + .join(if pio_scons_vars.release_build { + "sdkconfig.release" + } else { + "sdkconfig.debug" + }) + .as_path(), + )?; + + cfg_args.propagate("ESP_IDF"); + cfg_args.output("ESP_IDF"); + + let mcu = pio_scons_vars.mcu.as_str(); + + // Output the exact ESP32 MCU, so that we and crates depending directly on us can branch using e.g. #[cfg(esp32xxx)] + println!("cargo:rustc-cfg={}", mcu); + println!("cargo:MCU={}", mcu); + + let header = PathBuf::from("src") + .join("include") + .join(if mcu == "esp8266" { + "esp-8266-rtos-sdk" + } else { + "esp-idf" + }) + .join("bindings.h"); + + cargo::track_file(&header); + + bindgen::run( + bindgen::Factory::from_scons_vars(&pio_scons_vars)? + .builder()? + .ctypes_prefix("c_types") + .header(header.to_string_lossy()) + .blacklist_function("strtold") + .blacklist_function("_strtold_r") + .clang_args(if mcu == "esp32c3" { + vec!["-target", "riscv32"] + } else { + vec![] + }), + ) +} diff --git a/resources/cmake_project/CMakeLists.txt b/resources/cmake_project/CMakeLists.txt new file mode 100644 index 00000000000..10d70d3953e --- /dev/null +++ b/resources/cmake_project/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.5) + +set(SDKCONFIG $ENV{SDKCONFIG}) +set(SDKCONFIG_DEFAULTS $ENV{SDKCONFIG_DEFAULTS}) + +include($ENV{IDF_PATH}/tools/cmake/idf.cmake) +project(libespidf C) + +idf_build_process($ENV{IDF_TARGET}) +idf_build_get_property(aliases BUILD_COMPONENT_ALIASES) + +add_executable(libespidf.elf main.c) +target_link_libraries(libespidf.elf PUBLIC "${aliases}") +idf_build_executable(libespidf.elf) \ No newline at end of file diff --git a/resources/cmake_project/main.c b/resources/cmake_project/main.c new file mode 100644 index 00000000000..3b5b685b994 --- /dev/null +++ b/resources/cmake_project/main.c @@ -0,0 +1 @@ +void app_main() {} \ No newline at end of file From 993c53f7a330477aef22e60edffe1392e4bab3c3 Mon Sep 17 00:00:00 2001 From: Dominik Gschwind Date: Mon, 6 Sep 2021 20:17:28 +0200 Subject: [PATCH 2/8] Remove main wrapper --- build_native.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/build_native.rs b/build_native.rs index c6b26029c67..1815fac0379 100644 --- a/build_native.rs +++ b/build_native.rs @@ -63,14 +63,6 @@ fn esp_idf_version() -> git::Ref { } pub fn main() -> Result<()> { - if let Err(err) = run() { - println!("{:#}", err); - assert!(false); - } - Ok(()) -} - -pub fn run() -> Result<()> { let out_dir = path_buf![env::var("OUT_DIR")?]; let target = env::var("TARGET")?; let workspace_dir = out_dir.pop_times(6); From 2ab9ec4890eb6e28357a1fadb3ddb77aae168923 Mon Sep 17 00:00:00 2001 From: Dominik Gschwind Date: Tue, 7 Sep 2021 18:31:38 +0200 Subject: [PATCH 3/8] Fix patches not getting applied, use correct patches --- build_native.rs | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/build_native.rs b/build_native.rs index 1815fac0379..ddb54d7fd0b 100644 --- a/build_native.rs +++ b/build_native.rs @@ -28,13 +28,13 @@ const DEFAULT_ESP_IDF_REPOSITORY: &str = "https://github.com/espressif/esp-idf.g const DEFAULT_ESP_IDF_VERSION: &str = "v4.3"; const STABLE_PATCHES: &[&str] = &[ - "master_missing_xtensa_atomics_fix.diff", - "ping_setsockopt_fix.diff", + "patches/missing_xtensa_atomics_fix.diff", + "patches/pthread_destructor_fix.diff", + "patches/ping_setsockopt_fix.diff", ]; const MASTER_PATCHES: &[&str] = &[ - "pthread_destructor_fix.diff", - "ping_setsockopt_fix.diff", - "missing_xtensa_atomics_fix.diff", + "patches/master_missing_xtensa_atomics_fix.diff", + "patches/ping_setsockopt_fix.diff", ]; fn esp_idf_version() -> git::Ref { @@ -92,22 +92,30 @@ pub fn main() -> Result<()> { env::var(ESP_IDF_REPOSITORY_VAR).unwrap_or(DEFAULT_ESP_IDF_REPOSITORY.to_owned()); let mut esp_idf = Repository::new(&esp_idf_dir); - let esp_idf_modified = esp_idf.clone_ext( + esp_idf.clone_ext( &esp_idf_repo, CloneOptions::new() .force_ref(esp_idf_version.clone()) .depth(1), )?; - // Apply patches, only if `clone_ext` changed the repo - if esp_idf_modified { - match esp_idf_version { - git::Ref::Branch(b) if esp_idf.get_default_branch()?.as_ref() == Some(&b) => { - esp_idf.apply(MASTER_PATCHES)? - } - git::Ref::Tag(t) if t == DEFAULT_ESP_IDF_VERSION => esp_idf.apply(STABLE_PATCHES)?, - _ => (), - }; + // Apply patches, only if the patches were not previously applied. + let patch_set = match esp_idf_version { + git::Ref::Branch(b) if esp_idf.get_default_branch()?.as_ref() == Some(&b) => { + &MASTER_PATCHES[..] + } + git::Ref::Tag(t) if t == DEFAULT_ESP_IDF_VERSION => &STABLE_PATCHES[..], + _ => { + cargo::print_warning(format_args!( + "`esp-idf` version ({:?}) not officially supported by `esp-idf-sys`. \ + Supported versions are 'master', '{}'.", + &esp_idf_version, DEFAULT_ESP_IDF_VERSION + )); + &[] + } + }; + if !patch_set.is_empty() { + esp_idf.apply_once(patch_set.iter().map(|p| manifest_dir.join(p)))?; } // This is a workaround for msys or even git bash. From 4d4f71255c15145962f968db909839bfcc2f18d0 Mon Sep 17 00:00:00 2001 From: Dominik Gschwind Date: Tue, 7 Sep 2021 18:32:58 +0200 Subject: [PATCH 4/8] Clarify some `Chip` methods, fix python virtualenv path Use correct target name for `riscv32`. --- build_native.rs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/build_native.rs b/build_native.rs index ddb54d7fd0b..1ab50298e09 100644 --- a/build_native.rs +++ b/build_native.rs @@ -151,16 +151,21 @@ pub fn main() -> Result<()> { } else { python_env_dir.unwrap() }.into(); + + // TODO: better way to get the virtualenv python executable let python = embuild::which::which_in( "python", + #[cfg(windows)] Some(&python_env_dir.join("Scripts")), + #[cfg(not(windows))] + Some(&python_env_dir.join("bin")), env::current_dir()?, )?; // Install tools. - let chip_target_triple = chip.target_triple(); + let chip_target_triple = chip.gcc_toolchain(); let mut tools = vec!["ninja", chip_target_triple]; - tools.extend(chip.ulp_tool().iter()); + tools.extend(chip.ulp_gcc_toolchain().iter()); cmd!(python, &idf_tools_py, "--idf-path", &esp_idf_dir, "--non-interactive", "install"; env=("IDF_TOOLS_PATH", &sdk_dir), args=(tools))?; // Intall extra tools if requested, but don't fail compilation if this errors @@ -354,22 +359,23 @@ pub enum Chip { } impl Chip { - pub fn detect(target_triple: &str) -> Result { - if target_triple.starts_with("xtensa-esp") { - if target_triple.contains("esp32s3") { + pub fn detect(rust_target_triple: &str) -> Result { + if rust_target_triple.starts_with("xtensa-esp") { + if rust_target_triple.contains("esp32s3") { return Ok(Chip::ESP32S3); - } else if target_triple.contains("esp32s2") { + } else if rust_target_triple.contains("esp32s2") { return Ok(Chip::ESP32S2); } else { return Ok(Chip::ESP32); } - } else if target_triple.starts_with("riscv32imac-esp") { + } else if rust_target_triple.starts_with("riscv32imc-esp") { return Ok(Chip::ESP32C3); } - bail!("Unsupported target '{}'", target_triple) + bail!("Unsupported target '{}'", rust_target_triple) } - pub fn target_triple(self) -> &'static str { + /// The name of the gcc toolchain (to compile the `esp-idf`) for `idf_tools.py`. + pub fn gcc_toolchain(self) -> &'static str { match self { Self::ESP32 => "xtensa-esp32-elf", Self::ESP32S2 => "xtensa-esp32s2-elf", @@ -378,7 +384,9 @@ impl Chip { } } - pub fn ulp_tool(self) -> Option<&'static str> { + /// The name of the gcc toolchain for the ultra low-power co-processor for + /// `idf_tools.py`. + pub fn ulp_gcc_toolchain(self) -> Option<&'static str> { match self { Self::ESP32 => Some("esp32ulp-elf"), Self::ESP32S2 => Some("esp32s2ulp-elf"), From da44177dde3220f936e0252d91858787534af0d7 Mon Sep 17 00:00:00 2001 From: Dominik Gschwind Date: Wed, 8 Sep 2021 20:35:24 +0200 Subject: [PATCH 5/8] Fix pio build, tidy code Fix potentially using `:` to split PATH on windows. --- Cargo.toml | 4 ++-- build.rs | 8 +++---- build_native.rs | 62 +++++++++++++++++++++++++++++++++++++------------ build_pio.rs | 6 ++--- 4 files changed, 56 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 82751e88d6c..ec93d4af8aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ default = ["std", "embedded-svc/std", "native"] std = [] # Use `platformio` to build the `esp-idf` -platformio = [] +pio = [] # Use native `esp-idf` tooling to build it native = ["strum"] @@ -32,5 +32,5 @@ paste = "1" [build-dependencies] embuild = "0.23" -anyhow = {version = "1", features = ["backtrace"]} +anyhow = { version = "1" } strum = { version = "0.21", optional = true, features = ["derive"] } diff --git a/build.rs b/build.rs index 325cd7a3174..d89ce67f96c 100644 --- a/build.rs +++ b/build.rs @@ -1,11 +1,11 @@ #[cfg(not(any(feature = "pio", feature = "native")))] -compile_error!("One of the features `platformio` or `native` must be selected."); +compile_error!("One of the features `pio` or `native` must be selected."); #[cfg(all(feature = "pio", feature = "native"))] -compile_error!("The features `platformio` and `native` are mutually exclusive. Only one of them can be selected at a time."); +compile_error!("The features `pio` and `native` are mutually exclusive. Only one of them can be selected at a time."); -#[cfg(any(feature = "platformio", feature = "native"))] -#[cfg_attr(feature = "platformio", path = "build_pio.rs")] +#[cfg(any(feature = "pio", feature = "native"))] +#[cfg_attr(feature = "pio", path = "build_pio.rs")] #[cfg_attr(feature = "native", path = "build_native.rs")] mod build_impl; diff --git a/build_native.rs b/build_native.rs index 1ab50298e09..e1593dac02e 100644 --- a/build_native.rs +++ b/build_native.rs @@ -1,3 +1,27 @@ +/*! +Build the esp-idf natively using cmake. + +### Configuration +Envoronment variables are used to configure how the esp-idf is compiled. +The following environment variables are used by the build script: + +- `SDK_DIR`: The path to the directory where all esp-idf tools are installed. + Defaults to `.sdk`. +- `ESP_IDF_VERSION`: + The version used for the `esp-idf` can be one of the following: + - `commit:`: Uses the commit `` of the `esp-idf` repository. + Note that this will clone the whole `esp-idf` not just one commit. + - `tag:`: Uses the tag `` of the `esp-idf` repository. + - `branch:`: Uses the branch `` of the `esp-idf` repository. + - `v.` or `.`: Uses the tag `v.` of the `esp-idf` repository. + - ``: Uses the branch `` of the `esp-idf` repository. +- `ESP_IDF_REPOSITORY`: The URL to the git repository of the `esp-idf`. +- `ESP_IDF_SDKCONFIG_DEFAULTS` +- `ESP_IDF_SDKCONFIG` +- `ESP_IDF_EXTRA_TOOLS` +- `MCU` +*/ + use std::convert::TryFrom; use std::env; use std::ffi::OsString; @@ -122,21 +146,28 @@ pub fn main() -> Result<()> { // When using them `idf_tools.py` prints unix paths (ex. `/c/user/` instead of // `C:\user\`), so we correct this with an invocation of `cygpath` which converts the // path to the windows representation. - let to_win_path = if cfg!(windows) && cmd_output!("cygpath", "--version").is_ok() { + let cygpath_works = cfg!(windows) && cmd_output!("cygpath", "--version").is_ok(); + let to_win_path = if cygpath_works { |p: String| cmd_output!("cygpath", "-w", p).unwrap().to_string() } else { |p: String| p }; + let path_var_sep = if cygpath_works || cfg!(not(windows)) { + ':' + } else { + ';' + }; - // Create python virtualenv. + // Create python virtualenv or use a previously installed one. check_python_at_least(3, 0)?; let idf_tools_py = path_buf![&esp_idf_dir, "tools", "idf_tools.py"]; let get_python_env_dir = || -> Result { - Ok(cmd_output!(PYTHON, &idf_tools_py, "--idf-path", &esp_idf_dir, "--quiet", "export", "--format=key-value"; ignore_exitcode, env=("IDF_TOOLS_PATH", &sdk_dir)) + Ok(cmd_output!(PYTHON, &idf_tools_py, "--idf-path", &esp_idf_dir, "--quiet", "export", "--format=key-value"; + ignore_exitcode, env=("IDF_TOOLS_PATH", &sdk_dir)) .lines() .find(|s| s.trim_start().starts_with("IDF_PYTHON_ENV_PATH=")) - .ok_or(anyhow!("`idf_tools.py export` result contains no `IDF_PYTHON_ENV_PATH` item."))? + .ok_or(anyhow!("`idf_tools.py export` result contains no `IDF_PYTHON_ENV_PATH` item"))? .trim() .strip_prefix("IDF_PYTHON_ENV_PATH=").unwrap() .to_string()) @@ -146,12 +177,13 @@ pub fn main() -> Result<()> { let python_env_dir: PathBuf = if python_env_dir.is_err() || !Path::new(&python_env_dir.as_ref().unwrap()).exists() { - cmd!(PYTHON, &idf_tools_py, "--idf-path", &esp_idf_dir, "--quiet", "--non-interactive", "install-python-env"; env=("IDF_TOOLS_PATH", &sdk_dir))?; + cmd!(PYTHON, &idf_tools_py, "--idf-path", &esp_idf_dir, "--quiet", "--non-interactive", "install-python-env"; + env=("IDF_TOOLS_PATH", &sdk_dir))?; to_win_path(get_python_env_dir()?) } else { python_env_dir.unwrap() }.into(); - + // TODO: better way to get the virtualenv python executable let python = embuild::which::which_in( "python", @@ -163,15 +195,14 @@ pub fn main() -> Result<()> { )?; // Install tools. - let chip_target_triple = chip.gcc_toolchain(); - let mut tools = vec!["ninja", chip_target_triple]; + let mut tools = vec!["ninja", chip.gcc_toolchain()]; tools.extend(chip.ulp_gcc_toolchain().iter()); - cmd!(python, &idf_tools_py, "--idf-path", &esp_idf_dir, "--non-interactive", "install"; env=("IDF_TOOLS_PATH", &sdk_dir), args=(tools))?; + cmd!(python, &idf_tools_py, "--idf-path", &esp_idf_dir, "install"; env=("IDF_TOOLS_PATH", &sdk_dir), args=(tools))?; // Intall extra tools if requested, but don't fail compilation if this errors if let Some(extra_tools) = env::var_os(ESP_IDF_EXTRA_TOOLS_VAR) { cmd!( - python, &idf_tools_py, "--idf-path", &esp_idf_dir, "--non-interactive", "install"; + python, &idf_tools_py, "--idf-path", &esp_idf_dir, "install"; args=(extra_tools.to_string_lossy().split(';').filter(|s| !s.is_empty()).map(str::trim)), env=("IDF_TOOLS_PATH", &sdk_dir) ) @@ -179,12 +210,13 @@ pub fn main() -> Result<()> { } // Get the paths to the tools. - let mut bin_paths: Vec<_> = cmd_output!(python, &idf_tools_py, "--idf-path", &esp_idf_dir, "--quiet", "export", "--format=key-value"; ignore_exitcode, env=("IDF_TOOLS_PATH", &sdk_dir)) + let mut bin_paths: Vec<_> = cmd_output!(python, &idf_tools_py, "--idf-path", &esp_idf_dir, "--quiet", "export", "--format=key-value"; + ignore_exitcode, env=("IDF_TOOLS_PATH", &sdk_dir)) .lines() .find(|s| s.trim_start().starts_with("PATH=")) - .expect("`idf_tools.py export` result contains no `PATH` item.").trim() + .expect("`idf_tools.py export` result contains no `PATH` item").trim() .strip_prefix("PATH=").unwrap() - .split(if cfg!(windows) { &[';', ':'][..] } else { &[':'][..] }) + .split(path_var_sep) .map(|s| s.to_owned()) .collect(); bin_paths.pop(); @@ -322,11 +354,11 @@ pub fn main() -> Result<()> { cargo::set_rustc_cfg(chip, ""); cargo::set_metadata("MCU", chip); - build::LinkArgsBuilder::try_from(&target.link)? + build::LinkArgsBuilder::try_from(&target.link.unwrap())? .linker(&compiler) .working_directory(&cmake_build_dir) .force_ldproxy(true) - .build() + .build()? .propagate(); // In case other SYS crates need to have access to the ESP-IDF C headers diff --git a/build_pio.rs b/build_pio.rs index 47caa8c115e..bb8dd0f78b6 100644 --- a/build_pio.rs +++ b/build_pio.rs @@ -80,7 +80,7 @@ pub fn main() -> Result<()> { let pio_scons_vars = project::SconsVariables::from_dump(&project_path)?; build::LinkArgsBuilder::try_from(&pio_scons_vars)? - .build() + .build()? .propagate(); pio_scons_vars @@ -106,8 +106,8 @@ pub fn main() -> Result<()> { let mcu = pio_scons_vars.mcu.as_str(); // Output the exact ESP32 MCU, so that we and crates depending directly on us can branch using e.g. #[cfg(esp32xxx)] - println!("cargo:rustc-cfg={}", mcu); - println!("cargo:MCU={}", mcu); + cargo::set_rustc_cfg(mcu, ""); + cargo::set_metadata("MCU", mcu); let header = PathBuf::from("src") .join("include") From 3e1c2b51e61ec011da29e72b748b971db5a344cf Mon Sep 17 00:00:00 2001 From: Dominik Gschwind Date: Thu, 9 Sep 2021 21:28:11 +0200 Subject: [PATCH 6/8] Add documentation to README --- README.md | 62 ++++++++++++++++++++++++++++++++++++++++++++----- build_native.rs | 24 +------------------ 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 46a66d98452..4310a30d05d 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,20 @@ The ESP-IDF API in Rust, with support for each ESP chip (ESP32, ESP32S2, ESP32C3 ## Build -- The build requires the [Rust ESP32 STD compiler fork](https://github.com/ivmarkov/rust) to be configured and installed as per the instructions there. -- The relevant Espressif toolchain, as well as the ESP-IDF itself are all automatically downloaded during the build by utilizing the [cargo-pio](https://github.com/ivmarkov/cargo-pio) library crate. +- The build requires the [Rust ESP32 STD compiler fork](https://github.com/esp-rs/rust) to be configured and installed as per the instructions there. +- The relevant Espressif toolchain, as well as the `esp-idf` itself are all automatically + downloaded during the build by + - with the feature `pio` (default): utilizing [platformio](https://platformio.org/) (via + the [embuild](https://github.com/ivmarkov/embuild) crate) or + - with the feature `native` (*experimental*): utilizing native `esp-idf` tooling. - Check the ["Hello, World" demo](https://github.com/ivmarkov/rust-esp32-std-hello) for how to use and build this crate -## Bluetooth Support +## Feature `platformio` +This is currently the default for installing all build tools and building the `esp-idf` C +library. It uses [platformio](https://platformio.org/) via the +[embuild](https://github.com/ivmarkov/embuild) crate. + +### Bluetooth Support In order to enable Bluetooth support with either Bluedroid or NimBLE, there is some additional work: * Go to the root of your **binary crate** project (e.g., the ["Hello, World" demo](https://github.com/ivmarkov/rust-esp32-std-hello)) @@ -32,14 +41,55 @@ CONFIG_BTDM_CTRL_MODE_BTDM=n //CONFIG_BT_NIMBLE_ENABLED=y ``` -## Using cargo-pio to interactively modify ESP-IDF's `sdkconfig` file +### Using cargo-pio to interactively modify ESP-IDF's `sdkconfig` file To enable Bluetooth, or do other configurations to the ESP-IDF sdkconfig you might take advantage of the cargo-pio Cargo subcommand: * To install it, issue `cargo install cargo-pio --git https://github.com/ivmarkov/cargo-pio` * To open the ESP-IDF interactive menuconfig system, issue `cargo pio espidf menuconfig` in the root of your **binary crate** project * To use the generated/updated `sdkconfig` file, follow the steps described in the "Bluetooth Support" section -## More info +### More info + +If you are interested how it all works under the hood, check the [build_pio.rs](build.rs) +or script of this crate. + + +## Experimental feature `native` + +Download all tools and build the `esp-idf` using its own tooling. + +**Warning**: This is an experimental feature and subject to changes. + +Currently, this build script installs all needed tools to compile the `esp-idf` as well as the `esp-idf` itself +under `/.espressif`, this is subject to change in the future. + +### Requirements +- If using chips other than `esp32c3`: + - [Rust ESP32 compiler fork](https://github.com/esp-rs/rust) + - [libclang of the xtensa LLVM fork](https://github.com/espressif/llvm-project/releases) +- `python >= 3.7` +- `cmake >= 3.20` + +### Configuration +Environment variables are used to configure how the `esp-idf` is compiled. +The following environment variables are used by the build script: -If you are interested how it all works under the hood, check the [build.rs](https://github.com/ivmarkov/esp-idf-sys/blob/master/build.rs) script of this crate. +- `SDK_DIR`: The path to the directory where all esp-idf tools are installed, + defaults to `/.espressif`. +- `ESP_IDF_VERSION`: + The version used for the `esp-idf` can be one of the following: + - `commit:`: Uses the commit `` of the `esp-idf` repository. + Note that this will clone the whole `esp-idf` not just one commit. + - `tag:`: Uses the tag `` of the `esp-idf` repository. + - `branch:`: Uses the branch `` of the `esp-idf` repository. + - `v.` or `.`: Uses the tag `v.` of the `esp-idf` repository. + - ``: Uses the branch `` of the `esp-idf` repository. + It defaults to `v4.3`. +- `ESP_IDF_REPOSITORY`: The URL to the git repository of the `esp-idf`, defaults to . +- `ESP_IDF_SDKCONFIG_DEFAULTS`: A `;`-seperated list of paths to `sdkconfig.default` files to be used as base + values for the `sdkconfig`. +- `ESP_IDF_SDKCONFIG`: A path (absolute or relative) to the esp-idf `sdkconfig` file. +- `ESP_IDF_EXTRA_TOOLS`: A `;`-seperated list of additional tools to install with `idf_tools.py`. +- `MCU`: The mcu name (e.g. `esp32` or `esp32c3`). If not set this will be automatically detected from the + cargo target. \ No newline at end of file diff --git a/build_native.rs b/build_native.rs index e1593dac02e..3a16033170e 100644 --- a/build_native.rs +++ b/build_native.rs @@ -1,26 +1,4 @@ -/*! -Build the esp-idf natively using cmake. - -### Configuration -Envoronment variables are used to configure how the esp-idf is compiled. -The following environment variables are used by the build script: - -- `SDK_DIR`: The path to the directory where all esp-idf tools are installed. - Defaults to `.sdk`. -- `ESP_IDF_VERSION`: - The version used for the `esp-idf` can be one of the following: - - `commit:`: Uses the commit `` of the `esp-idf` repository. - Note that this will clone the whole `esp-idf` not just one commit. - - `tag:`: Uses the tag `` of the `esp-idf` repository. - - `branch:`: Uses the branch `` of the `esp-idf` repository. - - `v.` or `.`: Uses the tag `v.` of the `esp-idf` repository. - - ``: Uses the branch `` of the `esp-idf` repository. -- `ESP_IDF_REPOSITORY`: The URL to the git repository of the `esp-idf`. -- `ESP_IDF_SDKCONFIG_DEFAULTS` -- `ESP_IDF_SDKCONFIG` -- `ESP_IDF_EXTRA_TOOLS` -- `MCU` -*/ +//! Install tools and build the `esp-idf` using native tooling. use std::convert::TryFrom; use std::env; From 1679bb74314af73bca9ee4d94f6651f1876c9e8d Mon Sep 17 00:00:00 2001 From: Dominik Gschwind Date: Thu, 9 Sep 2021 21:58:43 +0200 Subject: [PATCH 7/8] Change DEFAULT_SDK_DIR to `.espressif` Set min python version to 3.7. Set default build feature to pio. Make `native` feature usable. --- Cargo.toml | 6 +++--- build.rs | 10 ++++++---- build_native.rs | 8 ++++---- build_pio.rs | 2 ++ resources/cmake_project/CMakeLists.txt | 2 +- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ec93d4af8aa..195db4783c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,13 +16,13 @@ links = "esp_idf" default-target = "x86_64-unknown-linux-gnu" [features] -default = ["std", "embedded-svc/std", "native"] +default = ["std", "embedded-svc/std", "pio"] std = [] # Use `platformio` to build the `esp-idf` pio = [] -# Use native `esp-idf` tooling to build it +# Experimental: Use native `esp-idf` tooling to build it native = ["strum"] [dependencies] @@ -31,6 +31,6 @@ embedded-svc = "0.8.3" paste = "1" [build-dependencies] -embuild = "0.23" +embuild = "0.23.5" anyhow = { version = "1" } strum = { version = "0.21", optional = true, features = ["derive"] } diff --git a/build.rs b/build.rs index d89ce67f96c..c4eaa860a48 100644 --- a/build.rs +++ b/build.rs @@ -1,12 +1,14 @@ #[cfg(not(any(feature = "pio", feature = "native")))] compile_error!("One of the features `pio` or `native` must be selected."); -#[cfg(all(feature = "pio", feature = "native"))] -compile_error!("The features `pio` and `native` are mutually exclusive. Only one of them can be selected at a time."); - +// 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 = "pio", path = "build_pio.rs")] #[cfg_attr(feature = "native", path = "build_native.rs")] +#[cfg_attr(all(feature = "pio", not(feature = "native")), path = "build_pio.rs")] mod build_impl; fn main() -> anyhow::Result<()> { diff --git a/build_native.rs b/build_native.rs index 3a16033170e..5b1ccfd3b63 100644 --- a/build_native.rs +++ b/build_native.rs @@ -25,7 +25,7 @@ const ESP_IDF_SDKCONFIG_VAR: &str = "ESP_IDF_SDKCONFIG"; const ESP_IDF_EXTRA_TOOLS_VAR: &str = "ESP_IDF_EXTRA_TOOLS"; const MCU_VAR: &str = "MCU"; -const DEFAULT_SDK_DIR: &str = ".sdk"; +const DEFAULT_SDK_DIR: &str = ".espressif"; const DEFAULT_ESP_IDF_REPOSITORY: &str = "https://github.com/espressif/esp-idf.git"; const DEFAULT_ESP_IDF_VERSION: &str = "v4.3"; @@ -87,7 +87,7 @@ pub fn main() -> Result<()> { let sdk_dir = path_buf![env::var(SDK_DIR_VAR).unwrap_or(DEFAULT_SDK_DIR.to_owned())] .abspath_relative_to(&workspace_dir); - // Clone esp-idf. + // Clone the esp-idf. let esp_idf_dir = sdk_dir.join("esp-idf"); let esp_idf_version = esp_idf_version(); let esp_idf_repo = @@ -137,7 +137,7 @@ pub fn main() -> Result<()> { }; // Create python virtualenv or use a previously installed one. - check_python_at_least(3, 0)?; + check_python_at_least(3, 7)?; let idf_tools_py = path_buf![&esp_idf_dir, "tools", "idf_tools.py"]; let get_python_env_dir = || -> Result { @@ -354,7 +354,7 @@ pub fn main() -> Result<()> { #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, EnumString)] #[repr(u32)] pub enum Chip { - /// Xtensa LX7 base dual core + /// Xtensa LX7 based dual core #[strum(serialize = "esp32")] ESP32 = 0, /// Xtensa LX7 based single core diff --git a/build_pio.rs b/build_pio.rs index bb8dd0f78b6..03cd573ca1c 100644 --- a/build_pio.rs +++ b/build_pio.rs @@ -1,3 +1,5 @@ +//! Install tools and build the `esp-idf` using `platformio`. + use std::convert::TryFrom; use std::{env, path::PathBuf}; diff --git a/resources/cmake_project/CMakeLists.txt b/resources/cmake_project/CMakeLists.txt index 10d70d3953e..788c5230100 100644 --- a/resources/cmake_project/CMakeLists.txt +++ b/resources/cmake_project/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.20) set(SDKCONFIG $ENV{SDKCONFIG}) set(SDKCONFIG_DEFAULTS $ENV{SDKCONFIG_DEFAULTS}) From 9027af900ce5a23cfe257ad13800fa51f86bc9bc Mon Sep 17 00:00:00 2001 From: Dominik Gschwind Date: Thu, 9 Sep 2021 22:04:09 +0200 Subject: [PATCH 8/8] Track `sdkconfig` and `sdkconfig_defaults` files. --- build_native.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build_native.rs b/build_native.rs index 5b1ccfd3b63..9679b032f32 100644 --- a/build_native.rs +++ b/build_native.rs @@ -224,9 +224,9 @@ pub fn main() -> Result<()> { let sdkconfig = env::var_os(ESP_IDF_SDKCONFIG_VAR) .filter(|v| !v.is_empty()) .map(|v| { - Path::new(&v) - .abspath_relative_to(&workspace_dir) - .into_os_string() + let path = Path::new(&v).abspath_relative_to(&workspace_dir); + cargo::track_file(&path); + path.into_os_string() }) .unwrap_or_else(|| OsString::new()); @@ -240,6 +240,7 @@ pub fn main() -> Result<()> { .filter(|v| !v.is_empty()) .map(|v| Path::new(v).abspath_relative_to(&workspace_dir)) { + cargo::track_file(&s); if !result.is_empty() { result.push(";"); }