mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 12:45:05 +00:00
Improves sourcemap path handling with pathdiff (#1217)
This commit is contained in:
@@ -38,6 +38,7 @@ Making a new release? Simply add the new header with the version and date undern
|
|||||||
* Fixed a bug where MacOS paths weren't being handled correctly. ([#1201])
|
* Fixed a bug where MacOS paths weren't being handled correctly. ([#1201])
|
||||||
* Fixed a bug where the notification timeout thread would fail to cancel on unmount ([#1211])
|
* Fixed a bug where the notification timeout thread would fail to cancel on unmount ([#1211])
|
||||||
* Added a "Forget" option to the sync reminder notification to avoid being reminded for that place in the future ([#1215])
|
* Added a "Forget" option to the sync reminder notification to avoid being reminded for that place in the future ([#1215])
|
||||||
|
* Improves relative path calculation for sourcemap generation to avoid issues with Windows UNC paths. ([#1217])
|
||||||
|
|
||||||
[#1176]: https://github.com/rojo-rbx/rojo/pull/1176
|
[#1176]: https://github.com/rojo-rbx/rojo/pull/1176
|
||||||
[#1179]: https://github.com/rojo-rbx/rojo/pull/1179
|
[#1179]: https://github.com/rojo-rbx/rojo/pull/1179
|
||||||
@@ -46,6 +47,7 @@ Making a new release? Simply add the new header with the version and date undern
|
|||||||
[#1201]: https://github.com/rojo-rbx/rojo/pull/1201
|
[#1201]: https://github.com/rojo-rbx/rojo/pull/1201
|
||||||
[#1211]: https://github.com/rojo-rbx/rojo/pull/1211
|
[#1211]: https://github.com/rojo-rbx/rojo/pull/1211
|
||||||
[#1215]: https://github.com/rojo-rbx/rojo/pull/1215
|
[#1215]: https://github.com/rojo-rbx/rojo/pull/1215
|
||||||
|
[#1217]: https://github.com/rojo-rbx/rojo/pull/1217
|
||||||
|
|
||||||
## [7.7.0-rc.1] (November 27th, 2025)
|
## [7.7.0-rc.1] (November 27th, 2025)
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Code contributions are welcome for features and bugs that have been reported in
|
|||||||
You'll want these tools to work on Rojo:
|
You'll want these tools to work on Rojo:
|
||||||
|
|
||||||
* Latest stable Rust compiler
|
* Latest stable Rust compiler
|
||||||
|
* Rustfmt and Clippy are used for code formatting and linting.
|
||||||
* Latest stable [Rojo](https://github.com/rojo-rbx/rojo)
|
* Latest stable [Rojo](https://github.com/rojo-rbx/rojo)
|
||||||
* [Rokit](https://github.com/rojo-rbx/rokit)
|
* [Rokit](https://github.com/rojo-rbx/rokit)
|
||||||
* [Luau Language Server](https://github.com/JohnnyMorganz/luau-lsp) (Only needed if working on the Studio plugin.)
|
* [Luau Language Server](https://github.com/JohnnyMorganz/luau-lsp) (Only needed if working on the Studio plugin.)
|
||||||
|
|||||||
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -1520,6 +1520,12 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathdiff"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
@@ -2068,6 +2074,7 @@ dependencies = [
|
|||||||
"num_cpus",
|
"num_cpus",
|
||||||
"opener",
|
"opener",
|
||||||
"paste",
|
"paste",
|
||||||
|
"pathdiff",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"profiling",
|
"profiling",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ clap = { version = "3.2.25", features = ["derive"] }
|
|||||||
profiling = "1.0.15"
|
profiling = "1.0.15"
|
||||||
yaml-rust2 = "0.10.3"
|
yaml-rust2 = "0.10.3"
|
||||||
data-encoding = "2.8.0"
|
data-encoding = "2.8.0"
|
||||||
|
pathdiff = "0.2.3"
|
||||||
|
|
||||||
blake3 = "1.5.0"
|
blake3 = "1.5.0"
|
||||||
float-cmp = "0.9.0"
|
float-cmp = "0.9.0"
|
||||||
@@ -124,7 +125,7 @@ semver = "1.0.22"
|
|||||||
rojo-insta-ext = { path = "crates/rojo-insta-ext" }
|
rojo-insta-ext = { path = "crates/rojo-insta-ext" }
|
||||||
|
|
||||||
criterion = "0.3.6"
|
criterion = "0.3.6"
|
||||||
insta = { version = "1.36.1", features = ["redactions", "yaml"] }
|
insta = { version = "1.36.1", features = ["redactions", "yaml", "json"] }
|
||||||
paste = "1.0.14"
|
paste = "1.0.14"
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
serde_yaml = "0.8.26"
|
serde_yaml = "0.8.26"
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
source: src/cli/sourcemap.rs
|
||||||
|
expression: sourcemap_contents
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"className": "DataModel",
|
||||||
|
"filePaths": "[...1 path omitted...]",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "ReplicatedStorage",
|
||||||
|
"className": "ReplicatedStorage",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "Project",
|
||||||
|
"className": "ModuleScript",
|
||||||
|
"filePaths": "[...1 path omitted...]",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "Module",
|
||||||
|
"className": "Folder",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "module",
|
||||||
|
"className": "ModuleScript",
|
||||||
|
"filePaths": "[...1 path omitted...]"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
source: src/cli/sourcemap.rs
|
||||||
|
expression: sourcemap_contents
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"className": "DataModel",
|
||||||
|
"filePaths": [
|
||||||
|
"default.project.json"
|
||||||
|
],
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "ReplicatedStorage",
|
||||||
|
"className": "ReplicatedStorage",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "Project",
|
||||||
|
"className": "ModuleScript",
|
||||||
|
"filePaths": [
|
||||||
|
"src/init.luau"
|
||||||
|
],
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "Module",
|
||||||
|
"className": "Folder",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "module",
|
||||||
|
"className": "ModuleScript",
|
||||||
|
"filePaths": [
|
||||||
|
"../module/module.luau"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ use fs_err::File;
|
|||||||
use memofs::Vfs;
|
use memofs::Vfs;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use rbx_dom_weak::{types::Ref, Ustr};
|
use rbx_dom_weak::{types::Ref, Ustr};
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -24,19 +24,20 @@ const PATH_STRIP_FAILED_ERR: &str = "Failed to create relative paths for project
|
|||||||
const ABSOLUTE_PATH_FAILED_ERR: &str = "Failed to turn relative path into absolute path!";
|
const ABSOLUTE_PATH_FAILED_ERR: &str = "Failed to turn relative path into absolute path!";
|
||||||
|
|
||||||
/// Representation of a node in the generated sourcemap tree.
|
/// Representation of a node in the generated sourcemap tree.
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct SourcemapNode<'a> {
|
struct SourcemapNode<'a> {
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
class_name: Ustr,
|
class_name: Ustr,
|
||||||
|
|
||||||
#[serde(
|
#[serde(
|
||||||
|
default,
|
||||||
skip_serializing_if = "Vec::is_empty",
|
skip_serializing_if = "Vec::is_empty",
|
||||||
serialize_with = "crate::path_serializer::serialize_vec_absolute"
|
serialize_with = "crate::path_serializer::serialize_vec_absolute"
|
||||||
)]
|
)]
|
||||||
file_paths: Vec<Cow<'a, Path>>,
|
file_paths: Vec<Cow<'a, Path>>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
children: Vec<SourcemapNode<'a>>,
|
children: Vec<SourcemapNode<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,12 +71,13 @@ pub struct SourcemapCommand {
|
|||||||
|
|
||||||
impl SourcemapCommand {
|
impl SourcemapCommand {
|
||||||
pub fn run(self) -> anyhow::Result<()> {
|
pub fn run(self) -> anyhow::Result<()> {
|
||||||
let project_path = resolve_path(&self.project);
|
let project_path = fs_err::canonicalize(resolve_path(&self.project))?;
|
||||||
|
|
||||||
log::trace!("Constructing in-memory filesystem");
|
log::trace!("Constructing filesystem with StdBackend");
|
||||||
let vfs = Vfs::new_default();
|
let vfs = Vfs::new_default();
|
||||||
vfs.set_watch_enabled(self.watch);
|
vfs.set_watch_enabled(self.watch);
|
||||||
|
|
||||||
|
log::trace!("Setting up session for sourcemap generation");
|
||||||
let session = ServeSession::new(vfs, project_path)?;
|
let session = ServeSession::new(vfs, project_path)?;
|
||||||
let mut cursor = session.message_queue().cursor();
|
let mut cursor = session.message_queue().cursor();
|
||||||
|
|
||||||
@@ -87,14 +89,17 @@ impl SourcemapCommand {
|
|||||||
|
|
||||||
// Pre-build a rayon threadpool with a low number of threads to avoid
|
// Pre-build a rayon threadpool with a low number of threads to avoid
|
||||||
// dynamic creation overhead on systems with a high number of cpus.
|
// dynamic creation overhead on systems with a high number of cpus.
|
||||||
|
log::trace!("Setting rayon global threadpool");
|
||||||
rayon::ThreadPoolBuilder::new()
|
rayon::ThreadPoolBuilder::new()
|
||||||
.num_threads(num_cpus::get().min(6))
|
.num_threads(num_cpus::get().min(6))
|
||||||
.build_global()
|
.build_global()
|
||||||
.unwrap();
|
.ok();
|
||||||
|
|
||||||
|
log::trace!("Writing initial sourcemap");
|
||||||
write_sourcemap(&session, self.output.as_deref(), filter, self.absolute)?;
|
write_sourcemap(&session, self.output.as_deref(), filter, self.absolute)?;
|
||||||
|
|
||||||
if self.watch {
|
if self.watch {
|
||||||
|
log::trace!("Setting up runtime for watch mode");
|
||||||
let rt = Runtime::new().unwrap();
|
let rt = Runtime::new().unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -208,7 +213,7 @@ fn recurse_create_node<'a>(
|
|||||||
} else {
|
} else {
|
||||||
for val in file_paths {
|
for val in file_paths {
|
||||||
output_file_paths.push(Cow::from(
|
output_file_paths.push(Cow::from(
|
||||||
val.strip_prefix(project_dir).expect(PATH_STRIP_FAILED_ERR),
|
pathdiff::diff_paths(val, project_dir).expect(PATH_STRIP_FAILED_ERR),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -250,3 +255,80 @@ fn write_sourcemap(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::cli::sourcemap::SourcemapNode;
|
||||||
|
use crate::cli::SourcemapCommand;
|
||||||
|
use insta::internals::Content;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn maps_relative_paths() {
|
||||||
|
let sourcemap_dir = tempfile::tempdir().unwrap();
|
||||||
|
let sourcemap_output = sourcemap_dir.path().join("sourcemap.json");
|
||||||
|
let project_path = fs_err::canonicalize(
|
||||||
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("test-projects")
|
||||||
|
.join("relative_paths")
|
||||||
|
.join("project"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let sourcemap_command = SourcemapCommand {
|
||||||
|
project: project_path,
|
||||||
|
output: Some(sourcemap_output.clone()),
|
||||||
|
include_non_scripts: false,
|
||||||
|
watch: false,
|
||||||
|
absolute: false,
|
||||||
|
};
|
||||||
|
assert!(sourcemap_command.run().is_ok());
|
||||||
|
|
||||||
|
let raw_sourcemap_contents = fs_err::read_to_string(sourcemap_output.as_path()).unwrap();
|
||||||
|
let sourcemap_contents =
|
||||||
|
serde_json::from_str::<SourcemapNode>(&raw_sourcemap_contents).unwrap();
|
||||||
|
insta::assert_json_snapshot!(sourcemap_contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn maps_absolute_paths() {
|
||||||
|
let sourcemap_dir = tempfile::tempdir().unwrap();
|
||||||
|
let sourcemap_output = sourcemap_dir.path().join("sourcemap.json");
|
||||||
|
let project_path = fs_err::canonicalize(
|
||||||
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("test-projects")
|
||||||
|
.join("relative_paths")
|
||||||
|
.join("project"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let sourcemap_command = SourcemapCommand {
|
||||||
|
project: project_path,
|
||||||
|
output: Some(sourcemap_output.clone()),
|
||||||
|
include_non_scripts: false,
|
||||||
|
watch: false,
|
||||||
|
absolute: true,
|
||||||
|
};
|
||||||
|
assert!(sourcemap_command.run().is_ok());
|
||||||
|
|
||||||
|
let raw_sourcemap_contents = fs_err::read_to_string(sourcemap_output.as_path()).unwrap();
|
||||||
|
let sourcemap_contents =
|
||||||
|
serde_json::from_str::<SourcemapNode>(&raw_sourcemap_contents).unwrap();
|
||||||
|
insta::assert_json_snapshot!(sourcemap_contents, {
|
||||||
|
".**.filePaths" => insta::dynamic_redaction(|mut value, _path| {
|
||||||
|
let mut paths_count = 0;
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Content::Seq(ref mut vec) => {
|
||||||
|
for path in vec.iter().map(|i| i.as_str().unwrap()) {
|
||||||
|
assert_eq!(fs_err::canonicalize(path).is_ok(), true, "path was not valid");
|
||||||
|
assert_eq!(Path::new(path).is_absolute(), true, "path was not absolute");
|
||||||
|
|
||||||
|
paths_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("Expected filePaths to be a sequence"),
|
||||||
|
}
|
||||||
|
format!("[...{} path{} omitted...]", paths_count, if paths_count != 1 { "s" } else { "" } )
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
14
test-projects/relative_paths/default.project.json
Normal file
14
test-projects/relative_paths/default.project.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"tree": {
|
||||||
|
"$className": "DataModel",
|
||||||
|
"ReplicatedStorage": {
|
||||||
|
"Project": {
|
||||||
|
"$path": "project/src",
|
||||||
|
"Module": {
|
||||||
|
"$path": "module"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
test-projects/relative_paths/module/module.luau
Normal file
1
test-projects/relative_paths/module/module.luau
Normal file
@@ -0,0 +1 @@
|
|||||||
|
return nil
|
||||||
14
test-projects/relative_paths/project/default.project.json
Normal file
14
test-projects/relative_paths/project/default.project.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"tree": {
|
||||||
|
"$className": "DataModel",
|
||||||
|
"ReplicatedStorage": {
|
||||||
|
"Project": {
|
||||||
|
"$path": "src/",
|
||||||
|
"Module": {
|
||||||
|
"$path": "../module"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
test-projects/relative_paths/project/src/init.luau
Normal file
1
test-projects/relative_paths/project/src/init.luau
Normal file
@@ -0,0 +1 @@
|
|||||||
|
return nil
|
||||||
Reference in New Issue
Block a user