Fix windows verbatim paths (#1295)

This commit is contained in:
EgoMoose
2026-07-05 14:26:35 -04:00
committed by GitHub
parent 0289491ccb
commit 7ade19293c
8 changed files with 93 additions and 4 deletions

View File

@@ -31,6 +31,12 @@ Making a new release? Simply add the new header with the version and date undern
## Unreleased
* Fixed `$path` values that point outside the project folder failing to match `syncRule`s on Windows, which broke `rojo sourcemap` with a "could not be turned into a Roblox Instance" error. ([#1290])
* Fixed `rojo serve` silently stopping syncing file changes on Windows when the served project path was a verbatim (`\\?\`) path, because tree paths and file-watcher event paths were canonicalized to different forms. ([#1290])
* Fixed `rojo sourcemap --absolute` emitting verbatim (`\\?\`) paths on Windows, which broke require types in luau-lsp. ([#1290])
[#1290]: https://github.com/rojo-rbx/rojo/pull/1290
## [7.7.0] (July 1st, 2026)
* `inf` and `nan` values in properties are now synced ([#1176])

8
Cargo.lock generated
View File

@@ -478,6 +478,12 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "either"
version = "1.15.0"
@@ -1316,6 +1322,7 @@ name = "memofs"
version = "0.4.0"
dependencies = [
"crossbeam-channel",
"dunce",
"fs-err",
"notify",
"serde",
@@ -2066,6 +2073,7 @@ dependencies = [
"crossbeam-channel",
"csv",
"data-encoding",
"dunce",
"embed-resource",
"env_logger",
"float-cmp",

View File

@@ -68,6 +68,7 @@ backtrace = "0.3.69"
bincode = "1.3.3"
crossbeam-channel = "0.5.12"
csv = "1.3.0"
dunce = "1.0.5"
env_logger = "0.9.3"
fs-err = "2.11.0"
futures = "0.3.30"

View File

@@ -16,6 +16,7 @@ homepage = "https://github.com/rojo-rbx/rojo/tree/master/memofs"
[dependencies]
crossbeam-channel = "0.5.12"
dunce = "1.0.5"
fs-err = "2.11.0"
notify = "4.0.17"
serde = { version = "1.0.197", features = ["derive"] }

View File

@@ -644,13 +644,49 @@ mod test {
let vfs = Vfs::new(StdBackend::new().unwrap());
let canonicalized = vfs.canonicalize(&file_path).unwrap();
assert_eq!(canonicalized, file_path.canonicalize().unwrap());
assert_eq!(canonicalized, dunce::canonicalize(&file_path).unwrap());
assert_eq!(
vfs.read_to_string(&canonicalized).unwrap().to_string(),
contents.to_string()
);
}
#[test]
#[cfg(windows)]
fn canonicalize_std_backend_not_verbatim() {
use std::path::{Component, Prefix};
let dir = tempfile::tempdir().unwrap();
let file_path = dir.path().join("file.txt");
fs_err::write(&file_path, "hello").unwrap();
let vfs = Vfs::new(StdBackend::new().unwrap());
let canonicalized = vfs.canonicalize(&file_path).unwrap();
let is_verbatim = matches!(
canonicalized.components().next(),
Some(Component::Prefix(prefix)) if matches!(
prefix.kind(),
Prefix::Verbatim(_) | Prefix::VerbatimDisk(_) | Prefix::VerbatimUNC(_, _)
)
);
assert!(
!is_verbatim,
"expected a non-verbatim path, got {:?}",
canonicalized
);
// Joining a relative parent path must preserve the `..` segment. On a
// verbatim path Rust would drop it lexically, which is the root cause
// of the bug.
let joined = canonicalized.join("..").join("sibling");
assert!(
joined.components().any(|c| c == Component::ParentDir),
"`..` should be preserved when joining onto {:?}",
canonicalized
);
}
#[test]
fn canonicalize_std_backend_missing_errors() {
let dir = tempfile::tempdir().unwrap();

View File

@@ -107,7 +107,7 @@ impl VfsBackend for StdBackend {
}
fn canonicalize(&mut self, path: &Path) -> io::Result<PathBuf> {
fs_err::canonicalize(path)
dunce::canonicalize(path)
}
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {

View File

@@ -72,7 +72,7 @@ pub struct SourcemapCommand {
impl SourcemapCommand {
pub fn run(self) -> anyhow::Result<()> {
let project_path = fs_err::canonicalize(resolve_path(&self.project)?)?;
let project_path = dunce::canonicalize(resolve_path(&self.project)?)?;
log::trace!("Constructing filesystem with StdBackend");
let vfs = Vfs::new_default()?;

View File

@@ -95,8 +95,9 @@ impl ServeSession {
/// currently loaded from the filesystem directly instead of through the
/// in-memory filesystem layer.
pub fn new<P: AsRef<Path>>(vfs: Vfs, start_path: P) -> Result<Self, ServeSessionError> {
let start_path = start_path.as_ref();
let start_time = Instant::now();
let start_path = vfs.canonicalize(start_path.as_ref())?;
let start_path = start_path.as_path();
log::trace!("Starting new ServeSession at path {}", start_path.display());
@@ -240,3 +241,39 @@ pub enum ServeSessionError {
source: anyhow::Error,
},
}
#[cfg(test)]
mod test {
use super::*;
use memofs::StdBackend;
#[test]
fn tree_is_keyed_by_canonical_paths() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(
dir.path().join("default.project.json"),
r#"{ "name": "test", "tree": { "$className": "Folder" } }"#,
)
.unwrap();
// `std::fs::canonicalize` yields a verbatim path on Windows.
// On other platforms it simply resolves the path (e.g. symlinks),
// which the session must also handle.
let start_path = std::fs::canonicalize(dir.path()).unwrap();
let vfs = Vfs::new(StdBackend::new().unwrap());
let session = ServeSession::new(vfs, &start_path).unwrap();
let project_file = start_path.join("default.project.json");
let canonical = session.vfs().canonicalize(&project_file).unwrap();
assert!(
!session.tree().get_ids_at_path(&canonical).is_empty(),
"project file {} should be tracked in the tree under its canonical \
path {}, matching what the watcher reports",
project_file.display(),
canonical.display(),
);
}
}