unity/
lib.rs

1use std::{
2    collections::HashMap,
3    io::BufRead,
4    path::{Path, PathBuf},
5};
6
7use kondo_lib::{ProjectType, ScanOptions};
8use serde::Deserialize;
9
10pub struct UnityProject {
11    pub path: PathBuf,
12    pub name: String,
13    pub unity_version: String,
14    pub package_dependencies: HashMap<String, String>,
15}
16
17pub fn discover_projects(path: &Path) -> impl Iterator<Item = UnityProject> + '_ {
18    kondo_lib::scan(
19        &path,
20        &ScanOptions {
21            follow_symlinks: false,
22            same_file_system: false,
23        },
24    )
25    .filter_map(|entry| match entry {
26        Ok(project) if matches!(project.project_type, ProjectType::Unity) => Some(project),
27        _ => None,
28    })
29    .filter_map(|project| {
30        let name = project_name(&project.path).ok()??;
31
32        let unity_version = {
33            let fs = std::fs::File::open(project.path.join("ProjectSettings/ProjectVersion.txt"))
34                .ok()?;
35            let reader = std::io::BufReader::new(fs);
36            const UNITY_VERSION_PREFIX: &str = "m_EditorVersion: ";
37            reader.lines().find_map(|line| {
38                if let Ok(line) = line {
39                    line.strip_prefix(UNITY_VERSION_PREFIX)
40                        .map(ToOwned::to_owned)
41                } else {
42                    None
43                }
44            })
45        }?;
46
47        let package_dependencies = {
48            #[derive(Deserialize)]
49            struct PackageManifest {
50                dependencies: HashMap<String, String>,
51            }
52
53            std::fs::read_to_string(project.path.join("Packages/manifest.json"))
54                .ok()
55                .and_then(|contents| serde_json::from_str::<PackageManifest>(&contents).ok())
56                .map(|manifest| manifest.dependencies)
57        }?;
58
59        Some(UnityProject {
60            name,
61            path: project.path,
62            unity_version,
63            package_dependencies,
64        })
65    })
66}
67
68pub fn project_name(path: &Path) -> Result<Option<String>, std::io::Error> {
69    let fs = std::fs::File::open(path.join("ProjectSettings/ProjectSettings.asset"))?;
70    let reader = std::io::BufReader::new(fs);
71    const PRODUCT_NAME_PREFIX: &str = "  productName: ";
72
73    for line in reader.lines() {
74        if let Some(name) = line?.strip_prefix(PRODUCT_NAME_PREFIX) {
75            return Ok(Some(name.to_owned()));
76        }
77    }
78
79    Ok(None)
80}