use std::env;
use std::path::PathBuf;
const GMSSL_RELEASE_TAG: &str = "v3.1.1";
const GMSSL_RELEASE_URL: &str =
"https://github.com/guanzhi/GmSSL/archive/refs/tags/v3.1.1.tar.gz";
fn main() {
if env::var("DOCS_RS").is_ok() {
println!("cargo:rustc-link-lib=gmssl");
println!("cargo:rerun-if-env-changed=DOCS_RS");
println!("cargo:rerun-if-changed=build.rs");
return;
}
if let Ok(dir) = env::var("GMSSL_DIR") {
let lib_dir = PathBuf::from(&dir).join("lib");
assert!(
lib_dir.exists(),
"GMSSL_DIR={} but {}/ not found. \
Unset GMSSL_DIR to build GmSSL from source instead.",
dir,
lib_dir.display()
);
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=gmssl");
println!("cargo:rerun-if-env-changed=GMSSL_DIR");
println!("cargo:rerun-if-changed=build.rs");
return;
}
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let source_dir = locate_gmssl_source(&manifest_dir);
println!(
"cargo:rerun-if-changed={}",
source_dir.join("CMakeLists.txt").display()
);
for watch_dir in &["src", "include"] {
let path = source_dir.join(watch_dir);
if path.exists() {
println!("cargo:rerun-if-changed={}", path.display());
}
}
let mut cmake_cfg = cmake::Config::new(&source_dir);
cmake_cfg.define("BUILD_SHARED_LIBS", "OFF");
if let Ok(cc) = env::var("CC") {
if !cc.is_empty() {
cmake_cfg.define("CMAKE_C_COMPILER", &cc);
}
}
cmake_cfg.define("ENABLE_SM2_PRIVATE_KEY_EXPORT", "ON");
let optional_features = &[
"ENABLE_SHA1",
"ENABLE_SHA2",
"ENABLE_AES",
"ENABLE_CHACHA20",
"ENABLE_SM4_ECB",
"ENABLE_SM4_OFB",
"ENABLE_SM4_CFB",
"ENABLE_SM4_CCM",
"ENABLE_SM4_XTS",
"ENABLE_SM4_CBC_MAC",
"ENABLE_SECP256R1",
"ENABLE_LMS",
"ENABLE_XMSS",
"ENABLE_SPHINCS",
"ENABLE_KYBER",
"ENABLE_TLS_DEBUG",
"ENABLE_SDF",
"ENABLE_SKF",
];
for feature in optional_features {
let env_var = format!("GMSSL_{}", feature);
let value = env::var(&env_var).unwrap_or_else(|_| "OFF".to_string());
if value == "ON" || value == "on" || value == "1" {
cmake_cfg.define(feature, "ON");
} else {
cmake_cfg.define(feature, "OFF");
}
}
if let Ok(extra) = env::var("GMSSL_CMAKE_DEFINES") {
for def in extra.split_whitespace() {
if let Some(eq) = def.find('=') {
cmake_cfg.define(&def[..eq], &def[eq + 1..]);
}
}
}
let dst = cmake_cfg.build();
let lib_dir = dst.join("lib");
assert!(
lib_dir.exists(),
"CMake build completed but no lib/ directory found at {}. \
Check the GmSSL CMake output above for errors.",
lib_dir.display()
);
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=static=gmssl");
let include_dir = dst.join("include");
if include_dir.exists() {
println!("cargo:include={}", include_dir.display());
}
if env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "macos" {
println!("cargo:rustc-link-lib=framework=Security");
}
if env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "windows" {
println!("cargo:rustc-link-lib=bcrypt");
println!("cargo:rustc-link-lib=ncrypt");
}
println!("cargo:rerun-if-env-changed=GMSSL_DIR");
println!("cargo:rerun-if-env-changed=GMSSL_CMAKE_DEFINES");
println!("cargo:rerun-if-changed=build.rs");
}
fn locate_gmssl_source(manifest_dir: &PathBuf) -> PathBuf {
let submodule_dir = manifest_dir.join("GmSSL");
if submodule_dir.join("CMakeLists.txt").exists() {
return submodule_dir;
}
if submodule_dir.exists() {
let workspace_root = manifest_dir
.parent()
.expect("gmssl-rs-sys must live inside a workspace");
if let Ok(status) = std::process::Command::new("git")
.args(["submodule", "update", "--init", "--depth", "1"])
.current_dir(workspace_root)
.status()
{
if status.success() && submodule_dir.join("CMakeLists.txt").exists() {
return submodule_dir;
}
}
}
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let tarball_dir = out_dir.join("gmssl-src");
let tarball_path = out_dir.join(format!("GmSSL-{}.tar.gz", GMSSL_RELEASE_TAG));
let extract_dir = out_dir.join(format!("GmSSL-{}", GMSSL_RELEASE_TAG));
if !tarball_path.exists() {
eprintln!("Downloading GmSSL {} source from GitHub...", GMSSL_RELEASE_TAG);
let status = std::process::Command::new("curl")
.args([
"-L",
"-o",
tarball_path.to_str().unwrap(),
GMSSL_RELEASE_URL,
])
.status()
.expect("failed to run curl. Is curl installed?");
assert!(status.success(), "Failed to download GmSSL source");
}
if !extract_dir.join("CMakeLists.txt").exists() {
eprintln!("Extracting GmSSL {} source...", GMSSL_RELEASE_TAG);
let _ = std::fs::create_dir_all(&tarball_dir);
let status = std::process::Command::new("tar")
.args([
"xzf",
tarball_path.to_str().unwrap(),
"-C",
tarball_dir.to_str().unwrap(),
])
.status()
.expect("failed to run tar. Is tar installed?");
assert!(status.success(), "Failed to extract GmSSL source");
}
let extracted = tarball_dir.join(format!("GmSSL-{}", GMSSL_RELEASE_TAG));
if !extracted.join("CMakeLists.txt").exists() {
let found = std::fs::read_dir(&tarball_dir)
.ok()
.and_then(|mut entries| {
entries.find_map(|e| {
let p = e.ok()?.path();
p.join("CMakeLists.txt").exists().then_some(p)
})
});
if let Some(path) = found {
return path;
}
panic!(
"GmSSL source not found after extracting {}. \
Please install GmSSL manually and set GMSSL_DIR.",
GMSSL_RELEASE_URL
);
}
extracted
}