use std::env;
#[cfg(unix)]
use std::ffi::OsString;
use std::fs;
use std::io::{stderr, Write};
#[cfg(unix)]
use std::path::Path;
use std::path::PathBuf;
use std::str;
use duct::cmd;
struct Metadata {
host: String,
target: String,
build_dir: PathBuf,
install_dir: PathBuf,
}
impl Metadata {
fn is_cross_compiling(&self) -> bool {
self.host != self.target
}
fn get_target_env(&self, name: &str) -> Option<String> {
if self.is_cross_compiling() {
env::var(format!("{}_{}", name, self.target.replace("-", "_"))).ok()
} else {
env::var(name).ok()
}
}
}
trait DuctExpressionExt: Sized {
fn set_target_env_vars(self, metadata: &Metadata) -> Self;
}
impl DuctExpressionExt for duct::Expression {
fn set_target_env_vars(mut self, metadata: &Metadata) -> Self {
if let Some(target_cc) = metadata.get_target_env("CC") {
self = self.env("CC", target_cc)
}
if let Some(target_ar) = metadata.get_target_env("AR") {
self = self.env("AR", target_ar)
}
self
}
}
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let metadata = Metadata {
host: env::var("HOST").unwrap(),
target: env::var("TARGET").unwrap(),
build_dir: out_dir.join("build"),
install_dir: out_dir.join("install"),
};
fs::create_dir_all(&metadata.build_dir).expect("failed to create build dir");
fs::create_dir_all(&metadata.install_dir).expect("failed to create install dir");
build(&metadata);
println!("cargo:root={}", metadata.install_dir.display());
}
#[cfg(unix)]
fn build(metadata: &Metadata) {
{
let mut cppflags = env::var("CPPFLAGS").ok().unwrap_or_else(String::new);
let mut cflags = env::var("CFLAGS").ok().unwrap_or_else(String::new);
cflags += " -fPIC";
if let Ok(openssl_root) = env::var("DEP_OPENSSL_ROOT") {
cppflags += &format!(" -I{}", Path::new(&openssl_root).join("include").display());
}
let mut configure_args = vec![
format!("--prefix={}", metadata.install_dir.display()),
"--enable-static".into(),
"--disable-shared".into(),
#[cfg(feature = "nls")]
"--enable-nls".into(),
#[cfg(not(feature = "nls"))]
"--disable-nls".into(),
format!("CPPFLAGS={}", cppflags),
format!("CFLAGS={}", cflags),
];
if metadata.is_cross_compiling() {
configure_args.push(format!("--build={}", metadata.host));
configure_args.push(format!("--host={}", metadata.target));
}
let configure_path = Path::new("krb5").join("src").join("configure");
let configure = cmd(configure_path, &configure_args)
.dir(&metadata.build_dir)
.set_target_env_vars(metadata)
.env_remove("CONFIG_SITE");
let configure_output = configure.stderr_capture().unchecked().run().unwrap();
let _ = stderr().write_all(&configure_output.stderr);
if !configure_output.status.success() && metadata.is_cross_compiling() {
if let Ok(s) = str::from_utf8(&configure_output.stderr) {
let last_error = s.trim_end();
if last_error.ends_with(
"Cannot test for constructor/destructor support when cross compiling",
) {
eprintln!("set krb5_cv_attr_constructor_destructor={{yes|no}} in the environment to skip this test");
}
if last_error.ends_with("Cannot test regcomp when cross compiling") {
eprintln!(
"set ac_cv_func_regcomp={{yes|no}} in the environment to skip this test"
);
}
if last_error.ends_with(
"Cannot test for printf positional argument support when cross compiling",
) {
eprintln!("set ac_cv_printf_positional={{yes|no}} in the environment to skip this test");
}
}
panic!("configure failed");
}
}
{
let mut make_flags = OsString::new();
let mut make_args = vec![];
if let Ok(s) = env::var("NUM_JOBS") {
match env::var_os("CARGO_MAKEFLAGS") {
Some(ref s)
if !(cfg!(windows)
|| cfg!(target_os = "openbsd")
|| cfg!(target_os = "netbsd")
|| cfg!(target_os = "freebsd")
|| cfg!(target_os = "bitrig")
|| cfg!(target_os = "dragonflybsd")) =>
{
make_flags = s.clone()
}
_ => make_args.push(format!("-j{}", s)),
}
}
cmd!("make", "install-mkdirs")
.dir(&metadata.build_dir)
.set_target_env_vars(metadata)
.run()
.expect("install-mkdirs failed");
for dir in &[
"util/support",
"util/et",
"util/profile",
"util/verto",
"include",
"lib",
#[cfg(feature = "binaries")]
"plugins/kdb/db2",
#[cfg(feature = "binaries")]
"clients",
] {
for target in &["all", "install"] {
cmd!("make", target)
.dir(metadata.build_dir.join(dir))
.set_target_env_vars(metadata)
.env("MAKEFLAGS", &make_flags)
.run()
.unwrap_or_else(|_| panic!("make failed in {}", dir));
}
}
}
}
#[cfg(windows)]
fn build(metadata: &Metadata) {
if metadata.host != metadata.target {
panic!("cross-compilation on a Windows host is not supported");
}
let output = cmd!("robocopy", "krb5\\src", &metadata.build_dir, "/s", "/e")
.unchecked()
.run()
.unwrap_or_else(|e| panic!("copying source tree failed: {}", e));
if !matches!(output.status.code(), Some(0..=7)) {
panic!("copying source tree failed: {:?}", output);
}
let nmake = |args: &[&str]| {
cmd("nmake", args)
.dir(&metadata.build_dir)
.env("KRB_INSTALL_DIR", &metadata.install_dir)
};
nmake(&["-f", "Makefile.in", "prep-windows"])
.run()
.unwrap_or_else(|e| panic!("nmake prep failed: {}", e));
nmake(&[])
.run()
.unwrap_or_else(|e| panic!("nmake build failed: {}", e));
nmake(&["install"])
.run()
.unwrap_or_else(|e| panic!("nmake install failed: {}", e));
}