use std::env; use std::ffi::CString; use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; use failure::{Error, ResultExt}; use lazy_static::lazy_static; use regex::Regex; const QUICKJS_SRC: &str = "quickjs-2019-08-18.tar.xz"; lazy_static! { static ref OUT_DIR: PathBuf = env::var_os("OUT_DIR").expect("OUT_DIR").into(); static ref CARGO_MANIFEST_DIR: PathBuf = env::var_os("CARGO_MANIFEST_DIR") .expect("CARGO_MANIFEST_DIR") .into(); static ref QUICKJS_DIR: PathBuf = OUT_DIR.join(QUICKJS_SRC.split('.').next().unwrap()); } fn unpack_source_files(quickjs_src: &Path, out_dir: &Path) -> Result<(), Error> { println!("extract `quickjs` from {:?} to {:?}", quickjs_src, out_dir); let f = fs::File::open(quickjs_src)?; let r = lzma::LzmaReader::new_decompressor(f)?; tar::Archive::new(r).unpack(&out_dir)?; Ok(()) } fn patch_makefile(makefile: &Path) -> Result<(), Error> { let content = fs::read_to_string(makefile)?; let content = if cfg!(feature = "debug") { Regex::new("^CFLAGS_OPT=(.*) -O2\n")?.replace(&content, "CFLAGS_OPT=$1 -O0 -g\n") } else { content.into() }; let content = if cfg!(feature = "pic") { content .replace("CFLAGS+=$(DEFINES)\n", "CFLAGS+=$(DEFINES) -fPIC\n") .into() } else { content }; fs::rename(makefile, makefile.with_extension("bak"))?; fs::write(makefile, content.as_bytes())?; Ok(()) } fn patch_quickjs(quickjs: &Path) -> Result<(), Error> { let mut content = fs::read_to_string(quickjs)?; if cfg!(feature = "dump_free") { content = content.replace("//#define DUMP_FREE\n", "#define DUMP_FREE\n"); } if cfg!(feature = "dump_closure") { content = content.replace("//#define DUMP_CLOSURE\n", "#define DUMP_CLOSURE\n"); } if cfg!(feature = "dump_bytecode") { content = content.replace("//#define DUMP_BYTECODE", "#define DUMP_BYTECODE"); } if cfg!(feature = "dump_gc") { content = content.replace("//#define DUMP_GC\n", "#define DUMP_GC\n"); } if cfg!(feature = "dump_gc_free") { content = content.replace("//#define DUMP_GC_FREE\n", "#define DUMP_GC_FREE\n"); } if cfg!(feature = "dump_leaks") { content = content.replace("//#define DUMP_LEAKS", "#define DUMP_LEAKS"); } if cfg!(feature = "dump_mem") { content = content.replace("//#define DUMP_MEM\n", "#define DUMP_MEM\n"); } if cfg!(feature = "dump_objects") { content = content.replace("//#define DUMP_OBJECTS", "#define DUMP_OBJECTS"); } if cfg!(feature = "dump_atoms") { content = content.replace("//#define DUMP_ATOMS", "#define DUMP_ATOMS"); } if cfg!(feature = "dump_shapes") { content = content.replace("//#define DUMP_SHAPES", "#define DUMP_SHAPES"); } if cfg!(feature = "dump_module_resolve") { content = content.replace( "//#define DUMP_MODULE_RESOLVE\n", "#define DUMP_MODULE_RESOLVE\n", ); } if cfg!(feature = "dump_promise") { content = content.replace("//#define DUMP_PROMISE\n", "#define DUMP_PROMISE\n"); } if cfg!(feature = "dump_read_object") { content = content.replace("//#define DUMP_READ_OBJECT\n", "#define DUMP_READ_OBJECT\n"); } fs::rename(quickjs, quickjs.with_extension("bak"))?; fs::write(quickjs, content.as_bytes())?; Ok(()) } fn patch_quickjs_libc(quickjs_libc: &Path) -> Result<(), Error> { let mut content = fs::read_to_string(quickjs_libc)?; if cfg!(target_os = "macos") { content = content .replace("(&st.st_atim)", "(&st.st_atimespec)") .replace("(&st.st_mtim)", "(&st.st_mtimespec)") .replace("(&st.st_ctim)", "(&st.st_ctimespec)"); } fs::rename(quickjs_libc, quickjs_libc.with_extension("bak"))?; fs::write(quickjs_libc, content.as_bytes())?; Ok(()) } fn build_libquickjs() -> Result<(), Error> { if !QUICKJS_DIR.join("quickjs.h").is_file() { unpack_source_files( &CARGO_MANIFEST_DIR.join(QUICKJS_SRC).canonicalize()?, OUT_DIR.as_path(), )?; } if !OUT_DIR.join("VERSION").is_file() { fs::copy(QUICKJS_DIR.join("VERSION"), OUT_DIR.join("VERSION"))?; } patch_makefile(&QUICKJS_DIR.join("Makefile"))?; patch_quickjs(&QUICKJS_DIR.join("quickjs.c"))?; patch_quickjs_libc(&QUICKJS_DIR.join("quickjs-libc.c"))?; let repl_c = if cfg!(feature = "bignum") { "repl-bn.c" } else { "repl.c" }; let qjscalc_c = "qjscalc.c"; let quickjs = format!( "quickjs{}{}", if cfg!(feature = "bignum") { ".bn" } else { "" }, if cfg!(feature = "lto") { ".lto" } else { "" } ); let libquickjs = format!("lib{}.a", quickjs); let mut targets = vec![libquickjs]; if cfg!(feature = "repl") { targets.push(repl_c.to_owned()); } if cfg!(feature = "qjscalc") { targets.push(qjscalc_c.to_owned()); } for target in &targets { if !QUICKJS_DIR.join(target).is_file() { println!("make {:?} ...", target); let output = Command::new("make") .arg(target) .current_dir(QUICKJS_DIR.as_path()) .output()?; println!("status: {}", output.status); println!("stdout: {}", CString::new(output.stdout)?.to_string_lossy()); eprintln!("stderr: {}", CString::new(output.stderr)?.to_string_lossy()); } } println!( "cargo:rustc-link-search=native={}", QUICKJS_DIR.to_string_lossy() ); println!("cargo:rustc-link-lib=static={}", quickjs); println!("cargo:rerun-if-changed={}", QUICKJS_SRC); if cfg!(feature = "repl") { cc::Build::new() .file(QUICKJS_DIR.join(repl_c)) .compile("repl"); } if cfg!(feature = "qjscalc") { cc::Build::new() .file(QUICKJS_DIR.join(qjscalc_c)) .compile("qjscalc"); } Ok(()) } #[cfg(feature = "gen")] fn gen_binding_files() -> Result<(), Error> { use failure::err_msg; let raw_file = OUT_DIR.join("raw.rs"); println!("generating binding files to {:?}", raw_file); bindgen::builder() .header(QUICKJS_DIR.join("quickjs-libc.h").to_string_lossy()) .clang_arg(format!("-I{}", QUICKJS_DIR.to_string_lossy())) .whitelist_var("JS_.*") .whitelist_type("JS.*") .whitelist_function("(__)?(JS|JS|js)_.*") .opaque_type("FILE") .blacklist_type("__.*") .default_enum_style(bindgen::EnumVariation::ModuleConsts) .generate() .map_err(|_| err_msg("generate binding file"))? .write_to_file(raw_file) .context("write binding file")?; Ok(()) } #[cfg(not(feature = "gen"))] fn gen_binding_files() -> Result<(), Error> { Ok(()) } fn main() -> Result<(), Error> { match &env::var("CARGO") { Ok(path) if path.ends_with("rls") => {} _ => { build_libquickjs().context("build quickjs library")?; gen_binding_files().context("generate binding files")?; } }; Ok(()) }