Skip to content

Commit e5beb67

Browse files
committed
run_make_support: rectify symlink handling
Avoid confusing Unix symlinks and Windows symlinks, and since their semantics are quite different we should avoid trying to make it to automagic in how symlinks are created and deleted. Notably, the tests should reflect what type of symlinks are to be created to match what std does to make it less surprising for test readers.
1 parent 170d6cb commit e5beb67

File tree

1 file changed

+133
-50
lines changed
  • src/tools/run-make-support/src

1 file changed

+133
-50
lines changed

src/tools/run-make-support/src/fs.rs

+133-50
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,6 @@
11
use std::io;
22
use std::path::{Path, PathBuf};
33

4-
// FIXME(jieyouxu): modify create_symlink to panic on windows.
5-
6-
/// Creates a new symlink to a path on the filesystem, adjusting for Windows or Unix.
7-
#[cfg(target_family = "windows")]
8-
pub fn create_symlink<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) {
9-
if link.as_ref().exists() {
10-
std::fs::remove_dir(link.as_ref()).unwrap();
11-
}
12-
if original.as_ref().is_file() {
13-
std::os::windows::fs::symlink_file(original.as_ref(), link.as_ref()).expect(&format!(
14-
"failed to create symlink {:?} for {:?}",
15-
link.as_ref().display(),
16-
original.as_ref().display(),
17-
));
18-
} else {
19-
std::os::windows::fs::symlink_dir(original.as_ref(), link.as_ref()).expect(&format!(
20-
"failed to create symlink {:?} for {:?}",
21-
link.as_ref().display(),
22-
original.as_ref().display(),
23-
));
24-
}
25-
}
26-
27-
/// Creates a new symlink to a path on the filesystem, adjusting for Windows or Unix.
28-
#[cfg(target_family = "unix")]
29-
pub fn create_symlink<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) {
30-
if link.as_ref().exists() {
31-
std::fs::remove_dir(link.as_ref()).unwrap();
32-
}
33-
std::os::unix::fs::symlink(original.as_ref(), link.as_ref()).expect(&format!(
34-
"failed to create symlink {:?} for {:?}",
35-
link.as_ref().display(),
36-
original.as_ref().display(),
37-
));
38-
}
39-
404
/// Copy a directory into another.
415
pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
426
fn copy_dir_all_inner(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
@@ -50,7 +14,34 @@ pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
5014
if ty.is_dir() {
5115
copy_dir_all_inner(entry.path(), dst.join(entry.file_name()))?;
5216
} else if ty.is_symlink() {
53-
copy_symlink(entry.path(), dst.join(entry.file_name()))?;
17+
// Traverse symlink once to find path of target entity.
18+
let target_path = std::fs::read_link(entry.path())?;
19+
20+
let new_symlink_path = dst.join(entry.file_name());
21+
#[cfg(windows)]
22+
{
23+
// Find metadata about target entity (shallow, no further symlink traversal in
24+
// case target is also a symlink).
25+
let target_metadata = std::fs::symlink_metadata(&target_path)?;
26+
let target_file_type = target_metadata.file_type();
27+
if target_file_type.is_dir() {
28+
std::os::windows::fs::symlink_dir(&target_path, new_symlink_path)?;
29+
} else {
30+
// Target may be a file or another symlink, in any case we can use
31+
// `symlink_file` here.
32+
std::os::windows::fs::symlink_file(&target_path, new_symlink_path)?;
33+
}
34+
}
35+
#[cfg(unix)]
36+
{
37+
std::os::unix::fs::symlink(target_path, new_symlink_path)?;
38+
}
39+
#[cfg(not(any(windows, unix)))]
40+
{
41+
// Technically there's also wasi, but I have no clue about wasi symlink
42+
// semantics and which wasi targets / environment support symlinks.
43+
unimplemented!("unsupported target");
44+
}
5445
} else {
5546
std::fs::copy(entry.path(), dst.join(entry.file_name()))?;
5647
}
@@ -69,12 +60,6 @@ pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
6960
}
7061
}
7162

72-
fn copy_symlink<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
73-
let target_path = std::fs::read_link(from).unwrap();
74-
create_symlink(target_path, to);
75-
Ok(())
76-
}
77-
7863
/// Helper for reading entries in a given directory.
7964
pub fn read_dir_entries<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, mut callback: F) {
8065
for entry in read_dir(dir) {
@@ -85,8 +70,17 @@ pub fn read_dir_entries<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, mut callback: F
8570
/// A wrapper around [`std::fs::remove_file`] which includes the file path in the panic message.
8671
#[track_caller]
8772
pub fn remove_file<P: AsRef<Path>>(path: P) {
88-
std::fs::remove_file(path.as_ref())
89-
.expect(&format!("the file in path \"{}\" could not be removed", path.as_ref().display()));
73+
if let Err(e) = std::fs::remove_file(path.as_ref()) {
74+
panic!("failed to remove file at `{}`: {e}", path.as_ref().display());
75+
}
76+
}
77+
78+
/// A wrapper around [`std::fs::remove_dir`] which includes the directory path in the panic message.
79+
#[track_caller]
80+
pub fn remove_dir<P: AsRef<Path>>(path: P) {
81+
if let Err(e) = std::fs::remove_dir(path.as_ref()) {
82+
panic!("failed to remove directory at `{}`: {e}", path.as_ref().display());
83+
}
9084
}
9185

9286
/// A wrapper around [`std::fs::copy`] which includes the file path in the panic message.
@@ -165,13 +159,32 @@ pub fn create_dir_all<P: AsRef<Path>>(path: P) {
165159
));
166160
}
167161

168-
/// A wrapper around [`std::fs::metadata`] which includes the file path in the panic message.
162+
/// A wrapper around [`std::fs::metadata`] which includes the file path in the panic message. Note
163+
/// that this will traverse symlinks and will return metadata about the target file. Use
164+
/// [`symlink_metadata`] if you don't want to traverse symlinks.
165+
///
166+
/// See [`std::fs::metadata`] docs for more details.
169167
#[track_caller]
170168
pub fn metadata<P: AsRef<Path>>(path: P) -> std::fs::Metadata {
171-
std::fs::metadata(path.as_ref()).expect(&format!(
172-
"the file's metadata in path \"{}\" could not be read",
173-
path.as_ref().display()
174-
))
169+
match std::fs::metadata(path.as_ref()) {
170+
Ok(m) => m,
171+
Err(e) => panic!("failed to read file metadata at `{}`: {e}", path.as_ref().display()),
172+
}
173+
}
174+
175+
/// A wrapper around [`std::fs::symlink_metadata`] which includes the file path in the panic
176+
/// message. Note that this will not traverse symlinks and will return metadata about the filesystem
177+
/// entity itself. Use [`metadata`] if you want to traverse symlinks.
178+
///
179+
/// See [`std::fs::symlink_metadata`] docs for more details.
180+
#[track_caller]
181+
pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> std::fs::Metadata {
182+
match std::fs::symlink_metadata(path.as_ref()) {
183+
Ok(m) => m,
184+
Err(e) => {
185+
panic!("failed to read file metadata (shallow) at `{}`: {e}", path.as_ref().display())
186+
}
187+
}
175188
}
176189

177190
/// A wrapper around [`std::fs::rename`] which includes the file path in the panic message.
@@ -205,3 +218,73 @@ pub fn shallow_find_dir_entries<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> {
205218
}
206219
output
207220
}
221+
222+
/// Create a new symbolic link to a directory.
223+
///
224+
/// # Removing the symlink
225+
///
226+
/// - On Windows, a symlink-to-directory needs to be removed with a corresponding [`fs::remove_dir`]
227+
/// and not [`fs::remove_file`].
228+
/// - On Unix, remove the symlink with [`fs::remove_file`].
229+
///
230+
/// [`fs::remove_dir`]: crate::fs::remove_dir
231+
/// [`fs::remove_file`]: crate::fs::remove_file
232+
pub fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) {
233+
#[cfg(unix)]
234+
{
235+
if let Err(e) = std::os::unix::fs::symlink(original.as_ref(), link.as_ref()) {
236+
panic!(
237+
"failed to create symlink: original=`{}`, link=`{}`: {e}",
238+
original.as_ref().display(),
239+
link.as_ref().display()
240+
);
241+
}
242+
}
243+
#[cfg(windows)]
244+
{
245+
if let Err(e) = std::os::windows::fs::symlink_dir(original.as_ref(), link.as_ref()) {
246+
panic!(
247+
"failed to create symlink-to-directory: original=`{}`, link=`{}`: {e}",
248+
original.as_ref().display(),
249+
link.as_ref().display()
250+
);
251+
}
252+
}
253+
#[cfg(not(any(windows, unix)))]
254+
{
255+
unimplemented!("target family not currently supported")
256+
}
257+
}
258+
259+
/// Create a new symbolic link to a file.
260+
///
261+
/// # Removing the symlink
262+
///
263+
/// On both Windows and Unix, a symlink-to-file needs to be removed with a corresponding
264+
/// [`fs::remove_file`](crate::fs::remove_file) and not [`fs::remove_dir`](crate::fs::remove_dir).
265+
pub fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) {
266+
#[cfg(unix)]
267+
{
268+
if let Err(e) = std::os::unix::fs::symlink(original.as_ref(), link.as_ref()) {
269+
panic!(
270+
"failed to create symlink: original=`{}`, link=`{}`: {e}",
271+
original.as_ref().display(),
272+
link.as_ref().display()
273+
);
274+
}
275+
}
276+
#[cfg(windows)]
277+
{
278+
if let Err(e) = std::os::windows::fs::symlink_file(original.as_ref(), link.as_ref()) {
279+
panic!(
280+
"failed to create symlink-to-file: original=`{}`, link=`{}`: {e}",
281+
original.as_ref().display(),
282+
link.as_ref().display()
283+
);
284+
}
285+
}
286+
#[cfg(not(any(windows, unix)))]
287+
{
288+
unimplemented!("target family not currently supported")
289+
}
290+
}

0 commit comments

Comments
 (0)