mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
Unity projects (a folder with Assets and ProjectSettings/ProjectVersion.txt) are detected without a generated solution: Assets / Packages become source roots, ProjectSettings resources, and Unity's cache and build folders are excluded before they exist. A Unity pane in the Project view shows the editor version and hides .meta files, generated C# project files and caches (each can be shown again). Operations on assets under Assets keep their .meta files consistent: rename and move carry the metadata (same GUID), delete removes both, copy generates new GUIDs and updates references inside the copied group, and file-move undo and workspace edits include the metadata. Destination metadata conflicts and links inside the project are rejected before anything changes. Co-authored-by: hacbit <[email protected]>
95 lines
3.7 KiB
Rust
95 lines
3.7 KiB
Rust
//! Unity project identity. Generated C# solutions are optional, not root markers.
|
|
|
|
pub(crate) mod assets;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use specta::Type;
|
|
use std::io::Read;
|
|
use std::path::Path;
|
|
|
|
pub const VERSION_FILE: &str = "ProjectSettings/ProjectVersion.txt";
|
|
/// Root-relative caches and build outputs, including folders not created yet.
|
|
pub const GENERATED_DIRS: &[&str] = &["Library", "Temp", "Obj", "obj", "Logs", "Build", "Builds", "UserSettings"];
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct UnityProjectInfo {
|
|
pub editor_version: Option<String>,
|
|
/// File names relative to the Unity root, in stable order.
|
|
pub solutions: Vec<String>,
|
|
pub projects: Vec<String>,
|
|
}
|
|
|
|
pub fn is_project(root: &Path) -> bool {
|
|
root.join("Assets").is_dir() && root.join(VERSION_FILE).is_file()
|
|
}
|
|
|
|
pub fn detect(root: &Path) -> Option<UnityProjectInfo> {
|
|
if !is_project(root) {
|
|
return None;
|
|
}
|
|
let mut text = String::new();
|
|
let _ = std::fs::File::open(root.join(VERSION_FILE)).and_then(|f| f.take(64 * 1024).read_to_string(&mut text));
|
|
let editor_version = text.lines().find_map(|line| {
|
|
let (key, value) = line.trim_start_matches('\u{feff}').split_once(':')?;
|
|
let value = value.trim();
|
|
(key.trim() == "m_EditorVersion" && !value.is_empty() && value.len() <= 128).then(|| value.to_owned())
|
|
});
|
|
let mut info = UnityProjectInfo { editor_version, solutions: Vec::new(), projects: Vec::new() };
|
|
if let Ok(entries) = std::fs::read_dir(root) {
|
|
for entry in entries.flatten() {
|
|
if !entry.file_type().is_ok_and(|t| t.is_file()) {
|
|
continue;
|
|
}
|
|
let name = entry.file_name().to_string_lossy().into_owned();
|
|
match entry.path().extension().and_then(|e| e.to_str()).map(str::to_ascii_lowercase).as_deref() {
|
|
Some("sln" | "slnx") => info.solutions.push(name),
|
|
Some("csproj") => info.projects.push(name),
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
info.solutions.sort();
|
|
info.projects.sort();
|
|
Some(info)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn detects_fresh_and_legacy_projects_without_packages_or_solutions() {
|
|
let temp = tempfile::tempdir().unwrap();
|
|
let root = temp.path();
|
|
std::fs::create_dir(root.join("Assets")).unwrap();
|
|
assert!(detect(root).is_none());
|
|
std::fs::create_dir(root.join("ProjectSettings")).unwrap();
|
|
std::fs::write(
|
|
root.join(VERSION_FILE),
|
|
"\u{feff}m_EditorVersion: 2022.3.62f1\r\nm_EditorVersionWithRevision: ignored\r\n",
|
|
)
|
|
.unwrap();
|
|
let info = detect(root).unwrap();
|
|
assert_eq!(info.editor_version.as_deref(), Some("2022.3.62f1"));
|
|
assert!(info.solutions.is_empty() && info.projects.is_empty());
|
|
std::fs::write(root.join("Game.sln"), "").unwrap();
|
|
std::fs::write(root.join("Game.slnx"), "").unwrap();
|
|
std::fs::write(root.join("Assembly-CSharp.csproj"), "").unwrap();
|
|
std::fs::create_dir(root.join("NotAProject.csproj")).unwrap();
|
|
let info = detect(root).unwrap();
|
|
assert_eq!(info.solutions, ["Game.sln", "Game.slnx"]);
|
|
assert_eq!(info.projects, ["Assembly-CSharp.csproj"]);
|
|
std::fs::write(root.join(VERSION_FILE), "malformed").unwrap();
|
|
assert_eq!(detect(root).unwrap().editor_version, None);
|
|
}
|
|
|
|
#[test]
|
|
fn ordinary_csharp_projects_are_not_unity_projects() {
|
|
let temp = tempfile::tempdir().unwrap();
|
|
std::fs::write(temp.path().join("App.csproj"), "").unwrap();
|
|
std::fs::create_dir(temp.path().join("Assets")).unwrap();
|
|
assert!(detect(temp.path()).is_none());
|
|
}
|
|
}
|