extern crate cc;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
pub fn source_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("openssl")
}
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
pub struct Build {
out_dir: Option<PathBuf>,
target: Option<String>,
host: Option<String>,
}
pub struct Artifacts {
include_dir: PathBuf,
lib_dir: PathBuf,
bin_dir: PathBuf,
libs: Vec<String>,
target: String,
}
impl Build {
pub fn new() -> Build {
Build {
out_dir: env::var_os("OUT_DIR").map(|s| PathBuf::from(s).join("openssl-build")),
target: env::var("TARGET").ok(),
host: env::var("HOST").ok(),
}
}
pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build {
self.out_dir = Some(path.as_ref().to_path_buf());
self
}
pub fn target(&mut self, target: &str) -> &mut Build {
self.target = Some(target.to_string());
self
}
pub fn host(&mut self, host: &str) -> &mut Build {
self.host = Some(host.to_string());
self
}
fn cmd_make(&self) -> Command {
let host = &self.host.as_ref().expect("HOST dir not set")[..];
if host.contains("dragonfly")
|| host.contains("freebsd")
|| host.contains("openbsd")
|| host.contains("solaris")
|| host.contains("illumos")
{
Command::new("gmake")
} else {
Command::new("make")
}
}
pub fn build(&mut self) -> Artifacts {
let target = &self.target.as_ref().expect("TARGET dir not set")[..];
let host = &self.host.as_ref().expect("HOST dir not set")[..];
let out_dir = self.out_dir.as_ref().expect("OUT_DIR not set");
let build_dir = out_dir.join("build");
let install_dir = out_dir.join("install");
if build_dir.exists() {
fs::remove_dir_all(&build_dir).unwrap();
}
if install_dir.exists() {
fs::remove_dir_all(&install_dir).unwrap();
}
let inner_dir = build_dir.join("src");
fs::create_dir_all(&inner_dir).unwrap();
cp_r(&source_dir(), &inner_dir);
apply_patches(target, &inner_dir);
let perl_program =
env::var("OPENSSL_SRC_PERL").unwrap_or(env::var("PERL").unwrap_or("perl".to_string()));
let mut configure = Command::new(perl_program);
configure.arg("./Configure");
if host.contains("pc-windows-gnu") {
configure.arg(&format!("--prefix={}", sanitize_sh(&install_dir)));
} else {
configure.arg(&format!("--prefix={}", install_dir.display()));
}
configure
.arg("no-dso")
.arg("no-shared")
.arg("no-ssl3")
.arg("no-unit-test")
.arg("no-comp")
.arg("no-zlib")
.arg("no-zlib-dynamic");
if cfg!(not(feature = "weak-crypto")) {
configure
.arg("no-md2")
.arg("no-rc5")
.arg("no-weak-ssl-ciphers");
}
if cfg!(not(feature = "camellia")) {
configure.arg("no-camellia");
}
if cfg!(not(feature = "idea")) {
configure.arg("no-idea");
}
if cfg!(not(feature = "seed")) {
configure.arg("no-seed");
}
if target.contains("musl") || target.contains("windows") {
configure.arg("no-engine");
}
if target.contains("musl") {
configure.arg("no-async");
}
if target.contains("android") {
configure.arg("no-stdio");
}
if target.contains("msvc") {
configure.arg("no-asm");
}
let os = match target {
"aarch64-apple-darwin" => "darwin64-arm64-cc",
"aarch64-linux-android" => "linux-aarch64",
"aarch64-unknown-freebsd" => "BSD-generic64",
"aarch64-unknown-linux-gnu" => "linux-aarch64",
"aarch64-unknown-linux-musl" => "linux-aarch64",
"aarch64-pc-windows-msvc" => "VC-WIN64-ARM",
"arm-linux-androideabi" => "linux-armv4",
"armv7-linux-androideabi" => "linux-armv4",
"arm-unknown-linux-gnueabi" => "linux-armv4",
"arm-unknown-linux-gnueabihf" => "linux-armv4",
"arm-unknown-linux-musleabi" => "linux-armv4",
"arm-unknown-linux-musleabihf" => "linux-armv4",
"armv6-unknown-freebsd" => "BSD-generic32",
"armv7-unknown-freebsd" => "BSD-generic32",
"armv7-unknown-linux-gnueabi" => "linux-armv4",
"armv7-unknown-linux-musleabi" => "linux-armv4",
"armv7-unknown-linux-gnueabihf" => "linux-armv4",
"armv7-unknown-linux-musleabihf" => "linux-armv4",
"asmjs-unknown-emscripten" => "gcc",
"i586-unknown-linux-gnu" => "linux-elf",
"i586-unknown-linux-musl" => "linux-elf",
"i686-apple-darwin" => "darwin-i386-cc",
"i686-linux-android" => "linux-elf",
"i686-pc-windows-gnu" => "mingw",
"i686-pc-windows-msvc" => "VC-WIN32",
"i686-unknown-freebsd" => "BSD-x86-elf",
"i686-unknown-linux-gnu" => "linux-elf",
"i686-unknown-linux-musl" => "linux-elf",
"mips-unknown-linux-gnu" => "linux-mips32",
"mips-unknown-linux-musl" => "linux-mips32",
"mips64-unknown-linux-gnuabi64" => "linux64-mips64",
"mips64el-unknown-linux-gnuabi64" => "linux64-mips64",
"mipsel-unknown-linux-gnu" => "linux-mips32",
"mipsel-unknown-linux-musl" => "linux-mips32",
"powerpc-unknown-linux-gnu" => "linux-ppc",
"powerpc64-unknown-freebsd" => "BSD-generic64",
"powerpc64-unknown-linux-gnu" => "linux-ppc64",
"powerpc64le-unknown-freebsd" => "BSD-generic64",
"powerpc64le-unknown-linux-gnu" => "linux-ppc64le",
"riscv64gc-unknown-linux-gnu" => "linux-generic64",
"s390x-unknown-linux-gnu" => "linux64-s390x",
"x86_64-apple-darwin" => "darwin64-x86_64-cc",
"x86_64-linux-android" => "linux-x86_64",
"x86_64-pc-windows-gnu" => "mingw64",
"x86_64-pc-windows-msvc" => "VC-WIN64A",
"x86_64-unknown-freebsd" => "BSD-x86_64",
"x86_64-unknown-dragonfly" => "BSD-x86_64",
"x86_64-unknown-illumos" => "solaris64-x86_64-gcc",
"x86_64-unknown-linux-gnu" => "linux-x86_64",
"x86_64-unknown-linux-musl" => "linux-x86_64",
"x86_64-unknown-openbsd" => "BSD-x86_64",
"x86_64-unknown-netbsd" => "BSD-x86_64",
"x86_64-sun-solaris" => "solaris64-x86_64-gcc",
"wasm32-unknown-emscripten" => "gcc",
"wasm32-unknown-unknown" => "gcc",
"wasm32-wasi" => "gcc",
"aarch64-apple-ios" => "ios64-cross",
"x86_64-apple-ios" => "iossimulator-xcrun",
_ => panic!("don't know how to configure OpenSSL for {}", target),
};
let mut ios_isysroot: std::option::Option<String> = None;
configure.arg(os);
if !target.contains("msvc") {
let mut cc = cc::Build::new();
cc.target(target).host(host).warnings(false).opt_level(2);
let compiler = cc.get_compiler();
configure.env("CC", compiler.path());
let path = compiler.path().to_str().unwrap();
configure.env_remove("CROSS_COMPILE");
if path.ends_with("-gcc") && !target.contains("unknown-linux-musl") {
let path = &path[..path.len() - 4];
if env::var_os("RANLIB").is_none() {
configure.env("RANLIB", format!("{}-ranlib", path));
}
if env::var_os("AR").is_none() {
configure.env("AR", format!("{}-ar", path));
}
}
let mut skip_next = false;
let mut is_isysroot = false;
for arg in compiler.args() {
if target.contains("musl") && arg == "-static" {
continue;
}
if target.contains("apple") {
if arg == "-arch" {
skip_next = true;
continue;
}
}
if target.contains("apple-ios") {
if arg == "-isysroot" {
is_isysroot = true;
continue;
}
if is_isysroot {
is_isysroot = false;
ios_isysroot = Some(arg.to_str().unwrap().to_string());
continue;
}
}
if skip_next {
skip_next = false;
continue;
}
configure.arg(arg);
}
if os.contains("iossimulator") {
if let Some(ref isysr) = ios_isysroot {
configure.env(
"CC",
&format!(
"xcrun -sdk iphonesimulator cc -isysroot {}",
sanitize_sh(&Path::new(isysr))
),
);
}
}
if target == "x86_64-pc-windows-gnu" {
configure.arg("-Wa,-mbig-obj");
}
if target.contains("pc-windows-gnu") && path.ends_with("-gcc") {
let windres = format!("{}-windres", &path[..path.len() - 4]);
configure.env("WINDRES", &windres);
}
if target.contains("emscripten") {
configure.arg("-D__STDC_NO_ATOMICS__");
}
if target.contains("musl") {
configure.arg("-DOPENSSL_NO_SECURE_MEMORY");
}
}
configure.current_dir(&inner_dir);
self.run_command(configure, "configuring OpenSSL build");
if target.contains("msvc") {
let mut build =
cc::windows_registry::find(target, "nmake.exe").expect("failed to find nmake");
build.arg("build_libs").current_dir(&inner_dir);
self.run_command(build, "building OpenSSL");
let mut install =
cc::windows_registry::find(target, "nmake.exe").expect("failed to find nmake");
install.arg("install_dev").current_dir(&inner_dir);
self.run_command(install, "installing OpenSSL");
} else {
let mut depend = self.cmd_make();
depend.arg("depend").current_dir(&inner_dir);
self.run_command(depend, "building OpenSSL dependencies");
let mut build = self.cmd_make();
build.arg("build_libs").current_dir(&inner_dir);
if !cfg!(windows) {
if let Some(s) = env::var_os("CARGO_MAKEFLAGS") {
build.env("MAKEFLAGS", s);
}
}
if let Some(ref isysr) = ios_isysroot {
let components: Vec<&str> = isysr.split("/SDKs/").collect();
build.env("CROSS_TOP", components[0]);
build.env("CROSS_SDK", components[1]);
}
self.run_command(build, "building OpenSSL");
let mut install = self.cmd_make();
install.arg("install_dev").current_dir(&inner_dir);
self.run_command(install, "installing OpenSSL");
}
let libs = if target.contains("msvc") {
vec!["libssl".to_string(), "libcrypto".to_string()]
} else {
vec!["ssl".to_string(), "crypto".to_string()]
};
fs::remove_dir_all(&inner_dir).unwrap();
Artifacts {
lib_dir: install_dir.join("lib"),
bin_dir: install_dir.join("bin"),
include_dir: install_dir.join("include"),
libs: libs,
target: target.to_string(),
}
}
fn run_command(&self, mut command: Command, desc: &str) {
println!("running {:?}", command);
let status = command.status().unwrap();
if !status.success() {
panic!(
"
Error {}:
Command: {:?}
Exit status: {}
",
desc, command, status
);
}
}
}
fn cp_r(src: &Path, dst: &Path) {
for f in fs::read_dir(src).unwrap() {
let f = f.unwrap();
let path = f.path();
let name = path.file_name().unwrap();
if name.to_str() == Some(".git") {
continue;
}
let dst = dst.join(name);
if f.file_type().unwrap().is_dir() {
fs::create_dir_all(&dst).unwrap();
cp_r(&path, &dst);
} else {
let _ = fs::remove_file(&dst);
fs::copy(&path, &dst).unwrap();
}
}
}
fn apply_patches(target: &str, inner: &Path) {
apply_patches_musl(target, inner);
}
fn apply_patches_musl(target: &str, inner: &Path) {
if !target.contains("musl") {
return;
}
let path = inner.join("crypto/rand/rand_unix.c");
let buf = fs::read_to_string(&path).unwrap();
let buf = buf
.replace("asm/unistd.h", "sys/syscall.h")
.replace("__NR_getrandom", "SYS_getrandom");
fs::write(path, buf).unwrap();
}
fn sanitize_sh(path: &Path) -> String {
if !cfg!(windows) {
return path.to_str().unwrap().to_string();
}
let path = path.to_str().unwrap().replace("\\", "/");
return change_drive(&path).unwrap_or(path);
fn change_drive(s: &str) -> Option<String> {
let mut ch = s.chars();
let drive = ch.next().unwrap_or('C');
if ch.next() != Some(':') {
return None;
}
if ch.next() != Some('/') {
return None;
}
Some(format!("/{}/{}", drive, &s[drive.len_utf8() + 2..]))
}
}
impl Artifacts {
pub fn include_dir(&self) -> &Path {
&self.include_dir
}
pub fn lib_dir(&self) -> &Path {
&self.lib_dir
}
pub fn libs(&self) -> &[String] {
&self.libs
}
pub fn print_cargo_metadata(&self) {
println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
for lib in self.libs.iter() {
println!("cargo:rustc-link-lib=static={}", lib);
}
println!("cargo:include={}", self.include_dir.display());
println!("cargo:lib={}", self.lib_dir.display());
if self.target.contains("msvc") {
println!("cargo:rustc-link-lib=user32");
}
}
}