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
|
## 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)
|
## [7.7.0] (July 1st, 2026)
|
||||||
|
|
||||||
* `inf` and `nan` values in properties are now synced ([#1176])
|
* `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",
|
"syn 2.0.111",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dunce"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
@@ -1316,6 +1322,7 @@ name = "memofs"
|
|||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
|
"dunce",
|
||||||
"fs-err",
|
"fs-err",
|
||||||
"notify",
|
"notify",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2066,6 +2073,7 @@ dependencies = [
|
|||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"csv",
|
"csv",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
|
"dunce",
|
||||||
"embed-resource",
|
"embed-resource",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"float-cmp",
|
"float-cmp",
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ backtrace = "0.3.69"
|
|||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
crossbeam-channel = "0.5.12"
|
crossbeam-channel = "0.5.12"
|
||||||
csv = "1.3.0"
|
csv = "1.3.0"
|
||||||
|
dunce = "1.0.5"
|
||||||
env_logger = "0.9.3"
|
env_logger = "0.9.3"
|
||||||
fs-err = "2.11.0"
|
fs-err = "2.11.0"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ homepage = "https://github.com/rojo-rbx/rojo/tree/master/memofs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossbeam-channel = "0.5.12"
|
crossbeam-channel = "0.5.12"
|
||||||
|
dunce = "1.0.5"
|
||||||
fs-err = "2.11.0"
|
fs-err = "2.11.0"
|
||||||
notify = "4.0.17"
|
notify = "4.0.17"
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
|||||||
@@ -644,13 +644,49 @@ mod test {
|
|||||||
|
|
||||||
let vfs = Vfs::new(StdBackend::new().unwrap());
|
let vfs = Vfs::new(StdBackend::new().unwrap());
|
||||||
let canonicalized = vfs.canonicalize(&file_path).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!(
|
assert_eq!(
|
||||||
vfs.read_to_string(&canonicalized).unwrap().to_string(),
|
vfs.read_to_string(&canonicalized).unwrap().to_string(),
|
||||||
contents.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]
|
#[test]
|
||||||
fn canonicalize_std_backend_missing_errors() {
|
fn canonicalize_std_backend_missing_errors() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ impl VfsBackend for StdBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn canonicalize(&mut self, path: &Path) -> io::Result<PathBuf> {
|
fn canonicalize(&mut self, path: &Path) -> io::Result<PathBuf> {
|
||||||
fs_err::canonicalize(path)
|
dunce::canonicalize(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ pub struct SourcemapCommand {
|
|||||||
|
|
||||||
impl SourcemapCommand {
|
impl SourcemapCommand {
|
||||||
pub fn run(self) -> anyhow::Result<()> {
|
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");
|
log::trace!("Constructing filesystem with StdBackend");
|
||||||
let vfs = Vfs::new_default()?;
|
let vfs = Vfs::new_default()?;
|
||||||
|
|||||||
@@ -95,8 +95,9 @@ impl ServeSession {
|
|||||||
/// currently loaded from the filesystem directly instead of through the
|
/// currently loaded from the filesystem directly instead of through the
|
||||||
/// in-memory filesystem layer.
|
/// in-memory filesystem layer.
|
||||||
pub fn new<P: AsRef<Path>>(vfs: Vfs, start_path: P) -> Result<Self, ServeSessionError> {
|
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_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());
|
log::trace!("Starting new ServeSession at path {}", start_path.display());
|
||||||
|
|
||||||
@@ -240,3 +241,39 @@ pub enum ServeSessionError {
|
|||||||
source: anyhow::Error,
|
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