From 9ab05ce18cc35b3235b0cc7665d6e860193c9b11 Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Thu, 23 Mar 2023 13:35:56 +0100 Subject: [PATCH] feat: Implement cross compilation for MSVC toolchain --- Cargo.toml | 4 +- build.rs | 179 +++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 156 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8758afc..23f0cd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "luajit2-sys" version = "0.0.2" description = "LuaJIT-2.1 FFI Bindings" authors = ["Aaron Loucks "] -edition = "2018" +edition = "2021" keywords = ["lua", "luajit", "script"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -16,5 +16,5 @@ libc = "0.2" [build-dependencies] bindgen = "0.64.0" -cc = "1.0.40" +cc = "1" fs_extra = "1.1.0" diff --git a/build.rs b/build.rs index bbafb9a..cfcebdd 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,4 @@ +use cc::Build; use fs_extra::dir; use fs_extra::dir::CopyOptions; use std::env; @@ -6,38 +7,101 @@ use std::process::{Command, Stdio}; const LIB_NAME: &str = "luajit"; const LUAJIT_HEADERS: [&str; 4] = ["lua.h", "lualib.h", "lauxlib.h", "luajit.h"]; +const LUAJIT_SRC: [&str; 69] = [ + // LJCORE_O + // The MSVC toolchain cannot compile this assembler file, + // as it contains GNU-specific directives + // "lj_vm.S", + "lj_assert.c", + "lj_gc.c", + "lj_err.c", + "lj_char.c", + "lj_bc.c", + "lj_obj.c", + "lj_buf.c", + "lj_str.c", + "lj_tab.c", + "lj_func.c", + "lj_udata.c", + "lj_meta.c", + "lj_debug.c", + "lj_prng.c", + "lj_state.c", + "lj_dispatch.c", + "lj_vmevent.c", + "lj_vmmath.c", + "lj_strscan.c", + "lj_strfmt.c", + "lj_strfmt_num.c", + "lj_serialize.c", + "lj_api.c", + "lj_profile.c", + "lj_lex.c", + "lj_parse.c", + "lj_bcread.c", + "lj_bcwrite.c", + "lj_load.c", + "lj_ir.c", + "lj_opt_mem.c", + "lj_opt_fold.c", + "lj_opt_narrow.c", + "lj_opt_dce.c", + "lj_opt_loop.c", + "lj_opt_split.c", + "lj_opt_sink.c", + "lj_mcode.c", + "lj_snap.c", + "lj_record.c", + "lj_crecord.c", + "lj_ffrecord.c", + "lj_asm.c", + "lj_trace.c", + "lj_gdbjit.c", + "lj_ctype.c", + "lj_cdata.c", + "lj_cconv.c", + "lj_ccall.c", + "lj_ccallback.c", + "lj_carith.c", + "lj_clib.c", + "lj_cparse.c", + "lj_lib.c", + "lj_alloc.c", + // LJLIB_O + "lib_aux.c", + "lib_base.c", + "lib_math.c", + "lib_bit.c", + "lib_string.c", + "lib_table.c", + "lib_io.c", + "lib_os.c", + "lib_package.c", + "lib_debug.c", + "lib_jit.c", + "lib_ffi.c", + "lib_buffer.c", + "lib_init.c", +]; -fn main() { - let luajit_dir = format!("{}/luajit", env!("CARGO_MANIFEST_DIR")); - let out_dir = env::var("OUT_DIR").unwrap(); - let src_dir = format!("{}/luajit/src", out_dir); - let lib_path = format!("{}/lib{}.a", &src_dir, LIB_NAME); - - dbg!(&luajit_dir); - dbg!(&out_dir); - dbg!(&src_dir); - dbg!(&lib_path); - - let mut copy_options = CopyOptions::new(); - copy_options.overwrite = true; - - dir::copy(&luajit_dir, &out_dir, ©_options).expect("Failed to copy LuaJIT source"); - +fn build_gcc(src_dir: &str) { let mut buildcmd = Command::new("make"); buildcmd.current_dir(&src_dir); buildcmd.stderr(Stdio::inherit()); - buildcmd.arg("BUILDMODE=static"); + buildcmd.arg("--no-silent"); + // We do need to cross-compile even here, so that `lj_vm.o` is created + // for the correct architecture. if env::var("CARGO_CFG_WINDOWS").is_ok() { buildcmd.arg("TARGET_SYS=Windows"); buildcmd.arg("CROSS=x86_64-w64-mingw32-"); } if cfg!(target_pointer_width = "32") { - buildcmd.env("HOST_CC", "gcc -m32"); + buildcmd.arg("HOST_CC='gcc -m32'"); buildcmd.arg("-e"); } else { - buildcmd.env("HOST_CC", "gcc"); + buildcmd.arg("HOST_CC='gcc'"); } let mut child = buildcmd.spawn().expect("failed to run make"); @@ -50,11 +114,66 @@ fn main() { { panic!("Failed to build luajit"); } +} + +fn build_msvc(src_dir: &str, out_dir: &str) { + let mut cc = Build::new(); + // cc can't handle many of the `cland-dl`-specific flags, so + // we need to port them manually from a `make -n` run. + cc.out_dir(out_dir) + // `llvm-as` (which the clang-based toolchain for MSVC would use to compile `lj_vm.S` + // assembler) doesn't support some of the GNU-specific directives. + // However, the previous host-targeted compilation already created the + // object, so we simply link that. + .object(format!("{src_dir}/lj_vm.o")) + .define("_FILE_OFFSET_BITS", "64") + .define("_LARGEFILE_SOURCE", None) + .define("LUA_MULTILIB", "\"lib\"") + .define("LUAJIT_UNWIND_EXTERNAL", None) + .flag("-fcolor-diagnostics") + // Disable warnings + .flag("/W0") + .flag("/U _FORTIFY_SOURCE") + // Link statically + .flag("/MT") + // Omit frame pointers + .flag("/Oy"); + + for f in LUAJIT_SRC { + cc.file(format!("{src_dir}/{f}")); + } + + cc.compile(LIB_NAME); +} + +fn main() { + let luajit_dir = format!("{}/luajit", env!("CARGO_MANIFEST_DIR")); + let out_dir = env::var("OUT_DIR").unwrap(); + let src_dir = format!("{}/luajit/src", out_dir); + + dbg!(&luajit_dir); + dbg!(&out_dir); + dbg!(&src_dir); + + let mut copy_options = CopyOptions::new(); + copy_options.overwrite = true; + + dir::copy(&luajit_dir, &out_dir, ©_options).expect("Failed to copy LuaJIT source"); + + // The first run builds with and for the host architecture. + // This also creates all the tools and generated sources that a compilation needs. + build_gcc(&src_dir); + + // Then, for cross-compilation, we can utilize those generated + // sources to re-compile just the library. + if env::var("CARGO_CFG_WINDOWS").is_ok() { + build_msvc(&src_dir, &out_dir); + } println!("cargo:lib-name={}", LIB_NAME); println!("cargo:include={}", src_dir); - println!("cargo:rustc-link-search=native={}", src_dir); - println!("cargo:rustc-link-lib=static={}", LIB_NAME); + println!("cargo:rustc-link-search={}", out_dir); + println!("cargo:rustc-link-lib={}", LIB_NAME); let mut bindings = bindgen::Builder::default(); @@ -74,14 +193,24 @@ fn main() { .ctypes_prefix("libc") .impl_debug(true) .use_core() - .clang_arg("-Iluajit/src") + .detect_include_paths(true) // Make it pretty .rustfmt_bindings(true) .sort_semantically(true) .merge_extern_blocks(true) - .parse_callbacks(Box::new(bindgen::CargoCallbacks)) - .generate() - .expect("Failed to generate bindings"); + .parse_callbacks(Box::new(bindgen::CargoCallbacks)); + + let bindings = if env::var("CARGO_CFG_WINDOWS").is_ok() { + bindings + .clang_arg("-I/xwin/sdk/include/ucrt") + .clang_arg("-I/xwin/sdk/include/um") + .clang_arg("-I/xwin/sdk/include/shared") + .clang_arg("-I/xwin/crt/include") + .generate() + .expect("Failed to generate bindings") + } else { + bindings.generate().expect("Failed to generate bindings") + }; let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); bindings