]> git.lizzy.rs Git - rust.git/blob - src/build_helper/lib.rs
Merge commit '27afd6ade4bb1123a8bf82001629b69d23d62aff' into clippyup
[rust.git] / src / build_helper / lib.rs
1 use std::ffi::{OsStr, OsString};
2 use std::fmt::Display;
3 use std::path::{Path, PathBuf};
4 use std::process::{Command, Stdio};
5 use std::time::{SystemTime, UNIX_EPOCH};
6 use std::{env, fs};
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     // it can show extra info in the second parameter
25     ($e:expr, $extra:expr) => {
26         match $e {
27             Ok(e) => e,
28             Err(e) => panic!("{} failed with {} ({:?})", stringify!($e), e, $extra),
29         }
30     };
31 }
32
33 /// Reads an environment variable and adds it to dependencies.
34 /// Supposed to be used for all variables except those set for build scripts by cargo
35 /// <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts>
36 pub fn tracked_env_var_os<K: AsRef<OsStr> + Display>(key: K) -> Option<OsString> {
37     println!("cargo:rerun-if-env-changed={}", key);
38     env::var_os(key)
39 }
40
41 // Because Cargo adds the compiler's dylib path to our library search path, llvm-config may
42 // break: the dylib path for the compiler, as of this writing, contains a copy of the LLVM
43 // shared library, which means that when our freshly built llvm-config goes to load it's
44 // associated LLVM, it actually loads the compiler's LLVM. In particular when building the first
45 // compiler (i.e., in stage 0) that's a problem, as the compiler's LLVM is likely different from
46 // the one we want to use. As such, we restore the environment to what bootstrap saw. This isn't
47 // perfect -- we might actually want to see something from Cargo's added library paths -- but
48 // for now it works.
49 pub fn restore_library_path() {
50     let key = tracked_env_var_os("REAL_LIBRARY_PATH_VAR").expect("REAL_LIBRARY_PATH_VAR");
51     if let Some(env) = tracked_env_var_os("REAL_LIBRARY_PATH") {
52         env::set_var(&key, &env);
53     } else {
54         env::remove_var(&key);
55     }
56 }
57
58 /// Run the command, printing what we are running.
59 pub fn run_verbose(cmd: &mut Command) {
60     println!("running: {:?}", cmd);
61     run(cmd);
62 }
63
64 pub fn run(cmd: &mut Command) {
65     if !try_run(cmd) {
66         std::process::exit(1);
67     }
68 }
69
70 pub fn try_run(cmd: &mut Command) -> bool {
71     let status = match cmd.status() {
72         Ok(status) => status,
73         Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)),
74     };
75     if !status.success() {
76         println!(
77             "\n\ncommand did not execute successfully: {:?}\n\
78              expected success, got: {}\n\n",
79             cmd, status
80         );
81     }
82     status.success()
83 }
84
85 pub fn run_suppressed(cmd: &mut Command) {
86     if !try_run_suppressed(cmd) {
87         std::process::exit(1);
88     }
89 }
90
91 pub fn try_run_suppressed(cmd: &mut Command) -> bool {
92     let output = match cmd.output() {
93         Ok(status) => status,
94         Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)),
95     };
96     if !output.status.success() {
97         println!(
98             "\n\ncommand did not execute successfully: {:?}\n\
99              expected success, got: {}\n\n\
100              stdout ----\n{}\n\
101              stderr ----\n{}\n\n",
102             cmd,
103             output.status,
104             String::from_utf8_lossy(&output.stdout),
105             String::from_utf8_lossy(&output.stderr)
106         );
107     }
108     output.status.success()
109 }
110
111 pub fn gnu_target(target: &str) -> &str {
112     match target {
113         "i686-pc-windows-msvc" => "i686-pc-win32",
114         "x86_64-pc-windows-msvc" => "x86_64-pc-win32",
115         "i686-pc-windows-gnu" => "i686-w64-mingw32",
116         "x86_64-pc-windows-gnu" => "x86_64-w64-mingw32",
117         s => s,
118     }
119 }
120
121 pub fn make(host: &str) -> PathBuf {
122     if host.contains("dragonfly")
123         || host.contains("freebsd")
124         || host.contains("netbsd")
125         || host.contains("openbsd")
126     {
127         PathBuf::from("gmake")
128     } else {
129         PathBuf::from("make")
130     }
131 }
132
133 #[track_caller]
134 pub fn output(cmd: &mut Command) -> String {
135     let output = match cmd.stderr(Stdio::inherit()).output() {
136         Ok(status) => status,
137         Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)),
138     };
139     if !output.status.success() {
140         panic!(
141             "command did not execute successfully: {:?}\n\
142              expected success, got: {}",
143             cmd, output.status
144         );
145     }
146     String::from_utf8(output.stdout).unwrap()
147 }
148
149 pub fn rerun_if_changed_anything_in_dir(dir: &Path) {
150     let mut stack = dir
151         .read_dir()
152         .unwrap()
153         .map(|e| e.unwrap())
154         .filter(|e| &*e.file_name() != ".git")
155         .collect::<Vec<_>>();
156     while let Some(entry) = stack.pop() {
157         let path = entry.path();
158         if entry.file_type().unwrap().is_dir() {
159             stack.extend(path.read_dir().unwrap().map(|e| e.unwrap()));
160         } else {
161             println!("cargo:rerun-if-changed={}", path.display());
162         }
163     }
164 }
165
166 /// Returns the last-modified time for `path`, or zero if it doesn't exist.
167 pub fn mtime(path: &Path) -> SystemTime {
168     fs::metadata(path).and_then(|f| f.modified()).unwrap_or(UNIX_EPOCH)
169 }
170
171 /// Returns `true` if `dst` is up to date given that the file or files in `src`
172 /// are used to generate it.
173 ///
174 /// Uses last-modified time checks to verify this.
175 pub fn up_to_date(src: &Path, dst: &Path) -> bool {
176     if !dst.exists() {
177         return false;
178     }
179     let threshold = mtime(dst);
180     let meta = match fs::metadata(src) {
181         Ok(meta) => meta,
182         Err(e) => panic!("source {:?} failed to get metadata: {}", src, e),
183     };
184     if meta.is_dir() {
185         dir_up_to_date(src, threshold)
186     } else {
187         meta.modified().unwrap_or(UNIX_EPOCH) <= threshold
188     }
189 }
190
191 fn dir_up_to_date(src: &Path, threshold: SystemTime) -> bool {
192     t!(fs::read_dir(src)).map(|e| t!(e)).all(|e| {
193         let meta = t!(e.metadata());
194         if meta.is_dir() {
195             dir_up_to_date(&e.path(), threshold)
196         } else {
197             meta.modified().unwrap_or(UNIX_EPOCH) < threshold
198         }
199     })
200 }
201
202 fn fail(s: &str) -> ! {
203     println!("\n\n{}\n\n", s);
204     std::process::exit(1);
205 }