jemallocator/jemalloc-sys/build.rs
gnzlbg db85601eac Allow jemalloc-sys to pick jemalloc from git
This PR allows automatically fetching the latest jemalloc from
its dev branch by defining the environment variable
`JEMALLOC_SYS_GIT_DEV_BRANCH`. This is useful when debugging
whether certain bugs are already fixed upstream, as well as
for detecting whether upstream changes break our tests.
This PR adds two build jobs that test the dev branch on the
tier-1 x86_64 linux and osx targets.
2019-04-03 20:03:09 +02:00

448 lines
15 KiB
Rust

// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
extern crate cc;
extern crate fs_extra;
use std::env;
use std::fs;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::process::Command;
// `jemalloc` is known not to work on these targets:
const UNSUPPORTED_TARGETS: &[&str] = &[
"rumprun",
"bitrig",
"emscripten",
"fuchsia",
"redox",
"wasm32",
];
// `jemalloc-sys` is not tested on these targets in CI:
const UNTESTED_TARGETS: &[&str] = &["openbsd", "msvc"];
// `jemalloc`'s background_thread support is known not to work on these targets:
const NO_BG_THREAD_TARGETS: &[&str] = &["musl"];
// targets that don't support unprefixed `malloc`
//
// “it was found that the `realpath` function in libc would allocate with libc malloc
// (not jemalloc malloc), and then the standard library would free with jemalloc free,
// causing a segfault.”
// https://github.com/rust-lang/rust/commit/e3b414d8612314e74e2b0ebde1ed5c6997d28e8d
// https://github.com/rust-lang/rust/commit/536011d929ecbd1170baf34e09580e567c971f95
// https://github.com/rust-lang/rust/commit/9f3de647326fbe50e0e283b9018ab7c41abccde3
// https://github.com/rust-lang/rust/commit/ed015456a114ae907a36af80c06f81ea93182a24
const NO_UNPREFIXED_MALLOC: &[&str] = &["android", "dragonfly", "musl", "darwin"];
macro_rules! info {
($($args:tt)*) => { println!($($args)*) }
}
macro_rules! warning {
($arg:tt, $($args:tt)*) => {
println!(concat!(concat!("cargo:warning=\"", $arg), "\""), $($args)*)
}
}
fn main() {
let target = env::var("TARGET").expect("TARGET was not set");
let host = env::var("HOST").expect("HOST was not set");
let num_jobs = env::var("NUM_JOBS").expect("NUM_JOBS was not set");
let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR was not set"));
let src_dir = env::current_dir().expect("failed to get current directory");
info!("TARGET={}", target.clone());
info!("HOST={}", host.clone());
info!("NUM_JOBS={}", num_jobs.clone());
info!("OUT_DIR={:?}", out_dir);
let build_dir = out_dir.join("build");
info!("BUILD_DIR={:?}", build_dir);
info!("SRC_DIR={:?}", src_dir);
if UNSUPPORTED_TARGETS.iter().any(|i| target.contains(i)) {
panic!("jemalloc does not support target: {}", target);
}
if UNTESTED_TARGETS.iter().any(|i| target.contains(i)) {
warning!("jemalloc support for `{}` is untested", target);
}
if let Some(jemalloc) = env::var_os("JEMALLOC_OVERRIDE") {
info!("jemalloc override set");
let jemalloc = PathBuf::from(jemalloc);
assert!(
jemalloc.exists(),
"Path to `jemalloc` in `JEMALLOC_OVERRIDE={}` does not exist",
jemalloc.display()
);
println!(
"cargo:rustc-link-search=native={}",
jemalloc.parent().unwrap().display()
);
let stem = jemalloc.file_stem().unwrap().to_str().unwrap();
let name = jemalloc.file_name().unwrap().to_str().unwrap();
let kind = if name.ends_with(".a") {
"static"
} else {
"dylib"
};
println!("cargo:rustc-link-lib={}={}", kind, &stem[3..]);
return;
}
fs::create_dir_all(&build_dir).unwrap();
// Disable -Wextra warnings - jemalloc doesn't compile free of warnings with
// it enabled: https://github.com/jemalloc/jemalloc/issues/1196
let compiler = cc::Build::new().extra_warnings(false).get_compiler();
let cflags = compiler
.args()
.iter()
.map(|s| s.to_str().unwrap())
.collect::<Vec<_>>()
.join(" ");
info!("CC={:?}", compiler.path());
info!("CFLAGS={:?}", cflags);
assert!(out_dir.exists(), "OUT_DIR does not exist");
let (jemalloc_repo_dir, run_autoconf) = if env::var("JEMALLOC_SYS_GIT_DEV_BRANCH").is_ok() {
let jemalloc_repo = out_dir.join("jemalloc_repo");
if jemalloc_repo.exists() {
fs::remove_dir_all(jemalloc_repo.clone()).unwrap();
}
let mut cmd = Command::new("git");
cmd.arg("clone")
.arg("--depth=1")
.arg("--branch=dev")
.arg("--")
.arg("https://github.com/jemalloc/jemalloc")
.arg(format!("{}", jemalloc_repo.display()));
run(&mut cmd);
(jemalloc_repo, true)
} else {
(PathBuf::from("jemalloc"), false)
};
info!("JEMALLOC_REPO_DIR={:?}", jemalloc_repo_dir);
let jemalloc_src_dir = out_dir.join("jemalloc");
info!("JEMALLOC_SRC_DIR={:?}", jemalloc_src_dir);
if jemalloc_src_dir.exists() {
fs::remove_dir_all(jemalloc_src_dir.clone()).unwrap();
}
// Copy jemalloc submodule to the OUT_DIR
let mut copy_options = fs_extra::dir::CopyOptions::new();
copy_options.overwrite = true;
copy_options.copy_inside = true;
fs_extra::dir::copy(&jemalloc_repo_dir, &jemalloc_src_dir, &copy_options)
.expect("failed to copy jemalloc source code to OUT_DIR");
assert!(jemalloc_src_dir.exists());
// Configuration files
let config_files = ["configure" /*"VERSION"*/];
// Verify that the configuration files are up-to-date
let verify_configure = env::var("JEMALLOC_SYS_VERIFY_CONFIGURE").is_ok();
if verify_configure || run_autoconf {
info!("Verifying that configuration files in `configure/` are up-to-date... ");
// The configuration file from the configure/directory should be used.
// The jemalloc git submodule shouldn't contain any configuration files.
assert!(
!jemalloc_src_dir.join("configure").exists(),
"the jemalloc submodule contains configuration files"
);
// Run autoconf:
let mut cmd = Command::new("autoconf");
cmd.current_dir(jemalloc_src_dir.clone());
run(&mut cmd);
for f in &config_files {
use std::io::Read;
fn read_content(file_path: &Path) -> String {
assert!(
file_path.exists(),
"config file path `{}` does not exist",
file_path.display()
);
let mut file = File::open(file_path).expect("file not found");
let mut content = String::new();
file.read_to_string(&mut content)
.expect("failed to read file");
content
}
if verify_configure {
let current = read_content(&jemalloc_src_dir.join(f));
let reference = read_content(&Path::new("configure").join(f));
assert_eq!(
current, reference,
"the current and reference configuration files \"{}\" differ",
f
);
}
}
} else {
// Copy the configuration files to jemalloc's source directory
for f in &config_files {
fs::copy(Path::new("configure").join(f), jemalloc_src_dir.join(f))
.expect("failed to copy config file to OUT_DIR");
}
}
// Run configure:
let configure = jemalloc_src_dir.join("configure");
let mut cmd = Command::new("sh");
cmd.arg(
configure
.to_str()
.unwrap()
.replace("C:\\", "/c/")
.replace("\\", "/"),
)
.current_dir(&build_dir)
.env("CC", compiler.path())
.env("CFLAGS", cflags.clone())
.env("LDFLAGS", cflags.clone())
.env("CPPFLAGS", cflags.clone())
.arg("--disable-cxx");
if target.contains("ios") {
// newer iOS deviced have 16kb page sizes:
// closed: https://github.com/alexcrichton/jemallocator/issues/68
cmd.arg("--with-lg-page=14");
}
// collect `malloc_conf` string:
let mut malloc_conf = String::new();
if let Some(bg) = BackgroundThreadSupport::new(&target) {
// `jemalloc` is compiled with background thread run-time support on
// available platforms by default so there is nothing to do to enable
// it.
if bg.always_enabled {
// Background thread support does not enable background threads at
// run-time, just support for enabling them via run-time configuration
// options (they are disabled by default)
// The `enable_background_threads` cargo feature forces background
// threads to be enabled at run-time by default:
malloc_conf += "background_thread:true";
}
} else {
// Background thread run-time support is disabled by
// disabling background threads at compile-time:
malloc_conf += "background_thread:false";
}
if let Ok(malloc_conf_opts) = env::var("JEMALLOC_SYS_WITH_MALLOC_CONF") {
malloc_conf += &format!(
"{}{}",
if malloc_conf.is_empty() { "" } else { "," },
malloc_conf_opts
);
}
if !malloc_conf.is_empty() {
info!("--with-malloc-conf={}", malloc_conf);
cmd.arg(format!("--with-malloc-conf={}", malloc_conf));
}
if let Ok(lg_page) = env::var("JEMALLOC_SYS_WITH_LG_PAGE") {
info!("--with-lg-page={}", lg_page);
cmd.arg(format!("--with-lg-page={}", lg_page));
}
if let Ok(lg_hugepage) = env::var("JEMALLOC_SYS_WITH_LG_HUGEPAGE") {
info!("--with-lg-hugepage={}", lg_hugepage);
cmd.arg(format!("--with-lg-hugepage={}", lg_hugepage));
}
if let Ok(lg_quantum) = env::var("JEMALLOC_SYS_WITH_LG_QUANTUM") {
info!("--with-lg-quantum={}", lg_quantum);
cmd.arg(format!("--with-lg-quantum={}", lg_quantum));
}
if let Ok(lg_vaddr) = env::var("JEMALLOC_SYS_WITH_LG_VADDR") {
info!("--with-lg-vaddr={}", lg_vaddr);
cmd.arg(format!("--with-lg-vaddr={}", lg_vaddr));
}
let mut use_prefix =
env::var("CARGO_FEATURE_UNPREFIXED_MALLOC_ON_SUPPORTED_PLATFORMS").is_err();
if !use_prefix && NO_UNPREFIXED_MALLOC.iter().any(|i| target.contains(i)) {
warning!(
"Unprefixed `malloc` requested on unsupported platform `{}` => using prefixed `malloc`",
target
);
use_prefix = true;
}
if use_prefix {
cmd.arg("--with-jemalloc-prefix=_rjem_");
println!("cargo:rustc-cfg=prefixed");
info!("--with-jemalloc-prefix=_rjem_");
}
cmd.arg("--with-private-namespace=_rjem_");
if env::var("CARGO_FEATURE_DEBUG").is_ok() {
info!("CARGO_FEATURE_DEBUG set");
cmd.arg("--enable-debug");
}
if env::var("CARGO_FEATURE_PROFILING").is_ok() {
info!("CARGO_FEATURE_PROFILING set");
cmd.arg("--enable-prof");
}
if env::var("CARGO_FEATURE_STATS").is_ok() {
info!("CARGO_FEATURE_STATS set");
cmd.arg("--enable-stats");
}
if env::var("CARGO_FEATURE_DISABLE_INITIAL_EXEC_TLS").is_ok() {
info!("CARGO_FEATURE_DISABLE_INITIAL_EXEC_TLS set");
cmd.arg("--disable-initial-exec-tls");
}
cmd.arg(format!("--host={}", gnu_target(&target)));
cmd.arg(format!("--build={}", gnu_target(&host)));
cmd.arg(format!("--prefix={}", out_dir.display()));
run(&mut cmd);
// Make:
let make = make_cmd(&host);
run(Command::new(make)
.current_dir(&build_dir)
.arg("srcroot=../jemalloc/")
.arg("-j")
.arg(num_jobs.clone()));
if env::var("JEMALLOC_SYS_RUN_JEMALLOC_TESTS").is_ok() {
info!("Building and running jemalloc tests...");
// Make tests:
run(Command::new(make)
.current_dir(&build_dir)
.arg("srcroot=../jemalloc/")
.arg("-j")
.arg(num_jobs.clone())
.arg("tests"));
// Run tests:
run(Command::new(make)
.current_dir(&build_dir)
.arg("srcroot=../jemalloc/")
.arg("check"));
}
// Make install:
run(Command::new(make)
.current_dir(&build_dir)
.arg("srcroot=../jemalloc/")
.arg("install_lib_static")
.arg("install_include")
.arg("-j")
.arg(num_jobs.clone()));
println!("cargo:root={}", out_dir.display());
// Linkage directives to pull in jemalloc and its dependencies.
//
// On some platforms we need to be sure to link in `pthread` which jemalloc
// depends on, and specifically on android we need to also link to libgcc.
// Currently jemalloc is compiled with gcc which will generate calls to
// intrinsics that are libgcc specific (e.g. those intrinsics aren't present in
// libcompiler-rt), so link that in to get that support.
if target.contains("windows") {
println!("cargo:rustc-link-lib=static=jemalloc");
} else {
println!("cargo:rustc-link-lib=static=jemalloc_pic");
}
println!("cargo:rustc-link-search=native={}/lib", build_dir.display());
if target.contains("android") {
println!("cargo:rustc-link-lib=gcc");
} else if !target.contains("windows") {
println!("cargo:rustc-link-lib=pthread");
}
println!("cargo:rerun-if-changed=jemalloc");
}
fn run(cmd: &mut Command) {
println!("running: {:?}", cmd);
let status = match cmd.status() {
Ok(status) => status,
Err(e) => panic!("failed to execute command: {}", e),
};
if !status.success() {
panic!(
"command did not execute successfully: {:?}\n\
expected success, got: {}",
cmd, status
);
}
}
fn gnu_target(target: &str) -> String {
match target {
"i686-pc-windows-msvc" => "i686-pc-win32".to_string(),
"x86_64-pc-windows-msvc" => "x86_64-pc-win32".to_string(),
"i686-pc-windows-gnu" => "i686-w64-mingw32".to_string(),
"x86_64-pc-windows-gnu" => "x86_64-w64-mingw32".to_string(),
s => s.to_string(),
}
}
fn make_cmd(host: &str) -> &'static str {
const GMAKE_HOSTS: &[&str] = &["bitrig", "dragonfly", "freebsd", "netbsd", "openbsd"];
if GMAKE_HOSTS.iter().any(|i| host.contains(i)) {
"gmake"
} else if host.contains("windows") {
"mingw32-make"
} else {
"make"
}
}
struct BackgroundThreadSupport {
always_enabled: bool,
}
impl BackgroundThreadSupport {
fn new(target: &str) -> Option<Self> {
let runtime_support = env::var("CARGO_FEATURE_BACKGROUND_THREADS_RUNTIME_SUPPORT").is_ok();
let always_enabled = env::var("CARGO_FEATURE_BACKGROUND_THREADS").is_ok();
if !runtime_support {
assert!(
!always_enabled,
"enabling `background_threads` requires `background_threads_runtime_support`"
);
return None;
}
if NO_BG_THREAD_TARGETS.iter().any(|i| target.contains(i)) {
warning!(
"`background_threads_runtime_support` not supported for `{}`",
target
);
}
Some(Self { always_enabled })
}
}