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.
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.
11 //! Various utility functions used throughout rustbuild.
13 //! Simple things like testing the various filesystem operations here and there,
14 //! not a lot of interesting happenings here unfortunately.
17 use std::ffi::OsString;
20 use std::path::{Path, PathBuf};
21 use std::process::Command;
22 use std::time::Instant;
24 use filetime::{self, FileTime};
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)
31 format!("lib{}.a", name)
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);
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);
47 panic!("failed to copy `{}` to `{}`: {}", src.display(),
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));
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)) {
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));
70 let _ = fs::remove_file(&dst);
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)) {
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);
95 let _ = fs::remove_file(&dst);
101 // Immediately recurse with an empty relative path
102 recurse(src, dst, Path::new(""), filter)
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)
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")
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"}
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();
130 list.insert(0, path);
132 cmd.env(dylib_path_var(), t!(env::join_paths(list)));
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") {
140 } else if cfg!(target_os = "macos") {
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()))
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();
160 file.push_str(".exe");
163 for c in components {
172 pub struct TimeIt(Instant);
174 /// Returns an RAII structure that prints out how long it took to drop.
175 pub fn timeit() -> TimeIt {
176 TimeIt(Instant::now())
179 impl Drop for TimeIt {
181 let time = self.0.elapsed();
182 println!("\tfinished in {}.{:03}",
184 time.subsec_nanos() / 1_000_000);
188 /// Symlinks two directories, using junctions on Windows and normal symlinks on
190 pub fn symlink_dir(src: &Path, dest: &Path) -> io::Result<()> {
191 let _ = fs::remove_dir(dest);
192 return symlink_dir_inner(src, dest);
195 fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> {
196 use std::os::unix::fs;
197 fs::symlink(src, dest)
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:
204 // http://www.flexhex.com/docs/articles/hard-links.phtml
209 fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> {
212 use std::os::windows::ffi::OsStrExt;
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;
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;
237 struct REPARSE_MOUNTPOINT_DATA_BUFFER {
239 ReparseDataLength: DWORD,
241 ReparseTargetLength: WORD,
242 ReparseTargetMaximumLength: WORD,
244 ReparseTarget: WCHAR,
248 fn CreateFileW(lpFileName: LPCWSTR,
249 dwDesiredAccess: DWORD,
251 lpSecurityAttributes: LPSECURITY_ATTRIBUTES,
252 dwCreationDisposition: DWORD,
253 dwFlagsAndAttributes: DWORD,
254 hTemplateFile: HANDLE)
256 fn DeviceIoControl(hDevice: HANDLE,
257 dwIoControlCode: DWORD,
259 nInBufferSize: DWORD,
261 nOutBufferSize: DWORD,
262 lpBytesReturned: LPDWORD,
263 lpOverlapped: LPOVERLAPPED) -> BOOL;
266 fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> {
267 Ok(s.as_ref().encode_wide().chain(Some(0)).collect())
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));
275 try!(fs::create_dir(junction));
277 let path = try!(to_u16s(junction));
280 let h = CreateFileW(path.as_ptr(),
282 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
285 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
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 _;
293 // FIXME: this conversion is very hacky
295 let v = v.iter().map(|x| *x as u16);
296 for c in v.chain(target.as_os_str().encode_wide().skip(4)) {
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;
309 let res = DeviceIoControl(h as *mut _,
310 FSCTL_SET_REPARSE_POINT,
311 data.as_ptr() as *mut _,
312 (*db).ReparseDataLength + 8,
318 Err(io::Error::last_os_error())