bindings/rust/extended/s2n-tls-sys/build.rs (200 lines of code) (raw):

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::path::{Path, PathBuf}; const EXTERNAL_BUILD_CFG_NAME: &str = "s2n_tls_external_build"; fn main() { println!("cargo:rustc-check-cfg=cfg({EXTERNAL_BUILD_CFG_NAME})"); let external = External::default(); if external.is_enabled() { external.link(); } else { build_vendored(); } } fn env<N: AsRef<str>>(name: N) -> String { let name = name.as_ref(); option_env(name).unwrap_or_else(|| panic!("missing env var {name:?}")) } fn option_env<N: AsRef<str>>(name: N) -> Option<String> { let name = name.as_ref(); println!("cargo:rerun-if-env-changed={}", name); std::env::var(name).ok() } struct FeatureDetector<'a> { builder: cc::Build, out_dir: &'a Path, } impl<'a> FeatureDetector<'a> { pub fn new(out_dir: &'a Path, libcrypto: &Libcrypto) -> Self { let builder = builder(libcrypto); Self { builder, out_dir } } pub fn supports(&self, name: &str) -> bool { let mut build = self.builder.get_compiler().to_command(); let global_flags = std::path::Path::new("lib/tests/features/GLOBAL.flags"); assert!( global_flags.exists(), "missing flags file: {:?}", global_flags.display() ); let global_flags = std::fs::read_to_string(global_flags).unwrap(); for flag in global_flags.trim().split(' ').filter(|f| !f.is_empty()) { build.arg(flag); } let base = std::path::Path::new("lib/tests/features").join(name); let file = base.with_extension("c"); assert!(file.exists(), "missing feature file: {:?}", file.display()); let probe_flags = base.with_extension("flags"); assert!( probe_flags.exists(), "missing flags file: {:?}", probe_flags.display() ); let probe_flags = std::fs::read_to_string(probe_flags).unwrap(); for flag in probe_flags.trim().split(' ').filter(|f| !f.is_empty()) { build.arg(flag); } build // just compile the file and don't link .arg("-c") .arg("-o") .arg(self.out_dir.join(name).with_extension("o")) .arg(&file); eprintln!("=== Testing feature {name} ==="); build.status().unwrap().success() } } fn build_vendored() { let libcrypto = Libcrypto::default(); let mut build = builder(&libcrypto); build.files(include!("./files.rs")); // https://doc.rust-lang.org/cargo/reference/environment-variables.html // * OPT_LEVEL, DEBUG — values of the corresponding variables for the profile currently being built. // * PROFILE — release for release builds, debug for other builds. This is determined based on if // the profile inherits from the dev or release profile. Using this environment variable is not // recommended. Using other environment variables like OPT_LEVEL provide a more correct view of // the actual settings being used. if env("OPT_LEVEL") != "0" { build.define("S2N_BUILD_RELEASE", "1"); build.define("NDEBUG", "1"); // build s2n-tls with LTO if supported if build.get_compiler().is_like_gnu() { build .flag_if_supported("-flto") .flag_if_supported("-ffat-lto-objects"); } } let out_dir = PathBuf::from(env("OUT_DIR")); let features = FeatureDetector::new(&out_dir, &libcrypto); let mut feature_names = std::fs::read_dir("lib/tests/features") .expect("missing features directory") .flatten() .filter(|file| { let file = file.path(); file.extension().map_or(false, |ext| ext == "c") }) .map(|file| { file.path() .file_stem() .unwrap() .to_str() .unwrap() .to_string() }) .collect::<Vec<_>>(); feature_names.sort(); for name in &feature_names { let is_supported = features.supports(name); eprintln!("{name}: {is_supported}"); if is_supported { build.define(name, "1"); // stacktraces are only available if execinfo is if name == "S2N_EXECINFO_AVAILABLE" && option_env("CARGO_FEATURE_STACKTRACE").is_some() { build.define("S2N_STACKTRACE", "1"); } } } // don't spit out a bunch of warnings to the end user, since they won't really be able // to do anything with it build.warnings(false); build.compile("s2n-tls"); // linking to the libcrypto is handled by the rust compiler through the // `extern crate aws_lc_rs as _;` statement included in the generated source // files. This is less brittle than manually linking the libcrypto artifact. // let consumers know where to find our header files let include_dir = out_dir.join("include"); std::fs::create_dir_all(&include_dir).unwrap(); std::fs::copy("lib/api/s2n.h", include_dir.join("s2n.h")).unwrap(); println!("cargo:include={}", include_dir.display()); } fn builder(libcrypto: &Libcrypto) -> cc::Build { let mut build = cc::Build::new(); let includes = [&libcrypto.include, "lib", "lib/api"]; if let Ok(cflags) = std::env::var("CFLAGS") { // cc will read the CFLAGS env variable and prepend the compiler // command with all flags and includes from it, which may conflict // with the includes we specify. To ensure that our includes show // up first in the compiler command, we prepend them to CFLAGS. std::env::set_var("CFLAGS", format!("-I {} {}", includes.join(" -I "), cflags)); } else { build.includes(includes); }; build .flag("-include") .flag("lib/utils/s2n_prelude.h") .flag("-std=c11") .flag("-fgnu89-inline") // make sure the stack is non-executable .flag_if_supported("-z relro") .flag_if_supported("-z now") .flag_if_supported("-z noexecstack") // we use some deprecated libcrypto features so don't warn here .flag_if_supported("-Wno-deprecated-declarations") .flag_if_supported("-Wa,-mbranches-within-32B-boundaries"); build } #[derive(PartialEq, Eq, PartialOrd, Ord)] struct Libcrypto { version: String, link: String, include: String, root: String, } impl Default for Libcrypto { fn default() -> Self { for (name, value) in std::env::vars() { if let Some(version) = name.strip_prefix("DEP_AWS_LC_") { if let Some(version) = version.strip_suffix("_INCLUDE") { let version = version.to_string(); println!("cargo:rerun-if-env-changed={}", name); let include = value; let root = env(format!("DEP_AWS_LC_{version}_ROOT")); let link = env(format!("DEP_AWS_LC_{version}_LIBCRYPTO")); return Self { version, link, include, root, }; } } } panic!("missing DEP_AWS_LC paths"); } } struct External { lib_dir: Option<PathBuf>, include_dir: Option<PathBuf>, } impl Default for External { fn default() -> Self { let dir = option_env("S2N_TLS_DIR").map(PathBuf::from); let lib_dir = option_env("S2N_TLS_LIB_DIR") .map(PathBuf::from) .or_else(|| dir.as_ref().map(|d| d.join("lib"))); let include_dir = option_env("S2N_TLS_INCLUDE_DIR") .map(PathBuf::from) .or_else(|| dir.as_ref().map(|d| d.join("include"))); Self { lib_dir, include_dir, } } } impl External { fn is_enabled(&self) -> bool { self.lib_dir.is_some() } fn link(&self) { println!("cargo:rustc-cfg={EXTERNAL_BUILD_CFG_NAME}"); // Propagate an external build flag to dependents, of the form // `DEP_S2N_TLS_EXTERNAL_BUILD=true`. println!("cargo:external_build=true"); println!( "cargo:rustc-link-search={}", self.lib_dir.as_ref().unwrap().display() ); println!("cargo:rustc-link-lib=s2n"); // tell rust we're linking with libcrypto println!("cargo:rustc-link-lib=crypto"); if let Some(include_dir) = self.include_dir.as_ref() { println!("cargo:include={}", include_dir.display()); } } }