use std::{ env, fs::{copy, create_dir_all, read_dir, write}, io, mem, path::{Path, PathBuf}, process::Command, }; use which::which; /// Unwraps a Result and panics in a way that prints a nicer error message to the user. macro_rules! panic_err { ($value:expr) => {{ $value.unwrap_or_else(|err| panic!("{err}")) }}; ($value:expr, $($arg:expr)+) => {{ $value.unwrap_or_else(|err| panic!("{}: {err}", format_args!($($arg),+))) }}; } /// Formats arguments in a way that is intended to address environment variables. macro_rules! env_name { ($($arg:expr),+) => {{ format!($($arg),+).replace("-", "_").to_uppercase() }}; } /// Expands to the value of an environment variable. macro_rules! env { ($($arg:expr),+) => { panic_err!(env::var(env_name!($($arg),+))) }; } /// Expands to the value of a cargo configuration environment variable. macro_rules! cfg { ($($arg:expr),+) => { env!("CARGO_CFG_{}", format_args!($($arg),+)) }; } /// Whether a cargo feature is enabled. macro_rules! feature { ($($arg:expr),+) => { env::var_os(env_name!("CARGO_FEATURE_{}", format_args!($($arg),+))).is_some() }; } fn main() { let src_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src"); let out_path = PathBuf::from(env!("OUT_DIR")); panic_err!( copy_recursive(&src_path, &out_path), "failed to copy luajit source (is the Git submodule initialised?)" ); panic_err!(write(out_path.join(".relver"), get_relver())); println!("cargo::rerun-if-changed={}", src_path.display()); // rerun if luajit source changed // NOTE: we cannot build bindings without building luajit first (luajit generates some headers) build_runtime(&out_path.join("src")); build_bindings(&out_path.join("src")); } fn get_relver() -> String { let out = panic_err!( Command::new("git") .args(["show", "-s", "--format=%ct"]) .output(), "failed to obtain luajit release version (is the Git command available?)" ); String::from_utf8_lossy(&out.stdout).into() } fn copy_recursive(src: impl AsRef, dst: impl AsRef) -> io::Result<()> { create_dir_all(dst.as_ref())?; for entry in read_dir(src)? { let entry = entry?; if entry.file_name().to_str() == Some(".git") { continue; } let path = dst.as_ref().join(entry.file_name()); if entry.file_type()?.is_dir() { copy_recursive(entry.path(), path)?; } else { copy(entry.path(), path)?; } } Ok(()) } fn find_make() -> Command { // always use gnu make on bsds let name = match parse_target(&env!("HOST")) { (_, _, "freebsd" | "openbsd" | "netbsd" | "dragonfly", _) => "gmake", _ => "make", }; Command::new(panic_err!(which(name), "failed to find make")) } fn find_cc(target: impl AsRef) -> PathBuf { let cc = cc::Build::new().target(target.as_ref()).get_compiler(); let path = panic_err!(which(cc.path()), "failed to find cc"); path.to_str().unwrap().into() } fn parse_target(target: &str) -> (&str, &str, &str, Option<&str>) { let mut i = target.splitn(4, "-"); let arch = i.next().unwrap(); let vendor = i.next().unwrap(); let os = i.next().unwrap(); let env = i.next(); (arch, vendor, os, env) } fn build_runtime(src_path: &Path) { let host = env!("HOST"); // host triplet let host_cc = find_cc(&host).to_str().unwrap().to_owned(); let host_ptr_width = 8 * mem::size_of::(); let target = env!("TARGET"); // target triplet let target_cc = find_cc(&target).to_str().unwrap().to_owned(); // let target_features: HashSet<&str> = HashSet::from_iter(cfg!("target_feature").split(",")); let target_ptr_width: usize = cfg!("target_pointer_width").parse().unwrap(); let mut make = find_make(); make.current_dir(src_path) .env_clear() .arg("-e") .env("PATH", env!("PATH")) .env("MAKEFLAGS", env!("CARGO_MAKEFLAGS")) .env("BUILDMODE", "static"); let ccopt = vec![format!("-O{} -fomit-frame-pointer", env!("OPT_LEVEL"))]; // propagate opt_level let mut ccdebug = vec![]; let mut xcflags = vec![]; if env!("OPT_LEVEL") == "0" { ccdebug.push("-g"); // generate debug information xcflags.push("-DLUAJIT_USE_GDBJIT"); // gdb support xcflags.push("-DLUA_USE_APICHECK -DLUA_USE_ASSERT"); // enable assertions } xcflags.push(if feature!("unwind") { "-DLUAJIT_UNWIND_EXTERNAL -funwind-tables" // external frame unwinding (C++ exceptions) } else { "-DLUAJIT_UNWIND_INTERNAL" // internal frame unwinding (setjmp/longjmp) }); if !feature!("jit") { xcflags.push("-DLUAJIT_DISABLE_JIT"); } if !feature!("ffi") { xcflags.push("-DLUAJIT_DISABLE_FFI"); } if !feature!("bundled-alloc") { xcflags.push("-DLUAJIT_USE_SYSMALLOC"); // using system malloc disables the bundled allocator } if feature!("lua52") { xcflags.push("-DLUAJIT_ENABLE_LUA52COMPAT"); } // host toolchain config match (host_ptr_width, target_ptr_width) { (64, 64) | (32, 32) => make.env("HOST_CC", &host_cc), (64, 32) => make.env("HOST_CC", format!("{host_cc} -m32")), (n, m) if n != m => panic!("cannot cross-compile on {n}-bit host for {m}-bit target"), (n, _) => panic!("unsupported {n}-bit architecture"), }; // target system config match cfg!("target_os").as_str() { "linux" | "android" => make.env("TARGET_SYS", "Linux"), "windows" => make.env("TARGET_SYS", "Windows"), "macos" => make.env("TARGET_SYS", "Darwin").env( "MACOSX_DEPLOYMENT_TARGET", env::var("MACOSX_DEPLOYMENT_TARGET").as_deref().unwrap_or( match cfg!("target_arch").as_str() { "x86_64" | "x86_64h" => "10.12", "aarch64" | "arm64" | "arm64e" => "11.0", arch => panic!("unsupported MacOS target architecture '{arch}'"), }, ), ), "ios" => make.env("TARGET_SYS", "iOS"), "solaris" => make.env("TARGET_SYS", "SunOS"), _ => make.env("TARGET_SYS", "Other"), }; // target toolchain config if let Some(cross) = target_cc.strip_suffix("gcc") { make.env("CC", "gcc").env("CROSS", cross); } else if let Some(cross) = target_cc.strip_suffix("clang") { make.env("CC", "clang").env("CROSS", cross); } else { let path = Path::new(&target_cc); make.env("CC", path.file_name().unwrap()) .env("CROSS", format!("{}/", path.parent().unwrap().display())); } // propagate linker config if let Ok(path) = env::var("RUSTC_LINKER") { make.env("TARGET_LD", panic_err!(which(path), "failed to find ld")); } make.env("CCOPT", ccopt.join(" ")) .env("CCDEBUG", ccdebug.join(" ")) .env("XCFLAGS", xcflags.join(" ")); if target == host { println!("--- begin compile for {target}:"); } else { println!("--- begin cross-compile on {host} for {target}:"); } println!("{make:?}"); let status = panic_err!(make.status(), "failed to execute make"); (!status.success()).then(|| panic!("failed to compile luajit: {status}: {make:?}")); println!( "cargo::rustc-env=LUAJIT_SYS_JITLIB={}", src_path.join("jit").display(), ); if feature!("runtime") { println!("cargo::rustc-link-search=native={}", src_path.display()); println!("cargo::rustc-link-lib=static=luajit"); } } fn build_bindings(src_path: &Path) { let mut clang_args = vec![format!("--target={}", env!("TARGET"))]; let abi = if feature!("unwind") { clang_args.push("-fexceptions".into()); bindgen::Abi::CUnwind } else { clang_args.push("-fno-exceptions".into()); bindgen::Abi::C }; let mut bindgen = bindgen::builder() .clang_args(clang_args) .allowlist_item(r"^(lua|LUA).*_.+$") .formatter(bindgen::Formatter::None) .default_macro_constant_type(bindgen::MacroTypeVariation::Signed) .generate_cstr(true) .override_abi(abi, ".*"); for header in ["lua.h", "lualib.h", "lauxlib.h", "luaconf.h", "luajit.h"] { bindgen = bindgen.header(src_path.join(header).to_str().unwrap()); } let path = src_path.join("bindgen.rs"); panic_err!(panic_err!(bindgen.generate(), "failed to generate bindings").write_to_file(&path)); println!("cargo::rustc-env=LUAJIT_SYS_BINDGEN={}", path.display()); }