Skip to content

Commit 1ac7e19

Browse files
[#49] Improve analyze file specification, CLI help and docs improvements (#76)
* Add some description for UnityDataTool --help * AnalyzeTool - pass parameters through structure * [#49] Allow analyze to accept multiple file or directory paths * Clarify UnityFileSystemApi section in README
1 parent 74ef49a commit 1ac7e19

9 files changed

Lines changed: 217 additions & 63 deletions

File tree

Analyzer/AnalyzerTool.cs

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,41 @@ namespace UnityDataTools.Analyzer;
1313

1414
public class AnalyzerTool
1515
{
16-
bool m_Verbose = false;
16+
AnalyzeOptions m_Options;
1717

1818
public List<ISQLiteFileParser> parsers = new List<ISQLiteFileParser>()
1919
{
2020
new AddressablesBuildLayoutParser(),
2121
new SerializedFileParser(),
2222
};
2323

24-
public int Analyze(
25-
string path,
26-
string databaseName,
27-
string searchPattern,
28-
bool skipReferences,
29-
bool skipCrc,
30-
bool verbose,
31-
bool noRecursion)
24+
public class AnalyzeOptions
3225
{
33-
m_Verbose = verbose;
26+
// Each entry is a file or a directory. Directories are scanned using SearchPattern and
27+
// NoRecursion; files are always included regardless of SearchPattern.
28+
public IReadOnlyList<string> Paths { get; init; }
29+
public string DatabaseName { get; init; }
30+
public string SearchPattern { get; init; } = "*";
31+
public bool SkipReferences { get; init; }
32+
public bool SkipCrc { get; init; }
33+
public bool Verbose { get; init; }
34+
public bool NoRecursion { get; init; }
35+
}
36+
37+
public int Analyze(AnalyzeOptions options)
38+
{
39+
m_Options = options;
3440

35-
using SQLiteWriter writer = new(databaseName);
41+
using SQLiteWriter writer = new(m_Options.DatabaseName);
3642

3743
try
3844
{
3945
writer.Begin();
4046
foreach (var parser in parsers)
4147
{
42-
parser.Verbose = verbose;
43-
parser.SkipReferences = skipReferences;
44-
parser.SkipCrc = skipCrc;
48+
parser.Verbose = m_Options.Verbose;
49+
parser.SkipReferences = m_Options.SkipReferences;
50+
parser.SkipCrc = m_Options.SkipCrc;
4551
parser.Init(writer.Connection);
4652

4753
}
@@ -55,17 +61,15 @@ public int Analyze(
5561
var timer = new Stopwatch();
5662
timer.Start();
5763

58-
var files = Directory.GetFiles(
59-
path,
60-
searchPattern,
61-
noRecursion ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories);
64+
var files = CollectFiles();
6265

6366
int countFailures = 0;
6467
int countSuccess = 0;
6568
int countIgnored = 0;
6669
int i = 1;
67-
foreach (var file in files)
70+
foreach (var (file, displayRoot) in files)
6871
{
72+
var relativePath = Path.GetRelativePath(displayRoot, file);
6973
bool foundParser = false;
7074
foreach (var parser in parsers)
7175
{
@@ -75,15 +79,14 @@ public int Analyze(
7579
try
7680
{
7781
parser.Parse(file);
78-
ReportProgress(Path.GetRelativePath(path, file), i, files.Length);
82+
ReportProgress(relativePath, i, files.Count);
7983
countSuccess++;
8084
}
8185
catch (SerializedFileOpenException e)
8286
{
8387
// Expected failure — the file content could not be parsed.
8488
// Don't print a stack trace; it adds no value for this known failure mode.
8589
EraseProgressLine();
86-
var relativePath = Path.GetRelativePath(path, file);
8790
Console.Error.WriteLine($"Failed to open: {relativePath}");
8891
var hint = SerializedFileDetector.GetOpenFailureHint(e.FilePath);
8992
if (hint != null)
@@ -94,9 +97,8 @@ public int Analyze(
9497
{
9598
// Unexpected failure (SQL error, I/O error, bug, etc.) — print full details.
9699
EraseProgressLine();
97-
var relativePath = Path.GetRelativePath(path, file);
98100
Console.Error.WriteLine($"Failed to process: {relativePath}");
99-
if (m_Verbose)
101+
if (m_Options.Verbose)
100102
{
101103
Console.Error.WriteLine($" Exception: {e.GetType().Name}: {e.Message}");
102104
if (e.InnerException != null)
@@ -109,9 +111,8 @@ public int Analyze(
109111
}
110112
if (!foundParser)
111113
{
112-
if (m_Verbose)
114+
if (m_Options.Verbose)
113115
{
114-
var relativePath = Path.GetRelativePath(path, file);
115116
Console.WriteLine();
116117
Console.WriteLine($"Ignoring {relativePath}");
117118
}
@@ -137,12 +138,46 @@ public int Analyze(
137138
return 0;
138139
}
139140

141+
// Expands the input paths into the concrete files to analyze. Each result pairs the file with the
142+
// root used to render its relative path in progress/error messages: the scanned directory for files
143+
// found by scanning, or the file's own directory for explicitly-named files. Duplicates reached via
144+
// more than one input are analyzed once.
145+
List<(string FullPath, string DisplayRoot)> CollectFiles()
146+
{
147+
var searchOption = m_Options.NoRecursion ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories;
148+
var collected = new List<(string FullPath, string DisplayRoot)>();
149+
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
150+
151+
foreach (var inputPath in m_Options.Paths)
152+
{
153+
if (Directory.Exists(inputPath))
154+
{
155+
foreach (var file in Directory.GetFiles(inputPath, m_Options.SearchPattern, searchOption))
156+
{
157+
if (seen.Add(Path.GetFullPath(file)))
158+
collected.Add((file, inputPath));
159+
}
160+
}
161+
else if (File.Exists(inputPath))
162+
{
163+
if (seen.Add(Path.GetFullPath(inputPath)))
164+
collected.Add((inputPath, Path.GetDirectoryName(Path.GetFullPath(inputPath))));
165+
}
166+
else
167+
{
168+
Console.Error.WriteLine($"Warning: path not found, skipping: {inputPath}");
169+
}
170+
}
171+
172+
return collected;
173+
}
174+
140175
int m_LastProgressMessageLength = 0;
141176

142177
void ReportProgress(string relativePath, int fileIndex, int cntFiles)
143178
{
144179
var message = $"Processing {fileIndex * 100 / cntFiles}% ({fileIndex}/{cntFiles}) {relativePath}";
145-
if (!m_Verbose)
180+
if (!m_Options.Verbose)
146181
{
147182
EraseProgressLine();
148183
Console.Write($"\r{message}");
@@ -158,7 +193,7 @@ void ReportProgress(string relativePath, int fileIndex, int cntFiles)
158193

159194
void EraseProgressLine()
160195
{
161-
if (!m_Verbose)
196+
if (!m_Options.Verbose)
162197
Console.Write($"\r{new string(' ', m_LastProgressMessageLength)}\r");
163198
else
164199
Console.WriteLine();

Documentation/analyzer.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,10 @@ The [AnalyzerTool](../Analyzer/AnalyzerTool.cs) class is the API entry point. Th
188188
Analyze. It is currently hard coded to write using the [SQLiteWriter](../Analyzer/SQLite/SQLiteWriter.cs),
189189
but this approach could be extended to add support for other outputs.
190190

191-
Calling this method will recursively process the files matching the search pattern in the provided
192-
path. It will add a row in the 'objects' table for each serialized object. This table contain basic
193-
information such as the size and the name of the object (if it has one).
191+
Calling this method processes the provided paths, which can be individual files or directories.
192+
Directories are scanned recursively for files matching the search pattern (unless recursion is
193+
disabled). It will add a row in the 'objects' table for each serialized object. This table contains
194+
basic information such as the size and the name of the object (if it has one).
194195

195196
## Extending the Library
196197

Documentation/buildreport.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,13 @@ SELECT build_time_asset_path from build_report_source_assets WHERE build_time_as
4949

5050
## Cross-Referencing with Build Output
5151

52-
For comprehensive analysis, run `analyze` on both the build output **and** the matching build report file. Use a clean build to ensure PackedAssets information is fully populated. You may need to copy the build report into the build output directory so both are found by `analyze`.
52+
For comprehensive analysis, run `analyze` on both the build output **and** the matching build report file. Use a clean build to ensure PackedAssets information is fully populated.
53+
54+
`analyze` accepts multiple path arguments, each of which can be a file or a directory, so you can pass the build output directory together with the build report path (or the directory containing it) in a single command:
55+
56+
```bash
57+
UnityDataTool analyze /path/to/build/output /path/to/Library/LastBuild.buildreport
58+
```
5359

5460
PackedAssets data provides source asset information for each object that isn't available when analyzing only the build output. Objects are listed in the same order as they appear in the output SerializedFile, .resS, or .resource file.
5561

@@ -64,11 +70,33 @@ PackedAssets data provides source asset information for each object that isn't a
6470

6571
## Working with Multiple Build Reports
6672

67-
Multiple build reports can be imported into the same database if their filenames differ. This enables:
73+
Multiple build reports can be imported into the same database if their filenames differ. Pass each report (and any build output directories) as separate path arguments to a single `analyze` command. This enables:
6874
- Comprehensive build history tracking
6975
- Cross-build comparisons
7076
- Identifying duplicated data between Player and AssetBundle builds
7177

78+
### Prior to Unity 6.6
79+
80+
Each build overwrites `Library/LastBuild.buildreport`. To compare builds, manually collect the report after each build, rename the copies so the filenames are unique (the analyzer keys serialized files by filename), then pass them to `analyze`:
81+
82+
```bash
83+
UnityDataTool analyze build1.buildreport build2.buildreport
84+
```
85+
86+
### Unity 6.6 and later
87+
88+
Player and content directory builds record a structured [build history](https://docs.unity3d.com/6000.6/Documentation/ScriptReference/Build.BuildHistory.html) (default location `Library/BuildHistory`). Unity assigns each build its own directory and gives every build report a unique GUID-based filename, so there is no need to copy or rename reports to compare them. Run `analyze` on the entire build history folder, or on specific build report directories:
89+
90+
```bash
91+
# Analyze every build in the history
92+
UnityDataTool analyze Library/BuildHistory
93+
94+
# Analyze two specific builds
95+
UnityDataTool analyze Library/BuildHistory/20260504-153912Z-2dd7642e Library/BuildHistory/20260504-153855Z-7aff42f4
96+
```
97+
98+
AssetBundle builds are not tracked in the build history; they still write only to `Library/LastBuild.buildreport`.
99+
72100
See the schema sections below for guidance on writing queries that handle multiple build reports correctly.
73101

74102
## Alternatives

Documentation/command-analyze.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,41 @@ The `analyze` command extracts information from Unity Archives (e.g. AssetBundle
55
## Quick Reference
66

77
```
8-
UnityDataTool analyze <path> [options]
8+
UnityDataTool analyze <paths>... [options]
99
```
1010

1111
| Option | Description | Default |
1212
|--------|-------------|---------|
13-
| `<path>` | Path to folder containing files to analyze | *(required)* |
13+
| `<paths>...` | One or more files or directories to analyze. Directories are scanned; files are analyzed directly. | *(required)* |
1414
| `-o, --output-file <file>` | Output database filename | `database.db` |
15-
| `-p, --search-pattern <pattern>` | File search pattern (`*` and `?` supported) | `*` |
15+
| `-p, --search-pattern <pattern>` | File search pattern applied when scanning directories (`*` and `?` supported) | `*` |
1616
| `-s, --skip-references` | Do not extract references (smaller DB, no `refs` table). CRC is still computed. | `false` |
1717
| `--skip-crc` | Skip the CRC32 checksum calculation (faster; `objects.crc32` will be 0) | `false` |
1818
| `-v, --verbose` | Show more information during analysis | `false` |
19-
| `--no-recurse` | Do not recurse into sub-directories | `false` |
19+
| `--no-recurse` | Do not recurse into sub-directories when scanning directories | `false` |
2020
| `-d, --typetree-data <file>` | Load an external TypeTree data file before processing (Unity 6.5+) ||
2121

22+
There is no way to append to an existing database, so every file you want in the results must be
23+
included in a single `analyze` invocation. Pass multiple paths to combine files from more than one
24+
location into the same database.
25+
2226
## Examples
2327

2428
Analyze all files in a directory:
2529
```bash
2630
UnityDataTool analyze /path/to/asset/bundles
2731
```
2832

33+
Analyze a single file (no need for `.` plus `-p`):
34+
```bash
35+
UnityDataTool analyze /path/to/asset/bundles/my.bundle
36+
```
37+
38+
Combine a build output directory with a build report file kept in a separate location:
39+
```bash
40+
UnityDataTool analyze /path/to/build/output /path/to/Library/LastBuild.buildreport
41+
```
42+
2943
Analyze only `.bundle` files and specify a custom database name:
3044
```bash
3145
UnityDataTool analyze /path/to/asset/bundles -o my_database.db -p "*.bundle"
@@ -42,7 +56,9 @@ See also [Analyze Examples](../../Documentation/analyze-examples.md).
4256

4357
## What Can Be Analyzed
4458

45-
The analyze command works with the following types of directories:
59+
Each path may be an individual file or a directory. Directories are scanned (honoring
60+
`--search-pattern` and `--no-recurse`); individually-named files are always analyzed. The analyze
61+
command works with the following types of input:
4662

4763
| Input Type | Description |
4864
|------------|-------------|

README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,15 @@ Refer to the [commit history](https://github.com/Unity-Technologies/UnityDataToo
4040

4141
## Getting UnityFileSystemApi
4242

43-
UnityFileSystemApi is distributed in the Tools folder of the Unity Editor (from version 2022.1.0a14). The UnityDataTools repository includes a Windows, Mac, and Linux copy of the library in the `UnityFileSystem/` directory.
43+
UnityDataTool uses the native `UnityFileSystemApi` library to read Unity Archives and SerializedFiles. **Normally you don't need to do anything with this library.** The repository already includes a recent Windows, Mac, and Linux copy in the [`UnityFileSystem/`](https://github.com/Unity-Technologies/UnityDataTools/tree/main/UnityFileSystem) directory, and using that bundled copy is the recommended way to run the tool.
4444

45-
The library is backward compatible and can read data files from most Unity versions, so typically the version that is provided with UnityDataTools can be used "as is".
45+
The library is backward compatible but not forward compatible: a given version can read content from the same or older Unity versions, but may be unable to read content produced by a newer Unity Editor than the library itself. The bundled copy is updated periodically as Unity evolves, so in practice it can read content from just about any Unity version.
4646

47-
To analyze data using the library from a specific version of the Unity Editor, copy the appropriate UnityFileSystemApi file from your Unity Editor installation (`{UnityEditor}/Data/Tools/`) to `UnityDataTool/UnityFileSystem/` prior to building:
47+
`UnityFileSystemApi` is also distributed in the `Data/Tools/` folder of the Unity Editor (for all versions since 2022.1.0a14). In the rare case that you need to read content from a Unity version newer than the bundled library, copy the matching file from your Unity Editor installation (`{UnityEditor}/Data/Tools/`) into the `UnityFileSystem/` directory before building:
4848

49-
The file name is as follows:
50-
51-
- Windows: `UnityFileSystemApi.dll`
52-
- Mac: `UnityFileSystemApi.dylib`
53-
- Linux: `UnityFileSystemApi.so`
49+
- Windows: [`UnityFileSystemApi.dll`](https://github.com/Unity-Technologies/UnityDataTools/blob/main/UnityFileSystem/UnityFileSystemApi.dll)
50+
- Mac: [`UnityFileSystemApi.dylib`](https://github.com/Unity-Technologies/UnityDataTools/blob/main/UnityFileSystem/UnityFileSystemApi.dylib)
51+
- Linux: [`UnityFileSystemApi.so`](https://github.com/Unity-Technologies/UnityDataTools/blob/main/UnityFileSystem/UnityFileSystemApi.so)
5452

5553
## How to Build
5654

0 commit comments

Comments
 (0)