use crate::cc_builder::CcBuilder;
use crate::OutputLib::{Crypto, Ssl};
use crate::{
allow_prebuilt_nasm, cargo_env, disable_jitter_entropy, effective_target, emit_warning,
execute_command, get_crate_cflags, is_crt_static, is_fips_build, is_no_asm,
is_no_pregenerated_src, optional_env, optional_env_optional_crate_target, sanitizer, set_env,
set_env_for_target, target_arch, target_env, target_os, test_clang_cl_command,
test_nasm_command, use_prebuilt_nasm, OutputLibType,
};
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::str::FromStr;
fn strip_lto_flags(cflags: &str) -> String {
cflags
.split_whitespace()
.filter(|flag| {
!flag.starts_with("-flto")
&& *flag != "-ffat-lto-objects"
&& *flag != "-fno-fat-lto-objects"
})
.collect::<Vec<_>>()
.join(" ")
}
pub(crate) struct CmakeBuilder {
manifest_dir: PathBuf,
out_dir: PathBuf,
build_prefix: Option<String>,
output_lib_type: OutputLibType,
}
fn test_prebuilt_nasm_script(script_path: &Path) -> bool {
execute_command(script_path.as_os_str(), &[]).executed
}
fn find_cmake_command() -> Option<OsString> {
if let Some(cmake) = optional_env_optional_crate_target("CMAKE") {
emit_warning(format!("CMAKE environment variable set: {}", cmake.clone()));
if execute_command(cmake.as_ref(), &["--version".as_ref()]).status {
Some(cmake.into())
} else {
None
}
} else if execute_command("cmake3".as_ref(), &["--version".as_ref()]).status {
Some("cmake3".into())
} else if execute_command("cmake".as_ref(), &["--version".as_ref()]).status {
Some("cmake".into())
} else {
None
}
}
impl CmakeBuilder {
pub(crate) fn new(
manifest_dir: PathBuf,
out_dir: PathBuf,
build_prefix: Option<String>,
output_lib_type: OutputLibType,
) -> Self {
Self {
manifest_dir,
out_dir,
build_prefix,
output_lib_type,
}
}
fn artifact_output_dir(&self) -> PathBuf {
self.out_dir.join("build").join("artifacts")
}
fn get_cmake_config(&self) -> cmake::Config {
cmake::Config::new(&self.manifest_dir)
}
fn apply_universal_build_options<'a>(
&self,
cmake_cfg: &'a mut cmake::Config,
) -> &'a cmake::Config {
let cc_builder = CcBuilder::new(
self.manifest_dir.clone(),
self.out_dir.clone(),
self.build_prefix.clone(),
self.output_lib_type,
);
let cc_build = cc::Build::new();
let (is_like_msvc, build_options) =
cc_builder.collect_universal_build_options(&cc_build, true);
for option in &build_options {
option.apply_cmake(cmake_cfg, is_like_msvc);
}
cmake_cfg
}
const GOCACHE_DIR_NAME: &'static str = "go-cache";
#[allow(clippy::too_many_lines)]
fn prepare_cmake_build(&self) -> cmake::Config {
if is_fips_build() {
unsafe {
env::set_var("GOFLAGS", "-buildvcs=false");
}
if env::var("GOCACHE").is_err() {
unsafe {
env::set_var(
"GOCACHE",
self.out_dir.join(Self::GOCACHE_DIR_NAME).as_os_str(),
);
}
}
}
let mut cmake_cfg = self.get_cmake_config();
if let Some(generator) = optional_env_optional_crate_target("CMAKE_GENERATOR") {
set_env("CMAKE_GENERATOR", generator);
}
if OutputLibType::default() == OutputLibType::Dynamic {
cmake_cfg.define("BUILD_SHARED_LIBS", "1");
if is_fips_build() {
cmake_cfg.no_default_flags(true);
}
} else {
cmake_cfg.define("BUILD_SHARED_LIBS", "0");
}
if let Some(prefix) = &self.build_prefix {
cmake_cfg.define("BORINGSSL_PREFIX", format!("{prefix}_"));
let include_path = self.manifest_dir.join("generated-include");
cmake_cfg.define(
"BORINGSSL_PREFIX_HEADERS",
include_path.display().to_string(),
);
}
cmake_cfg.define("BUILD_TESTING", "OFF");
cmake_cfg.define("BUILD_TOOL", "OFF");
cmake_cfg.define("ENABLE_SOURCE_MODIFICATION", "OFF");
if cfg!(feature = "ssl") {
cmake_cfg.define("BUILD_LIBSSL", "ON");
} else {
cmake_cfg.define("BUILD_LIBSSL", "OFF");
}
if is_fips_build() {
cmake_cfg.define("FIPS", "1");
} else {
if is_no_pregenerated_src() {
cmake_cfg.define("DISABLE_PERL", "OFF");
cmake_cfg.define("DISABLE_GO", "OFF");
} else {
cmake_cfg.define("DISABLE_PERL", "ON");
cmake_cfg.define("DISABLE_GO", "ON");
}
if Some(true) == disable_jitter_entropy() {
cmake_cfg.define("DISABLE_CPU_JITTER_ENTROPY", "ON");
}
if target_env() == "ohos" {
Self::configure_open_harmony(&mut cmake_cfg);
}
}
if is_no_asm() {
let opt_level = cargo_env("OPT_LEVEL");
if opt_level == "0" {
cmake_cfg.define("OPENSSL_NO_ASM", "1");
} else {
panic!("AWS_LC_SYS_NO_ASM only allowed for debug builds!")
}
}
Self::configure_sanitizer(&mut cmake_cfg);
if let Some(cflags) = get_crate_cflags() {
let cflags = if is_fips_build() {
strip_lto_flags(&cflags)
} else {
cflags
};
set_env_for_target("CFLAGS", &cflags);
set_env("CFLAGS", &cflags);
}
Self::preserve_cflag_optimization_flags(&mut cmake_cfg);
if target_os() == "windows" {
if is_fips_build() {
let opt_level = cargo_env("OPT_LEVEL");
let build_type = if opt_level.eq("0") || opt_level.eq("1") || opt_level.eq("2") {
"RELWITHDEBINFO"
} else {
"RELEASE"
};
cmake_cfg.define("CMAKE_BUILD_TYPE", build_type);
} else if use_prebuilt_nasm() {
self.configure_prebuilt_nasm(&mut cmake_cfg);
}
if target_env().as_str() == "msvc" {
let mut msvcrt = String::from_str("MultiThreaded").unwrap();
if is_crt_static() {
cmake_cfg.static_crt(true);
println!("cargo:rustc-link-arg=/ignore:4099");
} else {
msvcrt.push_str("DLL");
}
cmake_cfg.define("CMAKE_MSVC_RUNTIME_LIBRARY", msvcrt.as_str());
}
}
if let Some(toolchain) = optional_env_optional_crate_target("CMAKE_TOOLCHAIN_FILE") {
set_env_for_target("CMAKE_TOOLCHAIN_FILE", toolchain);
return cmake_cfg;
}
self.apply_universal_build_options(&mut cmake_cfg);
if target_os() == "windows" {
self.configure_windows(&mut cmake_cfg);
}
#[cfg(target_vendor = "apple")]
{
if target_arch() == "aarch64" {
cmake_cfg.define("CMAKE_OSX_ARCHITECTURES", "arm64");
cmake_cfg.define("CMAKE_SYSTEM_PROCESSOR", "arm64");
}
if target_arch() == "x86_64" {
cmake_cfg.define("CMAKE_OSX_ARCHITECTURES", "x86_64");
cmake_cfg.define("CMAKE_SYSTEM_PROCESSOR", "x86_64");
}
if target_os().trim() == "ios" {
cmake_cfg.define("CMAKE_SYSTEM_NAME", "iOS");
if effective_target().ends_with("-ios-sim") || target_arch() == "x86_64" {
cmake_cfg.define("CMAKE_OSX_SYSROOT", "iphonesimulator");
} else {
cmake_cfg.define("CMAKE_OSX_SYSROOT", "iphoneos");
}
cmake_cfg.define("CMAKE_THREAD_LIBS_INIT", "-lpthread");
}
if target_os().trim() == "macos" {
cmake_cfg.define("CMAKE_SYSTEM_NAME", "Darwin");
cmake_cfg.define("CMAKE_OSX_SYSROOT", "macosx");
}
if target_os().trim() == "tvos" {
cmake_cfg.define("CMAKE_SYSTEM_NAME", "tvOS");
if effective_target().ends_with("-tvos-sim") || target_arch() == "x86_64" {
cmake_cfg.define("CMAKE_OSX_SYSROOT", "appletvsimulator");
} else {
cmake_cfg.define("CMAKE_OSX_SYSROOT", "appletvos");
}
}
}
if target_os() == "android" {
self.configure_android(&mut cmake_cfg);
}
cmake_cfg
}
fn preserve_cflag_optimization_flags(cmake_cfg: &mut cmake::Config) {
if let Some(cflags) = get_crate_cflags() {
let split = cflags.split_whitespace();
for arg in split {
if arg.starts_with("-O") || arg.starts_with("/O") {
emit_warning(format!("Preserving optimization flag: {arg}"));
cmake_cfg.cflag(arg);
}
}
}
}
#[allow(clippy::unused_self)]
fn select_prebuilt_nasm_script(&self) -> PathBuf {
let sh_script = self.manifest_dir.join("builder").join("prebuilt-nasm.sh");
let bat_script = self.manifest_dir.join("builder").join("prebuilt-nasm.bat");
if test_prebuilt_nasm_script(&sh_script) {
emit_warning("Selected prebuilt-nasm.sh (shell script can execute)");
sh_script
} else if test_prebuilt_nasm_script(&bat_script) {
emit_warning(
"Selected prebuilt-nasm.bat (batch script can execute, shell script cannot)",
);
bat_script
} else {
let fallback_script = if cfg!(target_os = "windows") {
bat_script
} else {
sh_script
};
emit_warning(format!(
"WARNING: Neither prebuilt-nasm.sh nor prebuilt-nasm.bat could be verified as \
executable. Falling back to target-based selection: {}. If the build fails \
during assembly, verify that the selected script is appropriate for your \
build environment.",
fallback_script.file_name().unwrap().to_str().unwrap()
));
fallback_script
}
}
#[allow(clippy::unused_self)]
fn configure_android(&self, _cmake_cfg: &mut cmake::Config) {
if let Some(value) = optional_env_optional_crate_target("ANDROID_NDK_ROOT") {
set_env("ANDROID_NDK_ROOT", value);
} else {
emit_warning("ANDROID_NDK_ROOT not set.");
}
if let Some(value) = optional_env_optional_crate_target("ANDROID_NDK") {
set_env("ANDROID_NDK", value);
} else {
emit_warning("ANDROID_NDK not set.");
}
if let Some(value) = optional_env_optional_crate_target("ANDROID_STANDALONE_TOOLCHAIN") {
set_env("ANDROID_STANDALONE_TOOLCHAIN", value);
} else {
emit_warning("ANDROID_STANDALONE_TOOLCHAIN not set.");
}
}
#[allow(clippy::unused_self)]
fn configure_windows(&self, cmake_cfg: &mut cmake::Config) {
if is_fips_build() {
cmake_cfg.generator("Ninja");
let env_map = self
.collect_vcvarsall_bat()
.map_err(|x| panic!("{}", x))
.unwrap();
for (key, value) in env_map {
cmake_cfg.env(key, value);
}
}
match (target_env().as_str(), target_arch().as_str()) {
("msvc", "aarch64") => {
let cmake_generator = optional_env("CMAKE_GENERATOR");
if cmake_generator.is_none() || cmake_generator.unwrap().to_lowercase() != "ninja" {
cmake_cfg.generator_toolset(format!(
"ClangCL{}",
if cfg!(target_arch = "x86_64") {
",host=x64"
} else {
""
}
));
cmake_cfg.define("CMAKE_GENERATOR_PLATFORM", "ARM64");
}
cmake_cfg.define("CMAKE_SYSTEM_NAME", "Windows");
cmake_cfg.define("CMAKE_SYSTEM_PROCESSOR", "ARM64");
}
("msvc", _) => {
}
(_, arch) => {
cmake_cfg.define("CMAKE_SYSTEM_NAME", "Windows");
cmake_cfg.define("CMAKE_SYSTEM_PROCESSOR", arch);
}
}
}
fn vcvarsall_arch() -> &'static str {
let target = target_arch();
match (
cfg!(target_arch = "x86_64"),
cfg!(target_arch = "aarch64"),
target.as_str(),
) {
(true, _, "x86_64") => "x64",
(true, _, "aarch64") => "x64_arm64",
(true, _, "x86") => "x64_x86",
(_, true, "aarch64") => "arm64",
(_, true, "x86_64") => "arm64_x64",
(_, true, "x86") => "arm64_x86",
_ => "x64",
}
}
fn collect_vcvarsall_bat(&self) -> Result<HashMap<String, String>, String> {
let mut map: HashMap<String, String> = HashMap::new();
let script_path = self.manifest_dir.join("builder").join("printenv.bat");
let arch = OsString::from(Self::vcvarsall_arch());
let result = execute_command(script_path.as_os_str(), &[arch.as_os_str()]);
if !result.status {
eprintln!("{}", result.stdout);
return Err("Failed to run vcvarsall.bat.".to_owned());
}
eprintln!("{}", result.stdout);
let lines = result.stdout.lines();
for line in lines {
if let Some((var, val)) = line.split_once('=') {
map.insert(var.to_string(), val.to_string());
}
}
Ok(map)
}
fn configure_prebuilt_nasm(&self, cmake_cfg: &mut cmake::Config) {
emit_warning("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
emit_warning("!!! Using pre-built NASM binaries !!!");
emit_warning("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
let script_path = self.select_prebuilt_nasm_script();
let script_path = script_path.display().to_string();
let script_path = script_path.replace('\\', "/");
cmake_cfg.define("CMAKE_ASM_NASM_COMPILER", script_path.as_str());
cmake_cfg.define(
"CMAKE_ASM_NASM_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreaded",
"",
);
cmake_cfg.define(
"CMAKE_ASM_NASM_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDLL",
"",
);
cmake_cfg.define(
"CMAKE_ASM_NASM_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDebug",
"",
);
cmake_cfg.define(
"CMAKE_ASM_NASM_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDebugDLL",
"",
);
cmake_cfg.define(
"CMAKE_ASM_NASM_COMPILE_OPTIONS_MSVC_DEBUG_INFORMATION_FORMAT_ProgramDatabase",
"",
);
}
fn configure_sanitizer(cmake_cfg: &mut cmake::Config) {
let feature_sanitizer = if cfg!(feature = "asan") {
Some("asan")
} else {
None
};
let env_sanitizer = sanitizer();
let env_sanitizer = env_sanitizer.as_deref();
let active_sanitizer = match (feature_sanitizer, env_sanitizer) {
(Some(f), Some(e)) if f != e => {
panic!(
"Conflicting sanitizer configuration: feature flag '{f}' and \
AWS_LC_SYS_SANITIZER='{e}'. Remove one to resolve the conflict."
);
}
(Some(f), _) => Some(f),
(_, Some(e)) => Some(e),
_ => None,
};
if let Some(san) = active_sanitizer {
set_env_for_target("CC", "clang");
set_env_for_target("CXX", "clang++");
match san {
"asan" => {
cmake_cfg.define("ASAN", "1");
}
"msan" => {
assert!(
!is_fips_build(),
"MemorySanitizer is not supported for FIPS builds. \
The FIPS integrity check in bcm.c lacks an \
OPENSSL_MSAN guard, causing undefined-symbol link \
errors (BORINGSSL_bcm_text_start/end/hash)."
);
cmake_cfg.define("MSAN", "1");
}
"tsan" => {
cmake_cfg.define("TSAN", "1");
cmake_cfg.profile("Release");
}
other => {
panic!("Unsupported sanitizer: '{other}'. Supported values: asan, msan, tsan")
}
}
}
}
fn configure_open_harmony(cmake_cfg: &mut cmake::Config) {
let mut cflags = vec!["-Wno-unused-command-line-argument"];
let mut asmflags = vec![];
if optional_env_optional_crate_target("CMAKE_TOOLCHAIN_FILE").is_none() {
if let Ok(ndk) = env::var("OHOS_NDK_HOME") {
set_env_for_target(
"CMAKE_TOOLCHAIN_FILE",
format!("{ndk}/native/build/cmake/ohos.toolchain.cmake"),
);
} else if let Ok(sdk) = env::var("OHOS_SDK_NATIVE") {
set_env_for_target(
"CMAKE_TOOLCHAIN_FILE",
format!("{sdk}/build/cmake/ohos.toolchain.cmake"),
);
} else {
emit_warning(
"Neither OHOS_NDK_HOME nor OHOS_SDK_NATIVE are set! No toolchain found.",
);
}
}
match effective_target().as_str() {
"aarch64-unknown-linux-ohos" => {
cmake_cfg.define("OHOS_ARCH", "arm64-v8a");
}
"armv7-unknown-linux-ohos" => {
const ARM7_FLAGS: [&str; 6] = [
"-march=armv7-a",
"-mfloat-abi=softfp",
"-mtune=generic-armv7-a",
"-mthumb",
"-mfpu=neon",
"-DHAVE_NEON",
];
cflags.extend(ARM7_FLAGS);
asmflags.extend(ARM7_FLAGS);
cmake_cfg.define("OHOS_ARCH", "armeabi-v7a");
}
"x86_64-unknown-linux-ohos" => {
const X86_64_FLAGS: [&str; 3] = ["-msse4.1", "-DHAVE_NEON_X86", "-DHAVE_NEON"];
cflags.extend(X86_64_FLAGS);
asmflags.extend(X86_64_FLAGS);
cmake_cfg.define("OHOS_ARCH", "x86_64");
}
ohos_target => {
emit_warning(format!("Target: {ohos_target} is not support yet!").as_str());
}
}
cmake_cfg
.cflag(cflags.join(" ").as_str())
.cxxflag(cflags.join(" ").as_str())
.asmflag(asmflags.join(" ").as_str());
}
fn build_library(&self) -> PathBuf {
self.prepare_cmake_build()
.configure_arg("--no-warn-unused-cli")
.build()
}
}
impl crate::Builder for CmakeBuilder {
fn check_dependencies(&self) -> Result<(), String> {
let mut missing_dependency = false;
if target_os() == "windows" && target_arch() == "x86_64" {
if is_no_asm() && Some(true) == allow_prebuilt_nasm() {
eprintln!(
"Build environment has both `AWS_LC_SYS_PREBUILT_NASM` and `AWS_LC_SYS_NO_ASM` set.\
Please remove one of these environment variables.
See User Guide: https://aws.github.io/aws-lc-rs/index.html"
);
}
if !is_no_asm() && !test_nasm_command() && !use_prebuilt_nasm() {
eprintln!(
"Consider installing NASM or setting `AWS_LC_SYS_PREBUILT_NASM` in the build environment.\
See User Guide: https://aws.github.io/aws-lc-rs/index.html"
);
eprintln!("Missing dependency: nasm");
missing_dependency = true;
}
}
if target_os() == "windows"
&& target_arch() == "aarch64"
&& target_env() == "msvc"
&& !test_clang_cl_command()
{
eprintln!("Missing dependency: clang-cl");
missing_dependency = true;
}
if let Some(cmake_cmd) = find_cmake_command() {
unsafe {
env::set_var("CMAKE", cmake_cmd);
}
} else {
eprintln!("Missing dependency: cmake");
missing_dependency = true;
}
if missing_dependency {
return Err("Required build dependency is missing. Halting build.".to_owned());
}
Ok(())
}
fn build(&self) -> Result<(), String> {
self.build_library();
println!(
"cargo:rustc-link-search=native={}",
self.artifact_output_dir().display()
);
println!(
"cargo:rustc-link-lib={}={}",
self.output_lib_type.rust_lib_type(),
Crypto.libname(&self.build_prefix)
);
if cfg!(feature = "ssl") {
println!(
"cargo:rustc-link-lib={}={}",
self.output_lib_type.rust_lib_type(),
Ssl.libname(&self.build_prefix)
);
}
Ok(())
}
fn name(&self) -> &'static str {
"CMake"
}
}
#[cfg(test)]
mod tests {
use super::strip_lto_flags;
#[test]
fn test_strip_lto_flags() {
let input = "-O2 -ftree-vectorize -flto=auto -ffat-lto-objects -fexceptions -g -fPIC";
assert_eq!(
strip_lto_flags(input),
"-O2 -ftree-vectorize -fexceptions -g -fPIC"
);
assert_eq!(strip_lto_flags("-flto -O2"), "-O2");
assert_eq!(strip_lto_flags("-flto=thin -O2"), "-O2");
assert_eq!(strip_lto_flags("-flto=full -O2"), "-O2");
assert_eq!(strip_lto_flags("-flto=4 -O2"), "-O2");
assert_eq!(strip_lto_flags("-fno-fat-lto-objects -O2"), "-O2");
let no_lto = "-O2 -fPIC -g";
assert_eq!(strip_lto_flags(no_lto), no_lto);
assert_eq!(strip_lto_flags(""), "");
}
}