use std::{
env,
path::{Path, PathBuf},
};
struct Target {
name: String,
is_release: bool,
}
impl Target {
fn get() -> Self {
let mut target = env::var("TARGET").unwrap();
if target.starts_with("riscv") {
let mut split = target.split('-');
let arch = split.next().unwrap();
let bitness = &arch[5..7];
let rest = split.collect::<Vec<_>>().join("-");
target = format!("riscv{bitness}-{rest}");
}
Self {
name: target,
is_release: env::var("PROFILE").unwrap() == "release",
}
}
}
fn find_libsodium_env() {
let lib_dir = env::var("SODIUM_LIB_DIR").unwrap();
println!("cargo:rustc-link-search=native={lib_dir}");
let mode = if env::var("SODIUM_SHARED").is_ok() {
"dylib"
} else {
"static"
};
let name = if cfg!(target_env = "msvc") {
"libsodium"
} else {
"sodium"
};
println!("cargo:rustc-link-lib={mode}={name}");
println!("cargo:warning=Using unknown libsodium version.");
}
fn find_libsodium_vpkg() -> bool {
match vcpkg::probe_package("libsodium") {
Ok(lib) => {
println!("cargo:warning=Using unknown libsodium version");
for lib_dir in &lib.link_paths {
println!("cargo:lib={}", lib_dir.to_str().unwrap());
}
for include_dir in &lib.include_paths {
println!("cargo:include={}", include_dir.to_str().unwrap());
}
true
}
Err(_) => false,
}
}
fn find_libsodium_pkgconfig() -> bool {
match pkg_config::Config::new().probe("libsodium") {
Ok(lib) => {
for lib_dir in &lib.link_paths {
println!("cargo:lib={}", lib_dir.to_str().unwrap());
}
for include_dir in &lib.include_paths {
println!("cargo:include={}", include_dir.to_str().unwrap());
}
true
}
Err(_) => false,
}
}
fn extract_libsodium_precompiled_msvc(_: &str, _: &Path, install_dir: &Path) -> PathBuf {
use zip::read::ZipArchive;
let basename = "libsodium-1.0.20-stable-msvc";
let filename = format!("{}.zip", basename);
let signature_filename = format!("{}.zip.minisig", basename);
let archive_bin = retrieve_and_verify_archive(&filename, &signature_filename);
let mut archive = ZipArchive::new(std::io::Cursor::new(archive_bin)).unwrap();
archive.extract(install_dir).unwrap();
match Target::get().name.as_str() {
"i686-pc-windows-msvc" => get_precompiled_lib_dir_msvc_win32(install_dir),
"x86_64-pc-windows-msvc" => get_precompiled_lib_dir_msvc_x64(install_dir),
_ => panic!("Unsupported target"),
}
}
fn extract_libsodium_precompiled_mingw(_: &str, _: &Path, install_dir: &Path) -> PathBuf {
use libflate::gzip::Decoder;
use tar::Archive;
let basename = "libsodium-1.0.20-stable-mingw";
let filename = format!("{}.tar.gz", basename);
let signature_filename = format!("{}.tar.gz.minisig", basename);
let archive_bin = retrieve_and_verify_archive(&filename, &signature_filename);
let gz_decoder = Decoder::new(std::io::Cursor::new(archive_bin)).unwrap();
let mut archive = Archive::new(gz_decoder);
archive.unpack(install_dir).unwrap();
match Target::get().name.as_str() {
"i686-pc-windows-gnu" => install_dir.join("libsodium/i686-w64-mingw32/lib"),
"x86_64-pc-windows-gnu" => install_dir.join("libsodium/x86_64-w64-mingw32/lib"),
_ => panic!("Unsupported target"),
}
}
fn get_precompiled_lib_dir_msvc_win32(install_dir: &Path) -> PathBuf {
if Target::get().is_release {
install_dir.join("libsodium/Win32/Release/v143/static/")
} else {
install_dir.join("libsodium/Win32/Debug/v143/static/")
}
}
fn get_precompiled_lib_dir_msvc_x64(install_dir: &Path) -> PathBuf {
if Target::get().is_release {
install_dir.join("libsodium/x64/Release/v143/static/")
} else {
install_dir.join("libsodium/x64/Debug/v143/static/")
}
}
fn compile_libsodium_zig(target: &str, source_dir: &Path) -> Result<PathBuf, String> {
use std::process::Command;
let host = env::var("HOST").unwrap();
let cross_compiling = target != host;
let target = Target::get().name;
if cross_compiling && target != "wasm32-wasi" {
return Err(
"Cross-compiling with Zig is not implemented in this Rust file yet (except for WebAssembly)".to_string(),
);
}
let mut install_cmd = Command::new("zig");
let mut install_output = install_cmd.current_dir(source_dir).arg("build");
if target.as_str() == "wasm32-wasi" {
install_output = install_output.arg("--target").arg("wasm32-wasi");
};
if Target::get().is_release {
install_output = install_output.arg("-Doptimize=ReleaseFast");
}
let install_output = install_output.output();
let install_output = match install_output {
Ok(output) => output,
Err(error) => {
return Err(format!("Failed to run 'zig build': {}\n", error));
}
};
if !install_output.status.success() {
return Err(format!(
"\n{:?}\n{}\n{}\n",
install_cmd,
String::from_utf8_lossy(&install_output.stdout),
String::from_utf8_lossy(&install_output.stderr)
));
}
let install_path = source_dir.join("zig-out/lib");
Ok(install_path)
}
fn compile_libsodium_traditional(
target: &str,
source_dir: &Path,
install_dir: &Path,
) -> Result<PathBuf, String> {
use std::{fs, process::Command, str};
let build_compiler = cc::Build::new().get_compiler();
let mut compiler = build_compiler.path().to_str().unwrap().to_string();
let mut cflags = build_compiler.cflags_env().into_string().unwrap();
let ldflags = env::var("SODIUM_LDFLAGS").unwrap_or_default();
let host_arg;
let help;
let mut configure_extra = vec![];
if target.contains("-wasi") {
compiler = "zig cc --target=wasm32-wasi".to_string();
host_arg = "--host=wasm32-wasi".to_string();
configure_extra.push("--disable-ssp");
configure_extra.push("--without-pthreads");
env::set_var("AR", "zig ar");
env::set_var("RANLIB", "zig ranlib");
help = "The Zig SDK needs to be installed in order to cross-compile to WebAssembly\n";
} else if target.contains("-ios") {
let xcode_select_output = Command::new("xcode-select").arg("-p").output().unwrap();
if !xcode_select_output.status.success() {
return Err("Failed to run xcode-select -p".to_string());
}
let xcode_dir = str::from_utf8(&xcode_select_output.stdout)
.unwrap()
.trim()
.to_string();
let sdk_dir_simulator = Path::new(&xcode_dir)
.join("Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk")
.to_str()
.unwrap()
.to_string();
let sdk_dir_ios = Path::new(&xcode_dir)
.join("Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk")
.to_str()
.unwrap()
.to_string();
let ios_simulator_version_min = "9.0.0";
let ios_version_min = "9.0.0";
match target {
"aarch64-apple-ios" => {
cflags += " -arch arm64";
cflags += &format!(" -isysroot {sdk_dir_ios}");
cflags += &format!(" -mios-version-min={ios_version_min}");
host_arg = "--host=aarch64-apple-darwin23".to_string();
}
"armv7-apple-ios" => {
cflags += " -arch armv7";
cflags += &format!(" -isysroot {sdk_dir_ios}");
cflags += &format!(" -mios-version-min={ios_version_min}");
cflags += " -mthumb";
host_arg = "--host=arm-apple-darwin23".to_string();
}
"armv7s-apple-ios" => {
cflags += " -arch armv7s";
cflags += &format!(" -isysroot {sdk_dir_ios}");
cflags += &format!(" -mios-version-min={ios_version_min}");
cflags += " -mthumb";
host_arg = "--host=arm-apple-darwin23".to_string();
}
"x86_64-apple-ios" => {
cflags += " -arch x86_64";
cflags += &format!(" -isysroot {sdk_dir_simulator}");
cflags += &format!(" -mios-simulator-version-min={ios_simulator_version_min}");
host_arg = "--host=x86_64-apple-darwin23".to_string();
}
"aarch64-apple-ios-sim" => {
cflags += " -arch arm64";
cflags += &format!(" -isysroot {sdk_dir_simulator}");
cflags += &format!(" -mios-simulator-version-min={ios_simulator_version_min}");
host_arg = "--host=aarch64-apple-darwin23".to_string();
}
_ => return Err(format!("Unknown iOS build target: {}", target)),
}
help = "";
} else {
if target.contains("i686") {
compiler += " -m32 -maes";
cflags += " -march=i686";
}
let host = env::var("HOST").unwrap();
host_arg = format!("--host={target}");
let cross_compiling = target != host;
help = if cross_compiling {
"***********************************************************\n\
Use the 'cargo zigbuild' command to cross-compile Rust code\n\
with C dependencies such as libsodium.\n\
***********************************************************\n"
} else {
""
};
}
let prefix_arg = format!("--prefix={}", install_dir.to_str().unwrap());
let mut configure_cmd = Command::new(fs::canonicalize(source_dir.join("configure")).unwrap());
if !compiler.is_empty() {
configure_cmd.env("CC", &compiler);
}
if !cflags.is_empty() {
configure_cmd.env("CFLAGS", &cflags);
}
if !ldflags.is_empty() {
configure_cmd.env("LDFLAGS", &ldflags);
}
if env::var("SODIUM_DISABLE_PIE").is_ok() {
configure_cmd.arg("--disable-pie");
}
configure_cmd.arg("--disable-ssp");
#[cfg(feature = "optimized")]
configure_cmd.arg("--enable-opt");
#[cfg(feature = "minimal")]
configure_cmd.arg("--enable-minimal");
let configure_output = configure_cmd
.current_dir(source_dir)
.arg(&prefix_arg)
.arg(&host_arg)
.args(configure_extra)
.arg("--enable-shared=no")
.arg("--disable-dependency-tracking")
.output();
let configure_output = match configure_output {
Ok(output) => output,
Err(error) => {
return Err(format!("Failed to run './configure': {}\n{}", error, help));
}
};
if !configure_output.status.success() {
return Err(format!(
"\n{:?}\nCFLAGS={}\nLDFLAGS={}\nCC={}\n{}\n{}\n{}\n",
configure_cmd,
cflags,
ldflags,
compiler,
String::from_utf8_lossy(&configure_output.stdout),
String::from_utf8_lossy(&configure_output.stderr),
help
));
}
let j_arg = format!("-j{}", env::var("NUM_JOBS").unwrap());
let mut install_cmd = Command::new("make");
let install_output = install_cmd
.current_dir(source_dir)
.arg(j_arg)
.arg("install")
.output();
let install_output = match install_output {
Ok(install_output) => install_output,
Err(error) => {
return Err(format!("Failed to run 'make install': {}\n", error));
}
};
if !install_output.status.success() {
panic!(
"\n{}\n{}\n{}\n",
String::from_utf8_lossy(&configure_output.stdout),
String::from_utf8_lossy(&install_output.stdout),
String::from_utf8_lossy(&install_output.stderr)
);
}
Ok(install_dir.join("lib"))
}
fn get_cargo_install_dir() -> PathBuf {
PathBuf::from(env::var("OUT_DIR").unwrap()).join("installed")
}
fn retrieve_and_verify_archive(filename: &str, signature_filename: &str) -> Vec<u8> {
use minisign_verify::{PublicKey, Signature};
use std::fs::{self, File};
use std::io::prelude::*;
let pk =
PublicKey::from_base64("RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3").unwrap();
if let Ok(dist_dir) = env::var("SODIUM_DIST_DIR") {
let _ = fs::metadata(&dist_dir).expect("SODIUM_DIST_DIR directory does not exist");
let archive_path = PathBuf::from(&dist_dir).join(filename);
let signature_path = PathBuf::from(&dist_dir).join(signature_filename);
let mut archive_bin = vec![];
File::open(&archive_path)
.unwrap_or_else(|_| panic!("Failed to open archive [{:?}]", &archive_path))
.read_to_end(&mut archive_bin)
.unwrap();
let signature = Signature::from_file(&signature_path)
.unwrap_or_else(|_| panic!("Failed to open signature file [{:?}]", &signature_path));
pk.verify(&archive_bin, &signature, false)
.expect("Invalid signature");
return archive_bin;
}
let mut archive_bin = vec![];
let mut download = true;
#[cfg(not(feature = "fetch-latest"))]
{
if let Ok(mut file) = File::open(filename) {
if file.read_to_end(&mut archive_bin).is_ok() {
download = false;
}
}
}
if download {
let baseurl = "http://download.libsodium.org/libsodium/releases";
let agent = ureq::AgentBuilder::new()
.try_proxy_from_env(true)
.timeout(std::time::Duration::from_secs(300))
.build();
let response = agent.get(&format!("{}/{}", baseurl, filename)).call();
response
.unwrap()
.into_reader()
.read_to_end(&mut archive_bin)
.unwrap();
File::create(filename)
.unwrap()
.write_all(&archive_bin)
.unwrap();
let response = agent
.get(&format!("{}/{}", baseurl, signature_filename))
.call();
let mut signature_bin = vec![];
response
.unwrap()
.into_reader()
.read_to_end(&mut signature_bin)
.unwrap();
File::create(signature_filename)
.unwrap()
.write_all(&signature_bin)
.unwrap();
}
let signature = Signature::from_file(signature_filename).unwrap();
pk.verify(&archive_bin, &signature, false)
.expect("Invalid signature");
archive_bin
}
fn install_from_source() -> Result<(), String> {
use libflate::gzip::Decoder;
use std::fs;
use tar::Archive;
let mut target = Target::get().name;
if target.starts_with("riscv") {
let mut split = target.split('-');
let arch = split.next().unwrap();
let bitness = &arch[5..7];
let rest = split.collect::<Vec<_>>().join("-");
target = format!("riscv{bitness}-{rest}");
}
let basedir = "libsodium-stable";
let basename = "LATEST";
let filename = format!("{basename}.tar.gz");
let signature_filename = format!("{basename}.tar.gz.minisig");
let archive_bin = retrieve_and_verify_archive(&filename, &signature_filename);
let mut install_dir = get_cargo_install_dir();
let mut source_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("source");
if install_dir.to_str().unwrap().contains(' ') {
let fallback_path = PathBuf::from("/tmp/").join(basename).join(&target);
install_dir = fallback_path.join("installed");
source_dir = fallback_path.join("source");
println!(
"cargo:warning=The path to the usual build directory contains spaces and hence \
can't be used to build libsodium. Falling back to use {}. If running `cargo \
clean`, ensure you also delete this fallback directory",
fallback_path.to_str().unwrap()
);
}
fs::create_dir_all(&install_dir).unwrap();
fs::create_dir_all(&source_dir).unwrap();
let gz_decoder = Decoder::new(std::io::Cursor::new(archive_bin)).unwrap();
let mut archive = Archive::new(gz_decoder);
archive.unpack(&source_dir).unwrap();
source_dir.push(basedir);
let lib_dir = compile_libsodium_zig(&target, &source_dir)
.or_else(|_| compile_libsodium_traditional(&target, &source_dir, &install_dir))?;
if target.contains("msvc") {
println!("cargo:rustc-link-lib=static=libsodium");
} else {
println!("cargo:rustc-link-lib=static=sodium");
}
println!(
"cargo:rustc-link-search=native={}",
lib_dir.to_str().unwrap()
);
let include_dir = source_dir.join("src/libsodium/include");
println!("cargo:include={}", include_dir.to_str().unwrap());
println!("cargo:lib={}", lib_dir.to_str().unwrap());
Ok(())
}
fn main() {
dbg!("Compiling for target:", Target::get().name);
println!("cargo:rerun-if-env-changed=SODIUM_LIB_DIR");
println!("cargo:rerun-if-env-changed=SODIUM_SHARED");
println!("cargo:rerun-if-env-changed=SODIUM_USE_PKG_CONFIG");
println!("cargo:rerun-if-env-changed=VCPKGRS_DYNAMIC");
println!("cargo:rerun-if-env-changed=SODIUM_DISABLE_PIE");
let lib_dir_isset = env::var("SODIUM_LIB_DIR").is_ok();
let use_pkg_isset = if cfg!(feature = "use-pkg-config") {
true
} else {
env::var("SODIUM_USE_PKG_CONFIG").is_ok()
};
let shared_isset = env::var("SODIUM_SHARED").is_ok();
if lib_dir_isset && use_pkg_isset {
panic!("SODIUM_LIB_DIR is incompatible with SODIUM_USE_PKG_CONFIG. Set the only one env variable");
}
if lib_dir_isset {
find_libsodium_env();
return;
}
if use_pkg_isset {
if shared_isset {
println!("cargo:warning=SODIUM_SHARED has no effect with SODIUM_USE_PKG_CONFIG");
}
if !find_libsodium_pkgconfig() && !find_libsodium_vpkg() {
panic!("libsodium not found via pkg-config or vcpkg");
}
return;
}
if shared_isset {
println!("cargo:warning=SODIUM_SHARED has no effect for building libsodium from source");
}
let res = install_from_source();
if res.is_ok() {
return;
}
match Target::get().name.as_str() {
"i686-pc-windows-msvc" => {
let install_dir = get_cargo_install_dir();
let lib_dir =
extract_libsodium_precompiled_msvc("win32", Path::new("source"), &install_dir);
println!(
"cargo:rustc-link-search=native={}",
lib_dir.to_str().unwrap()
);
println!("cargo:rustc-link-lib=static=libsodium");
println!(
"cargo:include={}",
install_dir.join("include").to_str().unwrap()
);
}
"x86_64-pc-windows-msvc" => {
let install_dir = get_cargo_install_dir();
let lib_dir =
extract_libsodium_precompiled_msvc("x64", Path::new("source"), &install_dir);
println!(
"cargo:rustc-link-search=native={}",
lib_dir.to_str().unwrap()
);
println!("cargo:rustc-link-lib=static=libsodium");
println!(
"cargo:include={}",
install_dir.join("include").to_str().unwrap()
);
}
"i686-pc-windows-gnu" => {
let install_dir = get_cargo_install_dir();
let lib_dir =
extract_libsodium_precompiled_mingw("win32", Path::new("source"), &install_dir);
println!(
"cargo:rustc-link-search=native={}",
lib_dir.to_str().unwrap()
);
println!("cargo:rustc-link-lib=static=sodium");
println!(
"cargo:include={}",
install_dir.join("include").to_str().unwrap()
);
}
"x86_64-pc-windows-gnu" => {
let install_dir = get_cargo_install_dir();
let lib_dir =
extract_libsodium_precompiled_mingw("x64", Path::new("source"), &install_dir);
println!(
"cargo:rustc-link-search=native={}",
lib_dir.to_str().unwrap()
);
println!("cargo:rustc-link-lib=static=sodium");
println!(
"cargo:include={}",
install_dir.join("include").to_str().unwrap()
);
}
_ => {
panic!(
"Unable to compile or find precompiled libsodium for target [{}]: [{}]",
Target::get().name,
res.unwrap_err()
);
}
}
}