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") {
142 } else if cfg!(target_os = "haiku") {
149 /// Parses the `dylib_path_var()` environment variable, returning a list of
150 /// paths that are members of this lookup path.
151 pub fn dylib_path() -> Vec<PathBuf> {
152 env::split_paths(&env::var_os(dylib_path_var()).unwrap_or(OsString::new()))
156 /// `push` all components to `buf`. On windows, append `.exe` to the last component.
157 pub fn push_exe_path(mut buf: PathBuf, components: &[&str]) -> PathBuf {
158 let (&file, components) = components.split_last().expect("at least one component required");
159 let mut file = file.to_owned();
162 file.push_str(".exe");
165 for c in components {
174 pub struct TimeIt(Instant);
176 /// Returns an RAII structure that prints out how long it took to drop.
177 pub fn timeit() -> TimeIt {
178 TimeIt(Instant::now())
181 impl Drop for TimeIt {
183 let time = self.0.elapsed();
184 println!("\tfinished in {}.{:03}",
186 time.subsec_nanos() / 1_000_000);
190 /// Symlinks two directories, using junctions on Windows and normal symlinks on
192 pub fn symlink_dir(src: &Path, dest: &Path) -> io::Result<()> {
193 let _ = fs::remove_dir(dest);
194 return symlink_dir_inner(src, dest);
197 fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> {
198 use std::os::unix::fs;
199 fs::symlink(src, dest)
202 // Creating a directory junction on windows involves dealing with reparse
203 // points and the DeviceIoControl function, and this code is a skeleton of
204 // what can be found here:
206 // http://www.flexhex.com/docs/articles/hard-links.phtml
211 fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> {
214 use std::os::windows::ffi::OsStrExt;
216 const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024;
217 const GENERIC_WRITE: DWORD = 0x40000000;
218 const OPEN_EXISTING: DWORD = 3;
219 const FILE_FLAG_OPEN_REPARSE_POINT: DWORD = 0x00200000;
220 const FILE_FLAG_BACKUP_SEMANTICS: DWORD = 0x02000000;
221 const FSCTL_SET_REPARSE_POINT: DWORD = 0x900a4;
222 const IO_REPARSE_TAG_MOUNT_POINT: DWORD = 0xa0000003;
223 const FILE_SHARE_DELETE: DWORD = 0x4;
224 const FILE_SHARE_READ: DWORD = 0x1;
225 const FILE_SHARE_WRITE: DWORD = 0x2;
229 type HANDLE = *mut u8;
230 type LPCWSTR = *const u16;
231 type LPDWORD = *mut DWORD;
232 type LPOVERLAPPED = *mut u8;
233 type LPSECURITY_ATTRIBUTES = *mut u8;
234 type LPVOID = *mut u8;
239 struct REPARSE_MOUNTPOINT_DATA_BUFFER {
241 ReparseDataLength: DWORD,
243 ReparseTargetLength: WORD,
244 ReparseTargetMaximumLength: WORD,
246 ReparseTarget: WCHAR,
250 fn CreateFileW(lpFileName: LPCWSTR,
251 dwDesiredAccess: DWORD,
253 lpSecurityAttributes: LPSECURITY_ATTRIBUTES,
254 dwCreationDisposition: DWORD,
255 dwFlagsAndAttributes: DWORD,
256 hTemplateFile: HANDLE)
258 fn DeviceIoControl(hDevice: HANDLE,
259 dwIoControlCode: DWORD,
261 nInBufferSize: DWORD,
263 nOutBufferSize: DWORD,
264 lpBytesReturned: LPDWORD,
265 lpOverlapped: LPOVERLAPPED) -> BOOL;
268 fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> {
269 Ok(s.as_ref().encode_wide().chain(Some(0)).collect())
272 // We're using low-level APIs to create the junction, and these are more
273 // picky about paths. For example, forward slashes cannot be used as a
274 // path separator, so we should try to canonicalize the path first.
275 let target = try!(fs::canonicalize(target));
277 try!(fs::create_dir(junction));
279 let path = try!(to_u16s(junction));
282 let h = CreateFileW(path.as_ptr(),
284 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
287 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
290 let mut data = [0u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
291 let mut db = data.as_mut_ptr()
292 as *mut REPARSE_MOUNTPOINT_DATA_BUFFER;
293 let buf = &mut (*db).ReparseTarget as *mut _;
295 // FIXME: this conversion is very hacky
297 let v = v.iter().map(|x| *x as u16);
298 for c in v.chain(target.as_os_str().encode_wide().skip(4)) {
304 (*db).ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
305 (*db).ReparseTargetMaximumLength = (i * 2) as WORD;
306 (*db).ReparseTargetLength = ((i - 1) * 2) as WORD;
307 (*db).ReparseDataLength =
308 (*db).ReparseTargetLength as DWORD + 12;
311 let res = DeviceIoControl(h as *mut _,
312 FSCTL_SET_REPARSE_POINT,
313 data.as_ptr() as *mut _,
314 (*db).ReparseDataLength + 8,
320 Err(io::Error::last_os_error())