]>
Commit | Line | Data |
---|---|---|
a7813a04 XL |
1 | //! Various utility functions used throughout rustbuild. |
2 | //! | |
3 | //! Simple things like testing the various filesystem operations here and there, | |
4 | //! not a lot of interesting happenings here unfortunately. | |
5 | ||
7453a54e | 6 | use std::env; |
83c7162d | 7 | use std::fs; |
416331ca | 8 | use std::io; |
5bcae85e | 9 | use std::path::{Path, PathBuf}; |
ee023bcb | 10 | use std::process::{Command, Stdio}; |
dfeec247 | 11 | use std::str; |
ee023bcb | 12 | use std::time::{Instant, SystemTime, UNIX_EPOCH}; |
48663c56 | 13 | |
0731742a | 14 | use crate::builder::Builder; |
3dfed10e | 15 | use crate::config::{Config, TargetSelection}; |
7453a54e | 16 | |
ee023bcb FG |
17 | /// A helper macro to `unwrap` a result except also print out details like: |
18 | /// | |
19 | /// * The file/line of the panic | |
20 | /// * The expression that failed | |
21 | /// * The error itself | |
22 | /// | |
23 | /// This is currently used judiciously throughout the build system rather than | |
24 | /// using a `Result` with `try!`, but this may change one day... | |
25 | macro_rules! t { | |
26 | ($e:expr) => { | |
27 | match $e { | |
28 | Ok(e) => e, | |
29 | Err(e) => panic!("{} failed with {}", stringify!($e), e), | |
30 | } | |
31 | }; | |
32 | // it can show extra info in the second parameter | |
33 | ($e:expr, $extra:expr) => { | |
34 | match $e { | |
35 | Ok(e) => e, | |
36 | Err(e) => panic!("{} failed with {} ({:?})", stringify!($e), e, $extra), | |
37 | } | |
38 | }; | |
39 | } | |
40 | pub(crate) use t; | |
41 | ||
7453a54e SL |
42 | /// Given an executable called `name`, return the filename for the |
43 | /// executable for a particular target. | |
3dfed10e | 44 | pub fn exe(name: &str, target: TargetSelection) -> String { |
dfeec247 | 45 | if target.contains("windows") { format!("{}.exe", name) } else { name.to_string() } |
7453a54e SL |
46 | } |
47 | ||
9fa01778 | 48 | /// Returns `true` if the file name given looks like a dynamic library. |
7453a54e SL |
49 | pub fn is_dylib(name: &str) -> bool { |
50 | name.ends_with(".dylib") || name.ends_with(".so") || name.ends_with(".dll") | |
51 | } | |
52 | ||
6a06907d XL |
53 | /// Returns `true` if the file name given looks like a debug info file |
54 | pub fn is_debug_info(name: &str) -> bool { | |
55 | // FIXME: consider split debug info on other platforms (e.g., Linux, macOS) | |
56 | name.ends_with(".pdb") | |
57 | } | |
58 | ||
a7813a04 XL |
59 | /// Returns the corresponding relative library directory that the compiler's |
60 | /// dylibs will be found in. | |
3dfed10e | 61 | pub fn libdir(target: TargetSelection) -> &'static str { |
dfeec247 | 62 | if target.contains("windows") { "bin" } else { "lib" } |
7453a54e SL |
63 | } |
64 | ||
a7813a04 | 65 | /// Adds a list of lookup paths to `cmd`'s dynamic library lookup path. |
17df50a5 | 66 | /// If The dylib_path_par is already set for this cmd, the old value will be overwritten! |
ba9703b0 | 67 | pub fn add_dylib_path(path: Vec<PathBuf>, cmd: &mut Command) { |
7453a54e SL |
68 | let mut list = dylib_path(); |
69 | for path in path { | |
70 | list.insert(0, path); | |
71 | } | |
72 | cmd.env(dylib_path_var(), t!(env::join_paths(list))); | |
73 | } | |
74 | ||
a2a8927a | 75 | include!("dylib_util.rs"); |
c30ab7b3 | 76 | |
ba9703b0 XL |
77 | /// Adds a list of lookup paths to `cmd`'s link library lookup path. |
78 | pub fn add_link_lib_path(path: Vec<PathBuf>, cmd: &mut Command) { | |
79 | let mut list = link_lib_path(); | |
80 | for path in path { | |
81 | list.insert(0, path); | |
82 | } | |
83 | cmd.env(link_lib_path_var(), t!(env::join_paths(list))); | |
84 | } | |
85 | ||
86 | /// Returns the environment variable which the link library lookup path | |
87 | /// resides in for this platform. | |
88 | fn link_lib_path_var() -> &'static str { | |
89 | if cfg!(target_env = "msvc") { "LIB" } else { "LIBRARY_PATH" } | |
90 | } | |
91 | ||
92 | /// Parses the `link_lib_path_var()` environment variable, returning a list of | |
93 | /// paths that are members of this lookup path. | |
94 | fn link_lib_path() -> Vec<PathBuf> { | |
95 | let var = match env::var_os(link_lib_path_var()) { | |
96 | Some(v) => v, | |
97 | None => return vec![], | |
98 | }; | |
99 | env::split_paths(&var).collect() | |
100 | } | |
101 | ||
83c7162d | 102 | pub struct TimeIt(bool, Instant); |
476ff2be SL |
103 | |
104 | /// Returns an RAII structure that prints out how long it took to drop. | |
9fa01778 | 105 | pub fn timeit(builder: &Builder<'_>) -> TimeIt { |
83c7162d | 106 | TimeIt(builder.config.dry_run, Instant::now()) |
476ff2be SL |
107 | } |
108 | ||
109 | impl Drop for TimeIt { | |
110 | fn drop(&mut self) { | |
83c7162d XL |
111 | let time = self.1.elapsed(); |
112 | if !self.0 { | |
29967ef6 | 113 | println!("\tfinished in {}.{:03} seconds", time.as_secs(), time.subsec_millis()); |
83c7162d | 114 | } |
476ff2be SL |
115 | } |
116 | } | |
8bb4bdeb XL |
117 | |
118 | /// Symlinks two directories, using junctions on Windows and normal symlinks on | |
119 | /// Unix. | |
83c7162d | 120 | pub fn symlink_dir(config: &Config, src: &Path, dest: &Path) -> io::Result<()> { |
dfeec247 XL |
121 | if config.dry_run { |
122 | return Ok(()); | |
123 | } | |
8bb4bdeb XL |
124 | let _ = fs::remove_dir(dest); |
125 | return symlink_dir_inner(src, dest); | |
126 | ||
127 | #[cfg(not(windows))] | |
128 | fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> { | |
129 | use std::os::unix::fs; | |
130 | fs::symlink(src, dest) | |
131 | } | |
132 | ||
133 | // Creating a directory junction on windows involves dealing with reparse | |
134 | // points and the DeviceIoControl function, and this code is a skeleton of | |
135 | // what can be found here: | |
136 | // | |
137 | // http://www.flexhex.com/docs/articles/hard-links.phtml | |
8bb4bdeb | 138 | #[cfg(windows)] |
8bb4bdeb | 139 | fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> { |
8bb4bdeb XL |
140 | use std::ffi::OsStr; |
141 | use std::os::windows::ffi::OsStrExt; | |
dfeec247 | 142 | use std::ptr; |
8bb4bdeb | 143 | |
dfeec247 XL |
144 | use winapi::shared::minwindef::{DWORD, WORD}; |
145 | use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING}; | |
146 | use winapi::um::handleapi::CloseHandle; | |
147 | use winapi::um::ioapiset::DeviceIoControl; | |
148 | use winapi::um::winbase::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT}; | |
149 | use winapi::um::winioctl::FSCTL_SET_REPARSE_POINT; | |
150 | use winapi::um::winnt::{ | |
151 | FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_WRITE, | |
152 | IO_REPARSE_TAG_MOUNT_POINT, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, WCHAR, | |
153 | }; | |
154 | ||
155 | #[allow(non_snake_case)] | |
8bb4bdeb XL |
156 | #[repr(C)] |
157 | struct REPARSE_MOUNTPOINT_DATA_BUFFER { | |
158 | ReparseTag: DWORD, | |
159 | ReparseDataLength: DWORD, | |
160 | Reserved: WORD, | |
161 | ReparseTargetLength: WORD, | |
162 | ReparseTargetMaximumLength: WORD, | |
163 | Reserved1: WORD, | |
164 | ReparseTarget: WCHAR, | |
165 | } | |
166 | ||
8bb4bdeb XL |
167 | fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> { |
168 | Ok(s.as_ref().encode_wide().chain(Some(0)).collect()) | |
169 | } | |
170 | ||
171 | // We're using low-level APIs to create the junction, and these are more | |
172 | // picky about paths. For example, forward slashes cannot be used as a | |
173 | // path separator, so we should try to canonicalize the path first. | |
a1dfa0c6 | 174 | let target = fs::canonicalize(target)?; |
8bb4bdeb | 175 | |
a1dfa0c6 | 176 | fs::create_dir(junction)?; |
8bb4bdeb | 177 | |
a1dfa0c6 | 178 | let path = to_u16s(junction)?; |
8bb4bdeb XL |
179 | |
180 | unsafe { | |
dfeec247 XL |
181 | let h = CreateFileW( |
182 | path.as_ptr(), | |
183 | GENERIC_WRITE, | |
184 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | |
185 | ptr::null_mut(), | |
186 | OPEN_EXISTING, | |
187 | FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, | |
188 | ptr::null_mut(), | |
189 | ); | |
190 | ||
191 | let mut data = [0u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]; | |
192 | let db = data.as_mut_ptr() as *mut REPARSE_MOUNTPOINT_DATA_BUFFER; | |
2c00a5a8 | 193 | let buf = &mut (*db).ReparseTarget as *mut u16; |
8bb4bdeb XL |
194 | let mut i = 0; |
195 | // FIXME: this conversion is very hacky | |
196 | let v = br"\??\"; | |
197 | let v = v.iter().map(|x| *x as u16); | |
198 | for c in v.chain(target.as_os_str().encode_wide().skip(4)) { | |
199 | *buf.offset(i) = c; | |
200 | i += 1; | |
201 | } | |
202 | *buf.offset(i) = 0; | |
203 | i += 1; | |
204 | (*db).ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; | |
205 | (*db).ReparseTargetMaximumLength = (i * 2) as WORD; | |
206 | (*db).ReparseTargetLength = ((i - 1) * 2) as WORD; | |
dfeec247 | 207 | (*db).ReparseDataLength = (*db).ReparseTargetLength as DWORD + 12; |
8bb4bdeb XL |
208 | |
209 | let mut ret = 0; | |
dfeec247 XL |
210 | let res = DeviceIoControl( |
211 | h as *mut _, | |
212 | FSCTL_SET_REPARSE_POINT, | |
213 | data.as_ptr() as *mut _, | |
214 | (*db).ReparseDataLength + 8, | |
215 | ptr::null_mut(), | |
216 | 0, | |
217 | &mut ret, | |
218 | ptr::null_mut(), | |
219 | ); | |
220 | ||
221 | let out = if res == 0 { Err(io::Error::last_os_error()) } else { Ok(()) }; | |
0531ce1d XL |
222 | CloseHandle(h); |
223 | out | |
8bb4bdeb XL |
224 | } |
225 | } | |
226 | } | |
7cac9316 | 227 | |
7cac9316 XL |
228 | /// The CI environment rustbuild is running in. This mainly affects how the logs |
229 | /// are printed. | |
230 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] | |
231 | pub enum CiEnv { | |
232 | /// Not a CI environment. | |
233 | None, | |
dc9dc135 XL |
234 | /// The Azure Pipelines environment, for Linux (including Docker), Windows, and macOS builds. |
235 | AzurePipelines, | |
60c5eb7d XL |
236 | /// The GitHub Actions environment, for Linux (including Docker), Windows and macOS builds. |
237 | GitHubActions, | |
7cac9316 XL |
238 | } |
239 | ||
240 | impl CiEnv { | |
241 | /// Obtains the current CI environment. | |
242 | pub fn current() -> CiEnv { | |
dfeec247 | 243 | if env::var("TF_BUILD").map_or(false, |e| e == "True") { |
dc9dc135 | 244 | CiEnv::AzurePipelines |
dfeec247 | 245 | } else if env::var("GITHUB_ACTIONS").map_or(false, |e| e == "true") { |
60c5eb7d | 246 | CiEnv::GitHubActions |
7cac9316 XL |
247 | } else { |
248 | CiEnv::None | |
249 | } | |
250 | } | |
251 | ||
252 | /// If in a CI environment, forces the command to run with colors. | |
253 | pub fn force_coloring_in_ci(self, cmd: &mut Command) { | |
254 | if self != CiEnv::None { | |
255 | // Due to use of stamp/docker, the output stream of rustbuild is not | |
256 | // a TTY in CI, so coloring is by-default turned off. | |
257 | // The explicit `TERM=xterm` environment is needed for | |
258 | // `--color always` to actually work. This env var was lost when | |
259 | // compiling through the Makefile. Very strange. | |
260 | cmd.env("TERM", "xterm").args(&["--color", "always"]); | |
261 | } | |
262 | } | |
041b39d2 | 263 | } |
dc9dc135 XL |
264 | |
265 | pub fn forcing_clang_based_tests() -> bool { | |
266 | if let Some(var) = env::var_os("RUSTBUILD_FORCE_CLANG_BASED_TESTS") { | |
267 | match &var.to_string_lossy().to_lowercase()[..] { | |
268 | "1" | "yes" | "on" => true, | |
269 | "0" | "no" | "off" => false, | |
270 | other => { | |
271 | // Let's make sure typos don't go unnoticed | |
dfeec247 XL |
272 | panic!( |
273 | "Unrecognized option '{}' set in \ | |
274 | RUSTBUILD_FORCE_CLANG_BASED_TESTS", | |
275 | other | |
276 | ) | |
dc9dc135 XL |
277 | } |
278 | } | |
279 | } else { | |
280 | false | |
281 | } | |
282 | } | |
60c5eb7d | 283 | |
3dfed10e | 284 | pub fn use_host_linker(target: TargetSelection) -> bool { |
60c5eb7d XL |
285 | // FIXME: this information should be gotten by checking the linker flavor |
286 | // of the rustc target | |
dfeec247 XL |
287 | !(target.contains("emscripten") |
288 | || target.contains("wasm32") | |
289 | || target.contains("nvptx") | |
290 | || target.contains("fortanix") | |
17df50a5 XL |
291 | || target.contains("fuchsia") |
292 | || target.contains("bpf")) | |
60c5eb7d | 293 | } |
3c0e092e XL |
294 | |
295 | pub fn is_valid_test_suite_arg<'a, P: AsRef<Path>>( | |
296 | path: &'a Path, | |
297 | suite_path: P, | |
298 | builder: &Builder<'_>, | |
299 | ) -> Option<&'a str> { | |
300 | let suite_path = suite_path.as_ref(); | |
301 | let path = match path.strip_prefix(".") { | |
302 | Ok(p) => p, | |
303 | Err(_) => path, | |
304 | }; | |
305 | if !path.starts_with(suite_path) { | |
306 | return None; | |
307 | } | |
5099ac24 FG |
308 | let abs_path = builder.src.join(path); |
309 | let exists = abs_path.is_dir() || abs_path.is_file(); | |
3c0e092e | 310 | if !exists { |
5099ac24 | 311 | if let Some(p) = abs_path.to_str() { |
3c0e092e XL |
312 | builder.info(&format!("Warning: Skipping \"{}\": not a regular file or directory", p)); |
313 | } | |
314 | return None; | |
315 | } | |
316 | // Since test suite paths are themselves directories, if we don't | |
317 | // specify a directory or file, we'll get an empty string here | |
318 | // (the result of the test suite directory without its suite prefix). | |
319 | // Therefore, we need to filter these out, as only the first --test-args | |
320 | // flag is respected, so providing an empty --test-args conflicts with | |
321 | // any following it. | |
322 | match path.strip_prefix(suite_path).ok().and_then(|p| p.to_str()) { | |
323 | Some(s) if !s.is_empty() => Some(s), | |
324 | _ => None, | |
325 | } | |
326 | } | |
ee023bcb FG |
327 | |
328 | pub fn run(cmd: &mut Command, print_cmd_on_fail: bool) { | |
329 | if !try_run(cmd, print_cmd_on_fail) { | |
330 | std::process::exit(1); | |
331 | } | |
332 | } | |
333 | ||
334 | pub fn try_run(cmd: &mut Command, print_cmd_on_fail: bool) -> bool { | |
335 | let status = match cmd.status() { | |
336 | Ok(status) => status, | |
337 | Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)), | |
338 | }; | |
339 | if !status.success() && print_cmd_on_fail { | |
340 | println!( | |
341 | "\n\ncommand did not execute successfully: {:?}\n\ | |
342 | expected success, got: {}\n\n", | |
343 | cmd, status | |
344 | ); | |
345 | } | |
346 | status.success() | |
347 | } | |
348 | ||
349 | pub fn run_suppressed(cmd: &mut Command) { | |
350 | if !try_run_suppressed(cmd) { | |
351 | std::process::exit(1); | |
352 | } | |
353 | } | |
354 | ||
355 | pub fn try_run_suppressed(cmd: &mut Command) -> bool { | |
356 | let output = match cmd.output() { | |
357 | Ok(status) => status, | |
358 | Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)), | |
359 | }; | |
360 | if !output.status.success() { | |
361 | println!( | |
362 | "\n\ncommand did not execute successfully: {:?}\n\ | |
363 | expected success, got: {}\n\n\ | |
364 | stdout ----\n{}\n\ | |
365 | stderr ----\n{}\n\n", | |
366 | cmd, | |
367 | output.status, | |
368 | String::from_utf8_lossy(&output.stdout), | |
369 | String::from_utf8_lossy(&output.stderr) | |
370 | ); | |
371 | } | |
372 | output.status.success() | |
373 | } | |
374 | ||
375 | pub fn make(host: &str) -> PathBuf { | |
376 | if host.contains("dragonfly") | |
377 | || host.contains("freebsd") | |
378 | || host.contains("netbsd") | |
379 | || host.contains("openbsd") | |
380 | { | |
381 | PathBuf::from("gmake") | |
382 | } else { | |
383 | PathBuf::from("make") | |
384 | } | |
385 | } | |
386 | ||
387 | #[track_caller] | |
388 | pub fn output(cmd: &mut Command) -> String { | |
389 | let output = match cmd.stderr(Stdio::inherit()).output() { | |
390 | Ok(status) => status, | |
391 | Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)), | |
392 | }; | |
393 | if !output.status.success() { | |
394 | panic!( | |
395 | "command did not execute successfully: {:?}\n\ | |
396 | expected success, got: {}", | |
397 | cmd, output.status | |
398 | ); | |
399 | } | |
400 | String::from_utf8(output.stdout).unwrap() | |
401 | } | |
402 | ||
403 | /// Returns the last-modified time for `path`, or zero if it doesn't exist. | |
404 | pub fn mtime(path: &Path) -> SystemTime { | |
405 | fs::metadata(path).and_then(|f| f.modified()).unwrap_or(UNIX_EPOCH) | |
406 | } | |
407 | ||
408 | /// Returns `true` if `dst` is up to date given that the file or files in `src` | |
409 | /// are used to generate it. | |
410 | /// | |
411 | /// Uses last-modified time checks to verify this. | |
412 | pub fn up_to_date(src: &Path, dst: &Path) -> bool { | |
413 | if !dst.exists() { | |
414 | return false; | |
415 | } | |
416 | let threshold = mtime(dst); | |
417 | let meta = match fs::metadata(src) { | |
418 | Ok(meta) => meta, | |
419 | Err(e) => panic!("source {:?} failed to get metadata: {}", src, e), | |
420 | }; | |
421 | if meta.is_dir() { | |
422 | dir_up_to_date(src, threshold) | |
423 | } else { | |
424 | meta.modified().unwrap_or(UNIX_EPOCH) <= threshold | |
425 | } | |
426 | } | |
427 | ||
428 | fn dir_up_to_date(src: &Path, threshold: SystemTime) -> bool { | |
429 | t!(fs::read_dir(src)).map(|e| t!(e)).all(|e| { | |
430 | let meta = t!(e.metadata()); | |
431 | if meta.is_dir() { | |
432 | dir_up_to_date(&e.path(), threshold) | |
433 | } else { | |
434 | meta.modified().unwrap_or(UNIX_EPOCH) < threshold | |
435 | } | |
436 | }) | |
437 | } | |
438 | ||
439 | fn fail(s: &str) -> ! { | |
440 | println!("\n\n{}\n\n", s); | |
441 | std::process::exit(1); | |
442 | } | |
443 | ||
444 | /// Copied from `std::path::absolute` until it stabilizes. | |
445 | /// | |
446 | /// FIXME: this shouldn't exist. | |
447 | pub(crate) fn absolute(path: &Path) -> PathBuf { | |
448 | if path.as_os_str().is_empty() { | |
449 | panic!("can't make empty path absolute"); | |
450 | } | |
451 | #[cfg(unix)] | |
452 | { | |
453 | t!(absolute_unix(path), format!("could not make path absolute: {}", path.display())) | |
454 | } | |
455 | #[cfg(windows)] | |
456 | { | |
457 | t!(absolute_windows(path), format!("could not make path absolute: {}", path.display())) | |
458 | } | |
459 | #[cfg(not(any(unix, windows)))] | |
460 | { | |
461 | println!("warning: bootstrap is not supported on non-unix platforms"); | |
462 | t!(std::fs::canonicalize(t!(std::env::current_dir()))).join(path) | |
463 | } | |
464 | } | |
465 | ||
466 | #[cfg(unix)] | |
467 | /// Make a POSIX path absolute without changing its semantics. | |
468 | fn absolute_unix(path: &Path) -> io::Result<PathBuf> { | |
469 | // This is mostly a wrapper around collecting `Path::components`, with | |
470 | // exceptions made where this conflicts with the POSIX specification. | |
471 | // See 4.13 Pathname Resolution, IEEE Std 1003.1-2017 | |
472 | // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13 | |
473 | ||
474 | use std::os::unix::prelude::OsStrExt; | |
475 | let mut components = path.components(); | |
476 | let path_os = path.as_os_str().as_bytes(); | |
477 | ||
478 | let mut normalized = if path.is_absolute() { | |
479 | // "If a pathname begins with two successive <slash> characters, the | |
480 | // first component following the leading <slash> characters may be | |
481 | // interpreted in an implementation-defined manner, although more than | |
482 | // two leading <slash> characters shall be treated as a single <slash> | |
483 | // character." | |
484 | if path_os.starts_with(b"//") && !path_os.starts_with(b"///") { | |
485 | components.next(); | |
486 | PathBuf::from("//") | |
487 | } else { | |
488 | PathBuf::new() | |
489 | } | |
490 | } else { | |
491 | env::current_dir()? | |
492 | }; | |
493 | normalized.extend(components); | |
494 | ||
495 | // "Interfaces using pathname resolution may specify additional constraints | |
496 | // when a pathname that does not name an existing directory contains at | |
497 | // least one non- <slash> character and contains one or more trailing | |
498 | // <slash> characters". | |
499 | // A trailing <slash> is also meaningful if "a symbolic link is | |
500 | // encountered during pathname resolution". | |
501 | ||
502 | if path_os.ends_with(b"/") { | |
503 | normalized.push(""); | |
504 | } | |
505 | ||
506 | Ok(normalized) | |
507 | } | |
508 | ||
509 | #[cfg(windows)] | |
510 | fn absolute_windows(path: &std::path::Path) -> std::io::Result<std::path::PathBuf> { | |
511 | use std::ffi::OsString; | |
512 | use std::io::Error; | |
513 | use std::os::windows::ffi::{OsStrExt, OsStringExt}; | |
514 | use std::ptr::null_mut; | |
515 | #[link(name = "kernel32")] | |
516 | extern "system" { | |
517 | fn GetFullPathNameW( | |
518 | lpFileName: *const u16, | |
519 | nBufferLength: u32, | |
520 | lpBuffer: *mut u16, | |
521 | lpFilePart: *mut *const u16, | |
522 | ) -> u32; | |
523 | } | |
524 | ||
525 | unsafe { | |
526 | // encode the path as UTF-16 | |
527 | let path: Vec<u16> = path.as_os_str().encode_wide().chain([0]).collect(); | |
528 | let mut buffer = Vec::new(); | |
529 | // Loop until either success or failure. | |
530 | loop { | |
531 | // Try to get the absolute path | |
532 | let len = GetFullPathNameW( | |
533 | path.as_ptr(), | |
534 | buffer.len().try_into().unwrap(), | |
535 | buffer.as_mut_ptr(), | |
536 | null_mut(), | |
537 | ); | |
538 | match len as usize { | |
539 | // Failure | |
540 | 0 => return Err(Error::last_os_error()), | |
541 | // Buffer is too small, resize. | |
542 | len if len > buffer.len() => buffer.resize(len, 0), | |
543 | // Success! | |
544 | len => { | |
545 | buffer.truncate(len); | |
546 | return Ok(OsString::from_wide(&buffer).into()); | |
547 | } | |
548 | } | |
549 | } | |
550 | } | |
551 | } |