forked from rojo-rbx/rojo
Fix windows verbatim paths (#1295)
This commit is contained in:
@@ -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
8
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user