]> git.lizzy.rs Git - rust.git/blob - src/build_helper/lib.rs
Rollup merge of #63416 - RalfJung:apfloat, r=eddyb
[rust.git] / src / build_helper / lib.rs
1 use std::fs::File;
2 use std::path::{Path, PathBuf};
3 use std::process::{Command, Stdio};
4 use std::time::{SystemTime, UNIX_EPOCH};
5 use std::{env, fs};
6 use std::thread;
7
8 /// A helper macro to `unwrap` a result except also print out details like:
9 ///
10 /// * The file/line of the panic
11 /// * The expression that failed
12 /// * The error itself
13 ///
14 /// This is currently used judiciously throughout the build system rather than
15 /// using a `Result` with `try!`, but this may change one day...
16 #[macro_export]
17 macro_rules! t {
18     ($e:expr) => {
19         match $e {
20             Ok(e) => e,
21             Err(e) => panic!("{} failed with {}", stringify!($e), e),
22         }
23     };
24 }
25
26 // Because Cargo adds the compiler's dylib path to our library search path, llvm-config may
27 // break: the dylib path for the compiler, as of this writing, contains a copy of the LLVM
28 // shared library, which means that when our freshly built llvm-config goes to load it's
29 // associated LLVM, it actually loads the compiler's LLVM. In particular when building the first
30 // compiler (i.e., in stage 0) that's a problem, as the compiler's LLVM is likely different from
31 // the one we want to use. As such, we restore the environment to what bootstrap saw. This isn't
32 // perfect -- we might actually want to see something from Cargo's added library paths -- but
33 // for now it works.
34 pub fn restore_library_path() {
35     println!("cargo:rerun-if-env-changed=REAL_LIBRARY_PATH_VAR");
36     println!("cargo:rerun-if-env-changed=REAL_LIBRARY_PATH");
37     let key = env::var_os("REAL_LIBRARY_PATH_VAR").expect("REAL_LIBRARY_PATH_VAR");
38     if let Some(env) = env::var_os("REAL_LIBRARY_PATH") {
39         env::set_var(&key, &env);
40     } else {
41         env::remove_var(&key);
42     }
43 }
44
45 /// Run the command, printing what we are running.
46 pub fn run_verbose(cmd: &mut Command) {
47     println!("running: {:?}", cmd);
48     run(cmd);
49 }
50
51 pub fn run(cmd: &mut Command) {
52     if !try_run(cmd) {
53         std::process::exit(1);
54     }
55 }
56
57 pub fn try_run(cmd: &mut Command) -> bool {
58     let status = match cmd.status() {
59         Ok(status) => status,
60         Err(e) => fail(&format!(
61             "failed to execute command: {:?}\nerror: {}",
62             cmd, e
63         )),
64     };
65     if !status.success() {
66         println!(
67             "\n\ncommand did not execute successfully: {:?}\n\
68              expected success, got: {}\n\n",
69             cmd, status
70         );
71     }
72     status.success()
73 }
74
75 pub fn run_suppressed(cmd: &mut Command) {
76     if !try_run_suppressed(cmd) {
77         std::process::exit(1);
78     }
79 }
80
81 pub fn try_run_suppressed(cmd: &mut Command) -> bool {
82     let output = match cmd.output() {
83         Ok(status) => status,
84         Err(e) => fail(&format!(
85             "failed to execute command: {:?}\nerror: {}",
86             cmd, e
87         )),
88     };
89     if !output.status.success() {
90         println!(
91             "\n\ncommand did not execute successfully: {:?}\n\
92              expected success, got: {}\n\n\
93              stdout ----\n{}\n\
94              stderr ----\n{}\n\n",
95             cmd,
96             output.status,
97             String::from_utf8_lossy(&output.stdout),
98             String::from_utf8_lossy(&output.stderr)
99         );
100     }
101     output.status.success()
102 }
103
104 pub fn gnu_target(target: &str) -> &str {
105     match target {
106         "i686-pc-windows-msvc" => "i686-pc-win32",
107         "x86_64-pc-windows-msvc" => "x86_64-pc-win32",
108         "i686-pc-windows-gnu" => "i686-w64-mingw32",
109         "x86_64-pc-windows-gnu" => "x86_64-w64-mingw32",
110         s => s,
111     }
112 }
113
114 pub fn make(host: &str) -> PathBuf {
115     if host.contains("dragonfly") || host.contains("freebsd")
116         || host.contains("netbsd") || host.contains("openbsd")
117     {
118         PathBuf::from("gmake")
119     } else {
120         PathBuf::from("make")
121     }
122 }
123
124 pub fn output(cmd: &mut Command) -> String {
125     let output = match cmd.stderr(Stdio::inherit()).output() {
126         Ok(status) => status,
127         Err(e) => fail(&format!(
128             "failed to execute command: {:?}\nerror: {}",
129             cmd, e
130         )),
131     };
132     if !output.status.success() {
133         panic!(
134             "command did not execute successfully: {:?}\n\
135              expected success, got: {}",
136             cmd, output.status
137         );
138     }
139     String::from_utf8(output.stdout).unwrap()
140 }
141
142 pub fn rerun_if_changed_anything_in_dir(dir: &Path) {
143     let mut stack = dir.read_dir()
144         .unwrap()
145         .map(|e| e.unwrap())
146         .filter(|e| &*e.file_name() != ".git")
147         .collect::<Vec<_>>();
148     while let Some(entry) = stack.pop() {
149         let path = entry.path();
150         if entry.file_type().unwrap().is_dir() {
151             stack.extend(path.read_dir().unwrap().map(|e| e.unwrap()));
152         } else {
153             println!("cargo:rerun-if-changed={}", path.display());
154         }
155     }
156 }
157
158 /// Returns the last-modified time for `path`, or zero if it doesn't exist.
159 pub fn mtime(path: &Path) -> SystemTime {
160     fs::metadata(path)
161         .and_then(|f| f.modified())
162         .unwrap_or(UNIX_EPOCH)
163 }
164
165 /// Returns `true` if `dst` is up to date given that the file or files in `src`
166 /// are used to generate it.
167 ///
168 /// Uses last-modified time checks to verify this.
169 pub fn up_to_date(src: &Path, dst: &Path) -> bool {
170     if !dst.exists() {
171         return false;
172     }
173     let threshold = mtime(dst);
174     let meta = match fs::metadata(src) {
175         Ok(meta) => meta,
176         Err(e) => panic!("source {:?} failed to get metadata: {}", src, e),
177     };
178     if meta.is_dir() {
179         dir_up_to_date(src, threshold)
180     } else {
181         meta.modified().unwrap_or(UNIX_EPOCH) <= threshold
182     }
183 }
184
185 #[must_use]
186 pub struct NativeLibBoilerplate {
187     pub src_dir: PathBuf,
188     pub out_dir: PathBuf,
189 }
190
191 impl NativeLibBoilerplate {
192     /// On macOS we don't want to ship the exact filename that compiler-rt builds.
193     /// This conflicts with the system and ours is likely a wildly different
194     /// version, so they can't be substituted.
195     ///
196     /// As a result, we rename it here but we need to also use
197     /// `install_name_tool` on macOS to rename the commands listed inside of it to
198     /// ensure it's linked against correctly.
199     pub fn fixup_sanitizer_lib_name(&self, sanitizer_name: &str) {
200         if env::var("TARGET").unwrap() != "x86_64-apple-darwin" {
201             return
202         }
203
204         let dir = self.out_dir.join("build/lib/darwin");
205         let name = format!("clang_rt.{}_osx_dynamic", sanitizer_name);
206         let src = dir.join(&format!("lib{}.dylib", name));
207         let new_name = format!("lib__rustc__{}.dylib", name);
208         let dst = dir.join(&new_name);
209
210         println!("{} => {}", src.display(), dst.display());
211         fs::rename(&src, &dst).unwrap();
212         let status = Command::new("install_name_tool")
213             .arg("-id")
214             .arg(format!("@rpath/{}", new_name))
215             .arg(&dst)
216             .status()
217             .expect("failed to execute `install_name_tool`");
218         assert!(status.success());
219     }
220 }
221
222 impl Drop for NativeLibBoilerplate {
223     fn drop(&mut self) {
224         if !thread::panicking() {
225             t!(File::create(self.out_dir.join("rustbuild.timestamp")));
226         }
227     }
228 }
229
230 // Perform standard preparations for native libraries that are build only once for all stages.
231 // Emit rerun-if-changed and linking attributes for Cargo, check if any source files are
232 // updated, calculate paths used later in actual build with CMake/make or C/C++ compiler.
233 // If Err is returned, then everything is up-to-date and further build actions can be skipped.
234 // Timestamps are created automatically when the result of `native_lib_boilerplate` goes out
235 // of scope, so all the build actions should be completed until then.
236 pub fn native_lib_boilerplate(
237     src_dir: &Path,
238     out_name: &str,
239     link_name: &str,
240     search_subdir: &str,
241 ) -> Result<NativeLibBoilerplate, ()> {
242     rerun_if_changed_anything_in_dir(src_dir);
243
244     let out_dir = env::var_os("RUSTBUILD_NATIVE_DIR").unwrap_or_else(||
245         env::var_os("OUT_DIR").unwrap());
246     let out_dir = PathBuf::from(out_dir).join(out_name);
247     t!(fs::create_dir_all(&out_dir));
248     if link_name.contains('=') {
249         println!("cargo:rustc-link-lib={}", link_name);
250     } else {
251         println!("cargo:rustc-link-lib=static={}", link_name);
252     }
253     println!(
254         "cargo:rustc-link-search=native={}",
255         out_dir.join(search_subdir).display()
256     );
257
258     let timestamp = out_dir.join("rustbuild.timestamp");
259     if !up_to_date(Path::new("build.rs"), &timestamp) || !up_to_date(src_dir, &timestamp) {
260         Ok(NativeLibBoilerplate {
261             src_dir: src_dir.to_path_buf(),
262             out_dir,
263         })
264     } else {
265         Err(())
266     }
267 }
268
269 pub fn sanitizer_lib_boilerplate(sanitizer_name: &str)
270     -> Result<(NativeLibBoilerplate, String), ()>
271 {
272     let (link_name, search_path, apple) = match &*env::var("TARGET").unwrap() {
273         "x86_64-unknown-linux-gnu" => (
274             format!("clang_rt.{}-x86_64", sanitizer_name),
275             "build/lib/linux",
276             false,
277         ),
278         "x86_64-apple-darwin" => (
279             format!("clang_rt.{}_osx_dynamic", sanitizer_name),
280             "build/lib/darwin",
281             true,
282         ),
283         _ => return Err(()),
284     };
285     let to_link = if apple {
286         format!("dylib=__rustc__{}", link_name)
287     } else {
288         format!("static={}", link_name)
289     };
290     // This env var is provided by rustbuild to tell us where `compiler-rt`
291     // lives.
292     let dir = env::var_os("RUST_COMPILER_RT_ROOT").unwrap();
293     let lib = native_lib_boilerplate(
294         dir.as_ref(),
295         sanitizer_name,
296         &to_link,
297         search_path,
298     )?;
299     Ok((lib, link_name))
300 }
301
302 fn dir_up_to_date(src: &Path, threshold: SystemTime) -> bool {
303     t!(fs::read_dir(src)).map(|e| t!(e)).all(|e| {
304         let meta = t!(e.metadata());
305         if meta.is_dir() {
306             dir_up_to_date(&e.path(), threshold)
307         } else {
308             meta.modified().unwrap_or(UNIX_EPOCH) < threshold
309         }
310     })
311 }
312
313 fn fail(s: &str) -> ! {
314     println!("\n\n{}\n\n", s);
315     std::process::exit(1);
316 }