Auto merge of #3490 - RalfJung:paths, r=RalfJung

share code between win-to-unix and unix-to-win path conversion
This commit is contained in:
bors 2024-04-19 07:58:10 +00:00
commit 134ee309c9

View File

@ -251,7 +251,6 @@ fn alloc_path_as_wide_str(
this.alloc_os_str_as_wide_str(&os_str, memkind) this.alloc_os_str_as_wide_str(&os_str, memkind)
} }
#[allow(clippy::get_first)]
fn convert_path<'a>( fn convert_path<'a>(
&self, &self,
os_str: Cow<'a, OsStr>, os_str: Cow<'a, OsStr>,
@ -260,6 +259,65 @@ fn convert_path<'a>(
let this = self.eval_context_ref(); let this = self.eval_context_ref();
let target_os = &this.tcx.sess.target.os; let target_os = &this.tcx.sess.target.os;
/// Adjust a Windows path to Unix conventions such that it un-does everything that
/// `unix_to_windows` did, and such that if the Windows input path was absolute, then the
/// Unix output path is absolute.
fn windows_to_unix<T>(path: &mut Vec<T>)
where
T: From<u8> + Copy + Eq,
{
let sep = T::from(b'/');
// Make sure all path separators are `/`.
for c in path.iter_mut() {
if *c == b'\\'.into() {
*c = sep;
}
}
// If this starts with `//?/`, it was probably produced by `unix_to_windows`` and we
// remove the `//?` that got added to get the Unix path back out.
if path.get(0..4) == Some(&[sep, sep, b'?'.into(), sep]) {
// Remove first 3 characters. It still starts with `/` so it is absolute on Unix.
path.splice(0..3, std::iter::empty());
}
// If it starts with a drive letter (`X:/`), convert it to an absolute Unix path.
else if path.get(1..3) == Some(&[b':'.into(), sep]) {
// We add a `/` at the beginning, to store the absolute Windows
// path in something that looks like an absolute Unix path.
path.insert(0, sep);
}
}
/// Adjust a Unix path to Windows conventions such that it un-does everything that
/// `windows_to_unix` did, and such that if the Unix input path was absolute, then the
/// Windows output path is absolute.
fn unix_to_windows<T>(path: &mut Vec<T>)
where
T: From<u8> + Copy + Eq,
{
let sep = T::from(b'\\');
// Make sure all path separators are `\`.
for c in path.iter_mut() {
if *c == b'/'.into() {
*c = sep;
}
}
// If the path is `\X:\`, the leading separator was probably added by `windows_to_unix`
// and we should get rid of it again.
if path.get(2..4) == Some(&[b':'.into(), sep]) && path[0] == sep {
// The new path is still absolute on Windows.
path.remove(0);
}
// If this starts withs a `\` but not a `\\`, then this was absolute on Unix but is
// relative on Windows (relative to "the root of the current directory", e.g. the
// drive letter).
else if path.first() == Some(&sep) && path.get(1) != Some(&sep) {
// We add `\\?` so it starts with `\\?\` which is some magic path on Windows
// that *is* considered absolute. This way we store the absolute Unix path
// in something that looks like an absolute Windows path.
path.splice(0..0, [sep, sep, b'?'.into()]);
}
}
// Below we assume that everything non-Windows works like Unix, at least // Below we assume that everything non-Windows works like Unix, at least
// when it comes to file system path conventions. // when it comes to file system path conventions.
#[cfg(windows)] #[cfg(windows)]
@ -268,102 +326,30 @@ fn convert_path<'a>(
os_str os_str
} else { } else {
// Unix target, Windows host. // Unix target, Windows host.
let (from, to) = match direction { let mut path: Vec<u16> = os_str.encode_wide().collect();
PathConversion::HostToTarget => ('\\', '/'),
PathConversion::TargetToHost => ('/', '\\'),
};
let mut converted = os_str
.encode_wide()
.map(|wchar| if wchar == from as u16 { to as u16 } else { wchar })
.collect::<Vec<_>>();
// We also have to ensure that absolute paths remain absolute.
match direction { match direction {
PathConversion::HostToTarget => { PathConversion::HostToTarget => {
// If this is an absolute Windows path that starts with a drive letter (`C:/...` windows_to_unix(&mut path);
// after separator conversion), it would not be considered absolute by Unix
// target code.
if converted.get(1).copied() == Some(b':' as u16)
&& converted.get(2).copied() == Some(b'/' as u16)
{
// We add a `/` at the beginning, to store the absolute Windows
// path in something that looks like an absolute Unix path.
converted.insert(0, b'/' as u16);
}
} }
PathConversion::TargetToHost => { PathConversion::TargetToHost => {
// If the path is `\C:\`, the leading backslash was probably added by the above code unix_to_windows(&mut path);
// and we should get rid of it again.
if converted.get(0).copied() == Some(b'\\' as u16)
&& converted.get(2).copied() == Some(b':' as u16)
&& converted.get(3).copied() == Some(b'\\' as u16)
{
converted.remove(0);
}
// If the path starts with `\\`, it is a magic Windows path. Conveniently, paths
// starting with `//` on Unix are also magic where the first component can have
// "application-specific" meaning, which is reflected e.g. by `path::absolute`
// leaving leading `//` alone (but normalizing leading `///` to `/`). So we
// don't have to do anything, the magic Windows path should work mostly fine as
// a magic Unix path.
} }
} }
Cow::Owned(OsString::from_wide(&converted)) Cow::Owned(OsString::from_wide(&path))
}; };
#[cfg(unix)] #[cfg(unix)]
return if target_os == "windows" { return if target_os == "windows" {
// Windows target, Unix host. // Windows target, Unix host.
let (from, to) = match direction { let mut path: Vec<u8> = os_str.into_owned().into_encoded_bytes();
PathConversion::HostToTarget => (b'/', b'\\'),
PathConversion::TargetToHost => (b'\\', b'/'),
};
let mut converted = os_str
.as_bytes()
.iter()
.map(|&wchar| if wchar == from { to } else { wchar })
.collect::<Vec<_>>();
// We also have to ensure that absolute paths remain absolute.
match direction { match direction {
PathConversion::HostToTarget => { PathConversion::HostToTarget => {
// If the path is `/C:/`, the leading backslash was probably added by the below unix_to_windows(&mut path);
// driver letter handling and we should get rid of it again.
if converted.get(0).copied() == Some(b'\\')
&& converted.get(2).copied() == Some(b':')
&& converted.get(3).copied() == Some(b'\\')
{
converted.remove(0);
}
// If this starts withs a `\` but not a `\\`, then for Windows this is a
// relative path (relative to "the root of the current directory", e.g. the
// drive letter). But the host path on Unix is absolute as it starts with `/`.
else if converted.get(0).copied() == Some(b'\\')
&& converted.get(1).copied() != Some(b'\\')
{
// We add `\\?` so it starts with `\\?\` which is some magic path on Windows
// that *is* considered absolute. This way we store the absolute host path
// in something that looks like an absolute path to the (Windows) target.
converted.splice(0..0, b"\\\\?".iter().copied());
}
} }
PathConversion::TargetToHost => { PathConversion::TargetToHost => {
// If this starts with `//?/`, it was probably produced by the above code and we windows_to_unix(&mut path);
// remove the `//?` that got added to get the Unix path back out.
if converted.get(0).copied() == Some(b'/')
&& converted.get(1).copied() == Some(b'/')
&& converted.get(2).copied() == Some(b'?')
&& converted.get(3).copied() == Some(b'/')
{
// Remove first 3 characters
converted.splice(0..3, std::iter::empty());
}
// If it starts with a drive letter, convert it to an absolute Unix path.
else if converted.get(1).copied() == Some(b':')
&& converted.get(2).copied() == Some(b'/')
{
converted.insert(0, b'/');
} }
} }
} Cow::Owned(OsString::from_vec(path))
Cow::Owned(OsString::from_vec(converted))
} else { } else {
// Unix-on-Unix, all is fine. // Unix-on-Unix, all is fine.
os_str os_str