// Copyright Open Logistics Foundation // // Licensed under the Open Logistics Foundation License 1.3. // For details on the licensing terms, see the LICENSE file. // SPDX-License-Identifier: OLFL-1.3 use std::path::PathBuf; use std::{fmt::Write as _, fs::File, io::Write}; struct Config { out_dir: PathBuf, mbedtls_src: PathBuf, mbedtls_include: PathBuf, config_h: PathBuf, cflags: Vec, } fn main() { #[cfg(feature = "flexi_logger")] flexi_logger::Logger::try_with_env_or_str("trace") .unwrap() .start() .unwrap(); let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR not set")); let mbedtls_src = PathBuf::from("3rdparty/mbedtls"); let mbedtls_include = mbedtls_src.join("include"); let config_h = PathBuf::from("config.h").canonicalize().unwrap(); let cflags: Vec = vec![]; let config = Config { out_dir, mbedtls_src, mbedtls_include, config_h, cflags, }; print_rerun_files(&config); run_cmake(&config); run_bindgen(&config); } fn print_rerun_files(conf: &Config) { // Our build depends on the vendored mbedtls source and the config file. Since cargo is smart // enough to scan a full directory // (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed), it is // enough to specify those two components. println!("cargo:rerun-if-changed=config.h"); println!("cargo:rerun-if-changed={}", conf.mbedtls_src.display()); } fn run_cmake(conf: &Config) { let mut cmk = cmake::Config::new(&conf.mbedtls_src); // Set the config.h file path cmk.cflag(format!( r#"-DMBEDTLS_CONFIG_FILE="\"{}\"""#, conf.config_h.to_str().expect("config.h UTF-8 error") )); // Disabling examples, tests and file generation avoids a Python and/or Perl dependency. // Enabling any of these does not seem to offer any benefit for usage from Rust. cmk.define("ENABLE_PROGRAMS", "OFF") .define("ENABLE_TESTING", "OFF") .define("GEN_FILES", "OFF"); cmk.build_target("install"); // Set all additional cflags for cflag in &conf.cflags { cmk.cflag(cflag); } // Configuration required for bare-metal arm targets let target = std::env::var("TARGET") .expect("TARGET environment variable should be set in build scripts"); // thumbv6m-none-eabi, thumbv7em-none-eabi, thumbv7em-none-eabihf, thumbv7m-none-eabi // probably use arm-none-eabi-gcc which can cause the cmake compiler test to fail. if target.starts_with("thumbv") && target.contains("none-eabi") { // When building on Linux, -rdynamic flag is added automatically. Changing the // CMAKE_SYSTEM_NAME to Generic avoids this. cmk.define("CMAKE_SYSTEM_NAME", "Generic"); // The compiler test requires _exit which is not available. By just trying to compile // a library, we can fix it. cmk.define("CMAKE_TRY_COMPILE_TARGET_TYPE", "STATIC_LIBRARY"); } let dst = cmk.build(); // cmake installs the headers to OUT_DIR/include and the static libraries to OUT_DIR/lib. // For some 64bit systems, static libraries are placed under lib64. This is achieved by using // the `GNUInstallDirs` module, see https://stackoverflow.com/a/76528304. // Apparently, it is not easily possible to query the resulting installation directory from the // cmake process. So for simplicity, we just add both "lib" and "lib64" as search paths. let add_library_search_path = |dir| { let mut dst = dst.clone(); dst.push(dir); let library_dir = dst.to_str().expect("link-search UTF-8 error"); println!("cargo:rustc-link-search=native={library_dir}"); }; add_library_search_path("lib"); add_library_search_path("lib64"); println!("cargo:rustc-link-lib=mbedtls"); println!("cargo:rustc-link-lib=mbedx509"); println!("cargo:rustc-link-lib=mbedcrypto"); } fn run_bindgen(conf: &Config) { // List of header files which should be included in the bindgen generation step let header_files = [ // main mbedtls tls connection interface "mbedtls/ssl.h", // counter-mode deterministic random bit genenrator, one of two PRNGs shipped with mbedtls, // requires MBEDTLS_CTR_DRBG_C "mbedtls/ctr_drbg.h", // gives access to the mbedtls_debug_set_threshold() function which must be called to // enable debug output, requires MBEDTLS_DEBUG_C "mbedtls/debug.h", // gives access to the mbedtls_strerror() function to translate mbedtls error codes into a // string representation, requires MBEDTLS_ERROR_C "mbedtls/error.h", ]; // Write the header files to a temporary String let mut header = String::new(); for header_file in header_files { writeln!(header, "#include <{header_file}>").unwrap(); } // Get the currently configured C compiler to pass its flags as clang_args to bindgen let mut cc = cc::Build::new(); // Additionally specified cflags for cflag in &conf.cflags { cc.flag(cflag); } // Include path cc.include(&conf.mbedtls_include); // mbedtls_config.h file path let config_file_path = conf.config_h.to_str().expect("config.h UTF-8 error"); cc.define( "MBEDTLS_CONFIG_FILE", format!(r#""{}""#, config_file_path).as_str(), ); // Determine the sysroot for this compiler so that bindgen uses the correct headers let compiler = cc.get_compiler(); if compiler.is_like_gnu() { let output = compiler.to_command().args(["--print-sysroot"]).output(); match output { Ok(sysroot) if sysroot.status.success() => { let path = std::str::from_utf8(&sysroot.stdout).expect("Malformed sysroot"); let trimmed_path = path .strip_suffix("\r\n") .or(path.strip_suffix('\n')) .unwrap_or(path); cc.flag(&format!("--sysroot={}", trimmed_path)); } _ => {} // Do nothing for toolchains without a configured sysroot }; } // Generate bindings // // Since all public functions/types are properly prefixed with "mbedtls_" or "psa_", we can use // these prefixes as regexes for the allowlists. Additionally, we set the right set of // clang_args according to the current build configuration. These especially contain the // sysroot for (bare-metal) cross-compilation and the MBEDTLS_CONFIG_FILE define. let bindings = bindgen::builder() .enable_function_attribute_detection() .clang_args( cc.get_compiler() .args() .iter() .map(|arg| arg.to_str().unwrap()), ) .header_contents("bindgen-input.h", &header) .allowlist_recursively(false) .use_core() .ctypes_prefix("cty") .default_enum_style(bindgen::EnumVariation::Consts) .generate_comments(false) .derive_copy(true) .derive_debug(true) .derive_default(true) .prepend_enum_name(false) .translate_enum_integer_types(true) .layout_tests(false) .allowlist_function("^(?i)mbedtls_.*") .allowlist_type("^(?i)mbedtls_.*") .allowlist_var("^(?i)mbedtls_.*") .allowlist_function("^(?i)psa_.*") .allowlist_type("^(?i)psa_.*") .allowlist_var("^(?i)psa_.*") .generate() .expect("bindgen failed") .to_string(); // Write generated bindings to bindings.rs which will be included in lib.rs let bindings_rs = conf.out_dir.join("bindings.rs"); File::create(bindings_rs) .expect("Could not create/open {bindings_rs}") .write_all(bindings.as_bytes()) .expect("Could not write bindgen bindings to bindings.rs"); }