use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
let fips = env::var("CARGO_FEATURE_FIPS").is_ok();
let aws_lc = env::var("CARGO_FEATURE_AWS_LC").is_ok();
if !fips && !aws_lc {
panic!(
"no crypto backend feature selected -- enable exactly one of the `aws-lc` (default) \
or `fips` features."
);
}
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
let vendor_dir = manifest_dir.join("vendor");
let i2pd_dir = vendor_dir.join("i2pd-src");
let boost_dir = vendor_dir.join("boost-src");
let zlib_dir = vendor_dir.join("zlib-src");
let target = env::var("TARGET").expect("TARGET");
let host = env::var("HOST").expect("HOST");
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"));
let compilers = if target == host {
None } else {
Some(resolve_cross_compilers(&target))
};
let zlib_lib = build_zlib(&zlib_dir, &out_dir, compilers.as_ref());
let boost_install = build_boost(&boost_dir, compilers.as_ref());
let (crypto_crate, include_var, libcrypto_var, libssl_var) = if cfg!(feature = "fips") {
(
"aws-lc-fips-sys",
"DEP_AWS_LC_FIPS_0_13_16_INCLUDE",
"DEP_AWS_LC_FIPS_0_13_16_LIBCRYPTO",
"DEP_AWS_LC_FIPS_0_13_16_LIBSSL",
)
} else {
(
"aws-lc-sys",
"DEP_AWS_LC_0_43_0_INCLUDE",
"DEP_AWS_LC_0_43_0_LIBCRYPTO",
"DEP_AWS_LC_0_43_0_LIBSSL",
)
};
let openssl_include = PathBuf::from(env::var(include_var).unwrap_or_else(|_| {
panic!(
"{include_var} not set -- {crypto_crate}'s version pin drifted? update the `links` \
name referenced here to match Cargo.lock"
)
}));
let openssl_crypto_stub = openssl_stub_lib(&out_dir, "crypto", compilers.as_ref());
let openssl_ssl_stub = openssl_stub_lib(&out_dir, "ssl", compilers.as_ref());
let i2pd_install = build_i2pd(
&i2pd_dir,
&boost_install,
&openssl_include,
&openssl_crypto_stub,
&openssl_ssl_stub,
&zlib_dir,
&zlib_lib,
compilers.as_ref(),
);
println!(
"cargo:rustc-link-search=native={}",
i2pd_install.join("lib").display()
);
println!(
"cargo:rustc-link-search=native={}",
boost_install.join("lib").display()
);
println!("cargo:rustc-link-search=native={}", zlib_lib.parent().unwrap().display());
let aws_lc_crypto = env::var(libcrypto_var)
.unwrap_or_else(|_| panic!("{libcrypto_var} not set -- see the INCLUDE var comment above"));
let aws_lc_ssl = env::var(libssl_var).ok();
let mut shim = cc::Build::new();
shim.cpp(true).std("c++17").warnings(false);
if let Some(c) = &compilers {
shim.compiler(&c.cxx);
}
for flag in HARDENING_CFLAGS {
shim.flag_if_supported(flag);
}
shim.include(i2pd_dir.join("libi2pd"))
.include(i2pd_dir.join("libi2pd_client"))
.include(boost_install.join("include"))
.include(&openssl_include)
.include(zlib_dir)
.file("shim/shim.cpp")
.compile("tachyon_i2pd_shim");
println!("cargo:rustc-link-search=native={}", out_dir.display());
for lib in ["tachyon_i2pd_shim", "i2pd", "i2pdclient", "i2pdlang"] {
println!("cargo:rustc-link-lib=static:+verbatim=lib{lib}.a");
}
for lib in ["filesystem", "program_options", "atomic", "date_time", "container"] {
println!("cargo:rustc-link-lib=static:+verbatim=libboost_{lib}.a");
}
println!("cargo:rustc-link-lib=static:+verbatim=libz.a");
if let Some(aws_lc_ssl) = &aws_lc_ssl {
println!("cargo:rustc-link-lib=static:+verbatim=lib{aws_lc_ssl}.a");
}
println!("cargo:rustc-link-lib=static:+verbatim=lib{aws_lc_crypto}.a");
let bindings = bindgen::Builder::default()
.header("shim/shim.h")
.allowlist_function("i2pd_.*")
.allowlist_type("I2pd.*")
.generate()
.expect("bindgen failed to generate shim bindings");
bindings
.write_to_file(out_dir.join("bindings.rs"))
.expect("failed to write bindgen bindings.rs");
println!("cargo:rerun-if-changed=shim/shim.h");
println!("cargo:rerun-if-changed=shim/shim.cpp");
}
struct CrossCompilers {
cc: PathBuf,
cxx: PathBuf,
ar: Option<PathBuf>,
}
fn resolve_cross_compilers(target: &str) -> CrossCompilers {
let env_key = |prefix: &str| format!("{prefix}_{}", target.replace('-', "_"));
if let (Ok(cc), Ok(cxx)) = (env::var(env_key("CC")), env::var(env_key("CXX"))) {
return CrossCompilers {
cc: cc.into(),
cxx: cxx.into(),
ar: env::var(env_key("AR")).ok().map(PathBuf::from),
};
}
if let Ok(toolchain_dir) = env::var("MUSL_CROSS_TOOLCHAIN") {
let bin = PathBuf::from(&toolchain_dir).join("bin");
let cc = bin.join(format!("{target}-gcc"));
let cxx = bin.join(format!("{target}-g++"));
if cxx.exists() {
let ar = bin.join(format!("{target}-ar"));
return CrossCompilers { cc, cxx, ar: ar.exists().then_some(ar) };
}
}
let path_cc = format!("{target}-gcc");
let path_cxx = format!("{target}-g++");
if which(&path_cxx) {
return CrossCompilers {
cc: path_cc.into(),
cxx: path_cxx.into(),
ar: which(&format!("{target}-ar")).then(|| format!("{target}-ar").into()),
};
}
panic!(
"cross-compiling to `{target}` needs a real musl C++ toolchain (binutils+gcc+musl+\
libstdc++) -- no C++ compiler shipped by default on a glibc host can target musl. \
Point `MUSL_CROSS_TOOLCHAIN` at a `musl-cross-make` install (containing \
bin/{target}-g++), or set `CC_{t}`/`CXX_{t}` directly, or put `{target}-g++` on \
$PATH. See i2pd-sys/README.md for how to build one with musl-cross-make. (Building \
natively *inside* a musl system, e.g. an Alpine container, needs none of this --\
only true cross-compilation from a non-musl host does.)",
t = target.replace('-', "_"),
);
}
fn which(cmd: &str) -> bool {
Command::new(cmd)
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|s| s.success())
}
const HARDENING_CFLAGS: [&str; 2] = ["-D_FORTIFY_SOURCE=2", "-fstack-protector-strong"];
fn build_zlib(zlib_dir: &Path, out_dir: &Path, compilers: Option<&CrossCompilers>) -> PathBuf {
let sources = [
"adler32.c", "compress.c", "crc32.c", "deflate.c", "gzclose.c", "gzlib.c", "gzread.c",
"gzwrite.c", "infback.c", "inflate.c", "inftrees.c", "inffast.c", "trees.c", "uncompr.c",
"zutil.c",
];
let mut build = cc::Build::new();
build.warnings(false).include(zlib_dir).define("HAVE_UNISTD_H", None);
if let Some(c) = compilers {
build.compiler(&c.cc);
}
for flag in HARDENING_CFLAGS {
build.flag_if_supported(flag);
}
for src in sources {
build.file(zlib_dir.join(src));
}
build.compile("z");
out_dir.join("libz.a")
}
fn build_boost(boost_dir: &Path, compilers: Option<&CrossCompilers>) -> PathBuf {
let mut cfg = cmake::Config::new(boost_dir);
cfg.define(
"BOOST_INCLUDE_LIBRARIES",
"filesystem;program_options;atomic;system;date_time;asio;property_tree;dynamic_bitset",
)
.define("CMAKE_BUILD_TYPE", "Release")
.define("BUILD_TESTING", "OFF")
.build_target("install");
apply_cross(&mut cfg, compilers);
cfg.build()
}
fn openssl_stub_lib(out_dir: &Path, name: &str, compilers: Option<&CrossCompilers>) -> PathBuf {
let stub_dir = out_dir.join("openssl-stub");
std::fs::create_dir_all(&stub_dir).expect("create openssl-stub dir");
let stub_c = stub_dir.join(format!("{name}_stub.c"));
std::fs::write(&stub_c, "// intentionally empty -- see openssl_stub_lib in build.rs\n")
.expect("write stub source");
let mut build = cc::Build::new();
build.warnings(false);
if let Some(c) = compilers {
build.compiler(&c.cc);
}
build.file(&stub_c).compile(&format!("tachyon_openssl_stub_{name}"));
out_dir.join(format!("libtachyon_openssl_stub_{name}.a"))
}
#[allow(clippy::too_many_arguments)]
fn build_i2pd(
i2pd_dir: &Path,
boost_install: &Path,
openssl_include: &Path,
openssl_crypto_stub: &Path,
openssl_ssl_stub: &Path,
zlib_dir: &Path,
zlib_lib: &Path,
compilers: Option<&CrossCompilers>,
) -> PathBuf {
let mut cfg = cmake::Config::new(i2pd_dir.join("build"));
cfg.define("WITH_LIBRARY", "ON")
.define("WITH_BINARY", "OFF")
.define("WITH_UPNP", "OFF")
.define("WITH_HARDENING", "ON")
.define("BUILD_TESTING", "OFF")
.define("CMAKE_BUILD_TYPE", "Release")
.define("Boost_NO_SYSTEM_PATHS", "ON")
.define("Boost_USE_STATIC_LIBS", "ON")
.define("Boost_USE_STATIC_RUNTIME", "ON")
.define("BOOST_ROOT", boost_install)
.define("OPENSSL_INCLUDE_DIR", openssl_include)
.define("OPENSSL_CRYPTO_LIBRARY", openssl_crypto_stub)
.define("OPENSSL_SSL_LIBRARY", openssl_ssl_stub)
.define("OPENSSL_VERSION", "1.1.1")
.define("ZLIB_INCLUDE_DIR", zlib_dir)
.define("ZLIB_LIBRARY", zlib_lib)
.build_target("install");
for flag in HARDENING_CFLAGS {
cfg.cflag(flag).cxxflag(flag);
}
apply_cross(&mut cfg, compilers);
cfg.build()
}
fn apply_cross(cfg: &mut cmake::Config, compilers: Option<&CrossCompilers>) {
let Some(c) = compilers else { return };
cfg.define("CMAKE_C_COMPILER", &c.cc)
.define("CMAKE_CXX_COMPILER", &c.cxx)
.define("CMAKE_EXE_LINKER_FLAGS", "-static");
if let Some(ar) = &c.ar {
cfg.define("CMAKE_AR", ar);
}
}