1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10 
11 extern crate cc;
12 extern crate fs_extra;
13 
14 use std::env;
15 use std::fs;
16 use std::fs::File;
17 use std::path::{Path, PathBuf};
18 use std::process::Command;
19 
20 // `jemalloc` is known not to work on these targets:
21 const UNSUPPORTED_TARGETS: &[&str] = &[
22     "rumprun",
23     "bitrig",
24     "emscripten",
25     "fuchsia",
26     "redox",
27     "wasm32",
28 ];
29 
30 // `jemalloc-sys` is not tested on these targets in CI:
31 const UNTESTED_TARGETS: &[&str] = &["openbsd", "msvc"];
32 
33 // `jemalloc`'s background_thread support is known not to work on these targets:
34 const NO_BG_THREAD_TARGETS: &[&str] = &["musl"];
35 
36 // targets that don't support unprefixed `malloc`
37 //
38 // “it was found that the `realpath` function in libc would allocate with libc malloc
39 //  (not jemalloc malloc), and then the standard library would free with jemalloc free,
40 //  causing a segfault.”
41 // https://github.com/rust-lang/rust/commit/e3b414d8612314e74e2b0ebde1ed5c6997d28e8d
42 // https://github.com/rust-lang/rust/commit/536011d929ecbd1170baf34e09580e567c971f95
43 // https://github.com/rust-lang/rust/commit/9f3de647326fbe50e0e283b9018ab7c41abccde3
44 // https://github.com/rust-lang/rust/commit/ed015456a114ae907a36af80c06f81ea93182a24
45 const NO_UNPREFIXED_MALLOC: &[&str] = &["android", "dragonfly", "musl", "darwin"];
46 
47 macro_rules! info {
48     ($($args:tt)*) => { println!($($args)*) }
49 }
50 
51 macro_rules! warning {
52     ($arg:tt, $($args:tt)*) => {
53         println!(concat!(concat!("cargo:warning=\"", $arg), "\""), $($args)*)
54     }
55 }
56 
57 fn main() {
58     let target = env::var("TARGET").expect("TARGET was not set");
59     let host = env::var("HOST").expect("HOST was not set");
60     let num_jobs = env::var("NUM_JOBS").expect("NUM_JOBS was not set");
61     let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR was not set"));
62     let src_dir = env::current_dir().expect("failed to get current directory");
63 
64     info!("TARGET={}", target.clone());
65     info!("HOST={}", host.clone());
66     info!("NUM_JOBS={}", num_jobs.clone());
67     info!("OUT_DIR={:?}", out_dir);
68     let build_dir = out_dir.join("build");
69     info!("BUILD_DIR={:?}", build_dir);
70     info!("SRC_DIR={:?}", src_dir);
71 
72     if UNSUPPORTED_TARGETS.iter().any(|i| target.contains(i)) {
73         panic!("jemalloc does not support target: {}", target);
74     }
75 
76     if UNTESTED_TARGETS.iter().any(|i| target.contains(i)) {
77         warning!("jemalloc support for `{}` is untested", target);
78     }
79 
80     if let Some(jemalloc) = env::var_os("JEMALLOC_OVERRIDE") {
81         info!("jemalloc override set");
82         let jemalloc = PathBuf::from(jemalloc);
83         assert!(
84             jemalloc.exists(),
85             "Path to `jemalloc` in `JEMALLOC_OVERRIDE={}` does not exist",
86             jemalloc.display()
87         );
88         println!(
89             "cargo:rustc-link-search=native={}",
90             jemalloc.parent().unwrap().display()
91         );
92         let stem = jemalloc.file_stem().unwrap().to_str().unwrap();
93         let name = jemalloc.file_name().unwrap().to_str().unwrap();
94         let kind = if name.ends_with(".a") {
95             "static"
96         } else {
97             "dylib"
98         };
99         println!("cargo:rustc-link-lib={}={}", kind, &stem[3..]);
100         return;
101     }
102 
103     fs::create_dir_all(&build_dir).unwrap();
104     // Disable -Wextra warnings - jemalloc doesn't compile free of warnings with
105     // it enabled: https://github.com/jemalloc/jemalloc/issues/1196
106     let compiler = cc::Build::new().extra_warnings(false).get_compiler();
107     let cflags = compiler
108         .args()
109         .iter()
110         .map(|s| s.to_str().unwrap())
111         .collect::<Vec<_>>()
112         .join(" ");
113     info!("CC={:?}", compiler.path());
114     info!("CFLAGS={:?}", cflags);
115 
116     assert!(out_dir.exists(), "OUT_DIR does not exist");
117     let (jemalloc_repo_dir, run_autoconf) = if env::var("JEMALLOC_SYS_GIT_DEV_BRANCH").is_ok() {
118         let jemalloc_repo = out_dir.join("jemalloc_repo");
119         if jemalloc_repo.exists() {
120             fs::remove_dir_all(jemalloc_repo.clone()).unwrap();
121         }
122         let mut cmd = Command::new("git");
123         cmd.arg("clone")
124             .arg("--depth=1")
125             .arg("--branch=dev")
126             .arg("--")
127             .arg("https://github.com/jemalloc/jemalloc")
128             .arg(format!("{}", jemalloc_repo.display()));
129         run(&mut cmd);
130         (jemalloc_repo, true)
131     } else {
132         (PathBuf::from("jemalloc"), false)
133     };
134     info!("JEMALLOC_REPO_DIR={:?}", jemalloc_repo_dir);
135 
136     let jemalloc_src_dir = out_dir.join("jemalloc");
137     info!("JEMALLOC_SRC_DIR={:?}", jemalloc_src_dir);
138 
139     if jemalloc_src_dir.exists() {
140         fs::remove_dir_all(jemalloc_src_dir.clone()).unwrap();
141     }
142 
143     // Copy jemalloc submodule to the OUT_DIR
144     let mut copy_options = fs_extra::dir::CopyOptions::new();
145     copy_options.overwrite = true;
146     copy_options.copy_inside = true;
147     fs_extra::dir::copy(&jemalloc_repo_dir, &jemalloc_src_dir, &copy_options)
148         .expect("failed to copy jemalloc source code to OUT_DIR");
149     assert!(jemalloc_src_dir.exists());
150 
151     // Configuration files
152     let config_files = ["configure" /*"VERSION"*/];
153 
154     // Verify that the configuration files are up-to-date
155     let verify_configure = env::var("JEMALLOC_SYS_VERIFY_CONFIGURE").is_ok();
156     if verify_configure || run_autoconf {
157         info!("Verifying that configuration files in `configure/` are up-to-date... ");
158 
159         // The configuration file from the configure/directory should be used.
160         // The jemalloc git submodule shouldn't contain any configuration files.
161         assert!(
162             !jemalloc_src_dir.join("configure").exists(),
163             "the jemalloc submodule contains configuration files"
164         );
165 
166         // Run autoconf:
167         let mut cmd = Command::new("autoconf");
168         cmd.current_dir(jemalloc_src_dir.clone());
169         run(&mut cmd);
170 
171         for f in &config_files {
172             use std::io::Read;
173             fn read_content(file_path: &Path) -> String {
174                 assert!(
175                     file_path.exists(),
176                     "config file path `{}` does not exist",
177                     file_path.display()
178                 );
179                 let mut file = File::open(file_path).expect("file not found");
180                 let mut content = String::new();
181                 file.read_to_string(&mut content)
182                     .expect("failed to read file");
183                 content
184             }
185 
186             if verify_configure {
187                 let current = read_content(&jemalloc_src_dir.join(f));
188                 let reference = read_content(&Path::new("configure").join(f));
189                 assert_eq!(
190                     current, reference,
191                     "the current and reference configuration files \"{}\" differ",
192                     f
193                 );
194             }
195         }
196     } else {
197         // Copy the configuration files to jemalloc's source directory
198         for f in &config_files {
199             fs::copy(Path::new("configure").join(f), jemalloc_src_dir.join(f))
200                 .expect("failed to copy config file to OUT_DIR");
201         }
202     }
203 
204     // Run configure:
205     let configure = jemalloc_src_dir.join("configure");
206     let mut cmd = Command::new("sh");
207     cmd.arg(
208         configure
209             .to_str()
210             .unwrap()
211             .replace("C:\\", "/c/")
212             .replace("\\", "/"),
213     )
214     .current_dir(&build_dir)
215     .env("CC", compiler.path())
216     .env("CFLAGS", cflags.clone())
217     .env("LDFLAGS", cflags.clone())
218     .env("CPPFLAGS", cflags.clone())
219     .arg("--disable-cxx");
220 
221     if target.contains("ios") {
222         // newer iOS deviced have 16kb page sizes:
223         // closed: https://github.com/gnzlbg/jemallocator/issues/68
224         cmd.arg("--with-lg-page=14");
225     }
226 
227     // collect `malloc_conf` string:
228     let mut malloc_conf = String::new();
229 
230     if let Some(bg) = BackgroundThreadSupport::new(&target) {
231         // `jemalloc` is compiled with background thread run-time support on
232         // available platforms by default so there is nothing to do to enable
233         // it.
234 
235         if bg.always_enabled {
236             // Background thread support does not enable background threads at
237             // run-time, just support for enabling them via run-time configuration
238             // options (they are disabled by default)
239 
240             // The `enable_background_threads` cargo feature forces background
241             // threads to be enabled at run-time by default:
242             malloc_conf += "background_thread:true";
243         }
244     } else {
245         // Background thread run-time support is disabled by
246         // disabling background threads at compile-time:
247         malloc_conf += "background_thread:false";
248     }
249 
250     if let Ok(malloc_conf_opts) = env::var("JEMALLOC_SYS_WITH_MALLOC_CONF") {
251         malloc_conf += &format!(
252             "{}{}",
253             if malloc_conf.is_empty() { "" } else { "," },
254             malloc_conf_opts
255         );
256     }
257 
258     if !malloc_conf.is_empty() {
259         info!("--with-malloc-conf={}", malloc_conf);
260         cmd.arg(format!("--with-malloc-conf={}", malloc_conf));
261     }
262 
263     if let Ok(lg_page) = env::var("JEMALLOC_SYS_WITH_LG_PAGE") {
264         info!("--with-lg-page={}", lg_page);
265         cmd.arg(format!("--with-lg-page={}", lg_page));
266     }
267 
268     if let Ok(lg_hugepage) = env::var("JEMALLOC_SYS_WITH_LG_HUGEPAGE") {
269         info!("--with-lg-hugepage={}", lg_hugepage);
270         cmd.arg(format!("--with-lg-hugepage={}", lg_hugepage));
271     }
272 
273     if let Ok(lg_quantum) = env::var("JEMALLOC_SYS_WITH_LG_QUANTUM") {
274         info!("--with-lg-quantum={}", lg_quantum);
275         cmd.arg(format!("--with-lg-quantum={}", lg_quantum));
276     }
277 
278     if let Ok(lg_vaddr) = env::var("JEMALLOC_SYS_WITH_LG_VADDR") {
279         info!("--with-lg-vaddr={}", lg_vaddr);
280         cmd.arg(format!("--with-lg-vaddr={}", lg_vaddr));
281     }
282 
283     let mut use_prefix =
284         env::var("CARGO_FEATURE_UNPREFIXED_MALLOC_ON_SUPPORTED_PLATFORMS").is_err();
285 
286     if !use_prefix && NO_UNPREFIXED_MALLOC.iter().any(|i| target.contains(i)) {
287         warning!(
288             "Unprefixed `malloc` requested on unsupported platform `{}` => using prefixed `malloc`",
289             target
290         );
291         use_prefix = true;
292     }
293 
294     if use_prefix {
295         cmd.arg("--with-jemalloc-prefix=_rjem_");
296         println!("cargo:rustc-cfg=prefixed");
297         info!("--with-jemalloc-prefix=_rjem_");
298     }
299 
300     cmd.arg("--with-private-namespace=_rjem_");
301 
302     if env::var("CARGO_FEATURE_DEBUG").is_ok() {
303         info!("CARGO_FEATURE_DEBUG set");
304         cmd.arg("--enable-debug");
305     }
306 
307     if env::var("CARGO_FEATURE_PROFILING").is_ok() {
308         info!("CARGO_FEATURE_PROFILING set");
309         cmd.arg("--enable-prof");
310     }
311 
312     if env::var("CARGO_FEATURE_STATS").is_ok() {
313         info!("CARGO_FEATURE_STATS set");
314         cmd.arg("--enable-stats");
315     }
316 
317     if env::var("CARGO_FEATURE_DISABLE_INITIAL_EXEC_TLS").is_ok() {
318         info!("CARGO_FEATURE_DISABLE_INITIAL_EXEC_TLS set");
319         cmd.arg("--disable-initial-exec-tls");
320     }
321 
322     cmd.arg(format!("--host={}", gnu_target(&target)));
323     cmd.arg(format!("--build={}", gnu_target(&host)));
324     cmd.arg(format!("--prefix={}", out_dir.display()));
325 
326     run(&mut cmd);
327 
328     // Make:
329     let make = make_cmd(&host);
330     run(Command::new(make)
331         .current_dir(&build_dir)
332         .arg("srcroot=../jemalloc/")
333         .arg("-j")
334         .arg(num_jobs.clone()));
335 
336     if env::var("JEMALLOC_SYS_RUN_JEMALLOC_TESTS").is_ok() {
337         info!("Building and running jemalloc tests...");
338         // Make tests:
339         run(Command::new(make)
340             .current_dir(&build_dir)
341             .arg("srcroot=../jemalloc/")
342             .arg("-j")
343             .arg(num_jobs.clone())
344             .arg("tests"));
345 
346         // Run tests:
347         run(Command::new(make)
348             .current_dir(&build_dir)
349             .arg("srcroot=../jemalloc/")
350             .arg("check"));
351     }
352 
353     // Make install:
354     run(Command::new(make)
355         .current_dir(&build_dir)
356         .arg("srcroot=../jemalloc/")
357         .arg("install_lib_static")
358         .arg("install_include")
359         .arg("-j")
360         .arg(num_jobs.clone()));
361 
362     println!("cargo:root={}", out_dir.display());
363 
364     // Linkage directives to pull in jemalloc and its dependencies.
365     //
366     // On some platforms we need to be sure to link in `pthread` which jemalloc
367     // depends on, and specifically on android we need to also link to libgcc.
368     // Currently jemalloc is compiled with gcc which will generate calls to
369     // intrinsics that are libgcc specific (e.g. those intrinsics aren't present in
370     // libcompiler-rt), so link that in to get that support.
371     if target.contains("windows") {
372         println!("cargo:rustc-link-lib=static=jemalloc");
373     } else {
374         println!("cargo:rustc-link-lib=static=jemalloc_pic");
375     }
376     println!("cargo:rustc-link-search=native={}/lib", build_dir.display());
377     if target.contains("android") {
378         println!("cargo:rustc-link-lib=gcc");
379     } else if !target.contains("windows") {
380         println!("cargo:rustc-link-lib=pthread");
381     }
382     println!("cargo:rerun-if-changed=jemalloc");
383 }
384 
385 fn run(cmd: &mut Command) {
386     println!("running: {:?}", cmd);
387     let status = match cmd.status() {
388         Ok(status) => status,
389         Err(e) => panic!("failed to execute command: {}", e),
390     };
391     if !status.success() {
392         panic!(
393             "command did not execute successfully: {:?}\n\
394              expected success, got: {}",
395             cmd, status
396         );
397     }
398 }
399 
400 fn gnu_target(target: &str) -> String {
401     match target {
402         "i686-pc-windows-msvc" => "i686-pc-win32".to_string(),
403         "x86_64-pc-windows-msvc" => "x86_64-pc-win32".to_string(),
404         "i686-pc-windows-gnu" => "i686-w64-mingw32".to_string(),
405         "x86_64-pc-windows-gnu" => "x86_64-w64-mingw32".to_string(),
406         s => s.to_string(),
407     }
408 }
409 
410 fn make_cmd(host: &str) -> &'static str {
411     const GMAKE_HOSTS: &[&str] = &["bitrig", "dragonfly", "freebsd", "netbsd", "openbsd"];
412     if GMAKE_HOSTS.iter().any(|i| host.contains(i)) {
413         "gmake"
414     } else if host.contains("windows") {
415         "mingw32-make"
416     } else {
417         "make"
418     }
419 }
420 
421 struct BackgroundThreadSupport {
422     always_enabled: bool,
423 }
424 
425 impl BackgroundThreadSupport {
426     fn new(target: &str) -> Option<Self> {
427         let runtime_support = env::var("CARGO_FEATURE_BACKGROUND_THREADS_RUNTIME_SUPPORT").is_ok();
428         let always_enabled = env::var("CARGO_FEATURE_BACKGROUND_THREADS").is_ok();
429 
430         if !runtime_support {
431             assert!(
432                 !always_enabled,
433                 "enabling `background_threads` requires `background_threads_runtime_support`"
434             );
435             return None;
436         }
437 
438         if NO_BG_THREAD_TARGETS.iter().any(|i| target.contains(i)) {
439             warning!(
440                 "`background_threads_runtime_support` not supported for `{}`",
441                 target
442             );
443         }
444 
445         Some(Self { always_enabled })
446     }
447 }
448