]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/util.rs
dab20f44bc361d4e18cdeb0d50fa1236ebe320c2
[rust.git] / src / bootstrap / util.rs
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 //! Various utility functions used throughout rustbuild.
12 //!
13 //! Simple things like testing the various filesystem operations here and there,
14 //! not a lot of interesting happenings here unfortunately.
15
16 use std::env;
17 use std::ffi::OsString;
18 use std::fs;
19 use std::io;
20 use std::path::{Path, PathBuf};
21 use std::process::Command;
22 use std::time::Instant;
23
24 use filetime::{self, FileTime};
25
26 /// Returns the `name` as the filename of a static library for `target`.
27 pub fn staticlib(name: &str, target: &str) -> String {
28     if target.contains("windows") {
29         format!("{}.lib", name)
30     } else {
31         format!("lib{}.a", name)
32     }
33 }
34
35 /// Copies a file from `src` to `dst`, attempting to use hard links and then
36 /// falling back to an actually filesystem copy if necessary.
37 pub fn copy(src: &Path, dst: &Path) {
38     // A call to `hard_link` will fail if `dst` exists, so remove it if it
39     // already exists so we can try to help `hard_link` succeed.
40     let _ = fs::remove_file(&dst);
41
42     // Attempt to "easy copy" by creating a hard link (symlinks don't work on
43     // windows), but if that fails just fall back to a slow `copy` operation.
44     // let res = fs::hard_link(src, dst);
45     let res = fs::copy(src, dst);
46     if let Err(e) = res {
47         panic!("failed to copy `{}` to `{}`: {}", src.display(),
48                dst.display(), e)
49     }
50     let metadata = t!(src.metadata());
51     t!(fs::set_permissions(dst, metadata.permissions()));
52     let atime = FileTime::from_last_access_time(&metadata);
53     let mtime = FileTime::from_last_modification_time(&metadata);
54     t!(filetime::set_file_times(dst, atime, mtime));
55
56 }
57
58 /// Copies the `src` directory recursively to `dst`. Both are assumed to exist
59 /// when this function is called.
60 pub fn cp_r(src: &Path, dst: &Path) {
61     for f in t!(fs::read_dir(src)) {
62         let f = t!(f);
63         let path = f.path();
64         let name = path.file_name().unwrap();
65         let dst = dst.join(name);
66         if t!(f.file_type()).is_dir() {
67             t!(fs::create_dir_all(&dst));
68             cp_r(&path, &dst);
69         } else {
70             let _ = fs::remove_file(&dst);
71             copy(&path, &dst);
72         }
73     }
74 }
75
76 /// Copies the `src` directory recursively to `dst`. Both are assumed to exist
77 /// when this function is called. Unwanted files or directories can be skipped
78 /// by returning `false` from the filter function.
79 pub fn cp_filtered(src: &Path, dst: &Path, filter: &Fn(&Path) -> bool) {
80     // Inner function does the actual work
81     fn recurse(src: &Path, dst: &Path, relative: &Path, filter: &Fn(&Path) -> bool) {
82         for f in t!(fs::read_dir(src)) {
83             let f = t!(f);
84             let path = f.path();
85             let name = path.file_name().unwrap();
86             let dst = dst.join(name);
87             let relative = relative.join(name);
88             // Only copy file or directory if the filter function returns true
89             if filter(&relative) {
90                 if t!(f.file_type()).is_dir() {
91                     let _ = fs::remove_dir_all(&dst);
92                     t!(fs::create_dir(&dst));
93                     recurse(&path, &dst, &relative, filter);
94                 } else {
95                     let _ = fs::remove_file(&dst);
96                     copy(&path, &dst);
97                 }
98             }
99         }
100     }
101     // Immediately recurse with an empty relative path
102     recurse(src, dst, Path::new(""), filter)
103 }
104
105 /// Given an executable called `name`, return the filename for the
106 /// executable for a particular target.
107 pub fn exe(name: &str, target: &str) -> String {
108     if target.contains("windows") {
109         format!("{}.exe", name)
110     } else {
111         name.to_string()
112     }
113 }
114
115 /// Returns whether the file name given looks like a dynamic library.
116 pub fn is_dylib(name: &str) -> bool {
117     name.ends_with(".dylib") || name.ends_with(".so") || name.ends_with(".dll")
118 }
119
120 /// Returns the corresponding relative library directory that the compiler's
121 /// dylibs will be found in.
122 pub fn libdir(target: &str) -> &'static str {
123     if target.contains("windows") {"bin"} else {"lib"}
124 }
125
126 /// Adds a list of lookup paths to `cmd`'s dynamic library lookup path.
127 pub fn add_lib_path(path: Vec<PathBuf>, cmd: &mut Command) {
128     let mut list = dylib_path();
129     for path in path {
130         list.insert(0, path);
131     }
132     cmd.env(dylib_path_var(), t!(env::join_paths(list)));
133 }
134
135 /// Returns the environment variable which the dynamic library lookup path
136 /// resides in for this platform.
137 pub fn dylib_path_var() -> &'static str {
138     if cfg!(target_os = "windows") {
139         "PATH"
140     } else if cfg!(target_os = "macos") {
141         "DYLD_LIBRARY_PATH"
142     } else {
143         "LD_LIBRARY_PATH"
144     }
145 }
146
147 /// Parses the `dylib_path_var()` environment variable, returning a list of
148 /// paths that are members of this lookup path.
149 pub fn dylib_path() -> Vec<PathBuf> {
150     env::split_paths(&env::var_os(dylib_path_var()).unwrap_or(OsString::new()))
151         .collect()
152 }
153
154 /// `push` all components to `buf`. On windows, append `.exe` to the last component.
155 pub fn push_exe_path(mut buf: PathBuf, components: &[&str]) -> PathBuf {
156     let (&file, components) = components.split_last().expect("at least one component required");
157     let mut file = file.to_owned();
158
159     if cfg!(windows) {
160         file.push_str(".exe");
161     }
162
163     for c in components {
164         buf.push(c);
165     }
166
167     buf.push(file);
168
169     buf
170 }
171
172 pub struct TimeIt(Instant);
173
174 /// Returns an RAII structure that prints out how long it took to drop.
175 pub fn timeit() -> TimeIt {
176     TimeIt(Instant::now())
177 }
178
179 impl Drop for TimeIt {
180     fn drop(&mut self) {
181         let time = self.0.elapsed();
182         println!("\tfinished in {}.{:03}",
183                  time.as_secs(),
184                  time.subsec_nanos() / 1_000_000);
185     }
186 }
187
188 /// Symlinks two directories, using junctions on Windows and normal symlinks on
189 /// Unix.
190 pub fn symlink_dir(src: &Path, dest: &Path) -> io::Result<()> {
191     let _ = fs::remove_dir(dest);
192     return symlink_dir_inner(src, dest);
193
194     #[cfg(not(windows))]
195     fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> {
196         use std::os::unix::fs;
197         fs::symlink(src, dest)
198     }
199
200     // Creating a directory junction on windows involves dealing with reparse
201     // points and the DeviceIoControl function, and this code is a skeleton of
202     // what can be found here:
203     //
204     // http://www.flexhex.com/docs/articles/hard-links.phtml
205     //
206     // Copied from std
207     #[cfg(windows)]
208     #[allow(bad_style)]
209     fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> {
210         use std::ptr;
211         use std::ffi::OsStr;
212         use std::os::windows::ffi::OsStrExt;
213
214         const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024;
215         const GENERIC_WRITE: DWORD = 0x40000000;
216         const OPEN_EXISTING: DWORD = 3;
217         const FILE_FLAG_OPEN_REPARSE_POINT: DWORD = 0x00200000;
218         const FILE_FLAG_BACKUP_SEMANTICS: DWORD = 0x02000000;
219         const FSCTL_SET_REPARSE_POINT: DWORD = 0x900a4;
220         const IO_REPARSE_TAG_MOUNT_POINT: DWORD = 0xa0000003;
221         const FILE_SHARE_DELETE: DWORD = 0x4;
222         const FILE_SHARE_READ: DWORD = 0x1;
223         const FILE_SHARE_WRITE: DWORD = 0x2;
224
225         type BOOL = i32;
226         type DWORD = u32;
227         type HANDLE = *mut u8;
228         type LPCWSTR = *const u16;
229         type LPDWORD = *mut DWORD;
230         type LPOVERLAPPED = *mut u8;
231         type LPSECURITY_ATTRIBUTES = *mut u8;
232         type LPVOID = *mut u8;
233         type WCHAR = u16;
234         type WORD = u16;
235
236         #[repr(C)]
237         struct REPARSE_MOUNTPOINT_DATA_BUFFER {
238             ReparseTag: DWORD,
239             ReparseDataLength: DWORD,
240             Reserved: WORD,
241             ReparseTargetLength: WORD,
242             ReparseTargetMaximumLength: WORD,
243             Reserved1: WORD,
244             ReparseTarget: WCHAR,
245         }
246
247         extern "system" {
248             fn CreateFileW(lpFileName: LPCWSTR,
249                            dwDesiredAccess: DWORD,
250                            dwShareMode: DWORD,
251                            lpSecurityAttributes: LPSECURITY_ATTRIBUTES,
252                            dwCreationDisposition: DWORD,
253                            dwFlagsAndAttributes: DWORD,
254                            hTemplateFile: HANDLE)
255                            -> HANDLE;
256             fn DeviceIoControl(hDevice: HANDLE,
257                                dwIoControlCode: DWORD,
258                                lpInBuffer: LPVOID,
259                                nInBufferSize: DWORD,
260                                lpOutBuffer: LPVOID,
261                                nOutBufferSize: DWORD,
262                                lpBytesReturned: LPDWORD,
263                                lpOverlapped: LPOVERLAPPED) -> BOOL;
264         }
265
266         fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> {
267             Ok(s.as_ref().encode_wide().chain(Some(0)).collect())
268         }
269
270         // We're using low-level APIs to create the junction, and these are more
271         // picky about paths. For example, forward slashes cannot be used as a
272         // path separator, so we should try to canonicalize the path first.
273         let target = try!(fs::canonicalize(target));
274
275         try!(fs::create_dir(junction));
276
277         let path = try!(to_u16s(junction));
278
279         unsafe {
280             let h = CreateFileW(path.as_ptr(),
281                                 GENERIC_WRITE,
282                                 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
283                                 0 as *mut _,
284                                 OPEN_EXISTING,
285                                 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
286                                 ptr::null_mut());
287
288             let mut data = [0u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
289             let mut db = data.as_mut_ptr()
290                             as *mut REPARSE_MOUNTPOINT_DATA_BUFFER;
291             let buf = &mut (*db).ReparseTarget as *mut _;
292             let mut i = 0;
293             // FIXME: this conversion is very hacky
294             let v = br"\??\";
295             let v = v.iter().map(|x| *x as u16);
296             for c in v.chain(target.as_os_str().encode_wide().skip(4)) {
297                 *buf.offset(i) = c;
298                 i += 1;
299             }
300             *buf.offset(i) = 0;
301             i += 1;
302             (*db).ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
303             (*db).ReparseTargetMaximumLength = (i * 2) as WORD;
304             (*db).ReparseTargetLength = ((i - 1) * 2) as WORD;
305             (*db).ReparseDataLength =
306                     (*db).ReparseTargetLength as DWORD + 12;
307
308             let mut ret = 0;
309             let res = DeviceIoControl(h as *mut _,
310                                       FSCTL_SET_REPARSE_POINT,
311                                       data.as_ptr() as *mut _,
312                                       (*db).ReparseDataLength + 8,
313                                       ptr::null_mut(), 0,
314                                       &mut ret,
315                                       ptr::null_mut());
316
317             if res == 0 {
318                 Err(io::Error::last_os_error())
319             } else {
320                 Ok(())
321             }
322         }
323     }
324 }