diff --git a/Analyzer/Properties/Resources.Designer.cs b/Analyzer/Properties/Resources.Designer.cs
index d9a101a..c05afb2 100644
--- a/Analyzer/Properties/Resources.Designer.cs
+++ b/Analyzer/Properties/Resources.Designer.cs
@@ -282,6 +282,18 @@ internal static string ContentLayoutLoadableObjects {
}
}
+ internal static string ContentLayoutLoadableObjectsV2 {
+ get {
+ return ResourceManager.GetString("ContentLayoutLoadableObjectsV2", resourceCulture);
+ }
+ }
+
+ internal static string ContentLayoutLoadableObjectsView {
+ get {
+ return ResourceManager.GetString("ContentLayoutLoadableObjectsView", resourceCulture);
+ }
+ }
+
internal static string ContentLayoutLoadableSceneDependencies {
get {
return ResourceManager.GetString("ContentLayoutLoadableSceneDependencies", resourceCulture);
diff --git a/Analyzer/Properties/Resources.resx b/Analyzer/Properties/Resources.resx
index 27df1f5..6637926 100644
--- a/Analyzer/Properties/Resources.resx
+++ b/Analyzer/Properties/Resources.resx
@@ -247,6 +247,12 @@
..\Resources\ContentLayoutLoadableObjects.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
+
+ ..\Resources\ContentLayoutLoadableObjectsV2.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
+
+
+ ..\Resources\ContentLayoutLoadableObjectsView.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
+
..\Resources\ContentLayoutLoadableScenes.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
diff --git a/Analyzer/Resources/ContentLayoutLoadableDependencies.sql b/Analyzer/Resources/ContentLayoutLoadableDependencies.sql
index 850e554..0738263 100644
--- a/Analyzer/Resources/ContentLayoutLoadableDependencies.sql
+++ b/Analyzer/Resources/ContentLayoutLoadableDependencies.sql
@@ -2,5 +2,5 @@ CREATE TABLE IF NOT EXISTS content_layout_loadable_dependencies
(
-- The loadable objects each content file references.
serialized_file_index INTEGER, -- references content_layout_serialized_files.file_index
- object_id_hash TEXT -- references content_layout_loadable_objects.object_id_hash
+ loadable_index INTEGER -- references content_layout_loadable_objects.loadable_index
);
diff --git a/Analyzer/Resources/ContentLayoutLoadableObjects.sql b/Analyzer/Resources/ContentLayoutLoadableObjects.sql
index e34447b..e2ea293 100644
--- a/Analyzer/Resources/ContentLayoutLoadableObjects.sql
+++ b/Analyzer/Resources/ContentLayoutLoadableObjects.sql
@@ -2,13 +2,11 @@ CREATE TABLE IF NOT EXISTS content_layout_loadable_objects
(
-- The objects that can be loaded on demand, identified independently of the content file that
-- holds them, plus where each came from in the source project.
- object_id_hash TEXT, -- hash of GUID, LFID and identifier_type
+ loadable_index INTEGER, -- the json array index; how the other content_layout tables name a loadable
guid TEXT, -- AssetDatabase GUID of the source asset
- asset_path TEXT,
- lfid INTEGER, -- local file id of the object in the source asset
+ lfid INTEGER, -- local file id of the object in its output content file
identifier_type INTEGER,
serialized_file_index INTEGER, -- content_layout_serialized_files.file_index; NULL if dropped from the build
- output_lfid INTEGER, -- local file id of the object in its output content file
- is_root_asset INTEGER,
- PRIMARY KEY (object_id_hash)
+ is_root_asset INTEGER, -- 1-based position in the layout's RootAssets; 0 when not a root asset
+ PRIMARY KEY (loadable_index)
);
diff --git a/Analyzer/Resources/ContentLayoutLoadableObjectsV2.sql b/Analyzer/Resources/ContentLayoutLoadableObjectsV2.sql
new file mode 100644
index 0000000..0920870
--- /dev/null
+++ b/Analyzer/Resources/ContentLayoutLoadableObjectsV2.sql
@@ -0,0 +1,16 @@
+CREATE TABLE IF NOT EXISTS content_layout_loadable_objects
+(
+ -- The objects that can be loaded on demand, identified independently of the content file that
+ -- holds them, plus where each came from in the source project. This is the variant created when
+ -- a version 2 layout (Unity 6.6) is imported; it adds source columns that newer layout versions
+ -- no longer record.
+ loadable_index INTEGER, -- the json array index; how the other content_layout tables name a loadable
+ guid TEXT, -- AssetDatabase GUID of the source asset
+ lfid INTEGER, -- local file id of the object in its output content file
+ identifier_type INTEGER,
+ serialized_file_index INTEGER, -- content_layout_serialized_files.file_index; NULL if dropped from the build
+ is_root_asset INTEGER, -- 1-based position in the layout's RootAssets; 0 when not a root asset
+ asset_path TEXT, -- v2 layouts only: path of the source asset
+ source_lfid INTEGER, -- v2 layouts only: local file id of the object in the source asset
+ PRIMARY KEY (loadable_index)
+);
diff --git a/Analyzer/Resources/ContentLayoutLoadableObjectsView.sql b/Analyzer/Resources/ContentLayoutLoadableObjectsView.sql
new file mode 100644
index 0000000..d9930e5
--- /dev/null
+++ b/Analyzer/Resources/ContentLayoutLoadableObjectsView.sql
@@ -0,0 +1,11 @@
+CREATE VIEW IF NOT EXISTS content_layout_loadable_objects_view AS
+-- Loadables resolved to their analyzed object. The object columns are NULL in a layout-only
+-- database. Selects the loadable table's columns as-is, so the v2-only columns (asset_path,
+-- source_lfid) appear when the imported layout was version 2.
+SELECT l.*,
+ f.filename,
+ o.id AS object, t.name AS type, o.name, o.size
+FROM content_layout_loadable_objects l
+LEFT JOIN content_layout_serialized_files_view f ON f.file_index = l.serialized_file_index
+LEFT JOIN objects o ON o.serialized_file = f.serialized_file AND o.object_id = l.lfid
+LEFT JOIN types t ON t.id = o.type;
diff --git a/Analyzer/Resources/ContentLayoutSerializedFiles.sql b/Analyzer/Resources/ContentLayoutSerializedFiles.sql
index 5af0b21..4512326 100644
--- a/Analyzer/Resources/ContentLayoutSerializedFiles.sql
+++ b/Analyzer/Resources/ContentLayoutSerializedFiles.sql
@@ -3,9 +3,9 @@ CREATE TABLE IF NOT EXISTS content_layout_serialized_files
-- The content files (.cf) the ContentDirectory build produced, one row per entry in the
-- layout's SerializedFiles array.
file_index INTEGER, -- the json array index; how the other content_layout tables name a file
- cfid TEXT, -- symbolic .cfid reference string (for built-ins: the built-in path)
+ stable_id TEXT, -- identity hash used to reference this file (for built-ins: the built-in path)
is_builtin INTEGER,
- content_hash TEXT, -- NULL for built-ins; the filename is content_hash || '.cf'
+ artifact_index INTEGER, -- content_layout_binary_artifacts.artifact_index; NULL for built-ins
serialized_file INTEGER, -- serialized_files.id; NULL for built-ins and for a layout-only analyze
PRIMARY KEY (file_index)
);
diff --git a/Analyzer/Resources/ContentLayoutViews.sql b/Analyzer/Resources/ContentLayoutViews.sql
index 2db54f6..b3b2d7c 100644
--- a/Analyzer/Resources/ContentLayoutViews.sql
+++ b/Analyzer/Resources/ContentLayoutViews.sql
@@ -1,39 +1,31 @@
CREATE VIEW IF NOT EXISTS content_layout_serialized_files_view AS
--- One row per layout content file with its derived filename, artifact size and core-table link.
--- Built-in entries have no file on disk, so their path (cfid) is shown as the filename.
-SELECT f.file_index, f.cfid, f.is_builtin,
- CASE WHEN f.is_builtin = 1 THEN f.cfid ELSE f.content_hash || '.cf' END AS filename,
+-- One row per layout content file with its content hash, derived filename, artifact size and
+-- core-table link. Built-in entries have no file on disk, so their path (stable_id) is shown as
+-- the filename.
+SELECT f.file_index, f.stable_id, f.is_builtin, f.artifact_index,
+ CASE WHEN f.is_builtin = 1 THEN f.stable_id ELSE ba.content_hash || '.cf' END AS filename,
+ ba.content_hash,
ba.size,
f.serialized_file,
sf.archive
FROM content_layout_serialized_files f
-LEFT JOIN content_layout_binary_artifacts ba ON ba.content_hash = f.content_hash AND ba.category = 'contentfile'
+LEFT JOIN content_layout_binary_artifacts ba ON ba.artifact_index = f.artifact_index
LEFT JOIN serialized_files sf ON sf.id = f.serialized_file;
CREATE VIEW IF NOT EXISTS content_layout_source_assets_view AS
-- Source asset to the content file(s) it was built into.
-SELECT s.asset_path, f.file_index, f.content_hash || '.cf' AS filename, f.serialized_file
+SELECT s.asset_path, f.file_index, f.filename, f.serialized_file
FROM content_layout_source_assets s
-INNER JOIN content_layout_serialized_files f ON f.file_index = s.serialized_file_index;
+INNER JOIN content_layout_serialized_files_view f ON f.file_index = s.serialized_file_index;
CREATE VIEW IF NOT EXISTS content_layout_serialized_file_dependencies_view AS
-- File-to-file dependency edges with filenames resolved on both sides.
SELECT d.serialized_file_index, src.filename, d.position,
- d.dependency_index, dep.filename AS dependency_filename, dep.cfid AS dependency_cfid
+ d.dependency_index, dep.filename AS dependency_filename, dep.stable_id AS dependency_stable_id
FROM content_layout_serialized_file_dependencies d
INNER JOIN content_layout_serialized_files_view src ON src.file_index = d.serialized_file_index
INNER JOIN content_layout_serialized_files_view dep ON dep.file_index = d.dependency_index;
-CREATE VIEW IF NOT EXISTS content_layout_loadable_objects_view AS
--- Loadables resolved to their analyzed object. The object columns are NULL in a layout-only database.
-SELECT l.object_id_hash, l.guid, l.asset_path, l.lfid, l.is_root_asset,
- f.content_hash || '.cf' AS filename,
- o.id AS object, t.name AS type, o.name, o.size
-FROM content_layout_loadable_objects l
-LEFT JOIN content_layout_serialized_files f ON f.file_index = l.serialized_file_index
-LEFT JOIN objects o ON o.serialized_file = f.serialized_file AND o.object_id = l.output_lfid
-LEFT JOIN types t ON t.id = o.type;
-
CREATE VIEW IF NOT EXISTS content_layout_binary_artifacts_view AS
-- Artifacts with their on-disk filename derived from the content hash and category.
SELECT artifact_index, content_hash, category, size,
@@ -48,11 +40,10 @@ FROM content_layout_binary_artifacts;
CREATE VIEW IF NOT EXISTS content_layout_resource_files_view AS
-- The data files (.resS/.resource) each content file uses, derived from the artifact graph.
-SELECT f.file_index, f.content_hash || '.cf' AS filename,
+SELECT f.file_index, f.filename,
ra.category, rav.filename AS data_filename, ra.size
-FROM content_layout_serialized_files f
-INNER JOIN content_layout_binary_artifacts ca ON ca.content_hash = f.content_hash AND ca.category = 'contentfile'
-INNER JOIN content_layout_artifact_references r ON r.artifact_index = ca.artifact_index
+FROM content_layout_serialized_files_view f
+INNER JOIN content_layout_artifact_references r ON r.artifact_index = f.artifact_index
INNER JOIN content_layout_binary_artifacts ra ON ra.artifact_index = r.referenced_artifact_index
INNER JOIN content_layout_binary_artifacts_view rav ON rav.artifact_index = ra.artifact_index
WHERE ra.category IN ('texture', 'mesh', 'audio', 'video');
diff --git a/Analyzer/Resources/Init.sql b/Analyzer/Resources/Init.sql
index fa45529..92fb470 100644
--- a/Analyzer/Resources/Init.sql
+++ b/Analyzer/Resources/Init.sql
@@ -168,7 +168,7 @@ WHERE m.type = 'Material';
INSERT INTO types (id, name) VALUES (-1, 'Scene');
-PRAGMA user_version = 8;
+PRAGMA user_version = 9;
PRAGMA synchronous = OFF;
PRAGMA journal_mode = MEMORY;
diff --git a/Analyzer/SQLite/Commands/ContentLayout/AddContentLayoutLoadableDependency.cs b/Analyzer/SQLite/Commands/ContentLayout/AddContentLayoutLoadableDependency.cs
index 193587a..3f9168e 100644
--- a/Analyzer/SQLite/Commands/ContentLayout/AddContentLayoutLoadableDependency.cs
+++ b/Analyzer/SQLite/Commands/ContentLayout/AddContentLayoutLoadableDependency.cs
@@ -12,7 +12,7 @@ internal class AddContentLayoutLoadableDependency : AbstractCommand
protected override Dictionary Fields => new()
{
{ "serialized_file_index", SqliteType.Integer },
- { "object_id_hash", SqliteType.Text }
+ { "loadable_index", SqliteType.Integer }
};
}
}
diff --git a/Analyzer/SQLite/Commands/ContentLayout/AddContentLayoutLoadableObject.cs b/Analyzer/SQLite/Commands/ContentLayout/AddContentLayoutLoadableObject.cs
index 044b61b..144a484 100644
--- a/Analyzer/SQLite/Commands/ContentLayout/AddContentLayoutLoadableObject.cs
+++ b/Analyzer/SQLite/Commands/ContentLayout/AddContentLayoutLoadableObject.cs
@@ -5,20 +5,40 @@ namespace UnityDataTools.Analyzer.SQLite.Commands.ContentLayout
{
internal class AddContentLayoutLoadableObject : AbstractCommand
{
+ // Version 2 layouts (Unity 6.6) record the source asset path and source lfid of each
+ // loadable; newer versions do not. The table is created with those columns only when a
+ // v2 layout is imported, so v3 databases are not cluttered with always-NULL columns.
+ // Set before CreateCommand, which reads DDLSource and Fields.
+ public bool V2Columns { get; set; }
+
protected override string TableName => "content_layout_loadable_objects";
- protected override string DDLSource => Properties.Resources.ContentLayoutLoadableObjects;
+ protected override string DDLSource => V2Columns
+ ? Properties.Resources.ContentLayoutLoadableObjectsV2
+ : Properties.Resources.ContentLayoutLoadableObjects;
- protected override Dictionary Fields => new()
+ protected override Dictionary Fields
{
- { "object_id_hash", SqliteType.Text },
- { "guid", SqliteType.Text },
- { "asset_path", SqliteType.Text },
- { "lfid", SqliteType.Integer },
- { "identifier_type", SqliteType.Integer },
- { "serialized_file_index", SqliteType.Integer },
- { "output_lfid", SqliteType.Integer },
- { "is_root_asset", SqliteType.Integer }
- };
+ get
+ {
+ var fields = new Dictionary
+ {
+ { "loadable_index", SqliteType.Integer },
+ { "guid", SqliteType.Text },
+ { "lfid", SqliteType.Integer },
+ { "identifier_type", SqliteType.Integer },
+ { "serialized_file_index", SqliteType.Integer },
+ { "is_root_asset", SqliteType.Integer }
+ };
+
+ if (V2Columns)
+ {
+ fields.Add("asset_path", SqliteType.Text);
+ fields.Add("source_lfid", SqliteType.Integer);
+ }
+
+ return fields;
+ }
+ }
}
}
diff --git a/Analyzer/SQLite/Commands/ContentLayout/AddContentLayoutSerializedFile.cs b/Analyzer/SQLite/Commands/ContentLayout/AddContentLayoutSerializedFile.cs
index c2f639c..d453b67 100644
--- a/Analyzer/SQLite/Commands/ContentLayout/AddContentLayoutSerializedFile.cs
+++ b/Analyzer/SQLite/Commands/ContentLayout/AddContentLayoutSerializedFile.cs
@@ -12,9 +12,9 @@ internal class AddContentLayoutSerializedFile : AbstractCommand
protected override Dictionary Fields => new()
{
{ "file_index", SqliteType.Integer },
- { "cfid", SqliteType.Text },
+ { "stable_id", SqliteType.Text },
{ "is_builtin", SqliteType.Integer },
- { "content_hash", SqliteType.Text },
+ { "artifact_index", SqliteType.Integer },
{ "serialized_file", SqliteType.Integer }
};
}
diff --git a/Analyzer/SQLite/Parsers/ContentLayoutParser.cs b/Analyzer/SQLite/Parsers/ContentLayoutParser.cs
index e0b30f5..ecfdbb3 100644
--- a/Analyzer/SQLite/Parsers/ContentLayoutParser.cs
+++ b/Analyzer/SQLite/Parsers/ContentLayoutParser.cs
@@ -11,7 +11,9 @@ namespace UnityDataTools.Analyzer.SQLite.Parsers
{
// Imports the ContentLayout.json produced by BuildPipeline.BuildContentDirectory into the
// content_layout* tables (see Documentation/contentlayout.md for the file, and
- // Documentation/contentlayout-database.md for the resulting schema).
+ // Documentation/contentlayout-database.md for the resulting schema). Version 2 files
+ // (Unity 6.6) are upgraded to the current in-memory model on the way in, so a single write
+ // path serves both supported versions.
public class ContentLayoutParser : ISQLiteFileParser
{
private ContentLayoutSQLWriter m_Writer;
@@ -49,41 +51,73 @@ public bool CanParse(string filename)
public void Parse(string filename, string rootDirectory)
{
- ContentLayout layout;
- using (var reader = File.OpenText(filename))
- {
- var serializer = new JsonSerializer();
- layout = (ContentLayout)serializer.Deserialize(reader, typeof(ContentLayout));
- }
+ var version = BuildHistoryHelper.TryReadLayoutVersion(filename);
// The tool's failure summary only includes exception details in verbose mode, so
// report the reason for these expected failures directly.
- if (layout == null)
+ if (version == null)
{
- Fail($"\"{filename}\" does not contain a ContentLayout.");
+ throw Fail($"\"{filename}\" does not contain a ContentLayout.");
}
- if (layout.Version != ContentLayout.CurrentVersion)
+ // The upgrader and the writer throw InvalidDataException for dangling references in
+ // the layout (an ObjectIdHash, ContentHash or ArtifactIndex with no target).
+ try
{
- Fail($"Unsupported ContentLayout.json version {layout.Version} (this version of UnityDataTool supports version {ContentLayout.CurrentVersion}).");
+ ContentLayout layout;
+ LoadableObjectV2Data[] v2Data = null;
+
+ switch (version)
+ {
+ case Models.V2.ContentLayout.SchemaVersion:
+ layout = ContentLayoutV2Upgrader.Upgrade(
+ Deserialize(filename), out v2Data);
+ break;
+
+ case ContentLayout.CurrentVersion:
+ layout = Deserialize(filename);
+ break;
+
+ default:
+ throw Fail($"Unsupported ContentLayout.json version {version} (this version of UnityDataTool supports versions {Models.V2.ContentLayout.SchemaVersion} and {ContentLayout.CurrentVersion}).");
+ }
+
+ if (m_ImportedLayout != null)
+ {
+ throw Fail($"Only a single ContentLayout.json can be analyzed (already imported \"{m_ImportedLayout}\").");
+ }
+
+ // Only create the content_layout tables when a layout is actually imported. The v2
+ // variant of the loadable-objects table carries extra source columns.
+ m_Writer.Init(v2Data != null);
+ m_Writer.WriteContentLayout(filename, layout, v2Data);
+ m_ImportedLayout = filename;
}
+ catch (InvalidDataException e)
+ {
+ throw Fail($"\"{filename}\" is not a valid ContentLayout: {e.Message}");
+ }
+ }
- if (m_ImportedLayout != null)
+ private T Deserialize(string filename) where T : class
+ {
+ using var reader = File.OpenText(filename);
+ var serializer = new JsonSerializer();
+ var layout = (T)serializer.Deserialize(reader, typeof(T));
+
+ if (layout == null)
{
- Fail($"Only a single ContentLayout.json can be analyzed (already imported \"{m_ImportedLayout}\").");
+ throw Fail($"\"{filename}\" does not contain a ContentLayout.");
}
- // Only create the content_layout tables when a layout is actually imported.
- m_Writer.Init();
- m_Writer.WriteContentLayout(filename, layout);
- m_ImportedLayout = filename;
+ return layout;
}
- private static void Fail(string message)
+ private static Exception Fail(string message)
{
Console.Error.WriteLine();
Console.Error.WriteLine(message);
- throw new Exception(message);
+ return new Exception(message);
}
// Called after all files are processed, so the analyzed .cf files all have their
diff --git a/Analyzer/SQLite/Writers/ContentLayoutSQLWriter.cs b/Analyzer/SQLite/Writers/ContentLayoutSQLWriter.cs
index ccd55c1..c63cd34 100644
--- a/Analyzer/SQLite/Writers/ContentLayoutSQLWriter.cs
+++ b/Analyzer/SQLite/Writers/ContentLayoutSQLWriter.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using Microsoft.Data.Sqlite;
using UnityDataTools.Analyzer.SQLite.Commands.ContentLayout;
@@ -11,8 +12,10 @@ namespace UnityDataTools.Analyzer.SQLite.Writers
// Populates the content_layout* tables from a ContentLayout.json (see
// Documentation/contentlayout.md). The tables mirror the json structure, with two adjustments
// that make the data natural to query: the top-level RootAssets list is folded into the
- // is_root_asset flag, and the json's sentinel values (-1 for "dropped from build", missing
- // ContentHash for built-ins) are stored as NULL.
+ // is_root_asset column (holding the 1-based root position), and the json's sentinel values
+ // (-1 for "dropped from build" and for the built-in entry's missing artifact) are stored as
+ // NULL. Version 2 layouts arrive here already upgraded to the current model (see
+ // ContentLayoutV2Upgrader); their extra source data lands in v2-only columns.
internal class ContentLayoutSQLWriter : IDisposable
{
private AddContentLayout m_AddContentLayout = new();
@@ -47,14 +50,17 @@ public ContentLayoutSQLWriter(SqliteConnection database,
}
// Creates the content_layout tables and views. Called lazily on the first import so that
- // analyzing other content (AssetBundles, Player builds) doesn't create empty tables.
- public void Init()
+ // analyzing other content (AssetBundles, Player builds) doesn't create empty tables. The
+ // loadable-objects table and view get extra source columns when importing a v2 layout.
+ public void Init(bool v2LoadableColumns)
{
if (m_Initialized)
return;
m_Initialized = true;
+ m_AddLoadableObject.V2Columns = v2LoadableColumns;
+
m_AddContentLayout.CreateCommand(m_Database);
m_AddSerializedFile.CreateCommand(m_Database);
m_AddSourceAsset.CreateCommand(m_Database);
@@ -67,10 +73,17 @@ public void Init()
m_AddArtifactReference.CreateCommand(m_Database);
ExecuteDDL(Properties.Resources.ContentLayoutViews);
+ // Created separately because it must come after the loadable-objects table exists in
+ // its version-dependent form: the view selects that table's columns as-is.
+ ExecuteDDL(Properties.Resources.ContentLayoutLoadableObjectsView);
}
- public void WriteContentLayout(string filename, ContentLayout layout)
+ // v2Data holds the v2-only source fields of each loadable, aligned by index with
+ // layout.LoadableObjectIds; null when a current-version layout is imported.
+ public void WriteContentLayout(string filename, ContentLayout layout, IReadOnlyList v2Data)
{
+ var contentHashByFileIndex = ResolveContentHashes(layout);
+
using var transaction = m_Database.BeginTransaction();
SetTransaction(transaction);
@@ -85,18 +98,18 @@ public void WriteContentLayout(string filename, ContentLayout layout)
foreach (var file in layout.SerializedFiles)
{
m_AddSerializedFile.SetValue("file_index", file.Index);
- m_AddSerializedFile.SetValue("cfid", file.ID);
+ m_AddSerializedFile.SetValue("stable_id", file.StableId);
m_AddSerializedFile.SetValue("is_builtin", file.IsBuiltIn ? 1 : 0);
- m_AddSerializedFile.SetValue("content_hash",
- string.IsNullOrEmpty(file.ContentHash) ? null : file.ContentHash);
+ m_AddSerializedFile.SetValue("artifact_index",
+ file.ArtifactIndex < 0 ? null : file.ArtifactIndex);
// Filled in by LinkSerializedFiles when the analyzed input also contains the
// build content.
m_AddSerializedFile.SetValue("serialized_file", null);
m_AddSerializedFile.ExecuteNonQuery();
- if (!string.IsNullOrEmpty(file.ContentHash))
+ if (contentHashByFileIndex.TryGetValue(file.Index, out var contentHash))
{
- m_ImportedFiles.Add((file.Index, file.ContentHash));
+ m_ImportedFiles.Add((file.Index, contentHash));
}
// Empty arrays can be omitted from the json, leaving the fields null.
@@ -118,10 +131,10 @@ public void WriteContentLayout(string filename, ContentLayout layout)
m_AddSerializedFileDependency.ExecuteNonQuery();
}
- foreach (var objectIdHash in file.LoadableDependencies ?? [])
+ foreach (var loadableIndex in file.LoadableDependencies ?? [])
{
m_AddLoadableDependency.SetValue("serialized_file_index", file.Index);
- m_AddLoadableDependency.SetValue("object_id_hash", objectIdHash);
+ m_AddLoadableDependency.SetValue("loadable_index", loadableIndex);
m_AddLoadableDependency.ExecuteNonQuery();
}
@@ -133,19 +146,32 @@ public void WriteContentLayout(string filename, ContentLayout layout)
}
}
- var rootAssets = new HashSet(layout.RootAssets ?? []);
+ // RootAssets order is the build's root input order, recorded as a 1-based position.
+ var rootPositions = new Dictionary();
+ var rootAssets = layout.RootAssets ?? [];
+ for (int i = 0; i < rootAssets.Length; ++i)
+ {
+ rootPositions[rootAssets[i]] = i + 1;
+ }
- foreach (var loadable in layout.LoadableObjectIds ?? [])
+ var loadables = layout.LoadableObjectIds ?? [];
+ for (int i = 0; i < loadables.Length; ++i)
{
- m_AddLoadableObject.SetValue("object_id_hash", loadable.ObjectIdHash);
+ var loadable = loadables[i];
+ m_AddLoadableObject.SetValue("loadable_index", i);
m_AddLoadableObject.SetValue("guid", loadable.GUID);
- m_AddLoadableObject.SetValue("asset_path", loadable.AssetPath);
m_AddLoadableObject.SetValue("lfid", loadable.LFID);
m_AddLoadableObject.SetValue("identifier_type", loadable.IdentifierType);
m_AddLoadableObject.SetValue("serialized_file_index",
loadable.SerializedFile < 0 ? null : loadable.SerializedFile);
- m_AddLoadableObject.SetValue("output_lfid", loadable.OutputLFID);
- m_AddLoadableObject.SetValue("is_root_asset", rootAssets.Contains(loadable.ObjectIdHash) ? 1 : 0);
+ m_AddLoadableObject.SetValue("is_root_asset", rootPositions.GetValueOrDefault(i, 0));
+
+ if (v2Data != null)
+ {
+ m_AddLoadableObject.SetValue("asset_path", v2Data[i].AssetPath);
+ m_AddLoadableObject.SetValue("source_lfid", v2Data[i].SourceLfid);
+ }
+
m_AddLoadableObject.ExecuteNonQuery();
}
@@ -185,31 +211,54 @@ public void WriteContentLayout(string filename, ContentLayout layout)
// Indexes are created after the bulk insert so that a very large layout imports fast.
ExecuteDDL(Properties.Resources.ContentLayoutIndexes);
- PopulateContentFileDependencies(layout);
+ PopulateContentFileDependencies(layout, contentHashByFileIndex);
+ }
+
+ // The content hash of each non-built-in layout file, resolved through its ArtifactIndex.
+ // The hash gives the on-disk filename, which is how the layout entries are matched to the
+ // analyzed files.
+ private static Dictionary ResolveContentHashes(ContentLayout layout)
+ {
+ var artifactHashByIndex = (layout.BinaryArtifacts ?? []).ToDictionary(a => a.Index, a => a.ContentHash);
+ var contentHashByFileIndex = new Dictionary();
+
+ foreach (var file in layout.SerializedFiles ?? [])
+ {
+ if (file.ArtifactIndex < 0)
+ continue;
+
+ if (!artifactHashByIndex.TryGetValue(file.ArtifactIndex, out var hash) || string.IsNullOrEmpty(hash))
+ {
+ throw new InvalidDataException(
+ $"SerializedFile {file.Index} references BinaryArtifacts[{file.ArtifactIndex}], which does not exist.");
+ }
+
+ contentHashByFileIndex.Add(file.Index, hash);
+ }
+
+ return contentHashByFileIndex;
}
// Fills the shared dependency map that resolves the external references of the analyzed
// content files (see ContentFileDependencyMap): for each content file, the dependency
- // indexes become the target names, in order. Built-in dependencies (no content hash) map to
+ // indexes become the target names, in order. Built-in dependencies (no artifact) map to
// null so the reference falls back to the external table path. Keys and resolved names use
// the normalized content-hash name so they match the analyzed files regardless of whether
// those carry the ".cf" extension on disk.
- private void PopulateContentFileDependencies(ContentLayout layout)
+ private void PopulateContentFileDependencies(ContentLayout layout, Dictionary contentHashByFileIndex)
{
- var hashByIndex = (layout.SerializedFiles ?? []).ToDictionary(f => f.Index, f => f.ContentHash);
-
foreach (var file in layout.SerializedFiles ?? [])
{
- if (string.IsNullOrEmpty(file.ContentHash))
+ if (!contentHashByFileIndex.TryGetValue(file.Index, out var contentHash))
continue;
var resolved = (file.SerializedFileDependencies ?? [])
- .Select(i => hashByIndex.TryGetValue(i, out var hash) && !string.IsNullOrEmpty(hash)
+ .Select(i => contentHashByFileIndex.TryGetValue(i, out var hash)
? ContentFileDependencyMap.NormalizeFileName(hash)
: null)
.ToArray();
- m_ContentFileDependencies.Add(ContentFileDependencyMap.NormalizeFileName(file.ContentHash), resolved);
+ m_ContentFileDependencies.Add(ContentFileDependencyMap.NormalizeFileName(contentHash), resolved);
}
}
diff --git a/Analyzer/Util/BuildHistoryHelper.cs b/Analyzer/Util/BuildHistoryHelper.cs
index 11f57d8..184d53b 100644
--- a/Analyzer/Util/BuildHistoryHelper.cs
+++ b/Analyzer/Util/BuildHistoryHelper.cs
@@ -110,17 +110,35 @@ public static List CollectBuildFiles(string buildFolder)
// file (layouts of large builds are big; the hash is one of the first properties). Returns
// null when the value cannot be found or the file is not valid json.
public static string TryReadBuildManifestHash(string contentLayoutPath)
+ {
+ return TryReadTopLevelProperty(contentLayoutPath, "BuildManifestHash",
+ reader => reader.ReadAsString()?.ToLowerInvariant());
+ }
+
+ // Reads the top-level Version of a ContentLayout.json without parsing the whole file (it is
+ // the first property). Returns null when the value cannot be found or the file is not valid
+ // json.
+ public static int? TryReadLayoutVersion(string contentLayoutPath)
+ {
+ return TryReadTopLevelProperty(contentLayoutPath, "Version",
+ reader => reader.ReadAsInt32());
+ }
+
+ // Streams the start of a json file looking for a top-level property, without parsing the
+ // whole file. Returns null when the property is not found within the first tokens or the
+ // file is not valid json.
+ private static T TryReadTopLevelProperty(string path, string propertyName, Func readValue)
{
try
{
- using var reader = new JsonTextReader(File.OpenText(contentLayoutPath));
+ using var reader = new JsonTextReader(File.OpenText(path));
for (int i = 0; i < 64 && reader.Read(); ++i)
{
if (reader.TokenType == JsonToken.PropertyName && reader.Depth == 1 &&
- "BuildManifestHash".Equals(reader.Value))
+ propertyName.Equals(reader.Value))
{
- return reader.ReadAsString()?.ToLowerInvariant();
+ return readValue(reader);
}
}
}
@@ -128,7 +146,7 @@ public static string TryReadBuildManifestHash(string contentLayoutPath)
{
}
- return null;
+ return default;
}
static bool HasExtension(string path, string extension)
diff --git a/Analyzer/Util/ContentLayoutV2Upgrader.cs b/Analyzer/Util/ContentLayoutV2Upgrader.cs
new file mode 100644
index 0000000..ac54c53
--- /dev/null
+++ b/Analyzer/Util/ContentLayoutV2Upgrader.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using UnityDataTools.Models;
+
+namespace UnityDataTools.Analyzer.Util
+{
+ // Source data of a v2 loadable that has no place in the current ContentLayout model, kept for
+ // the v2-only database columns. Aligned by index with ContentLayout.LoadableObjectIds.
+ internal record LoadableObjectV2Data(string AssetPath, long SourceLfid);
+
+ // Converts a version 2 ContentLayout (Unity 6.6) into the current in-memory model so a single
+ // import path handles both versions. v3 only removed and reorganized data, so the conversion
+ // is exact: hash-based references become indices and each ContentHash becomes the index of the
+ // matching binary artifact. The v2-only source fields are returned separately.
+ internal static class ContentLayoutV2Upgrader
+ {
+ private const string CfidExtension = ".cfid";
+
+ public static ContentLayout Upgrade(Models.V2.ContentLayout v2, out LoadableObjectV2Data[] v2Data)
+ {
+ var artifactIndexByHash = (v2.BinaryArtifacts ?? [])
+ .Where(a => a.Category == Models.V2.BuildArtifactCategory.ContentFile)
+ .ToDictionary(a => a.ContentHash, a => a.Index);
+
+ var loadables = v2.LoadableObjectIds ?? [];
+ var loadableIndexByHash = new Dictionary(loadables.Length);
+ for (int i = 0; i < loadables.Length; ++i)
+ {
+ loadableIndexByHash.Add(loadables[i].ObjectIdHash, i);
+ }
+
+ var layout = new ContentLayout
+ {
+ Version = v2.Version,
+ BuildManifestHash = v2.BuildManifestHash,
+ SerializedFiles = (v2.SerializedFiles ?? []).Select(f => new SerializedFileLayout
+ {
+ Index = f.Index,
+ StableId = StripCfidExtension(f.ID),
+ IsBuiltIn = f.IsBuiltIn,
+ SourceAssets = f.SourceAssets,
+ SerializedFileDependencies = f.SerializedFileDependencies,
+ LoadableDependencies = f.LoadableDependencies?
+ .Select(hash => ResolveLoadable(loadableIndexByHash, hash)).ToArray(),
+ LoadableSceneDependencies = f.LoadableSceneDependencies,
+ ArtifactIndex = ResolveArtifact(artifactIndexByHash, f),
+ }).ToArray(),
+ RootAssets = v2.RootAssets?
+ .Select(hash => ResolveLoadable(loadableIndexByHash, hash)).ToArray(),
+ LoadableObjectIds = loadables.Select(l => new LoadableObjectIdLayout
+ {
+ GUID = l.GUID,
+ // The current definition of LFID: the output-file lfid when the object was
+ // placed in the build, the source lfid otherwise.
+ LFID = l.SerializedFile >= 0 ? l.OutputLFID : l.LFID,
+ IdentifierType = l.IdentifierType,
+ SerializedFile = l.SerializedFile,
+ }).ToArray(),
+ LoadableSceneIds = (v2.LoadableSceneIds ?? []).Select(s => new LoadableSceneIdLayout
+ {
+ GUID = s.GUID,
+ Path = s.Path,
+ SerializedFile = s.SerializedFile,
+ }).ToArray(),
+ BinaryArtifacts = (v2.BinaryArtifacts ?? []).Select(a => new BinaryArtifact
+ {
+ Index = a.Index,
+ ContentHash = a.ContentHash,
+ Category = a.Category,
+ Size = a.Size,
+ ArtifactReferences = a.ArtifactReferences,
+ }).ToArray(),
+ };
+
+ v2Data = loadables.Select(l => new LoadableObjectV2Data(l.AssetPath, l.LFID)).ToArray();
+ return layout;
+ }
+
+ private static int ResolveLoadable(Dictionary loadableIndexByHash, string objectIdHash)
+ {
+ if (!loadableIndexByHash.TryGetValue(objectIdHash, out var index))
+ {
+ throw new InvalidDataException(
+ $"ObjectIdHash \"{objectIdHash}\" has no matching LoadableObjectIds entry.");
+ }
+
+ return index;
+ }
+
+ private static int ResolveArtifact(Dictionary artifactIndexByHash, Models.V2.SerializedFileLayout file)
+ {
+ // Built-in entries are not produced by the build and have no ContentHash.
+ if (string.IsNullOrEmpty(file.ContentHash))
+ return -1;
+
+ if (!artifactIndexByHash.TryGetValue(file.ContentHash, out var index))
+ {
+ throw new InvalidDataException(
+ $"SerializedFile {file.Index} has ContentHash \"{file.ContentHash}\" with no matching contentfile BinaryArtifact.");
+ }
+
+ return index;
+ }
+
+ // v2 IDs carry a ".cfid" extension (built-in entries use a path instead); the current
+ // schema uses the bare identity hash, so strip it for a uniform stable_id column.
+ private static string StripCfidExtension(string id)
+ {
+ return id != null && id.EndsWith(CfidExtension, StringComparison.Ordinal)
+ ? id.Substring(0, id.Length - CfidExtension.Length)
+ : id;
+ }
+ }
+}
diff --git a/Documentation/analyze-examples-contentlayout.md b/Documentation/analyze-examples-contentlayout.md
index 07e2c75..111bdf0 100644
--- a/Documentation/analyze-examples-contentlayout.md
+++ b/Documentation/analyze-examples-contentlayout.md
@@ -62,10 +62,10 @@ WHERE s.asset_path = 'Assets/Scenes/Scene1.unity';
## The loadables and what they resolve to (layout + content)
Each loadable with the analyzed object it points at (type, name, size), and whether it is a root
-asset of the build:
+asset of the build (`is_root_asset` holds the root's 1-based input position, 0 for non-roots):
```sql
-SELECT * FROM content_layout_loadable_objects_view ORDER BY is_root_asset DESC, asset_path;
+SELECT * FROM content_layout_loadable_objects_view ORDER BY is_root_asset DESC, name;
```
## Data files of a serialized file (layout only)
@@ -75,7 +75,7 @@ serialized file:
```sql
SELECT * FROM content_layout_resource_files_view
-WHERE filename = 'c0152db4dd710be51b2decb997325f34.cf';
+WHERE filename = 'eb3abd5ab5b0d790980fe9e9df872484.cf';
```
## Checking reference resolution (layout + content)
diff --git a/Documentation/analyzer-schema.md b/Documentation/analyzer-schema.md
index d4a0564..0777830 100644
--- a/Documentation/analyzer-schema.md
+++ b/Documentation/analyzer-schema.md
@@ -552,6 +552,7 @@ Any schema change - a new or changed table, view or column - must bump the pragm
| 6 | `archives.name` is UNIQUE ([#51](https://github.com/Unity-Technologies/UnityDataTools/issues/51)) |
| 7 | Unity 6.6 `build_reports` columns and `build_report_content_*` tables ([#107](https://github.com/Unity-Technologies/UnityDataTools/issues/107)); `asset_name` / `asset_extension` columns on `build_report_source_assets` ([#110](https://github.com/Unity-Technologies/UnityDataTools/issues/110)) |
| 8 | `archives.name` is the path relative to the scanned directory, not the bare file name ([#149](https://github.com/Unity-Technologies/UnityDataTools/issues/149)) |
+| 9 | `content_layout*` tables track the version 3 layout schema of Unity 6.7: loadables keyed by `loadable_index`, `stable_id` / `artifact_index` columns replace `cfid` / `content_hash`, `is_root_asset` records the root position, and the v2-only columns (`asset_path`, `source_lfid`) exist only in databases imported from a version 2 layout ([#131](https://github.com/Unity-Technologies/UnityDataTools/issues/131)) |
## Related documentation
diff --git a/Documentation/contentdirectory-format.md b/Documentation/contentdirectory-format.md
index 147c1fa..64656ba 100644
--- a/Documentation/contentdirectory-format.md
+++ b/Documentation/contentdirectory-format.md
@@ -52,7 +52,7 @@ runtime.
Every build artifact is named by the hash of its own content:
-- **Content Files** use the `.cf` extension (for example `c0152db4dd710be51b2decb997325f34.cf`).
+- **Content Files** use the `.cf` extension (for example `eb3abd5ab5b0d790980fe9e9df872484.cf`).
- **`.resS`** files hold streamed texture and mesh data.
- **`.resource`** files hold audio and video data.
@@ -73,7 +73,7 @@ it is best to avoid assumption that the extensions are always present.
### The build manifest
The build manifest is a JSON file, also named by its content hash (for example
-`baff06b928d147276f2245dd3b19216a.json`). It records everything needed to load the content: the list of
+`e320fc78984f8430afa90a591fd02004.json`). It records everything needed to load the content: the list of
Content Files, their dependencies, and the loadable objects and scenes. Its schema is internal and may
change substantially, so this page does not document it. Instead, use
[`ContentLayout.json`](contentlayout.md), which presents the same information (plus source-asset
@@ -145,8 +145,8 @@ on) are not packed with it — they land in their own Content Files following th
[granularity rules](#build-layout-granularity), and the scene's file reaches them through the
manifest dependency list. This differs from Player builds, where each scene comes with companion
`sharedassets` files holding its assets. Scene Content Files also differ in their object IDs: the
-scene's objects get small sequential IDs (1, 2, 3, ...), rather than the 64-bit hash-based IDs used
-in other Content Files.
+scene's objects get small sequential IDs (1, 2, 3, ...), rather than keeping the local file ids of
+their source objects like other Content Files do.
In `ContentLayout.json` (schema details on the [ContentLayout.json](contentlayout.md) page), the
top-level `LoadableSceneIds` array lists each scene with its source path and the Content File that
@@ -155,27 +155,29 @@ scenes:
```json
"LoadableSceneIds": [
- { "GUID": "590cfeb4e0ff90f4e92f9e1262bcfe6f", "Path": "Assets/Scenes/Scene2.unity", "SerializedFile": 6 },
- { "GUID": "162c015549f8733449ac70ae78ad3aa5", "Path": "Assets/Scenes/Scene1.unity", "SerializedFile": 2 }
+ { "GUID": "590cfeb4e0ff90f4e92f9e1262bcfe6f", "Path": "Assets/Scenes/Scene2.unity", "SerializedFile": 12 },
+ { "GUID": "162c015549f8733449ac70ae78ad3aa5", "Path": "Assets/Scenes/Scene1.unity", "SerializedFile": 6 }
]
```
-The scene's own `SerializedFiles` entry names the scene as its single source asset, and its symbolic
-`ID` is derived from the scene's GUID:
+The scene's own `SerializedFiles` entry names the scene as its single source asset, and its
+`StableId` is derived from the scene's GUID:
```json
{
- "Index": 2,
- "ID": "162c015549f8733449ac70ae78ad3aa5.cfid",
+ "Index": 6,
+ "StableId": "162c015549f8733449ac70ae78ad3aa5",
+ "ArtifactIndex": 6,
"SourceAssets": [ "Assets/Scenes/Scene1.unity" ],
- "SerializedFileDependencies": [ 0, 1, 9 ],
- "ContentHash": "c271b85494f5e4cc35c4ec4a776324af"
+ "SerializedFileDependencies": [ 0, 1, 13 ]
}
```
-So this scene lives in `c271b85494f5e4cc35c4ec4a776324af.cf`, and depends on three other files: the
-built-in `unity default resources` entry, the Content File built from `Resources/unity_builtin_extra`
-(the default sprite material), and the file holding the Sprite it shows.
+Its `ArtifactIndex` points at the `BinaryArtifacts` entry whose `ContentHash` is
+`8ec44fb05f86ef7dcb7bbef683d8a330`, so this scene lives in `8ec44fb05f86ef7dcb7bbef683d8a330.cf`. It
+depends on three other files: the built-in `unity default resources` entry, the Content File built
+from `Resources/unity_builtin_extra` (the default sprite material), and the file holding the Sprite
+it shows.
A `LoadableSceneId` reference is an on-demand reference, like a `Loadable`: loading the referencing
file does not load the scene. The file holding the reference records the scene by path in its
@@ -186,12 +188,12 @@ GUID. The reference build's `SceneList` asset, a ScriptableObject with a diction
```json
{
- "Index": 7,
- "ID": "70a210050f71a924aa83be7146547111.cfid",
+ "Index": 3,
+ "StableId": "70a210050f71a924aa83be7146547111",
+ "ArtifactIndex": 3,
"SourceAssets": [ "Assets/ScriptableObjects/SceneList.asset" ],
- "SerializedFileDependencies": [ 8 ],
- "LoadableSceneDependencies": [ "Assets/Scenes/Scene2.unity", "Assets/Scenes/Scene1.unity" ],
- "ContentHash": "8ad4924ac5e264fde63d8e95a0dab8ab"
+ "SerializedFileDependencies": [ 4 ],
+ "LoadableSceneDependencies": [ "Assets/Scenes/Scene2.unity", "Assets/Scenes/Scene1.unity" ]
}
```
@@ -230,24 +232,25 @@ File**, in the manifest, rather than in the file's own external-reference table.
reproduced in [`ContentLayout.json`](contentlayout.md), which is what the examples below use.
Consider `ContentDirectoryRoot.asset`, which directly references several ScriptableObjects. Its entry
-in `ContentLayout.json` names the source asset, the file's own content hash, and — crucially — an
-ordered list of the files it depends on:
+in `ContentLayout.json` names the source asset, the artifact holding the file's content, and —
+crucially — an ordered list of the files it depends on:
```json
{
- "Index": 5,
- "ID": "52b43dad178849b42ac753005736e7bb.cfid",
+ "Index": 10,
+ "StableId": "52b43dad178849b42ac753005736e7bb",
+ "ArtifactIndex": 10,
"SourceAssets": [ "Assets/ScriptableObjects/ContentDirectoryRoot.asset" ],
- "SerializedFileDependencies": [ 8, 4, 11, 13, 7 ],
- "ContentHash": "c0152db4dd710be51b2decb997325f34"
+ "SerializedFileDependencies": [ 4, 5, 2, 9, 3 ]
}
```
-The `ContentHash` tells us this asset lives in `c0152db4dd710be51b2decb997325f34.cf`. Dumping that file
-shows the object's references and the file's external-reference table:
+`BinaryArtifacts` entry 10 has the content hash `eb3abd5ab5b0d790980fe9e9df872484`, so this asset
+lives in `eb3abd5ab5b0d790980fe9e9df872484.cf`. Dumping that file shows the object's references and
+the file's external-reference table:
```
-UnityDataTool dump --stdout c0152db4dd710be51b2decb997325f34.cf
+UnityDataTool dump --stdout eb3abd5ab5b0d790980fe9e9df872484.cf
```
```
External References
@@ -257,13 +260,13 @@ path(3): "21679be819d6e9146a63bb02a7e51f2f.cfid" GUID: 0000000000000000000000000
path(4): "78532141fd7679a458405eb16bdb75fd.cfid" GUID: 00000000000000000000000000000000 Type: 0
path(5): "70a210050f71a924aa83be7146547111.cfid" GUID: 00000000000000000000000000000000 Type: 0
-ID: -775554941117088049 (ClassID: 114) MonoBehaviour
+ID: 11400000 (ClassID: 114) MonoBehaviour
...
data[1] (SerializedKeyValue`2)
key (string) SingleAudioClipLoadableReference
value (PPtr<$ScriptableObject>)
m_FileID (int) 3
- m_PathID (SInt64) -2313013086301746513
+ m_PathID (SInt64) 11400000
```
The external table lists symbolic names ending in `.cfid`, **not** the content-hash filenames.
@@ -275,29 +278,29 @@ through the build. A key goal of the content directory design is to reduce the a
Because of that, the content of the external table is effectively ignored by the loading system for
Content File references. What counts is the ordered dependency list in the manifest. The
-`"SerializedFileDependencies": [8, 4, 11, 13, 7]` array corresponds, in exact size and order, to the
+`"SerializedFileDependencies": [4, 5, 2, 9, 3]` array corresponds, in exact size and order, to the
five entries of the external table.
So resolving the reference to `SingleAudioClipLoadableReference` above:
1. The `PPtr` has `m_FileID` 3. A non-zero `m_FileID` is a 1-based index into the external table (0
would mean "this same file").
-2. Index 3 maps to the 3rd entry of `SerializedFileDependencies`, which is `11`.
-3. `ContentLayout.json` entry with `"Index": 11` is `SingleAudioClipLoadableReference.asset`, whose
- `ContentHash` is `5c43454a3823f172a2a326410a36ba6b`.
-4. The referenced file is therefore `5c43454a3823f172a2a326410a36ba6b.cf`.
+2. Index 3 maps to the 3rd entry of `SerializedFileDependencies`, which is `2`.
+3. `ContentLayout.json` entry with `"Index": 2` is `SingleAudioClipLoadableReference.asset`, whose
+ `ArtifactIndex` leads to the content hash `38abbace71a8a756c679a7ff3f501f02`.
+4. The referenced file is therefore `38abbace71a8a756c679a7ff3f501f02.cf`.
A partial mapping for this file, showing how `m_FileID` values in the external table resolve through
-the dependency list `[8, 4, 11, 13, 7]` to a content-hash filename:
+the dependency list `[4, 5, 2, 9, 3]` to a content-hash filename:
```mermaid
flowchart TD
- Root["ContentDirectoryRoot
cfid 52b43dad…
file c0152db4….cf
deps [8, 4, 11, 13, 7]"]
- Loadable["LoadableAudioClipReference
Index 4 · cfid 4038ff67…
file bfcf18a2….cf"]
- Single["SingleAudioClipLoadableReference
Index 11 · cfid 21679be8…
file 5c43454a….cf"]
+ Root["ContentDirectoryRoot
cfid 52b43dad…
file eb3abd5a….cf
deps [4, 5, 2, 9, 3]"]
+ Loadable["LoadableAudioClipReference
Index 5 · cfid 4038ff67…
file 2357d63c….cf"]
+ Single["SingleAudioClipLoadableReference
Index 2 · cfid 21679be8…
file 38abbace….cf"]
- Root -->|"m_FileID 2 → dep 4 → Index 4"| Loadable
- Root -->|"m_FileID 3 → dep 11 → Index 11"| Single
+ Root -->|"m_FileID 2 → dep 5 → Index 5"| Loadable
+ Root -->|"m_FileID 3 → dep 2 → Index 2"| Single
```
> [!NOTE]
@@ -322,7 +325,7 @@ audio clips and a texture, so it exercises both kinds of data file. Dumping the
`Assets/Textures/GreenStatic.png`'s texture data shows the Texture2D pointing at its `.resS` file:
```
-ID: -6933612096100796476 (ClassID: 83) AudioClip
+ID: 8300000 (ClassID: 83) AudioClip
m_Name (string) a
...
m_Resource (StreamedResource)
@@ -331,7 +334,7 @@ ID: -6933612096100796476 (ClassID: 83) AudioClip
m_Size (UInt64) 47424
```
```
-ID: 1183010003894172340 (ClassID: 28) Texture2D
+ID: 2800000 (ClassID: 28) Texture2D
m_Name (string) GreenStatic
m_StreamData (StreamingInfo)
offset (UInt64) 0
@@ -366,8 +369,8 @@ File's `ArtifactReferences` point at the resource artifacts it needs. Continuing
above:
```json
-{ "Index": 11, "ContentHash": "730d2d641a53eeb1e633f2ff60d730e9", "Category": "contentfile", "Size": 1288, "ArtifactReferences": [12] },
-{ "Index": 12, "ContentHash": "4226b5c16a50dab6eff0f08dd1253d4b", "Category": "audio", "Size": 47424 }
+{ "Index": 11, "ContentHash": "39105176560358e0cd444e7a0a513ed3", "Category": "contentfile", "Size": 1400, "ArtifactReferences": [17] },
+{ "Index": 17, "ContentHash": "4226b5c16a50dab6eff0f08dd1253d4b", "Category": "audio", "Size": 47424 }
```
When [`analyze`](command-analyze.md) imports the layout it preserves this: the artifacts land in
diff --git a/Documentation/contentlayout-database.md b/Documentation/contentlayout-database.md
index b7217c3..72b9821 100644
--- a/Documentation/contentlayout-database.md
+++ b/Documentation/contentlayout-database.md
@@ -10,10 +10,11 @@ For what `ContentLayout.json` contains conceptually, see [ContentLayout.json](co
* The `content_layout*` tables and views are only created when a `ContentLayout.json` is actually part of the analyzed input, so databases produced from AssetBundles or Player builds are not cluttered with empty tables.
* A single layout per database is supported. When several `ContentLayout.json` files are on the input, the one whose `BuildManifestHash` matches the analyzed build is selected and the rest are ignored (see [the `analyze` command](command-analyze.md#contentdirectory-builds)).
-* The tables mirror the json structure. Entries that cross-reference each other by array index in the json do the same in the database: `file_index` and `artifact_index` columns hold the json array indexes.
+* The tables mirror the json structure of the current layout version (version 3, Unity 6.7). Entries that cross-reference each other by array index in the json do the same in the database: `file_index`, `artifact_index` and `loadable_index` columns hold the json array indexes.
+* Version 2 layouts (Unity 6.6) are upgraded on import, so both versions produce the same schema and the same queries work on both. See [Layout version differences](#layout-version-differences).
* Two adjustments are made so the data is natural to query:
- * The json's sentinel values are stored as SQL `NULL`: a `SerializedFile` value of `-1` (an object dropped from the build) and the missing `ContentHash` of built-in entries.
- * The top-level `RootAssets` list is folded into the `is_root_asset` flag of `content_layout_loadable_objects`.
+ * The json's `-1` sentinels are stored as SQL `NULL`: the `SerializedFile` of an object dropped from the build, and the `ArtifactIndex` of the built-in entry.
+ * The top-level `RootAssets` list is folded into the `is_root_asset` column of `content_layout_loadable_objects`, which records each root's 1-based position in the list (0 for non-roots), preserving the root input order.
* This page is the reference for the columns; [`ContentLayout.cs`](../UnityDataModels/ContentLayout.cs) is the reference for the json schema. A produced database also carries short notes on its non-obvious columns inside the `CREATE` statements, so `sqlite3 Analysis.db ".schema content_layout_serialized_files"` describes them without needing this page.
## Tables
@@ -36,9 +37,9 @@ One row per serialized file (`.cf` Content File) of the build.
| Column | Type | Description |
|---|---|---|
| `file_index` | INTEGER | The json array index. How every other `content_layout` table names a file. Primary key. |
-| `cfid` | TEXT | The symbolic `.cfid` reference string. For built-ins, the built-in path. |
+| `stable_id` | TEXT | The identity hash used to reference this file from other files (see [Stable ids](contentlayout.md#stable-ids)). Stored without the `.cfid` extension that v2 layouts carry. For built-ins, the built-in path. |
| `is_builtin` | INTEGER | 1 for a built-in entry, which is not a file produced by this build. |
-| `content_hash` | TEXT | Gives the filename, `content_hash \|\| '.cf'`. NULL for built-ins. |
+| `artifact_index` | INTEGER | References `content_layout_binary_artifacts.artifact_index`, whose `content_hash` gives the filename (`content_hash \|\| '.cf'` — `content_layout_serialized_files_view` derives it). NULL for built-ins. |
| `serialized_file` | INTEGER | [`serialized_files.id`](analyzer-schema.md#serialized_files), connecting the layout to the analyzed objects. NULL for built-ins. See [below](#analyzing-the-layout-with-or-without-the-build-content) for what the referenced row contains when the build content was not analyzed. |
### content_layout_source_assets
@@ -68,14 +69,16 @@ The objects that can be loaded on demand, identified independently of the serial
| Column | Type | Description |
|---|---|---|
-| `object_id_hash` | TEXT | Hash of the GUID, LFID and `identifier_type`. Primary key, and how `content_layout_loadable_dependencies` names a loadable. |
+| `loadable_index` | INTEGER | The json array index. Primary key, and how `content_layout_loadable_dependencies` names a loadable. |
| `guid` | TEXT | AssetDatabase GUID of the source asset. |
-| `asset_path` | TEXT | Project path of the source asset. |
-| `lfid` | INTEGER | Local file id of the object within the source asset. |
+| `lfid` | INTEGER | Local file id of the object in its output serialized file when placed, otherwise the source local file id. This is the join key to the analyzed [`objects`](analyzer-schema.md#objects) (`objects.object_id`). |
| `identifier_type` | INTEGER | Distinguishes the kinds of identifier a loadable can have. |
| `serialized_file_index` | INTEGER | References `content_layout_serialized_files.file_index`. NULL when the object was dropped from the build (json value `-1`, e.g. server-build shader references). |
-| `output_lfid` | INTEGER | Local file id of the object in its output serialized file. |
-| `is_root_asset` | INTEGER | 1 for the root assets the build was made from. |
+| `is_root_asset` | INTEGER | The 1-based position in the layout's `RootAssets` (the root input order); 0 when not a root asset. |
+| `asset_path` | TEXT | **v2 layouts only.** Project path of the source asset. |
+| `source_lfid` | INTEGER | **v2 layouts only.** Local file id of the object within the source asset. |
+
+The last two columns exist only in a database imported from a version 2 layout — see [Layout version differences](#layout-version-differences).
### content_layout_loadable_scenes
@@ -94,7 +97,7 @@ The loadable objects each serialized file references.
| Column | Type | Description |
|---|---|---|
| `serialized_file_index` | INTEGER | References `content_layout_serialized_files.file_index`. |
-| `object_id_hash` | TEXT | References `content_layout_loadable_objects.object_id_hash`. |
+| `loadable_index` | INTEGER | References `content_layout_loadable_objects.loadable_index`. |
### content_layout_loadable_scene_dependencies
@@ -131,10 +134,10 @@ Both columns together are the primary key.
| View | Description |
|------|-------------|
-| `content_layout_serialized_files_view` | One row per layout serialized file with the derived filename, its artifact size, and the core-table link (`serialized_file`, `archive`). Built-in entries show their path as the filename. |
+| `content_layout_serialized_files_view` | One row per layout serialized file with its content hash, derived filename, artifact size, and the core-table link (`serialized_file`, `archive`), resolved through the artifact link. Built-in entries show their path as the filename. |
| `content_layout_source_assets_view` | Source asset → the file(s) it was built into. |
| `content_layout_serialized_file_dependencies_view` | The dependency edges with filenames resolved on both sides. |
-| `content_layout_loadable_objects_view` | The loadables resolved to their analyzed object (`object`, `type`, `name`, `size`). |
+| `content_layout_loadable_objects_view` | The loadables resolved to their analyzed object (`object`, `type`, `name`, `size`). In a database from a v2 layout it also carries the `asset_path` and `source_lfid` columns. |
| `content_layout_binary_artifacts_view` | The artifacts with their derived on-disk filename (content hash + category-based extension). |
| `content_layout_resource_files_view` | The `.resS`/`.resource` data files each serialized file uses, derived from the artifact graph. Note: the same resource can be referenced by multiple serialized files. |
@@ -147,7 +150,18 @@ SELECT category, COUNT(*) AS count, SUM(size) AS bytes
FROM content_layout_binary_artifacts GROUP BY category ORDER BY bytes DESC;
```
-Indexes are created after the tables are populated, so importing a very large layout stays fast. The `content_hash` and `asset_path` indexes carry the views; the rest serve reverse lookups such as "who depends on X" and "which loadables live in file Y".
+Indexes are created after the tables are populated, so importing a very large layout stays fast. The indexes serve reverse lookups such as "who depends on X", "which loadables live in file Y" and "which artifact has this hash".
+
+## Layout version differences
+
+The [`analyze` command](command-analyze.md) accepts layout versions 2 (Unity 6.6) and 3 (Unity 6.7 and later; see [Schema versioning](contentlayout.md#schema-versioning)). A version 2 file is upgraded to the version 3 structure during import: the `.cfid` extension is stripped from the stable ids, and its hash-based references (`ObjectIdHash` strings in `LoadableDependencies` and `RootAssets`, `ContentHash` on the files) are resolved to the `loadable_index` / `artifact_index` values the tables store. The original file version stays queryable in `content_layout.version`.
+
+Version 2 layouts record two things about a loadable that newer versions no longer contain, and these become extra columns of `content_layout_loadable_objects` (and its view) that exist **only** in a database imported from a v2 file:
+
+* `asset_path` — the loadable's source asset path. In v2 builds a serialized file could combine several source assets (clustering), so the path identified which one the loadable came from. Unity 6.7 builds don't remap, and the layout only records the per-file `SourceAssets` lists.
+* `source_lfid` — the object's local file id within its source asset. In v2 builds the id in the output file (`lfid`) could differ from the source id; in 6.7 output they match (MonoScripts excepted), so only `lfid` exists.
+
+The `ObjectIdHash` concept was removed in version 3 and is not stored for either version — loadables are keyed by their array index.
## Analyzing the layout with or without the build content
@@ -155,7 +169,7 @@ A `ContentLayout.json` can be analyzed on its own — useful for running SQL que
* When the build content is part of the analyzed input, each `content_layout_serialized_files` row links to its analyzed file, and the views resolve across that link (e.g. `content_layout_loadable_objects_view` shows the object, type, name and size of each loadable).
* When a file was not part of the analyzed input (a layout-only analyze, or a subset of a build), its layout entry links to a placeholder `serialized_files` row instead: the row holds only the filename, with `archive` NULL and no objects. The link column is therefore always valid, and whether a file was actually analyzed is visible through its objects: `EXISTS (SELECT 1 FROM objects o WHERE o.serialized_file = f.serialized_file)`. In a layout-only database, expect NULL in the `archive` column of `content_layout_serialized_files_view` and in the `object`, `type`, `name` and `size` columns of `content_layout_loadable_objects_view`.
-* Built-in entries (`is_builtin = 1`) always have a NULL `content_hash` and `serialized_file` — they are not files produced by the build.
+* Built-in entries (`is_builtin = 1`) always have a NULL `artifact_index` and `serialized_file` — they are not files produced by the build.
## Related documentation
diff --git a/Documentation/contentlayout.md b/Documentation/contentlayout.md
index 30ce27a..981ab32 100644
--- a/Documentation/contentlayout.md
+++ b/Documentation/contentlayout.md
@@ -40,18 +40,27 @@ The file uses a few terms consistently:
|--------|-------------|
| `Version` | Schema version of the file. See [Schema versioning](#schema-versioning). |
| `BuildManifestHash` | Hash of the build manifest this layout corresponds to. |
-| `SerializedFiles` | One entry per serialized file in the build. Each entry records the source assets it contains, its content hash, and its dependencies on other serialized files, loadables, and loadable scenes. The same source asset can appear in more than one serialized file (for example, a single FBX file can be split into multiple output files). |
-| `RootAssets` | The `ObjectIdHash` of each root asset the build was made from. Each one has a corresponding entry in `LoadableObjectIds`. |
-| `LoadableObjectIds` | The objects that can be loaded on demand. Each entry records its `ObjectIdHash`, where the object lives in the built content (which serialized file) and where it came from in the source project (GUID, asset path, local file ID, and identifier type). |
+| `SerializedFiles` | One entry per serialized file in the build. Each entry records its stable id (see below), the source assets it contains, the index of its artifact in `BinaryArtifacts`, and its dependencies on other serialized files, loadables, and loadable scenes. The same source asset can appear in more than one serialized file (for example, a single FBX file can be split into multiple output files). |
+| `RootAssets` | The root assets the build was made from, as indices into `LoadableObjectIds`, in root input order. |
+| `LoadableObjectIds` | The objects that can be loaded on demand. Each entry records where the object lives in the built content (which serialized file) and its identity (source asset GUID, local file ID, and identifier type). |
| `LoadableSceneIds` | The scenes in the build, each with its source project path and GUID, and the serialized file that contains it. |
| `BinaryArtifacts` | The artifacts that make up the build output. See [Binary artifacts](#binary-artifacts). |
+### Stable ids
+
+Each serialized file has a **stable id**: an identity hash used to reference the file from other
+serialized files in a way that doesn't break when its content changes (unlike the content hash,
+which names the produced artifact and changes with every content change). Inside the built files,
+the external reference tables list these ids with a `.cfid` extension as symbolic placeholder names —
+see [Content Directory Format](contentdirectory-format.md). The synthetic built-in entry uses its
+resource path (`Library/unity default resources`) as its stable id.
+
### Binary artifacts
`BinaryArtifacts` is essentially the list of files in the build output. Each entry has a `Category` and a `Size`, and lists its direct dependencies in `ArtifactReferences`:
* The entry with category `manifest` is the root of the graph.
-* Entries with category `contentfile` each have a matching entry in `SerializedFiles` (matched by content hash).
+* Entries with category `contentfile` each have a matching entry in `SerializedFiles` (which references its artifact by index through `ArtifactIndex`).
* `BinaryArtifacts` also reports the additional data files that hold audio, video, texture, and mesh data (the `.resource` and `.resS` files).
* BinaryArtifacts are identified by the hash of their content. When saved as a file, the filename is the hash and the file extension is based on the category.
@@ -61,7 +70,14 @@ The file uses a few terms consistently:
The schema is subject to change. The `Version` field records the schema version of the file, independently of the Unity version that produced it. When the schema changes, the version number increments.
-[`ContentLayout.cs`](../UnityDataModels/ContentLayout.cs) always represents the latest schema version (currently version 2).
+[`ContentLayout.cs`](../UnityDataModels/ContentLayout.cs) always represents the latest schema version (currently version 3, written by Unity 6.7). The version 2 schema written by Unity 6.6 remains available as a reference definition in [`ContentLayoutV2.cs`](../UnityDataModels/ContentLayoutV2.cs) (namespace `UnityDataTools.Models.V2`).
+
+The [`analyze` command](command-analyze.md) accepts versions 2 and 3, importing both into the same database schema (see [ContentLayout in the Analyze Database](contentlayout-database.md)). Version 3 made these changes relative to version 2:
+
+* `SerializedFiles` entries carry `StableId` (the bare identity hash, no `.cfid` extension) instead of `ID`, and `ArtifactIndex` (the index of the file's artifact in `BinaryArtifacts`, `-1` for the built-in entry) instead of `ContentHash`.
+* `LoadableDependencies` and `RootAssets` reference loadables as indices into `LoadableObjectIds` instead of `ObjectIdHash` strings, and `ObjectIdHash` itself was removed.
+* `LoadableObjectIds` entries were trimmed to `{GUID, LFID, IdentifierType, SerializedFile}`. `AssetPath` and `OutputLFID` were removed: Unity 6.7 no longer remaps objects into clusters, so the local file id of an object in its output file matches the source id (MonoScripts excepted), and `LFID` now records the output-file id directly for placed objects.
+* `IsBuiltIn` is only written when true.
## Related documentation
diff --git a/ReferenceFinder/ReferenceFinderTool.cs b/ReferenceFinder/ReferenceFinderTool.cs
index 2edc9e7..fe83f1e 100644
--- a/ReferenceFinder/ReferenceFinderTool.cs
+++ b/ReferenceFinder/ReferenceFinderTool.cs
@@ -201,7 +201,9 @@ FROM object_view o
using var loadableCommand = m_LoadableObjectIds.Count > 0 ? db.CreateCommand() : null;
if (loadableCommand != null)
{
- loadableCommand.CommandText = "SELECT asset_path, filename FROM content_layout_loadable_objects_view WHERE object = @id";
+ // The guid is the loadable's identity in every layout version (v3 layouts no
+ // longer record the asset path).
+ loadableCommand.CommandText = "SELECT guid, filename FROM content_layout_loadable_objects_view WHERE object = @id";
loadableCommand.Parameters.Add("@id", SqliteType.Integer);
}
@@ -224,7 +226,7 @@ FROM object_view o
if (loadableReader.Read())
{
m_Writer.WriteLine("Found reference in loadable:");
- m_Writer.WriteLine(loadableReader.GetString(0));
+ m_Writer.WriteLine($"GUID {loadableReader.GetString(0)}");
m_Writer.WriteLine($"(SerializedFile = {loadableReader.GetString(1)})");
}
}
diff --git a/TestCommon/Data/ContentLayoutVersions/v2/ContentLayout.json b/TestCommon/Data/ContentLayoutVersions/v2/ContentLayout.json
new file mode 100644
index 0000000..3fad48c
--- /dev/null
+++ b/TestCommon/Data/ContentLayoutVersions/v2/ContentLayout.json
@@ -0,0 +1 @@
+{"Version":2,"BuildManifestHash":"baff06b928d147276f2245dd3b19216a","SerializedFiles":[{"Index":0,"ID":"Library/unity default resources","IsBuiltIn":true,"SourceAssets":[],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[]},{"Index":1,"ID":"0000000000000000f000000000000000.cfid","IsBuiltIn":false,"SourceAssets":["Resources/unity_builtin_extra"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[],"ContentHash":"6a60c190881f43bb4a91a360b9e8850d"},{"Index":2,"ID":"162c015549f8733449ac70ae78ad3aa5.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Scenes/Scene1.unity"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[0,1,9],"ContentHash":"c271b85494f5e4cc35c4ec4a776324af"},{"Index":3,"ID":"20408785117e138f38c2cc4ca0e638c8.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Audio/6.mp3"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[],"ContentHash":"93bb3159569284a5c7bcceb4acb91bae"},{"Index":4,"ID":"4038ff673d390134d924b57fcbed0432.cfid","IsBuiltIn":false,"SourceAssets":["Assets/ScriptableObjects/LoadableAudioClipReference.asset"],"LoadableDependencies":["20408785117e138f38c2cc4ca0e638c8","dd30e3af3b8bddf1ea36d768fc41aafd"],"LoadableSceneDependencies":[],"SerializedFileDependencies":[8],"ContentHash":"bfcf18a244e45f867750e0223a9b3a43"},{"Index":5,"ID":"52b43dad178849b42ac753005736e7bb.cfid","IsBuiltIn":false,"SourceAssets":["Assets/ScriptableObjects/ContentDirectoryRoot.asset"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[8,4,11,13,7],"ContentHash":"c0152db4dd710be51b2decb997325f34"},{"Index":6,"ID":"590cfeb4e0ff90f4e92f9e1262bcfe6f.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Scenes/Scene2.unity"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[0,1,9],"ContentHash":"4d49f1aa2acf2ecf20f51660e1bf77ea"},{"Index":7,"ID":"70a210050f71a924aa83be7146547111.cfid","IsBuiltIn":false,"SourceAssets":["Assets/ScriptableObjects/SceneList.asset"],"LoadableDependencies":[],"LoadableSceneDependencies":["Assets/Scenes/Scene2.unity","Assets/Scenes/Scene1.unity"],"SerializedFileDependencies":[8],"ContentHash":"8ad4924ac5e264fde63d8e95a0dab8ab"},{"Index":8,"ID":"a2a42d71dddef12e8889849faf59bdd7.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Scripts/DirectScriptableObjectReference.cs","Assets/Scripts/LoadableAudioClipReference.cs","Assets/Scripts/SceneList.cs","Assets/Scripts/SerializationDemo.cs"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[],"ContentHash":"86d71ff2bb38e064697257d35d6421b8"},{"Index":9,"ID":"baf46637edbb6ff884858f6630b66eda.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Textures/GreenStatic.png"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[12],"ContentHash":"61fa28f6b22b739b19ece22ea0b3f29f"},{"Index":10,"ID":"dd30e3af3b8bddf1ea36d768fc41aafd.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Audio/a.mp3"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[],"ContentHash":"730d2d641a53eeb1e633f2ff60d730e9"},{"Index":11,"ID":"21679be819d6e9146a63bb02a7e51f2f.cfid","IsBuiltIn":false,"SourceAssets":["Assets/ScriptableObjects/SingleAudioClipLoadableReference.asset"],"LoadableDependencies":["dd30e3af3b8bddf1ea36d768fc41aafd"],"LoadableSceneDependencies":[],"SerializedFileDependencies":[8],"ContentHash":"5c43454a3823f172a2a326410a36ba6b"},{"Index":12,"ID":"34814583296b527bf650f548effcd62c.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Textures/GreenStatic.png"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[],"ContentHash":"c046f78347acd69f03fa7f80362fde94"},{"Index":13,"ID":"78532141fd7679a458405eb16bdb75fd.cfid","IsBuiltIn":false,"SourceAssets":["Assets/ScriptableObjects/SerializationDemo.asset"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[8],"ContentHash":"51690f09f541d7442ce8faf26fd84696"}],"RootAssets":["3dcb957e6cb206bfec2aaadd3d11d85c"],"LoadableObjectIds":[{"ObjectIdHash":"20408785117e138f38c2cc4ca0e638c8","GUID":"278c261333bf8604eb5c83790d02004d","AssetPath":"Assets/Audio/6.mp3","LFID":8300000,"IdentifierType":3,"SerializedFile":3,"OutputLFID":6313987888022012634},{"ObjectIdHash":"3dcb957e6cb206bfec2aaadd3d11d85c","GUID":"52b43dad178849b42ac753005736e7bb","AssetPath":"Assets/ScriptableObjects/ContentDirectoryRoot.asset","LFID":11400000,"IdentifierType":2,"SerializedFile":5,"OutputLFID":-775554941117088049},{"ObjectIdHash":"dd30e3af3b8bddf1ea36d768fc41aafd","GUID":"b65a7916245593b4e89f4bd0aa920533","AssetPath":"Assets/Audio/a.mp3","LFID":8300000,"IdentifierType":3,"SerializedFile":10,"OutputLFID":-6933612096100796476}],"LoadableSceneIds":[{"GUID":"590cfeb4e0ff90f4e92f9e1262bcfe6f","Path":"Assets/Scenes/Scene2.unity","SerializedFile":6},{"GUID":"162c015549f8733449ac70ae78ad3aa5","Path":"Assets/Scenes/Scene1.unity","SerializedFile":2}],"BinaryArtifacts":[{"Index":0,"ContentHash":"6a60c190881f43bb4a91a360b9e8850d","Category":"contentfile","Size":96364,"ArtifactReferences":[1]},{"Index":1,"ContentHash":"ea7c9fa2d58ef71544726747da8ade6d","Category":"texture","Size":524280},{"Index":2,"ContentHash":"c271b85494f5e4cc35c4ec4a776324af","Category":"contentfile","Size":17572},{"Index":3,"ContentHash":"93bb3159569284a5c7bcceb4acb91bae","Category":"contentfile","Size":1288,"ArtifactReferences":[4]},{"Index":4,"ContentHash":"68c9d0b12420e1951eec7790bd0754fe","Category":"audio","Size":30688},{"Index":5,"ContentHash":"bfcf18a244e45f867750e0223a9b3a43","Category":"contentfile","Size":2184},{"Index":6,"ContentHash":"c0152db4dd710be51b2decb997325f34","Category":"contentfile","Size":1524},{"Index":7,"ContentHash":"4d49f1aa2acf2ecf20f51660e1bf77ea","Category":"contentfile","Size":17572},{"Index":8,"ContentHash":"8ad4924ac5e264fde63d8e95a0dab8ab","Category":"contentfile","Size":1304},{"Index":9,"ContentHash":"86d71ff2bb38e064697257d35d6421b8","Category":"contentfile","Size":2012},{"Index":10,"ContentHash":"61fa28f6b22b739b19ece22ea0b3f29f","Category":"contentfile","Size":8516},{"Index":11,"ContentHash":"730d2d641a53eeb1e633f2ff60d730e9","Category":"contentfile","Size":1288,"ArtifactReferences":[12]},{"Index":12,"ContentHash":"4226b5c16a50dab6eff0f08dd1253d4b","Category":"audio","Size":47424},{"Index":13,"ContentHash":"5c43454a3823f172a2a326410a36ba6b","Category":"contentfile","Size":2136},{"Index":14,"ContentHash":"c046f78347acd69f03fa7f80362fde94","Category":"contentfile","Size":2260,"ArtifactReferences":[15]},{"Index":15,"ContentHash":"f0a44ad4a4babd121543fd44032928e7","Category":"texture","Size":215970},{"Index":16,"ContentHash":"51690f09f541d7442ce8faf26fd84696","Category":"contentfile","Size":4812},{"Index":17,"ContentHash":"baff06b928d147276f2245dd3b19216a","Category":"manifest","Size":2858,"ArtifactReferences":[0,2,3,5,6,7,8,9,10,11,13,14,16]}]}
\ No newline at end of file
diff --git a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/BuildContentTEP.json b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/BuildContentTEP.json
index 3604806..ec94edf 100644
--- a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/BuildContentTEP.json
+++ b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/BuildContentTEP.json
@@ -3,206 +3,124 @@
"Instructions": "1) Open Chrome or Edge, 2) go to chrome://tracing, 3) click Load, 4) navigate to this file.",
-"UnityVersion": "6000.6.0b5",
-"OperatingSystem": "Windows 11 (10.0.26200) Professional",
-"ProcessorType": "12th Gen Intel(R) Core(TM) i9-12900K",
-"ProcessorFrequencyMHz": 3187,
-"ProcessorCount": 24,
-"PhysicalProcessorCount": 16,
-"PhysicalMemoryMB": 65277,
+"UnityVersion": "6000.7.0b3",
+"OperatingSystem": "Windows 11 (10.0.26100) Professional",
+"ProcessorType": "11th Gen Intel(R) Core(TM) i9-11950H @ 2.60GHz",
+"ProcessorFrequencyMHz": 2611,
+"ProcessorCount": 16,
+"PhysicalProcessorCount": 8,
+"PhysicalMemoryMB": 65239,
"traceEvents": [
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362598077, "dur": 34236, "name": "ApplyBuildHistoryRetention"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362632316, "dur": 7444502, "name": "UnifiedBuild"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362632361, "dur": 43, "name": "Validate Root Assets"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362635968, "dur": 38037, "name": "InitializeBuildCallbacks:BuildProcessors, SceneProcessors, FilterAssembliesProcessors, ShaderProcessors, ComputeShaderProcessors"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362674023, "dur": 17, "name": "OnBuildStepBegin", "args": {"step": "Refresh Asset Database", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362674340, "dur": 71987, "name": "BuildPipeline::RegisterCustomDependencies"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362746611, "dur": 248, "name": "OnBuildStepEnd", "args": {"step": "Refresh Asset Database", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362746897, "dur": 13, "name": "OnBuildStepBegin", "args": {"step": "Call Pre-Process callbacks", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362747701, "dur": 106, "name": "ContentSelectionBuildPreprocessor.OnPreprocessBuild:WithContext"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362747816, "dur": 7, "name": "OnBuildStepEnd", "args": {"step": "Call Pre-Process callbacks", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362751163, "dur": 58703, "name": "ConfigureTargetPlatform"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362751166, "dur": 11, "name": "OnBuildStepBegin", "args": {"step": "Configure target platform", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362755616, "dur": 54191, "name": "CompressAssets"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362809813, "dur": 24, "name": "OnBuildStepEnd", "args": {"step": "Configure target platform", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362809868, "dur": 1564153, "name": "ReCompileScripts"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367362809874, "dur": 13, "name": "OnBuildStepBegin", "args": {"step": "Compile scripts", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367364373957, "dur": 27, "name": "OnBuildStepEnd", "args": {"step": "Compile scripts", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367364374447, "dur": 12585, "name": "LoadingTypeDB"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367364387044, "dur": 292487, "name": "DeleteCachedBuildArtifacts"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367364679545, "dur": 4805694, "name": "GatherBuildMetaData"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367364679561, "dur": 22, "name": "OnBuildStepBegin", "args": {"step": "Gather and collect build metadata", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367364679845, "dur": 4805096, "name": "BuildPipeline::GatherAllAssetMetadata"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369484945, "dur": 10, "name": "BuildPipeline::GatherMainObjectIdsForInitialAssets"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369484958, "dur": 46, "name": "BuildPipeline::GatherBuiltInObjectIds"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369485008, "dur": 74, "name": "BuildPipeline::BuildObjectIdToGlobalObjectIdMap"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369485084, "dur": 50, "name": "BuildPipeline::BuildObjectIdToObjectBuildMetaDataMap"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369485138, "dur": 17, "name": "BuildPipeline::CombineGlobalUsageFromMetadata"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369485192, "dur": 20, "name": "OnBuildStepEnd", "args": {"step": "Gather and collect build metadata", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369485246, "dur": 11, "name": "OnBuildStepBegin", "args": {"step": "Calculate build layout", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369485546, "dur": 154, "name": "BuildLayout"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369485702, "dur": 7, "name": "OnBuildStepEnd", "args": {"step": "Calculate build layout", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369485727, "dur": 8, "name": "OnBuildStepBegin", "args": {"step": "Aggregate build usage for objects", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486002, "dur": 17, "name": "BuildPipeline::AggregateBuildUsageForObjectIds"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486020, "dur": 5, "name": "OnBuildStepEnd", "args": {"step": "Aggregate build usage for objects", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486042, "dur": 5, "name": "OnBuildStepBegin", "args": {"step": "Generate build instructions", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486213, "dur": 196, "name": "BuildPipeline::BuildInstructionHelper::GenerateBuildInstructions"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486220, "dur": 29, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486261, "dur": 30, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486295, "dur": 3, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486302, "dur": 8, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486316, "dur": 12, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486334, "dur": 16, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486354, "dur": 6, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486364, "dur": 5, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486372, "dur": 5, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486380, "dur": 3, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486387, "dur": 6, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486396, "dur": 3, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486403, "dur": 5, "name": "GenerateBuildInstruction"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486432, "dur": 92721, "name": "SerializeInstructions"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486438, "dur": 8, "name": "OnBuildStepBegin", "args": {"step": "Serialize build instructions", "depth": "2"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486639, "dur": 4466, "name": "SerializeBuildInstructions", "args": {"Count": "13", "changedCount": "13", "unchangedCount": "0", "bytesWritten": "8288", "bytesHashed": "8288"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486640, "dur": 49, "name": "InstructionSet:LoadVersions"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486690, "dur": 6, "name": "InstructionSet:HashInstructions", "args": {"totalBytesHashed": "8288"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486698, "dur": 41, "name": "InstructionSet:PrepareWriteTasks", "args": {"taskCount": "13", "bytesWritten": "8288"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369486740, "dur": 4098, "name": "InstructionSet:WriteChangedInstructions", "args": {"changedCount": "13", "bytesWritten": "8288"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369490840, "dur": 234, "name": "InstructionSet:UpdateVersionMap"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369491106, "dur": 87994, "name": "AssetDatabase Refresh"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369579106, "dur": 17, "name": "OnBuildStepEnd", "args": {"step": "Serialize build instructions", "depth": "2"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369579154, "dur": 3, "name": "OnBuildStepEnd", "args": {"step": "Generate build instructions", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369579184, "dur": 8, "name": "OnBuildStepBegin", "args": {"step": "Execute instructions", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369579496, "dur": 444475, "name": "ExecuteBuildInstructions"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369579502, "dur": 444468, "name": "BuildPipeline::BuildInstructionHelper::ProduceSerializedFilesForBuildInstructions"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369579503, "dur": 49, "name": "Build Instructions to GUIDs"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369579553, "dur": 12, "name": "Filter Up to Date Build Instructions"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369579566, "dur": 343033, "name": "Precompile Shaders From Build Instructions"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369579568, "dur": 8, "name": "OnBuildStepBegin", "args": {"step": "Precompile Shaders", "depth": "2"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369579840, "dur": 342697, "name": "BuildPipeline::PreCompileShadersFromInstructions"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369580263, "dur": 342246, "name": "BuildPipeline::PreCompileShaders"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369580283, "dur": 342214, "name": "Compiling Shader", "args": {"Shader": "Sprites/Default"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369602565, "dur": 8652, "name": "OnPreprocessShaderVariants", "args": {"shader": "Sprites/Default"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369611626, "dur": 199, "name": "OnPreprocessShaderVariants", "args": {"shader": "Sprites/Default"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369612128, "dur": 194, "name": "OnPreprocessShaderVariants", "args": {"shader": "Sprites/Default"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369612567, "dur": 180, "name": "OnPreprocessShaderVariants", "args": {"shader": "Sprites/Default"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369922544, "dur": 25, "name": "OnBuildStepEnd", "args": {"step": "Precompile Shaders", "depth": "2"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369922602, "dur": 14, "name": "OnBuildStepBegin", "args": {"step": "Write serialized files", "depth": "2"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367369922910, "dur": 101013, "name": "Write serialized files"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370023928, "dur": 16, "name": "OnBuildStepEnd", "args": {"step": "Write serialized files", "depth": "2"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370023972, "dur": 3, "name": "OnBuildStepEnd", "args": {"step": "Execute instructions", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370023984, "dur": 1131, "name": "BuildPipeline::BuildInstructionHelper::PopulateContentFileBuildResults"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370025121, "dur": 13, "name": "OnBuildStepBegin", "args": {"step": "Generate build manifest", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370025432, "dur": 161, "name": "BuildPipeline::CreateManifestJsonText"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370025594, "dur": 99, "name": "WriteBuildManifestToUDS"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370025724, "dur": 6, "name": "OnBuildStepEnd", "args": {"step": "Generate build manifest", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370025744, "dur": 16437, "name": "BuildPipeline::ExportPlayerArtifactsToDirectory"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370042255, "dur": 31, "name": "OnBuildStepBegin", "args": {"step": "Clean output directory", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370042675, "dur": 844, "name": "BuildPipeline::CleanupUnreferencedFilesFromBuildOutput"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370043525, "dur": 22, "name": "OnBuildStepEnd", "args": {"step": "Clean output directory", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370044119, "dur": 2573, "name": "PopulateBuildReport"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370044126, "dur": 23, "name": "OnBuildStepBegin", "args": {"step": "Populate build report", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370044532, "dur": 2027, "name": "BuildPipeline::CalculateAndRecordContentSummary"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370046649, "dur": 16, "name": "OnBuildStepEnd", "args": {"step": "Populate build report", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370046698, "dur": 11, "name": "OnBuildStepBegin", "args": {"step": "Record type usage", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370046975, "dur": 5926, "name": "BuildPipeline::WriteTypeUsage"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370047732, "dur": 1663, "name": "BuildPipeline::PopulateRuntimeClassRegistry"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370052908, "dur": 20, "name": "OnBuildStepEnd", "args": {"step": "Record type usage", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370052970, "dur": 8, "name": "OnBuildStepBegin", "args": {"step": "Generate build layout", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370053283, "dur": 348, "name": "BuildPipeline::CreateLayoutJsonText"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370054085, "dur": 13, "name": "OnBuildStepEnd", "args": {"step": "Generate build layout", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370054366, "dur": 13, "name": "OnBuildStepBegin", "args": {"step": "Finalize ContentBuildRoot", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370054757, "dur": 6, "name": "OnBuildStepEnd", "args": {"step": "Finalize ContentBuildRoot", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370054779, "dur": 0, "name": "ResetActiveTargetPlatform"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370054781, "dur": 8607, "name": "BuildPipeline::CleanupAfterBuild"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370054782, "dur": 6, "name": "OnBuildStepBegin", "args": {"step": "Clean up after build", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370063334, "dur": 15, "name": "OnBuildStepEnd", "args": {"step": "Clean up after build", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370063390, "dur": 10762, "name": "Unloading unused assets immediately"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370063393, "dur": 8, "name": "OnBuildStepBegin", "args": {"step": "Unloading unused assets", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370074105, "dur": 22, "name": "OnBuildStepEnd", "args": {"step": "Unloading unused assets", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370076813, "dur": 1, "name": "ResetActiveTargetPlatform"},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370076822, "dur": 16, "name": "OnBuildStepBegin", "args": {"step": "Call Post-Process callbacks", "depth": "1"}},
-{"pid": 69268, "tid": 67564, "ph": "X", "ts": 367370077629, "dur": 8, "name": "OnBuildStepEnd", "args": {"step": "Call Post-Process callbacks", "depth": "1"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368569908, "dur": 36341, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/ScriptableObjects/ContentDirectoryRoot.asset"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368569990, "dur": 15153, "name": "LoadingTypeDB"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368585199, "dur": 20723, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368620401, "dur": 2803, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/ScriptableObjects/SceneList.asset"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368620488, "dur": 2522, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368630899, "dur": 2304, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/ScriptableObjects/LoadableAudioClipReference.asset"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368631008, "dur": 2052, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368635069, "dur": 2965, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/ScriptableObjects/SerializationDemo.asset"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368635173, "dur": 2695, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368639960, "dur": 4252, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/Audio/a.mp3"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368640015, "dur": 4049, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368645709, "dur": 254483, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/Scenes/Scene2.unity"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368652566, "dur": 4569, "name": "InitializeBuildCallbacks:SceneProcessors"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368657205, "dur": 55295, "name": "BuildPipeline::CalculateBuildMetaDataForScene"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368699911, "dur": 1934, "name": "UGUIBuildAnalytics.OnProcessScene:WithReport:Scene2"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368701870, "dur": 3614, "name": "TimelineAnalyticsProcess.OnProcessScene:WithReport:Scene2"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368705508, "dur": 671, "name": "MuiltiplayerRolesScenePostprocessor.OnProcessScene:WithReport:Scene2"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368706196, "dur": 2011, "name": "UnityBuildPostprocessor.OnProcessScene:WithReport:Scene2"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368914029, "dur": 486, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/Scripts/SceneList.cs"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368914139, "dur": 239, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368916085, "dur": 562, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/Scripts/SerializationDemo.cs"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368916160, "dur": 245, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368918359, "dur": 2405, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/Textures/GreenStatic.png"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368918510, "dur": 1919, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368922591, "dur": 49503, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/0000000000000000e000000000000000.metadataimportproxy"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368922667, "dur": 49117, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368974934, "dur": 394, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/Scripts/DirectScriptableObjectReference.cs"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368975008, "dur": 198, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368976516, "dur": 382697, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/0000000000000000f000000000000000.metadataimportproxy"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367368976577, "dur": 382386, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369360749, "dur": 1514, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/ScriptableObjects/SingleAudioClipLoadableReference.asset"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369360818, "dur": 1316, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369363654, "dur": 13755, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/Scenes/Scene1.unity"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369363742, "dur": 92, "name": "InitializeBuildCallbacks:SceneProcessors"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369363874, "dur": 9393, "name": "BuildPipeline::CalculateBuildMetaDataForScene"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369371340, "dur": 12, "name": "UGUIBuildAnalytics.OnProcessScene:WithReport:Scene1"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369371360, "dur": 25, "name": "TimelineAnalyticsProcess.OnProcessScene:WithReport:Scene1"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369371390, "dur": 8, "name": "MuiltiplayerRolesScenePostprocessor.OnProcessScene:WithReport:Scene1"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369371402, "dur": 43, "name": "UnityBuildPostprocessor.OnProcessScene:WithReport:Scene1"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369478448, "dur": 870, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/Audio/6.mp3"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369478550, "dur": 670, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369480842, "dur": 329, "name": "BuildMetaDataImporter(worker)", "args": {"assetPath": "Assets/Scripts/LoadableAudioClipReference.cs"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369480906, "dur": 164, "name": "BuildPipeline::CalculateBuildMetaDataForAsset"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369925282, "dur": 6406, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/21679be819d6e9146a63bb02a7e51f2f.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369925403, "dur": 6240, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/21679be819d6e9146a63bb02a7e51f2f.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369925632, "dur": 722, "name": "BuildPipeline::PopulateWriteData"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369933892, "dur": 1565, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/4038ff673d390134d924b57fcbed0432.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369934003, "dur": 1419, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/4038ff673d390134d924b57fcbed0432.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369934044, "dur": 375, "name": "BuildPipeline::PopulateWriteData"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369937330, "dur": 10975, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/590cfeb4e0ff90f4e92f9e1262bcfe6f.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369937439, "dur": 8824, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/590cfeb4e0ff90f4e92f9e1262bcfe6f.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369944428, "dur": 12, "name": "BuildPipeline::PopulateWriteData"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369957909, "dur": 2040, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/34814583296b527bf650f548effcd62c.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369958063, "dur": 1842, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/34814583296b527bf650f548effcd62c.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369958084, "dur": 483, "name": "BuildPipeline::PopulateWriteData"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369961954, "dur": 3038, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/52b43dad178849b42ac753005736e7bb.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369962061, "dur": 2894, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/52b43dad178849b42ac753005736e7bb.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369962142, "dur": 1390, "name": "BuildPipeline::PopulateWriteData"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369966962, "dur": 1838, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/baf46637edbb6ff884858f6630b66eda.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369967066, "dur": 1699, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/baf46637edbb6ff884858f6630b66eda.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369967083, "dur": 481, "name": "BuildPipeline::PopulateWriteData"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369970678, "dur": 6548, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/0000000000000000f000000000000000.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369970800, "dur": 6348, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/0000000000000000f000000000000000.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369970814, "dur": 7, "name": "BuildPipeline::PopulateWriteData"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369979323, "dur": 1741, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/20408785117e138f38c2cc4ca0e638c8.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369979434, "dur": 1596, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/20408785117e138f38c2cc4ca0e638c8.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369979474, "dur": 476, "name": "BuildPipeline::PopulateWriteData"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369982756, "dur": 1623, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/a2a42d71dddef12e8889849faf59bdd7.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369982863, "dur": 1477, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/a2a42d71dddef12e8889849faf59bdd7.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369982881, "dur": 9, "name": "BuildPipeline::PopulateWriteData"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369986184, "dur": 1620, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/70a210050f71a924aa83be7146547111.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369986298, "dur": 1472, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/70a210050f71a924aa83be7146547111.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369986324, "dur": 387, "name": "BuildPipeline::PopulateWriteData"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369989676, "dur": 9586, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/162c015549f8733449ac70ae78ad3aa5.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369989789, "dur": 7521, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/162c015549f8733449ac70ae78ad3aa5.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367369996158, "dur": 12, "name": "BuildPipeline::PopulateWriteData"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367370009818, "dur": 1787, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/dd30e3af3b8bddf1ea36d768fc41aafd.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367370009956, "dur": 1611, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/dd30e3af3b8bddf1ea36d768fc41aafd.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367370010006, "dur": 427, "name": "BuildPipeline::PopulateWriteData"},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367370013464, "dur": 2861, "name": "BuildInstructionImporter(worker)", "args": {"assetPath": "Library/BuildInstructions/78532141fd7679a458405eb16bdb75fd.buildinst"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367370013572, "dur": 2713, "name": "BuildPipeline::WriteSerializedFile", "args": {"filePath": "mem:/Temp/78532141fd7679a458405eb16bdb75fd.cfid"}},
-{"pid": 60488, "tid": 12660, "ph": "X", "ts": 367370013598, "dur": 429, "name": "BuildPipeline::PopulateWriteData"}
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268473364, "dur": 21661, "name": "ApplyBuildHistoryRetention"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268495033, "dur": 11415267, "name": "UnifiedBuild"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268495094, "dur": 2516, "name": "Validate Root Assets"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268502215, "dur": 26011, "name": "InitializeBuildCallbacks:BuildProcessors, SceneProcessors, FilterAssembliesProcessors, ShaderProcessors, ComputeShaderProcessors"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268528249, "dur": 36, "name": "OnBuildStepBegin", "args": {"step": "Refresh Asset Database", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268528603, "dur": 31373, "name": "BuildPipeline::RegisterCustomDependencies"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268560194, "dur": 232, "name": "OnBuildStepEnd", "args": {"step": "Refresh Asset Database", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268560477, "dur": 9, "name": "OnBuildStepBegin", "args": {"step": "Call Pre-Process callbacks", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268561391, "dur": 23102, "name": "ContentSelectionBuildPreprocessor.OnPreprocessBuild:WithContext"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268584514, "dur": 19, "name": "OnBuildStepEnd", "args": {"step": "Call Pre-Process callbacks", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268584615, "dur": 43479, "name": "ConfigureTargetPlatform"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268584620, "dur": 11, "name": "OnBuildStepBegin", "args": {"step": "Configure target platform", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268587752, "dur": 40282, "name": "CompressAssets"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268628041, "dur": 16, "name": "OnBuildStepEnd", "args": {"step": "Configure target platform", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268628098, "dur": 3777327, "name": "ReCompileScripts"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268628106, "dur": 13, "name": "OnBuildStepBegin", "args": {"step": "Compile scripts", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821268628468, "dur": 374265, "name": "CompilePlayerScripts:EditorPreRecompile"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821269003631, "dur": 2893000, "name": "CompilePlayerScripts:CompilePlayerAssemblies"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821271896905, "dur": 479335, "name": "CompilePlayerScripts:ExtractEditorTypeDB"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821272376532, "dur": 16646, "name": "GenerateAssemblyTypeInfos:Editor"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821272393193, "dur": 7640, "name": "GenerateAssemblyTypeInfos:Player"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821272400855, "dur": 2, "name": "PostProcessAssemblyTypeInfos"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821272405357, "dur": 25, "name": "OnBuildStepEnd", "args": {"step": "Compile scripts", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821272468167, "dur": 2414, "name": "LoadingTypeDB"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821272470592, "dur": 93, "name": "DeleteCachedBuildArtifacts"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821272483052, "dur": 21, "name": "OnBuildStepBegin", "args": {"step": "Single-pass import", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821272483340, "dur": 6951949, "name": "SinglePassImport", "args": {"assetsProcessed": "10", "cached": "0", "uncached": "10"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279435295, "dur": 12, "name": "OnBuildStepEnd", "args": {"step": "Single-pass import", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279435330, "dur": 109, "name": "PruneUnreachableContentFiles"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279435441, "dur": 19, "name": "Aggregate shader usage"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279435464, "dur": 14, "name": "OnBuildStepBegin", "args": {"step": "Deferred shader pass", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279435712, "dur": 1, "name": "BuildPipeline::RunSinglePassDeferredShaderPass"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279435715, "dur": 11, "name": "OnBuildStepEnd", "args": {"step": "Deferred shader pass", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279435741, "dur": 3838, "name": "BuildMonoScriptContentFile"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279439585, "dur": 14, "name": "OnBuildStepBegin", "args": {"step": "Build builtin-extra content file", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279439827, "dur": 394616, "name": "BuildBuiltinExtraContentFile"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279465505, "dur": 8747, "name": "OnPreprocessShaderVariants", "args": {"shader": "Sprites/Default"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279474696, "dur": 216, "name": "OnPreprocessShaderVariants", "args": {"shader": "Sprites/Default"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279475264, "dur": 190, "name": "OnPreprocessShaderVariants", "args": {"shader": "Sprites/Default"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279475789, "dur": 215, "name": "OnPreprocessShaderVariants", "args": {"shader": "Sprites/Default"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279834450, "dur": 17, "name": "OnBuildStepEnd", "args": {"step": "Build builtin-extra content file", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279834492, "dur": 99, "name": "PruneUnreachableContentFiles"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279834602, "dur": 16, "name": "ValidateLoadables"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279834620, "dur": 19, "name": "BuildPipeline::BuildInstructionHelper::PopulateContentFileBuildResults"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279834630, "dur": 9, "name": "Sort Content File Build Results"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279834711, "dur": 16, "name": "OnBuildStepBegin", "args": {"step": "Generate build manifest", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279834985, "dur": 179, "name": "BuildPipeline::CreateManifestJsonText"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279835164, "dur": 123, "name": "WriteBuildManifestToUDS"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279835331, "dur": 8, "name": "OnBuildStepEnd", "args": {"step": "Generate build manifest", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279835371, "dur": 7523, "name": "BuildPipeline::ExportPlayerArtifactsToDirectory", "args": {"artifactCount": "18", "force": "true", "filesCopied": "18", "bytesCopied": "920270", "filesCached": "0", "bytesCached": "0"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279835379, "dur": 40, "name": "ExportPlayerArtifacts:BuildExportList"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279835428, "dur": 7453, "name": "BuildPipeline::ExportArtifactFilesToDirectory", "args": {"fileCount": "18", "force": "true", "filesCopied": "18", "bytesCopied": "920270", "filesCached": "0", "bytesCached": "0"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279842981, "dur": 31, "name": "OnBuildStepBegin", "args": {"step": "Clean output directory", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279843301, "dur": 2435, "name": "BuildPipeline::CleanupUnreferencedFilesFromBuildOutput"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279845741, "dur": 13, "name": "OnBuildStepEnd", "args": {"step": "Clean output directory", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279846028, "dur": 1648, "name": "PopulateBuildReport"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279846033, "dur": 16, "name": "OnBuildStepBegin", "args": {"step": "Populate build report", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279846284, "dur": 1239, "name": "BuildPipeline::CalculateAndRecordContentSummary"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279847644, "dur": 11, "name": "OnBuildStepEnd", "args": {"step": "Populate build report", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279847678, "dur": 8, "name": "OnBuildStepBegin", "args": {"step": "Record type usage", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279847876, "dur": 9848, "name": "BuildPipeline::WriteTypeUsage"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279848752, "dur": 6259, "name": "BuildPipeline::PopulateRuntimeClassRegistry"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279857729, "dur": 12, "name": "OnBuildStepEnd", "args": {"step": "Record type usage", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279857769, "dur": 9, "name": "OnBuildStepBegin", "args": {"step": "Generate build layout", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279857954, "dur": 374, "name": "BuildPipeline::CreateLayoutJsonText"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279858618, "dur": 301, "name": "Validate Circular References"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279858645, "dur": 46, "name": "Detect Circular References"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279858696, "dur": 219, "name": "Write Circular References Report"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279858925, "dur": 11, "name": "OnBuildStepEnd", "args": {"step": "Generate build layout", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279859060, "dur": 9, "name": "OnBuildStepBegin", "args": {"step": "Finalize ContentBuildRoot", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279859587, "dur": 11, "name": "OnBuildStepEnd", "args": {"step": "Finalize ContentBuildRoot", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279859624, "dur": 0, "name": "ResetActiveTargetPlatform"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279859626, "dur": 966, "name": "BuildPipeline::CleanupAfterBuild"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279859628, "dur": 8, "name": "OnBuildStepBegin", "args": {"step": "Clean up after build", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279860567, "dur": 6, "name": "OnBuildStepEnd", "args": {"step": "Clean up after build", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279860594, "dur": 48802, "name": "Unloading unused assets immediately"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279860596, "dur": 5, "name": "OnBuildStepBegin", "args": {"step": "Unloading unused assets", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279909316, "dur": 33, "name": "OnBuildStepEnd", "args": {"step": "Unloading unused assets", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279910294, "dur": 2, "name": "ResetActiveTargetPlatform"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279910327, "dur": 20, "name": "OnBuildStepBegin", "args": {"step": "Call Post-Process callbacks", "depth": "1"}},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279911860, "dur": 455, "name": "TextCorePostBuildProcessor.OnPostprocessBuild:WithContext"},
+{"pid": 26788, "tid": 27868, "ph": "X", "ts": 1821279912332, "dur": 19, "name": "OnBuildStepEnd", "args": {"step": "Call Post-Process callbacks", "depth": "1"}},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279000269, "dur": 69492, "name": "SinglePassBuildImporter(worker)", "args": {"assetPath": "Assets/ScriptableObjects/ContentDirectoryRoot.asset"}},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279000692, "dur": 4276, "name": "LoadingTypeDB"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279023609, "dur": 19009, "name": "BuildPipeline::GatherSinglePassAssetObjects"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279042648, "dur": 26998, "name": "WriteAssetContentFiles"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279086091, "dur": 6121, "name": "SinglePassBuildImporter(worker)", "args": {"assetPath": "Assets/ScriptableObjects/SerializationDemo.asset"}},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279086366, "dur": 1238, "name": "BuildPipeline::GatherSinglePassAssetObjects"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279087611, "dur": 4509, "name": "WriteAssetContentFiles"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279094839, "dur": 6327, "name": "SinglePassBuildImporter(worker)", "args": {"assetPath": "Assets/ScriptableObjects/SingleAudioClipLoadableReference.asset"}},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279095178, "dur": 1396, "name": "BuildPipeline::GatherSinglePassAssetObjects"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279096580, "dur": 4547, "name": "WriteAssetContentFiles"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279103055, "dur": 3930, "name": "SinglePassBuildImporter(worker)", "args": {"assetPath": "Assets/ScriptableObjects/SceneList.asset"}},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279103372, "dur": 1643, "name": "BuildPipeline::GatherSinglePassAssetObjects"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279105022, "dur": 1920, "name": "WriteAssetContentFiles"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279108771, "dur": 3382, "name": "SinglePassBuildImporter(worker)", "args": {"assetPath": "Assets/Audio/a.mp3"}},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279108991, "dur": 1814, "name": "BuildPipeline::GatherSinglePassAssetObjects"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279110810, "dur": 1297, "name": "WriteAssetContentFiles"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279113657, "dur": 3473, "name": "SinglePassBuildImporter(worker)", "args": {"assetPath": "Assets/ScriptableObjects/LoadableAudioClipReference.asset"}},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279113896, "dur": 968, "name": "BuildPipeline::GatherSinglePassAssetObjects"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279114879, "dur": 2217, "name": "WriteAssetContentFiles"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279118946, "dur": 245046, "name": "SinglePassBuildImporter(worker)", "args": {"assetPath": "Assets/Scenes/Scene1.unity"}},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279123081, "dur": 1533, "name": "InitializeBuildCallbacks:SceneProcessors"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279375795, "dur": 24672, "name": "SinglePassBuildImporter(worker)", "args": {"assetPath": "Assets/Scenes/Scene2.unity"}},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279376073, "dur": 92, "name": "InitializeBuildCallbacks:SceneProcessors"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279411224, "dur": 10706, "name": "SinglePassBuildImporter(worker)", "args": {"assetPath": "Assets/Textures/GreenStatic.png"}},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279411503, "dur": 7757, "name": "BuildPipeline::GatherSinglePassAssetObjects"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279419266, "dur": 2625, "name": "WriteAssetContentFiles"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279423522, "dur": 1689, "name": "SinglePassBuildImporter(worker)", "args": {"assetPath": "Assets/Audio/6.mp3"}},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279423760, "dur": 624, "name": "BuildPipeline::GatherSinglePassAssetObjects"},
+{"pid": 28108, "tid": 31520, "ph": "X", "ts": 1821279424388, "dur": 786, "name": "WriteAssetContentFiles"}
]
}
\ No newline at end of file
diff --git a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/BuildLog.jsonl b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/BuildLog.jsonl
index 018a102..f4fb14e 100644
--- a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/BuildLog.jsonl
+++ b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/BuildLog.jsonl
@@ -1,46 +1,35 @@
-{"timestamp":"2026-07-14T18:37:14.230563100Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"BuildStart\",\"rootAssetCount\":1,\"startTime\":\"2026-07-14T18:37:14.2095142Z\",\"targetPlatform\":19,\"subtarget\":2,\"metadataPath\":\"Library/BuildHistory/20260714-183714Z-f64157fb\",\"outputPath\":\"C:/UnitySrc/UnityDataTools/UnityProjects/LeadingEdge/../../TestCommon/Data/LeadingEdgeBuilds/ContentDirectory\",\"rootAssetPaths\":[\"Assets/ScriptableObjects/ContentDirectoryRoot.asset\"]}"}
-{"timestamp":"2026-07-14T18:37:14.307101700Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Refresh Asset Database\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:14.379914600Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Refresh Asset Database\",\"depth\":1,\"durationTicks\":723116}"}
-{"timestamp":"2026-07-14T18:37:14.379954000Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Call Pre-Process callbacks\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:14.380867700Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Call Pre-Process callbacks\",\"depth\":1,\"durationTicks\":9190}"}
-{"timestamp":"2026-07-14T18:37:14.384219100Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Configure target platform\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:14.442890200Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Configure target platform\",\"depth\":1,\"durationTicks\":586438}"}
-{"timestamp":"2026-07-14T18:37:14.442923300Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Compile scripts\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:16.007045100Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Compile scripts\",\"depth\":1,\"durationTicks\":15640772}"}
-{"timestamp":"2026-07-14T18:37:16.312658400Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Gather and collect build metadata\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:20.600853000Z","thread_id":"67564","log_level":"Warning","message":"[Worker0] Fallback fonts are not supported in content directory builds. Skipping fallback fonts for font LegacyRuntime."}
-{"timestamp":"2026-07-14T18:37:21.118264900Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Gather and collect build metadata\",\"depth\":1,\"durationTicks\":48056324}"}
-{"timestamp":"2026-07-14T18:37:21.118294500Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Calculate build layout\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.118751100Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Calculate build layout\",\"depth\":1,\"durationTicks\":4561}"}
-{"timestamp":"2026-07-14T18:37:21.118771700Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Aggregate build usage for objects\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.119066000Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Aggregate build usage for objects\",\"depth\":1,\"durationTicks\":2929}"}
-{"timestamp":"2026-07-14T18:37:21.119082700Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Generate build instructions\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.119485600Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Serialize build instructions\",\"depth\":2}"}
-{"timestamp":"2026-07-14T18:37:21.212177400Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Serialize build instructions\",\"depth\":2,\"durationTicks\":926677}"}
-{"timestamp":"2026-07-14T18:37:21.212194500Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Generate build instructions\",\"depth\":1,\"durationTicks\":931121}"}
-{"timestamp":"2026-07-14T18:37:21.212228900Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Execute instructions\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.212616700Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Precompile Shaders\",\"depth\":2}"}
-{"timestamp":"2026-07-14T18:37:21.555624500Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Precompile Shaders\",\"depth\":2,\"durationTicks\":3429711}"}
-{"timestamp":"2026-07-14T18:37:21.555652700Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Write serialized files\",\"depth\":2}"}
-{"timestamp":"2026-07-14T18:37:21.656995800Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Write serialized files\",\"depth\":2,\"durationTicks\":1013235}"}
-{"timestamp":"2026-07-14T18:37:21.657011800Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Execute instructions\",\"depth\":1,\"durationTicks\":4447880}"}
-{"timestamp":"2026-07-14T18:37:21.658176900Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Generate build manifest\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.658769600Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Generate build manifest\",\"depth\":1,\"durationTicks\":6036}"}
-{"timestamp":"2026-07-14T18:37:21.675268600Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"BuildStatistic\",\"context\":\"BuildOutput\",\"statistics\":[{\"name\":\"sizeReusedContentInOutputDirectory\",\"value\":0,\"unit\":\"bytes\"},{\"name\":\"sizeWrittenToOutputDirectory\",\"value\":980052,\"unit\":\"bytes\"},{\"name\":\"filesReusedInOutputDirectory\",\"value\":0,\"unit\":\"count\"},{\"name\":\"filesWrittenToOutputDirectory\",\"value\":18,\"unit\":\"count\"}]}"}
-{"timestamp":"2026-07-14T18:37:21.675327700Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Clean output directory\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.676605500Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Clean output directory\",\"depth\":1,\"durationTicks\":12714}"}
-{"timestamp":"2026-07-14T18:37:21.677202600Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Populate build report\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.679717700Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Populate build report\",\"depth\":1,\"durationTicks\":25225}"}
-{"timestamp":"2026-07-14T18:37:21.679745900Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Record type usage\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.685985100Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Record type usage\",\"depth\":1,\"durationTicks\":62055}"}
-{"timestamp":"2026-07-14T18:37:21.686015000Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Generate build layout\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.687145400Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Generate build layout\",\"depth\":1,\"durationTicks\":11120}"}
-{"timestamp":"2026-07-14T18:37:21.687177200Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"BuildStatistic\",\"context\":\"BuildComplete\",\"statistics\":[{\"name\":\"loadableCount\",\"value\":3,\"unit\":\"count\"},{\"name\":\"objectsProcessed\",\"value\":810,\"unit\":\"count\"},{\"name\":\"objectsBuilt\",\"value\":28,\"unit\":\"count\"},{\"name\":\"scenesBuilt\",\"value\":2,\"unit\":\"count\"},{\"name\":\"serializedFilesBuilt\",\"value\":13,\"unit\":\"count\"},{\"name\":\"archivesBuilt\",\"value\":0,\"unit\":\"count\"},{\"name\":\"serializedFilesReused\",\"value\":0,\"unit\":\"count\"},{\"name\":\"serializedFilesRebuilt\",\"value\":13,\"unit\":\"count\"},{\"name\":\"reusedSerializedFileSize\",\"value\":0,\"unit\":\"bytes\"},{\"name\":\"rebuiltSerializedFileSize\",\"value\":158832,\"unit\":\"bytes\"}]}"}
-{"timestamp":"2026-07-14T18:37:21.687419000Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Finalize ContentBuildRoot\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.687802300Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Finalize ContentBuildRoot\",\"depth\":1,\"durationTicks\":3912}"}
-{"timestamp":"2026-07-14T18:37:21.687823800Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Clean up after build\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.696410500Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Clean up after build\",\"depth\":1,\"durationTicks\":85462}"}
-{"timestamp":"2026-07-14T18:37:21.696438900Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Unloading unused assets\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.707177300Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Unloading unused assets\",\"depth\":1,\"durationTicks\":107079}"}
-{"timestamp":"2026-07-14T18:37:21.709880500Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Call Post-Process callbacks\",\"depth\":1}"}
-{"timestamp":"2026-07-14T18:37:21.710682200Z","thread_id":"67564","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Call Post-Process callbacks\",\"depth\":1,\"durationTicks\":8055}"}
+{"timestamp":"2026-09-21T16:53:11.246415000Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"BuildStart\",\"rootAssetCount\":1,\"startTime\":\"2026-09-21T16:53:11.2245541Z\",\"targetPlatform\":19,\"subtarget\":2,\"metadataPath\":\"Library/BuildHistory/20260921-165311Z-f096bba0\",\"outputPath\":\"C:/UnitySrc/UnityDataTools/UnityProjects/LeadingEdge/../../TestCommon/Data/LeadingEdgeBuilds/ContentDirectory\",\"rootAssetPaths\":[\"Assets/ScriptableObjects/ContentDirectoryRoot.asset\"]}"}
+{"timestamp":"2026-09-21T16:53:11.302562000Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Refresh Asset Database\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:11.334682400Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Refresh Asset Database\",\"depth\":1,\"durationTicks\":317327}"}
+{"timestamp":"2026-09-21T16:53:11.334714100Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Call Pre-Process callbacks\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:11.358805500Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Call Pre-Process callbacks\",\"depth\":1,\"durationTicks\":240262}"}
+{"timestamp":"2026-09-21T16:53:11.358857600Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Configure target platform\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:11.402288900Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Configure target platform\",\"depth\":1,\"durationTicks\":434120}"}
+{"timestamp":"2026-09-21T16:53:11.402340300Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Compile scripts\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:15.179216900Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Compile scripts\",\"depth\":1,\"durationTicks\":37768315}"}
+{"timestamp":"2026-09-21T16:53:15.256887100Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Single-pass import\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:22.208354200Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Single-pass import\",\"depth\":1,\"durationTicks\":69514845}"}
+{"timestamp":"2026-09-21T16:53:22.208519300Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Deferred shader pass\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:22.208766800Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Deferred shader pass\",\"depth\":1,\"durationTicks\":2513}"}
+{"timestamp":"2026-09-21T16:53:22.212645600Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Build builtin-extra content file\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:22.607474200Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Build builtin-extra content file\",\"depth\":1,\"durationTicks\":3948183}"}
+{"timestamp":"2026-09-21T16:53:22.607726700Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Generate build manifest\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:22.608338200Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Generate build manifest\",\"depth\":1,\"durationTicks\":6193}"}
+{"timestamp":"2026-09-21T16:53:22.615936400Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"BuildStatistic\",\"context\":\"BuildOutput\",\"statistics\":[{\"name\":\"sizeReusedContentInOutputDirectory\",\"value\":0,\"unit\":\"bytes\"},{\"name\":\"sizeWrittenToOutputDirectory\",\"value\":920270,\"unit\":\"bytes\"},{\"name\":\"filesReusedInOutputDirectory\",\"value\":0,\"unit\":\"count\"},{\"name\":\"filesWrittenToOutputDirectory\",\"value\":18,\"unit\":\"count\"}]}"}
+{"timestamp":"2026-09-21T16:53:22.616013100Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Clean output directory\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:22.618757800Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Clean output directory\",\"depth\":1,\"durationTicks\":27605}"}
+{"timestamp":"2026-09-21T16:53:22.619051600Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Populate build report\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:22.620657900Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Populate build report\",\"depth\":1,\"durationTicks\":16094}"}
+{"timestamp":"2026-09-21T16:53:22.620680100Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Record type usage\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:22.630744400Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Record type usage\",\"depth\":1,\"durationTicks\":100472}"}
+{"timestamp":"2026-09-21T16:53:22.630771700Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Generate build layout\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:22.631933600Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Generate build layout\",\"depth\":1,\"durationTicks\":11536}"}
+{"timestamp":"2026-09-21T16:53:22.631960700Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"BuildStatistic\",\"context\":\"BuildComplete\",\"statistics\":[{\"name\":\"loadableCount\",\"value\":3,\"unit\":\"count\"},{\"name\":\"objectsBuilt\",\"value\":28,\"unit\":\"count\"},{\"name\":\"scenesBuilt\",\"value\":2,\"unit\":\"count\"},{\"name\":\"serializedFilesBuilt\",\"value\":13,\"unit\":\"count\"},{\"name\":\"archivesBuilt\",\"value\":0,\"unit\":\"count\"},{\"name\":\"serializedFilesReused\",\"value\":0,\"unit\":\"count\"},{\"name\":\"serializedFilesRebuilt\",\"value\":13,\"unit\":\"count\"},{\"name\":\"reusedSerializedFileSize\",\"value\":0,\"unit\":\"bytes\"},{\"name\":\"rebuiltSerializedFileSize\",\"value\":99584,\"unit\":\"bytes\"}]}"}
+{"timestamp":"2026-09-21T16:53:22.632062300Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Finalize ContentBuildRoot\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:22.632598300Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Finalize ContentBuildRoot\",\"depth\":1,\"durationTicks\":5245}"}
+{"timestamp":"2026-09-21T16:53:22.632628900Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Clean up after build\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:22.633573200Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Clean up after build\",\"depth\":1,\"durationTicks\":9366}"}
+{"timestamp":"2026-09-21T16:53:22.633593800Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Unloading unused assets\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:22.682366400Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Unloading unused assets\",\"depth\":1,\"durationTicks\":487076}"}
+{"timestamp":"2026-09-21T16:53:22.683343700Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepStart\",\"stepName\":\"Call Post-Process callbacks\",\"depth\":1}"}
+{"timestamp":"2026-09-21T16:53:22.685363800Z","thread_id":"27868","log_level":"Info","message":"{\"type\":\"StepEnd\",\"stepName\":\"Call Post-Process callbacks\",\"depth\":1,\"durationTicks\":20019}"}
diff --git a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/BuildReportSummary.json b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/BuildReportSummary.json
index e1bf60d..087d583 100644
--- a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/BuildReportSummary.json
+++ b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/BuildReportSummary.json
@@ -3,7 +3,7 @@
"BuildContentOptions": [
"CleanBuildCache"
],
- "BuildManifestHash": "baff06b928d147276f2245dd3b19216a",
+ "BuildManifestHash": "e320fc78984f8430afa90a591fd02004",
"BuildName": "ContentDirectory",
"BuildOptions": [
"None"
@@ -11,17 +11,19 @@
"BuildProfilePath": "",
"BuildResult": 1,
"BuildResultName": "Succeeded",
- "BuildSessionGUID": "f64157fb08bb9f645971d39c1203bd03",
- "BuildStartedAt": "2026-07-14T18:37:14.2095142Z",
+ "BuildSessionGUID": "f096bba0b9ce4c44194edd738d64b9df",
+ "BuildStartedAt": "2026-09-21T16:53:11.2245541Z",
"BuildType": 3,
"BuildTypeName": "ContentDirectory",
+ "ContentSourceBuildSessionGUID": "00000000000000000000000000000000",
"OutputPath": "C:/UnitySrc/UnityDataTools/UnityProjects/LeadingEdge/../../TestCommon/Data/LeadingEdgeBuilds/ContentDirectory",
"Platform": 19,
"PlatformName": "StandaloneWindows64",
+ "ProducerPackage": "",
"Subtarget": 2,
"SubtargetName": "Player",
"TotalErrors": 0,
- "TotalSizeBytes": 980084,
- "TotalTimeMs": 7515,
- "TotalWarnings": 1
+ "TotalSizeBytes": 920302,
+ "TotalTimeMs": 11469,
+ "TotalWarnings": 0
}
\ No newline at end of file
diff --git a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ContentBuildId.txt b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ContentBuildId.txt
new file mode 100644
index 0000000..bf3fddd
--- /dev/null
+++ b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ContentBuildId.txt
@@ -0,0 +1 @@
+ce87639b203da27885e650bfe54d2599
\ No newline at end of file
diff --git a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ContentLayout.json b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ContentLayout.json
index 3fad48c..70e4491 100644
--- a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ContentLayout.json
+++ b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ContentLayout.json
@@ -1 +1 @@
-{"Version":2,"BuildManifestHash":"baff06b928d147276f2245dd3b19216a","SerializedFiles":[{"Index":0,"ID":"Library/unity default resources","IsBuiltIn":true,"SourceAssets":[],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[]},{"Index":1,"ID":"0000000000000000f000000000000000.cfid","IsBuiltIn":false,"SourceAssets":["Resources/unity_builtin_extra"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[],"ContentHash":"6a60c190881f43bb4a91a360b9e8850d"},{"Index":2,"ID":"162c015549f8733449ac70ae78ad3aa5.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Scenes/Scene1.unity"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[0,1,9],"ContentHash":"c271b85494f5e4cc35c4ec4a776324af"},{"Index":3,"ID":"20408785117e138f38c2cc4ca0e638c8.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Audio/6.mp3"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[],"ContentHash":"93bb3159569284a5c7bcceb4acb91bae"},{"Index":4,"ID":"4038ff673d390134d924b57fcbed0432.cfid","IsBuiltIn":false,"SourceAssets":["Assets/ScriptableObjects/LoadableAudioClipReference.asset"],"LoadableDependencies":["20408785117e138f38c2cc4ca0e638c8","dd30e3af3b8bddf1ea36d768fc41aafd"],"LoadableSceneDependencies":[],"SerializedFileDependencies":[8],"ContentHash":"bfcf18a244e45f867750e0223a9b3a43"},{"Index":5,"ID":"52b43dad178849b42ac753005736e7bb.cfid","IsBuiltIn":false,"SourceAssets":["Assets/ScriptableObjects/ContentDirectoryRoot.asset"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[8,4,11,13,7],"ContentHash":"c0152db4dd710be51b2decb997325f34"},{"Index":6,"ID":"590cfeb4e0ff90f4e92f9e1262bcfe6f.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Scenes/Scene2.unity"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[0,1,9],"ContentHash":"4d49f1aa2acf2ecf20f51660e1bf77ea"},{"Index":7,"ID":"70a210050f71a924aa83be7146547111.cfid","IsBuiltIn":false,"SourceAssets":["Assets/ScriptableObjects/SceneList.asset"],"LoadableDependencies":[],"LoadableSceneDependencies":["Assets/Scenes/Scene2.unity","Assets/Scenes/Scene1.unity"],"SerializedFileDependencies":[8],"ContentHash":"8ad4924ac5e264fde63d8e95a0dab8ab"},{"Index":8,"ID":"a2a42d71dddef12e8889849faf59bdd7.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Scripts/DirectScriptableObjectReference.cs","Assets/Scripts/LoadableAudioClipReference.cs","Assets/Scripts/SceneList.cs","Assets/Scripts/SerializationDemo.cs"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[],"ContentHash":"86d71ff2bb38e064697257d35d6421b8"},{"Index":9,"ID":"baf46637edbb6ff884858f6630b66eda.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Textures/GreenStatic.png"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[12],"ContentHash":"61fa28f6b22b739b19ece22ea0b3f29f"},{"Index":10,"ID":"dd30e3af3b8bddf1ea36d768fc41aafd.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Audio/a.mp3"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[],"ContentHash":"730d2d641a53eeb1e633f2ff60d730e9"},{"Index":11,"ID":"21679be819d6e9146a63bb02a7e51f2f.cfid","IsBuiltIn":false,"SourceAssets":["Assets/ScriptableObjects/SingleAudioClipLoadableReference.asset"],"LoadableDependencies":["dd30e3af3b8bddf1ea36d768fc41aafd"],"LoadableSceneDependencies":[],"SerializedFileDependencies":[8],"ContentHash":"5c43454a3823f172a2a326410a36ba6b"},{"Index":12,"ID":"34814583296b527bf650f548effcd62c.cfid","IsBuiltIn":false,"SourceAssets":["Assets/Textures/GreenStatic.png"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[],"ContentHash":"c046f78347acd69f03fa7f80362fde94"},{"Index":13,"ID":"78532141fd7679a458405eb16bdb75fd.cfid","IsBuiltIn":false,"SourceAssets":["Assets/ScriptableObjects/SerializationDemo.asset"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[8],"ContentHash":"51690f09f541d7442ce8faf26fd84696"}],"RootAssets":["3dcb957e6cb206bfec2aaadd3d11d85c"],"LoadableObjectIds":[{"ObjectIdHash":"20408785117e138f38c2cc4ca0e638c8","GUID":"278c261333bf8604eb5c83790d02004d","AssetPath":"Assets/Audio/6.mp3","LFID":8300000,"IdentifierType":3,"SerializedFile":3,"OutputLFID":6313987888022012634},{"ObjectIdHash":"3dcb957e6cb206bfec2aaadd3d11d85c","GUID":"52b43dad178849b42ac753005736e7bb","AssetPath":"Assets/ScriptableObjects/ContentDirectoryRoot.asset","LFID":11400000,"IdentifierType":2,"SerializedFile":5,"OutputLFID":-775554941117088049},{"ObjectIdHash":"dd30e3af3b8bddf1ea36d768fc41aafd","GUID":"b65a7916245593b4e89f4bd0aa920533","AssetPath":"Assets/Audio/a.mp3","LFID":8300000,"IdentifierType":3,"SerializedFile":10,"OutputLFID":-6933612096100796476}],"LoadableSceneIds":[{"GUID":"590cfeb4e0ff90f4e92f9e1262bcfe6f","Path":"Assets/Scenes/Scene2.unity","SerializedFile":6},{"GUID":"162c015549f8733449ac70ae78ad3aa5","Path":"Assets/Scenes/Scene1.unity","SerializedFile":2}],"BinaryArtifacts":[{"Index":0,"ContentHash":"6a60c190881f43bb4a91a360b9e8850d","Category":"contentfile","Size":96364,"ArtifactReferences":[1]},{"Index":1,"ContentHash":"ea7c9fa2d58ef71544726747da8ade6d","Category":"texture","Size":524280},{"Index":2,"ContentHash":"c271b85494f5e4cc35c4ec4a776324af","Category":"contentfile","Size":17572},{"Index":3,"ContentHash":"93bb3159569284a5c7bcceb4acb91bae","Category":"contentfile","Size":1288,"ArtifactReferences":[4]},{"Index":4,"ContentHash":"68c9d0b12420e1951eec7790bd0754fe","Category":"audio","Size":30688},{"Index":5,"ContentHash":"bfcf18a244e45f867750e0223a9b3a43","Category":"contentfile","Size":2184},{"Index":6,"ContentHash":"c0152db4dd710be51b2decb997325f34","Category":"contentfile","Size":1524},{"Index":7,"ContentHash":"4d49f1aa2acf2ecf20f51660e1bf77ea","Category":"contentfile","Size":17572},{"Index":8,"ContentHash":"8ad4924ac5e264fde63d8e95a0dab8ab","Category":"contentfile","Size":1304},{"Index":9,"ContentHash":"86d71ff2bb38e064697257d35d6421b8","Category":"contentfile","Size":2012},{"Index":10,"ContentHash":"61fa28f6b22b739b19ece22ea0b3f29f","Category":"contentfile","Size":8516},{"Index":11,"ContentHash":"730d2d641a53eeb1e633f2ff60d730e9","Category":"contentfile","Size":1288,"ArtifactReferences":[12]},{"Index":12,"ContentHash":"4226b5c16a50dab6eff0f08dd1253d4b","Category":"audio","Size":47424},{"Index":13,"ContentHash":"5c43454a3823f172a2a326410a36ba6b","Category":"contentfile","Size":2136},{"Index":14,"ContentHash":"c046f78347acd69f03fa7f80362fde94","Category":"contentfile","Size":2260,"ArtifactReferences":[15]},{"Index":15,"ContentHash":"f0a44ad4a4babd121543fd44032928e7","Category":"texture","Size":215970},{"Index":16,"ContentHash":"51690f09f541d7442ce8faf26fd84696","Category":"contentfile","Size":4812},{"Index":17,"ContentHash":"baff06b928d147276f2245dd3b19216a","Category":"manifest","Size":2858,"ArtifactReferences":[0,2,3,5,6,7,8,9,10,11,13,14,16]}]}
\ No newline at end of file
+{"Version":3,"BuildManifestHash":"e320fc78984f8430afa90a591fd02004","SerializedFiles":[{"Index":0,"StableId":"Library/unity default resources","IsBuiltIn":true,"ArtifactIndex":-1,"SourceAssets":[],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[]},{"Index":1,"StableId":"80b335c0244a430ff7eadfc72ee8fc6a","ArtifactIndex":1,"SourceAssets":["Resources/unity_builtin_extra"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[]},{"Index":2,"StableId":"21679be819d6e9146a63bb02a7e51f2f","ArtifactIndex":2,"SourceAssets":["Assets/ScriptableObjects/SingleAudioClipLoadableReference.asset"],"LoadableDependencies":[1],"LoadableSceneDependencies":[],"SerializedFileDependencies":[4]},{"Index":3,"StableId":"70a210050f71a924aa83be7146547111","ArtifactIndex":3,"SourceAssets":["Assets/ScriptableObjects/SceneList.asset"],"LoadableDependencies":[],"LoadableSceneDependencies":["Assets/Scenes/Scene2.unity","Assets/Scenes/Scene1.unity"],"SerializedFileDependencies":[4]},{"Index":4,"StableId":"a2a42d71dddef12e8889849faf59bdd7","ArtifactIndex":4,"SourceAssets":["Assets/Scripts/DirectScriptableObjectReference.cs","Assets/Scripts/LoadableAudioClipReference.cs","Assets/Scripts/SceneList.cs","Assets/Scripts/SerializationDemo.cs"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[]},{"Index":5,"StableId":"4038ff673d390134d924b57fcbed0432","ArtifactIndex":5,"SourceAssets":["Assets/ScriptableObjects/LoadableAudioClipReference.asset"],"LoadableDependencies":[0,1],"LoadableSceneDependencies":[],"SerializedFileDependencies":[4]},{"Index":6,"StableId":"162c015549f8733449ac70ae78ad3aa5","ArtifactIndex":6,"SourceAssets":["Assets/Scenes/Scene1.unity"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[0,1,13]},{"Index":7,"StableId":"34814583296b527bf650f548effcd62c","ArtifactIndex":7,"SourceAssets":["Assets/Textures/GreenStatic.png"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[]},{"Index":8,"StableId":"20408785117e138f38c2cc4ca0e638c8","ArtifactIndex":8,"SourceAssets":["Assets/Audio/6.mp3"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[]},{"Index":9,"StableId":"78532141fd7679a458405eb16bdb75fd","ArtifactIndex":9,"SourceAssets":["Assets/ScriptableObjects/SerializationDemo.asset"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[4]},{"Index":10,"StableId":"52b43dad178849b42ac753005736e7bb","ArtifactIndex":10,"SourceAssets":["Assets/ScriptableObjects/ContentDirectoryRoot.asset"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[4,5,2,9,3]},{"Index":11,"StableId":"dd30e3af3b8bddf1ea36d768fc41aafd","ArtifactIndex":11,"SourceAssets":["Assets/Audio/a.mp3"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[]},{"Index":12,"StableId":"590cfeb4e0ff90f4e92f9e1262bcfe6f","ArtifactIndex":12,"SourceAssets":["Assets/Scenes/Scene2.unity"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[0,1,13]},{"Index":13,"StableId":"baf46637edbb6ff884858f6630b66eda","ArtifactIndex":13,"SourceAssets":["Assets/Textures/GreenStatic.png"],"LoadableDependencies":[],"LoadableSceneDependencies":[],"SerializedFileDependencies":[7]}],"RootAssets":[2],"LoadableObjectIds":[{"GUID":"278c261333bf8604eb5c83790d02004d","LFID":8300000,"IdentifierType":3,"SerializedFile":8},{"GUID":"b65a7916245593b4e89f4bd0aa920533","LFID":8300000,"IdentifierType":3,"SerializedFile":11},{"GUID":"52b43dad178849b42ac753005736e7bb","LFID":11400000,"IdentifierType":2,"SerializedFile":10}],"LoadableSceneIds":[{"GUID":"590cfeb4e0ff90f4e92f9e1262bcfe6f","Path":"Assets/Scenes/Scene2.unity","SerializedFile":12},{"GUID":"162c015549f8733449ac70ae78ad3aa5","Path":"Assets/Scenes/Scene1.unity","SerializedFile":6}],"BinaryArtifacts":[{"Index":0,"ContentHash":"e320fc78984f8430afa90a591fd02004","Category":"manifest","Size":2324,"ArtifactReferences":[1,2,3,4,5,6,7,8,9,10,11,12,13]},{"Index":1,"ContentHash":"ccccf25fc67d2bea8415c0846c1602dc","Category":"contentfile","Size":32252,"ArtifactReferences":[14]},{"Index":2,"ContentHash":"38abbace71a8a756c679a7ff3f501f02","Category":"contentfile","Size":2952},{"Index":3,"ContentHash":"70ae0762d8f82ea593516bcc52c94bf0","Category":"contentfile","Size":1896},{"Index":4,"ContentHash":"79bdcb3e9bc659d3519594280240cd1b","Category":"contentfile","Size":1900},{"Index":5,"ContentHash":"2357d63c989be24891a7e85b77bce4a6","Category":"contentfile","Size":3000},{"Index":6,"ContentHash":"8ec44fb05f86ef7dcb7bbef683d8a330","Category":"contentfile","Size":17540},{"Index":7,"ContentHash":"214959d6771e8c749dd41eca123456b2","Category":"contentfile","Size":2468,"ArtifactReferences":[15]},{"Index":8,"ContentHash":"9a17f57112aa00edaa582242bc31fb71","Category":"contentfile","Size":1400,"ArtifactReferences":[16]},{"Index":9,"ContentHash":"491d099506657727cb6683c78c0e2383","Category":"contentfile","Size":4428},{"Index":10,"ContentHash":"eb3abd5ab5b0d790980fe9e9df872484","Category":"contentfile","Size":2084},{"Index":11,"ContentHash":"39105176560358e0cd444e7a0a513ed3","Category":"contentfile","Size":1400,"ArtifactReferences":[17]},{"Index":12,"ContentHash":"3cfc30da76f416cbd860bd47c9e5ed95","Category":"contentfile","Size":17540},{"Index":13,"ContentHash":"812fe00f6f62ce8504f14dfe20976ba2","Category":"contentfile","Size":10724},{"Index":14,"ContentHash":"ea7c9fa2d58ef71544726747da8ade6d","Category":"texture","Size":524280},{"Index":15,"ContentHash":"f0a44ad4a4babd121543fd44032928e7","Category":"texture","Size":215970},{"Index":16,"ContentHash":"68c9d0b12420e1951eec7790bd0754fe","Category":"audio","Size":30688},{"Index":17,"ContentHash":"4226b5c16a50dab6eff0f08dd1253d4b","Category":"audio","Size":47424}]}
\ No newline at end of file
diff --git a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ContentSizeSummary.txt b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ContentSizeSummary.txt
index 33f9a8b..8800a72 100644
--- a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ContentSizeSummary.txt
+++ b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ContentSizeSummary.txt
@@ -3,19 +3,19 @@ Build Report - Content Size Statistics
Serialized File count 13
Resource file count 4
Object count 28
-Serialized Files 155.1 kb 16.3%
-Serialized Files Headers 142.0 kb 14.9%
-Resource data (Texture,Audio...) 799.2 kb 83.7%
-Total build size 954.3 kb
+Serialized Files 97.2 kb 10.8%
+Serialized Files Headers 84.2 kb 9.4%
+Resource data (Texture,Audio...) 799.2 kb 89.2%
+Total build size 896.4 kb
Largest Types:
- 512.2 kb 53.7% Cubemap
- 211.1 kb 22.1% Texture2D
- 76.5 kb 8.0% AudioClip
+ 512.2 kb 57.1% Cubemap
+ 211.1 kb 23.5% Texture2D
+ 76.5 kb 8.5% AudioClip
Largest Assets:
- 519.5 kb 54.4% Resources/unity_builtin_extra
- 211.5 kb 22.2% Assets/Textures/GreenStatic.png
- 46.4 kb 4.9% Assets/Audio/a.mp3
- 30.1 kb 3.2% Assets/Audio/6.mp3
+ 519.4 kb 57.9% Resources/unity_builtin_extra
+ 211.5 kb 23.6% Assets/Textures/GreenStatic.png
+ 46.4 kb 5.2% Assets/Audio/a.mp3
+ 30.1 kb 3.4% Assets/Audio/6.mp3
-------------------------------------------------------------------------------
diff --git a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ScriptsOnlyCache.yaml b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ScriptsOnlyCache.yaml
index ef2bfee..2fe8a7a 100644
--- a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ScriptsOnlyCache.yaml
+++ b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/ScriptsOnlyCache.yaml
@@ -19,5 +19,5 @@ ScriptsOnlyBuild:
scriptHashData: []
platform: 19
scenePathNames: []
- nativeClasses: 000000003000000012000000820000001c0000001b00000059000000150000000900000003000000680000009d00000004000000020000000100000019000000d4000000c4000000530000007de237150f01000072000000080000003100000073000000d5000000i
+ nativeClasses: 00000000300000001200000082000000150000001c0000001b00000059000000020000007200000008000000730000003100000001000000040000000900000003000000680000009d000000c400000019000000d4000000530000007de237150f010000d5000000i
playerPath:
diff --git a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/circularrefs.json b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/circularrefs.json
new file mode 100644
index 0000000..74b6e1f
--- /dev/null
+++ b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/circularrefs.json
@@ -0,0 +1,3 @@
+{
+ "clusters": []
+}
\ No newline at end of file
diff --git a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/f096bba0b9ce4c44194edd738d64b9df.buildreport b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/f096bba0b9ce4c44194edd738d64b9df.buildreport
new file mode 100644
index 0000000..f4c30fb
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/f096bba0b9ce4c44194edd738d64b9df.buildreport differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/f64157fb08bb9f645971d39c1203bd03.buildreport b/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/f64157fb08bb9f645971d39c1203bd03.buildreport
deleted file mode 100644
index db603fe..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/BuildReport-ContentDirectory/f64157fb08bb9f645971d39c1203bd03.buildreport and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/214959d6771e8c749dd41eca123456b2.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/214959d6771e8c749dd41eca123456b2.cf
new file mode 100644
index 0000000..dd71013
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/214959d6771e8c749dd41eca123456b2.cf differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/2357d63c989be24891a7e85b77bce4a6.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/2357d63c989be24891a7e85b77bce4a6.cf
new file mode 100644
index 0000000..b7cb544
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/2357d63c989be24891a7e85b77bce4a6.cf differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/38abbace71a8a756c679a7ff3f501f02.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/38abbace71a8a756c679a7ff3f501f02.cf
new file mode 100644
index 0000000..82bde3c
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/38abbace71a8a756c679a7ff3f501f02.cf differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/39105176560358e0cd444e7a0a513ed3.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/39105176560358e0cd444e7a0a513ed3.cf
new file mode 100644
index 0000000..4898d8a
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/39105176560358e0cd444e7a0a513ed3.cf differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/3cfc30da76f416cbd860bd47c9e5ed95.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/3cfc30da76f416cbd860bd47c9e5ed95.cf
new file mode 100644
index 0000000..692ee57
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/3cfc30da76f416cbd860bd47c9e5ed95.cf differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/491d099506657727cb6683c78c0e2383.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/491d099506657727cb6683c78c0e2383.cf
new file mode 100644
index 0000000..33f8f2f
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/491d099506657727cb6683c78c0e2383.cf differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/4d49f1aa2acf2ecf20f51660e1bf77ea.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/4d49f1aa2acf2ecf20f51660e1bf77ea.cf
deleted file mode 100644
index b805513..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/4d49f1aa2acf2ecf20f51660e1bf77ea.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/51690f09f541d7442ce8faf26fd84696.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/51690f09f541d7442ce8faf26fd84696.cf
deleted file mode 100644
index e82c8f4..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/51690f09f541d7442ce8faf26fd84696.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/5c43454a3823f172a2a326410a36ba6b.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/5c43454a3823f172a2a326410a36ba6b.cf
deleted file mode 100644
index 98b2c87..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/5c43454a3823f172a2a326410a36ba6b.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/61fa28f6b22b739b19ece22ea0b3f29f.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/61fa28f6b22b739b19ece22ea0b3f29f.cf
deleted file mode 100644
index b5c3c31..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/61fa28f6b22b739b19ece22ea0b3f29f.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/6a60c190881f43bb4a91a360b9e8850d.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/6a60c190881f43bb4a91a360b9e8850d.cf
deleted file mode 100644
index 101d567..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/6a60c190881f43bb4a91a360b9e8850d.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/70ae0762d8f82ea593516bcc52c94bf0.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/70ae0762d8f82ea593516bcc52c94bf0.cf
new file mode 100644
index 0000000..af52b65
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/70ae0762d8f82ea593516bcc52c94bf0.cf differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/730d2d641a53eeb1e633f2ff60d730e9.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/730d2d641a53eeb1e633f2ff60d730e9.cf
deleted file mode 100644
index 37c0390..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/730d2d641a53eeb1e633f2ff60d730e9.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/79bdcb3e9bc659d3519594280240cd1b.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/79bdcb3e9bc659d3519594280240cd1b.cf
new file mode 100644
index 0000000..c36c503
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/79bdcb3e9bc659d3519594280240cd1b.cf differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/812fe00f6f62ce8504f14dfe20976ba2.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/812fe00f6f62ce8504f14dfe20976ba2.cf
new file mode 100644
index 0000000..2c2e7e0
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/812fe00f6f62ce8504f14dfe20976ba2.cf differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/86d71ff2bb38e064697257d35d6421b8.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/86d71ff2bb38e064697257d35d6421b8.cf
deleted file mode 100644
index fca0685..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/86d71ff2bb38e064697257d35d6421b8.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/8ad4924ac5e264fde63d8e95a0dab8ab.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/8ad4924ac5e264fde63d8e95a0dab8ab.cf
deleted file mode 100644
index b81825d..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/8ad4924ac5e264fde63d8e95a0dab8ab.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/8ec44fb05f86ef7dcb7bbef683d8a330.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/8ec44fb05f86ef7dcb7bbef683d8a330.cf
new file mode 100644
index 0000000..d97285e
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/8ec44fb05f86ef7dcb7bbef683d8a330.cf differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/93bb3159569284a5c7bcceb4acb91bae.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/93bb3159569284a5c7bcceb4acb91bae.cf
deleted file mode 100644
index df31167..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/93bb3159569284a5c7bcceb4acb91bae.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/9a17f57112aa00edaa582242bc31fb71.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/9a17f57112aa00edaa582242bc31fb71.cf
new file mode 100644
index 0000000..35f613a
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/9a17f57112aa00edaa582242bc31fb71.cf differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/BuildManifestHash.txt b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/BuildManifestHash.txt
index 647aa06..9fc4123 100644
--- a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/BuildManifestHash.txt
+++ b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/BuildManifestHash.txt
@@ -1 +1 @@
-baff06b928d147276f2245dd3b19216a
\ No newline at end of file
+e320fc78984f8430afa90a591fd02004
\ No newline at end of file
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/baff06b928d147276f2245dd3b19216a.json b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/baff06b928d147276f2245dd3b19216a.json
deleted file mode 100644
index 5cac3c9..0000000
--- a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/baff06b928d147276f2245dd3b19216a.json
+++ /dev/null
@@ -1 +0,0 @@
-{"Version":1,"BuildInfo":{"BuildName":"ContentDirectory","BuiltWithTypeTrees":true},"RootAssets":["3dcb957e6cb206bfec2aaadd3d11d85c"],"Loadables":[{"ObjectIdHash":"20408785117e138f38c2cc4ca0e638c8","SerializedFile":3,"Identifier":6313987888022012634},{"ObjectIdHash":"3dcb957e6cb206bfec2aaadd3d11d85c","SerializedFile":5,"Identifier":-775554941117088049},{"ObjectIdHash":"dd30e3af3b8bddf1ea36d768fc41aafd","SerializedFile":10,"Identifier":-6933612096100796476}],"LoadableScenes":[{"GUID":"162c015549f8733449ac70ae78ad3aa5","Path":"Assets/Scenes/Scene1.unity","SerializedFile":2},{"GUID":"590cfeb4e0ff90f4e92f9e1262bcfe6f","Path":"Assets/Scenes/Scene2.unity","SerializedFile":6}],"SerializedFiles":[{"Index":0,"ID":"Library/unity default resources","IsBuiltIn":true,"SerializedFileDependencies":[]},{"Index":1,"ID":"0000000000000000f000000000000000.cfid","IsBuiltIn":false,"SerializedFileDependencies":[],"ContentHash":"6a60c190881f43bb4a91a360b9e8850d"},{"Index":2,"ID":"162c015549f8733449ac70ae78ad3aa5.cfid","IsBuiltIn":false,"SerializedFileDependencies":[0,1,9],"ContentHash":"c271b85494f5e4cc35c4ec4a776324af"},{"Index":3,"ID":"20408785117e138f38c2cc4ca0e638c8.cfid","IsBuiltIn":false,"SerializedFileDependencies":[],"ContentHash":"93bb3159569284a5c7bcceb4acb91bae"},{"Index":4,"ID":"4038ff673d390134d924b57fcbed0432.cfid","IsBuiltIn":false,"SerializedFileDependencies":[8],"ContentHash":"bfcf18a244e45f867750e0223a9b3a43"},{"Index":5,"ID":"52b43dad178849b42ac753005736e7bb.cfid","IsBuiltIn":false,"SerializedFileDependencies":[8,4,11,13,7],"ContentHash":"c0152db4dd710be51b2decb997325f34"},{"Index":6,"ID":"590cfeb4e0ff90f4e92f9e1262bcfe6f.cfid","IsBuiltIn":false,"SerializedFileDependencies":[0,1,9],"ContentHash":"4d49f1aa2acf2ecf20f51660e1bf77ea"},{"Index":7,"ID":"70a210050f71a924aa83be7146547111.cfid","IsBuiltIn":false,"SerializedFileDependencies":[8],"ContentHash":"8ad4924ac5e264fde63d8e95a0dab8ab"},{"Index":8,"ID":"a2a42d71dddef12e8889849faf59bdd7.cfid","IsBuiltIn":false,"SerializedFileDependencies":[],"ContentHash":"86d71ff2bb38e064697257d35d6421b8"},{"Index":9,"ID":"baf46637edbb6ff884858f6630b66eda.cfid","IsBuiltIn":false,"SerializedFileDependencies":[12],"ContentHash":"61fa28f6b22b739b19ece22ea0b3f29f"},{"Index":10,"ID":"dd30e3af3b8bddf1ea36d768fc41aafd.cfid","IsBuiltIn":false,"SerializedFileDependencies":[],"ContentHash":"730d2d641a53eeb1e633f2ff60d730e9"},{"Index":11,"ID":"21679be819d6e9146a63bb02a7e51f2f.cfid","IsBuiltIn":false,"SerializedFileDependencies":[8],"ContentHash":"5c43454a3823f172a2a326410a36ba6b"},{"Index":12,"ID":"34814583296b527bf650f548effcd62c.cfid","IsBuiltIn":false,"SerializedFileDependencies":[],"ContentHash":"c046f78347acd69f03fa7f80362fde94"},{"Index":13,"ID":"78532141fd7679a458405eb16bdb75fd.cfid","IsBuiltIn":false,"SerializedFileDependencies":[8],"ContentHash":"51690f09f541d7442ce8faf26fd84696"}]}
\ No newline at end of file
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/bfcf18a244e45f867750e0223a9b3a43.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/bfcf18a244e45f867750e0223a9b3a43.cf
deleted file mode 100644
index 85cf1da..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/bfcf18a244e45f867750e0223a9b3a43.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/c0152db4dd710be51b2decb997325f34.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/c0152db4dd710be51b2decb997325f34.cf
deleted file mode 100644
index 03a4020..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/c0152db4dd710be51b2decb997325f34.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/c046f78347acd69f03fa7f80362fde94.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/c046f78347acd69f03fa7f80362fde94.cf
deleted file mode 100644
index 2126d12..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/c046f78347acd69f03fa7f80362fde94.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/c271b85494f5e4cc35c4ec4a776324af.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/c271b85494f5e4cc35c4ec4a776324af.cf
deleted file mode 100644
index 1a9ca1c..0000000
Binary files a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/c271b85494f5e4cc35c4ec4a776324af.cf and /dev/null differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/ccccf25fc67d2bea8415c0846c1602dc.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/ccccf25fc67d2bea8415c0846c1602dc.cf
new file mode 100644
index 0000000..560c2aa
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/ccccf25fc67d2bea8415c0846c1602dc.cf differ
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/e320fc78984f8430afa90a591fd02004.json b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/e320fc78984f8430afa90a591fd02004.json
new file mode 100644
index 0000000..796b02b
--- /dev/null
+++ b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/e320fc78984f8430afa90a591fd02004.json
@@ -0,0 +1 @@
+{"Version":2,"BuildInfo":{"BuildName":"ContentDirectory","BuiltWithTypeTrees":true},"RootAssets":[{"SerializedFile":10,"Identifier":11400000}],"LoadableScenes":[{"GUID":"590cfeb4e0ff90f4e92f9e1262bcfe6f","Path":"Assets/Scenes/Scene2.unity","SerializedFile":12},{"GUID":"162c015549f8733449ac70ae78ad3aa5","Path":"Assets/Scenes/Scene1.unity","SerializedFile":6}],"SerializedFiles":[{"Index":0,"StableId":"Library/unity default resources","IsBuiltIn":true,"SerializedFileDependencies":[]},{"Index":1,"StableId":"80b335c0244a430ff7eadfc72ee8fc6a","SerializedFileDependencies":[],"ContentHash":"ccccf25fc67d2bea8415c0846c1602dc"},{"Index":2,"StableId":"21679be819d6e9146a63bb02a7e51f2f","SerializedFileDependencies":[4],"ContentHash":"38abbace71a8a756c679a7ff3f501f02"},{"Index":3,"StableId":"70a210050f71a924aa83be7146547111","SerializedFileDependencies":[4],"ContentHash":"70ae0762d8f82ea593516bcc52c94bf0"},{"Index":4,"StableId":"a2a42d71dddef12e8889849faf59bdd7","SerializedFileDependencies":[],"ContentHash":"79bdcb3e9bc659d3519594280240cd1b"},{"Index":5,"StableId":"4038ff673d390134d924b57fcbed0432","SerializedFileDependencies":[4],"ContentHash":"2357d63c989be24891a7e85b77bce4a6"},{"Index":6,"StableId":"162c015549f8733449ac70ae78ad3aa5","SerializedFileDependencies":[0,1,13],"ContentHash":"8ec44fb05f86ef7dcb7bbef683d8a330"},{"Index":7,"StableId":"34814583296b527bf650f548effcd62c","SerializedFileDependencies":[],"ContentHash":"214959d6771e8c749dd41eca123456b2"},{"Index":8,"StableId":"20408785117e138f38c2cc4ca0e638c8","SerializedFileDependencies":[],"ContentHash":"9a17f57112aa00edaa582242bc31fb71"},{"Index":9,"StableId":"78532141fd7679a458405eb16bdb75fd","SerializedFileDependencies":[4],"ContentHash":"491d099506657727cb6683c78c0e2383"},{"Index":10,"StableId":"52b43dad178849b42ac753005736e7bb","SerializedFileDependencies":[4,5,2,9,3],"ContentHash":"eb3abd5ab5b0d790980fe9e9df872484"},{"Index":11,"StableId":"dd30e3af3b8bddf1ea36d768fc41aafd","SerializedFileDependencies":[],"ContentHash":"39105176560358e0cd444e7a0a513ed3"},{"Index":12,"StableId":"590cfeb4e0ff90f4e92f9e1262bcfe6f","SerializedFileDependencies":[0,1,13],"ContentHash":"3cfc30da76f416cbd860bd47c9e5ed95"},{"Index":13,"StableId":"baf46637edbb6ff884858f6630b66eda","SerializedFileDependencies":[7],"ContentHash":"812fe00f6f62ce8504f14dfe20976ba2"}]}
\ No newline at end of file
diff --git a/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/eb3abd5ab5b0d790980fe9e9df872484.cf b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/eb3abd5ab5b0d790980fe9e9df872484.cf
new file mode 100644
index 0000000..f53dd75
Binary files /dev/null and b/TestCommon/Data/LeadingEdgeBuilds/ContentDirectory/eb3abd5ab5b0d790980fe9e9df872484.cf differ
diff --git a/UnityDataModels/ContentLayout.cs b/UnityDataModels/ContentLayout.cs
index 5b19dfb..d75cefe 100644
--- a/UnityDataModels/ContentLayout.cs
+++ b/UnityDataModels/ContentLayout.cs
@@ -3,9 +3,9 @@
//
// See Documentation/contentlayout.md for further details.
//
-// ContentLayout always represents the latest schema version (currently 2). If the schema changes
-// significantly in future, older versions can be preserved under a version-specific namespace while this type
-// continues to track the latest version.
+// ContentLayout always represents the latest schema version (currently 3, written by Unity 6.7).
+// Older versions are preserved under a version-specific namespace: the version 2 schema (Unity 6.6)
+// is defined in ContentLayoutV2.cs as UnityDataTools.Models.V2.ContentLayout.
namespace UnityDataTools.Models
{
///
@@ -40,12 +40,15 @@ public struct SerializedFileLayout
/// Index of this entry inside .
public int Index;
- /// Stable identifier used to reference this SerializedFile from other SerializedFiles
- /// in a way that doesn't break when the content changes. Currently based on the cluster or guid of the source.
- public string ID;
+ /// Stable identity hash of this SerializedFile, used to reference it from other
+ /// SerializedFiles in a way that doesn't break when the content changes. Computed from the
+ /// cluster or object identity of the source. For the synthetic built-in entry this is the
+ /// resource path instead.
+ public string StableId;
/// True for synthetic entries representing built-in Unity resources that are not produced
- /// by the build (currently only "Library/unity default resources"). Such entries have no ContentHash.
+ /// by the build (currently only "Library/unity default resources"); such entries resolve through
+ /// the PersistentManager at runtime and have no artifact. Only written when true; absent means false.
public bool IsBuiltIn;
/// The source assets included in this SerializedFile.
@@ -55,34 +58,31 @@ public struct SerializedFileLayout
/// other SerializedFiles that need to be loaded prior to loading this SerializedFile.
public int[] SerializedFileDependencies;
- /// ObjectIdHash values for loadable objects referenced from this SerializedFile.
- public string[] LoadableDependencies;
+ /// Indices into for loadable objects
+ /// referenced from this SerializedFile.
+ public int[] LoadableDependencies;
/// Scene paths for scenes referenced from this SerializedFile.
public string[] LoadableSceneDependencies;
- /// xxhash3 hash of the content, used for the filename (+".cf") and for lookup into UDS.
- /// Matches the of the corresponding entry in
- /// .
- public string ContentHash;
+ /// Index into for the artifact holding this
+ /// SerializedFile's content, or -1 for the built-in entry, which has no artifact.
+ public int ArtifactIndex;
}
///
/// Records a loadable object in the build. Listed at the top level of the ContentLayout so that a
/// loadable's identity is described independently of the SerializedFile that happens to contain it.
+ /// Referenced by index from and
+ /// .
///
public class LoadableObjectIdLayout
{
- /// Hash of the GUID, LFID and IdentifierType.
- public string ObjectIdHash;
-
/// AssetDatabase GUID of the source asset.
public string GUID;
- /// Path of the source asset.
- public string AssetPath;
-
- /// Local file id of the source object.
+ /// Local file id of the object in the output SerializedFile when placed,
+ /// otherwise the source local file id. Identical to the source id except for MonoScripts.
public long LFID;
/// Identifier type of the source object.
@@ -91,10 +91,6 @@ public class LoadableObjectIdLayout
/// Index into for the file that contains this
/// loadable, or -1 if it was dropped (e.g. server build shader references).
public int SerializedFile = -1;
-
- /// Local file id of the object within its output Content File (the one identified by the
- /// index).
- public long OutputLFID;
}
///
@@ -121,7 +117,8 @@ public class BinaryArtifact
public int Index;
/// Content addressable hash. For ContentFile artifacts, the matching
- /// can be found by ContentHash.
+ /// references this entry via
+ /// .
public string ContentHash;
/// One of the strings in .
@@ -165,7 +162,7 @@ public string FileExtension
/// In-memory representation of the ContentLayout.json file written by the build.
///
/// The Layout is a companion to the BuildManifest, recording additional details about the build
- /// (including source assets and information about which object an ObjectId hash refers to).
+ /// (including source assets and information about which source object each loadable refers to).
/// It is not shipped with the build; it exists for tools and tests that analyze build output.
/// The schema is subject to change and there is currently no backward compatibility.
///
@@ -173,7 +170,12 @@ public class ContentLayout
{
/// The schema version this type represents.
// v1 -> v2: added OutputLFID to LoadableObjectIds entries.
- public const int CurrentVersion = 2;
+ // v2 -> v3: ObjectIdHash deleted. LoadableObjectIds entries trimmed to {GUID, LFID,
+ // IdentifierType, SerializedFile}; LoadableDependencies and RootAssets became
+ // indices into LoadableObjectIds. SerializedFiles entries carry StableId
+ // (the identity hash, no extension) instead of ID, and ArtifactIndex (index
+ // into BinaryArtifacts) instead of ContentHash.
+ public const int CurrentVersion = 3;
/// Schema version of the ContentLayout.json file.
public int Version;
@@ -184,8 +186,8 @@ public class ContentLayout
/// The SerializedFiles in the build output.
public SerializedFileLayout[] SerializedFiles;
- /// ObjectIdHash values of the root assets; resolve via .
- public string[] RootAssets;
+ /// Indices into of the root assets, in root input order.
+ public int[] RootAssets;
/// Loadable objects in the build.
public LoadableObjectIdLayout[] LoadableObjectIds;
diff --git a/UnityDataModels/ContentLayoutV2.cs b/UnityDataModels/ContentLayoutV2.cs
new file mode 100644
index 0000000..174795b
--- /dev/null
+++ b/UnityDataModels/ContentLayoutV2.cs
@@ -0,0 +1,170 @@
+// The version 2 schema of the ContentLayout.json file, produced by BuildPipeline.BuildContentDirectory
+// in Unity 6.6. Preserved as a reference definition for users working with Unity 6.6 build output.
+//
+// The latest schema is defined in ContentLayout.cs (UnityDataTools.Models.ContentLayout); see
+// Documentation/contentlayout.md for the differences between the versions.
+namespace UnityDataTools.Models.V2
+{
+ ///
+ /// Category strings used by .
+ ///
+ public static class BuildArtifactCategory
+ {
+ /// Texture data, written to a streaming resource (.resS) file.
+ public const string Texture = "texture";
+
+ /// Mesh data, written to a streaming resource (.resS) file.
+ public const string Mesh = "mesh";
+
+ /// Audio data, written to a resource (.resource) file.
+ public const string Audio = "audio";
+
+ /// Video data, written to a resource (.resource) file.
+ public const string Video = "video";
+
+ /// A SerializedFile / Content File (.cf).
+ public const string ContentFile = "contentfile";
+
+ /// A manifest file (.json).
+ public const string Manifest = "manifest";
+ }
+
+ ///
+ /// Describes a SerializedFile in the build output.
+ ///
+ public struct SerializedFileLayout
+ {
+ /// Index of this entry inside .
+ public int Index;
+
+ /// Stable identifier used to reference this SerializedFile from other SerializedFiles
+ /// in a way that doesn't break when the content changes. Currently based on the cluster or guid of the source.
+ public string ID;
+
+ /// True for synthetic entries representing built-in Unity resources that are not produced
+ /// by the build (currently only "Library/unity default resources"). Such entries have no ContentHash.
+ public bool IsBuiltIn;
+
+ /// The source assets included in this SerializedFile.
+ public string[] SourceAssets;
+
+ /// Indices into the containing array, identifying
+ /// other SerializedFiles that need to be loaded prior to loading this SerializedFile.
+ public int[] SerializedFileDependencies;
+
+ /// ObjectIdHash values for loadable objects referenced from this SerializedFile.
+ public string[] LoadableDependencies;
+
+ /// Scene paths for scenes referenced from this SerializedFile.
+ public string[] LoadableSceneDependencies;
+
+ /// xxhash3 hash of the content, used for the filename (+".cf") and for lookup into UDS.
+ /// Matches the of the corresponding entry in
+ /// .
+ public string ContentHash;
+ }
+
+ ///
+ /// Records a loadable object in the build. Listed at the top level of the ContentLayout so that a
+ /// loadable's identity is described independently of the SerializedFile that happens to contain it.
+ ///
+ public class LoadableObjectIdLayout
+ {
+ /// Hash of the GUID, LFID and IdentifierType.
+ public string ObjectIdHash;
+
+ /// AssetDatabase GUID of the source asset.
+ public string GUID;
+
+ /// Path of the source asset.
+ public string AssetPath;
+
+ /// Local file id of the source object.
+ public long LFID;
+
+ /// Identifier type of the source object.
+ public int IdentifierType;
+
+ /// Index into for the file that contains this
+ /// loadable, or -1 if it was dropped (e.g. server build shader references).
+ public int SerializedFile = -1;
+
+ /// Local file id of the object within its output Content File (the one identified by the
+ /// index).
+ public long OutputLFID;
+ }
+
+ ///
+ /// Records a scene exposed as loadable in the build, and the SerializedFile that contains it.
+ ///
+ public class LoadableSceneIdLayout
+ {
+ /// AssetDatabase GUID of the scene.
+ public string GUID;
+
+ /// Scene path.
+ public string Path;
+
+ /// Index into , or -1 if not mapped.
+ public int SerializedFile = -1;
+ }
+
+ ///
+ /// Describes a binary artifact produced by the build (SerializedFile, streaming resource, manifest, etc.).
+ ///
+ public class BinaryArtifact
+ {
+ /// Index of this entry inside .
+ public int Index;
+
+ /// Content addressable hash. For ContentFile artifacts, the matching
+ /// can be found by ContentHash.
+ public string ContentHash;
+
+ /// One of the strings in .
+ public string Category;
+
+ /// Size in bytes.
+ public ulong Size;
+
+ /// Indices into identifying additional artifacts
+ /// referenced from this one (e.g. mesh, audio).
+ /// Note: this does not track references to other ContentFiles — those are recorded in
+ /// .
+ public int[] ArtifactReferences;
+ }
+
+ ///
+ /// In-memory representation of a version 2 ContentLayout.json file (Unity 6.6).
+ ///
+ /// The Layout is a companion to the BuildManifest, recording additional details about the build
+ /// (including source assets and information about which object an ObjectId hash refers to).
+ /// It is not shipped with the build; it exists for tools and tests that analyze build output.
+ ///
+ public class ContentLayout
+ {
+ /// The schema version this type represents.
+ public const int SchemaVersion = 2;
+
+ /// Schema version of the ContentLayout.json file.
+ public int Version;
+
+ /// Hash of the BuildManifest this layout corresponds to.
+ public string BuildManifestHash;
+
+ /// The SerializedFiles in the build output.
+ public SerializedFileLayout[] SerializedFiles;
+
+ /// ObjectIdHash values of the root assets; resolve via .
+ public string[] RootAssets;
+
+ /// Loadable objects in the build.
+ public LoadableObjectIdLayout[] LoadableObjectIds;
+
+ /// Loadable scenes in the build.
+ public LoadableSceneIdLayout[] LoadableSceneIds;
+
+ /// Binary artifacts produced by the build.
+ public BinaryArtifact[] BinaryArtifacts;
+ }
+}
diff --git a/UnityDataTool.Tests/AnalyzeBuildHistoryTests.cs b/UnityDataTool.Tests/AnalyzeBuildHistoryTests.cs
index 9ba0137..838b456 100644
--- a/UnityDataTool.Tests/AnalyzeBuildHistoryTests.cs
+++ b/UnityDataTool.Tests/AnalyzeBuildHistoryTests.cs
@@ -16,7 +16,7 @@ namespace UnityDataTools.UnityDataTool.Tests;
// reference build.
public class AnalyzeBuildHistoryTests
{
- private const string BuildHash = "baff06b928d147276f2245dd3b19216a";
+ private const string BuildHash = "e320fc78984f8430afa90a591fd02004";
private string m_TestOutputFolder;
private string m_ContentDirectory;
@@ -68,42 +68,6 @@ private string CreateStaleBuildFolder(string historyRoot, string name)
return folder;
}
- private static async Task<(int exitCode, string stdErr)> RunCapturingStdErr(params string[] args)
- {
- using var sw = new StringWriter();
- var currentError = Console.Error;
- int exitCode;
- try
- {
- Console.SetError(sw);
- exitCode = await Program.Main(args);
- }
- finally
- {
- Console.SetError(currentError);
- }
-
- return (exitCode, sw.ToString());
- }
-
- private static async Task<(int exitCode, string stdOut)> RunCapturingStdOut(params string[] args)
- {
- using var sw = new StringWriter();
- var currentOut = Console.Out;
- int exitCode;
- try
- {
- Console.SetOut(sw);
- exitCode = await Program.Main(args);
- }
- finally
- {
- Console.SetOut(currentOut);
- }
-
- return (exitCode, sw.ToString());
- }
-
[Test]
public async Task Analyze_WithBuildHistory_ImportsLayoutAndBuildReport()
{
@@ -151,7 +115,7 @@ public async Task Analyze_BuildHistoryWithoutMatch_Fails()
CreateStaleBuildFolder(history, "Build-Stale");
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
- var (exitCode, stdErr) = await RunCapturingStdErr(
+ var (exitCode, stdErr) = await ConsoleTestHelper.RunCapturingStdErr(
"analyze", m_ContentDirectory, "--build-history", history, "-o", databasePath);
Assert.AreEqual(1, exitCode, "a build history without the analyzed build must fail");
@@ -170,7 +134,7 @@ public async Task Analyze_BuildHistoryWithoutContentDirectoryInput_Fails()
"Data", "LeadingEdgeBuilds", "AssetBundles", "assetbundleroot");
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
- var (exitCode, stdErr) = await RunCapturingStdErr(
+ var (exitCode, stdErr) = await ConsoleTestHelper.RunCapturingStdErr(
"analyze", bundlePath, "--build-history", history, "-o", databasePath);
Assert.AreEqual(1, exitCode);
@@ -218,7 +182,7 @@ public async Task Analyze_MultipleMatchingBuilds_UsesMostRecent()
File.Delete(Path.Combine(noSummary, "BuildReportSummary.json"));
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
- var (exitCode, stdOut) = await RunCapturingStdOut(
+ var (exitCode, stdOut) = await ConsoleTestHelper.RunCapturingStdOut(
"analyze", m_ContentDirectory, "--build-history", history, "-o", databasePath);
Assert.AreEqual(0, exitCode);
@@ -239,7 +203,7 @@ public async Task Analyze_BuildHistoryAndSameLayoutPassedPositionally_UsesOneLay
var positionalLayout = Path.Combine(m_FixtureReportFolder, "ContentLayout.json");
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
- var (exitCode, stdErr) = await RunCapturingStdErr(
+ var (exitCode, stdErr) = await ConsoleTestHelper.RunCapturingStdErr(
"analyze", m_ContentDirectory, positionalLayout, "--build-history", history, "-o", databasePath);
Assert.AreEqual(0, exitCode);
@@ -262,7 +226,7 @@ public async Task Analyze_ArchiveBuildWithBuildHistory_MatchesThroughArchiveHash
"Data", "contentdirectory-zstd");
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
- var (exitCode, stdErr) = await RunCapturingStdErr(
+ var (exitCode, stdErr) = await ConsoleTestHelper.RunCapturingStdErr(
"analyze", archiveBuild, "--build-history", history, "-o", databasePath);
Assert.AreEqual(1, exitCode);
diff --git a/UnityDataTool.Tests/AnalyzeContentLayoutTests.cs b/UnityDataTool.Tests/AnalyzeContentLayoutTests.cs
index 0ea6b1c..4d82777 100644
--- a/UnityDataTool.Tests/AnalyzeContentLayoutTests.cs
+++ b/UnityDataTool.Tests/AnalyzeContentLayoutTests.cs
@@ -10,10 +10,11 @@ namespace UnityDataTools.UnityDataTool.Tests;
#pragma warning disable NUnit2005, NUnit2006
// Tests the import of ContentLayout.json into the content_layout* tables (issue #99). Runs
-// against the ContentLayout.json of the LeadingEdge ContentDirectory reference build, whose
-// content is well-known (see UnityProjects/LeadingEdge/Assets/Editor/BuildContentDirectory.cs):
+// against the version 3 ContentLayout.json of the LeadingEdge ContentDirectory reference build,
+// whose content is well-known (see UnityProjects/LeadingEdge/Assets/Editor/BuildContentDirectory.cs):
// 14 serialized files (1 built-in), 3 loadable objects with ContentDirectoryRoot as the single
-// root asset, 2 loadable scenes, and 18 binary artifacts.
+// root asset, 2 loadable scenes, and 18 binary artifacts. The version 2 import is covered by
+// AnalyzeContentLayoutV2Tests.
public class AnalyzeContentLayoutTests
{
private string m_TestOutputFolder;
@@ -27,7 +28,7 @@ public void OneTimeSetup()
m_ContentLayoutPath = Path.Combine(TestContext.CurrentContext.TestDirectory,
"Data", "LeadingEdgeBuilds", "BuildReport-ContentDirectory", "ContentLayout.json");
m_BuildReportPath = Path.Combine(TestContext.CurrentContext.TestDirectory,
- "Data", "LeadingEdgeBuilds", "BuildReport-ContentDirectory", "f64157fb08bb9f645971d39c1203bd03.buildreport");
+ "Data", "LeadingEdgeBuilds", "BuildReport-ContentDirectory", "f096bba0b9ce4c44194edd738d64b9df.buildreport");
Directory.CreateDirectory(m_TestOutputFolder);
Directory.SetCurrentDirectory(m_TestOutputFolder);
}
@@ -52,10 +53,10 @@ public async Task Analyze_ContentLayoutOnly_ImportsLayoutTables()
// Header row identifying the imported layout.
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM content_layout", 1,
"a single layout should be imported");
- SQLTestHelper.AssertQueryInt(db, "SELECT version FROM content_layout", 2,
+ SQLTestHelper.AssertQueryInt(db, "SELECT version FROM content_layout", 3,
"the layout schema version should be recorded");
SQLTestHelper.AssertQueryString(db, "SELECT build_manifest_hash FROM content_layout",
- "baff06b928d147276f2245dd3b19216a", "the BuildManifestHash should be recorded");
+ "e320fc78984f8430afa90a591fd02004", "the BuildManifestHash should be recorded");
// Row counts of each table, matching the json content.
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM content_layout_serialized_files", 14,
@@ -77,39 +78,58 @@ public async Task Analyze_ContentLayoutOnly_ImportsLayoutTables()
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM content_layout_artifact_references", 17,
"one row per artifact reference");
- // The built-in entry keeps its human-readable ID and has no content hash.
+ // The built-in entry keeps its human-readable path as the stable id and has no artifact.
SQLTestHelper.AssertQueryString(db,
- "SELECT cfid FROM content_layout_serialized_files WHERE file_index = 0",
+ "SELECT stable_id FROM content_layout_serialized_files WHERE file_index = 0",
"Library/unity default resources", "the built-in entry should be at index 0");
SQLTestHelper.AssertQueryInt(db,
"SELECT COUNT(*) FROM content_layout_serialized_files WHERE is_builtin = 1", 1,
"the reference build has a single built-in entry");
SQLTestHelper.AssertQueryInt(db,
- "SELECT COUNT(*) FROM content_layout_serialized_files WHERE is_builtin = 1 AND content_hash IS NOT NULL", 0,
- "built-in entries have no content hash");
+ "SELECT COUNT(*) FROM content_layout_serialized_files WHERE is_builtin = 1 AND artifact_index IS NOT NULL", 0,
+ "built-in entries have no artifact");
// The ContentDirectoryRoot file and its dependencies, preserving the json array order
- // (json SerializedFileDependencies for index 5: [8, 4, 11, 13, 7]).
+ // (json SerializedFileDependencies for index 10: [4, 5, 2, 9, 3]).
SQLTestHelper.AssertQueryString(db,
- "SELECT cfid FROM content_layout_serialized_files WHERE file_index = 5",
- "52b43dad178849b42ac753005736e7bb.cfid", "cfid of the ContentDirectoryRoot file");
+ "SELECT stable_id FROM content_layout_serialized_files WHERE file_index = 10",
+ "52b43dad178849b42ac753005736e7bb", "stable id of the ContentDirectoryRoot file");
SQLTestHelper.AssertQueryString(db,
- "SELECT content_hash FROM content_layout_serialized_files WHERE file_index = 5",
- "c0152db4dd710be51b2decb997325f34", "content hash of the ContentDirectoryRoot file");
+ @"SELECT ba.content_hash FROM content_layout_serialized_files f
+ INNER JOIN content_layout_binary_artifacts ba ON ba.artifact_index = f.artifact_index
+ WHERE f.file_index = 10",
+ "eb3abd5ab5b0d790980fe9e9df872484", "artifact link of the ContentDirectoryRoot file");
SQLTestHelper.AssertQueryString(db,
@"SELECT GROUP_CONCAT(dependency_index) FROM (
SELECT dependency_index FROM content_layout_serialized_file_dependencies
- WHERE serialized_file_index = 5 ORDER BY position)",
- "8,4,11,13,7", "dependency order must match the json array order");
+ WHERE serialized_file_index = 10 ORDER BY position)",
+ "4,5,2,9,3", "dependency order must match the json array order");
SQLTestHelper.AssertQueryString(db,
- "SELECT asset_path FROM content_layout_source_assets WHERE serialized_file_index = 5",
+ "SELECT asset_path FROM content_layout_source_assets WHERE serialized_file_index = 10",
"Assets/ScriptableObjects/ContentDirectoryRoot.asset", "source asset of the root file");
- // RootAssets is folded into the is_root_asset flag.
- SQLTestHelper.AssertQueryString(db,
- "SELECT asset_path FROM content_layout_loadable_objects WHERE is_root_asset = 1",
- "Assets/ScriptableObjects/ContentDirectoryRoot.asset",
+ // RootAssets is folded into is_root_asset, which holds the 1-based root position
+ // (json RootAssets: [2]).
+ SQLTestHelper.AssertQueryInt(db,
+ "SELECT loadable_index FROM content_layout_loadable_objects WHERE is_root_asset = 1", 2,
"ContentDirectoryRoot is the only root asset");
+ SQLTestHelper.AssertQueryString(db,
+ "SELECT guid FROM content_layout_loadable_objects WHERE is_root_asset = 1",
+ "52b43dad178849b42ac753005736e7bb", "the root loadable records its source asset guid");
+
+ // The loadable dependencies are indices into the loadables (json file 5: [0, 1]).
+ SQLTestHelper.AssertQueryString(db,
+ @"SELECT GROUP_CONCAT(loadable_index) FROM (
+ SELECT loadable_index FROM content_layout_loadable_dependencies
+ WHERE serialized_file_index = 5 ORDER BY loadable_index)",
+ "0,1", "loadable dependencies reference loadables by index");
+
+ // v3 layouts do not record the source asset path or source lfid of a loadable, so the
+ // v2-only columns must not exist in this database.
+ SQLTestHelper.AssertQueryInt(db,
+ @"SELECT COUNT(*) FROM pragma_table_info('content_layout_loadable_objects')
+ WHERE name IN ('asset_path', 'source_lfid')", 0,
+ "the v2-only columns exist only in databases imported from a v2 layout");
// Even without the build content, every non-built-in entry links to a serialized_files
// row: a placeholder holding just the filename (archive NULL, no objects).
@@ -117,9 +137,9 @@ SELECT dependency_index FROM content_layout_serialized_file_dependencies
"SELECT COUNT(*) FROM content_layout_serialized_files WHERE is_builtin = 0 AND serialized_file IS NULL", 0,
"a layout-only analyze links every entry to a placeholder row");
SQLTestHelper.AssertQueryInt(db,
- @"SELECT COUNT(*) FROM content_layout_serialized_files f
- INNER JOIN serialized_files sf ON sf.id = f.serialized_file
- WHERE sf.name != f.content_hash || '.cf' OR sf.archive IS NOT NULL", 0,
+ @"SELECT COUNT(*) FROM content_layout_serialized_files_view v
+ INNER JOIN serialized_files sf ON sf.id = v.serialized_file
+ WHERE sf.name != v.filename OR sf.archive IS NOT NULL", 0,
"placeholder rows hold the filename and no archive");
SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM objects", 0,
"a layout-only analyze produces no objects");
@@ -131,7 +151,7 @@ SELECT dependency_index FROM content_layout_serialized_file_dependencies
13, "one contentfile artifact per non-built-in serialized file");
SQLTestHelper.AssertQueryString(db,
"SELECT filename FROM content_layout_binary_artifacts_view WHERE category = 'manifest'",
- "baff06b928d147276f2245dd3b19216a.json", "the artifact filename is derived from the category");
+ "e320fc78984f8430afa90a591fd02004.json", "the artifact filename is derived from the category");
// Views are created with the tables and their joins produce the expected rows.
SQLTestHelper.AssertViewExists(db, "content_layout_serialized_files_view");
@@ -148,16 +168,15 @@ SELECT dependency_index FROM content_layout_serialized_file_dependencies
"Library/unity default resources", "built-in entries show their path as the filename");
SQLTestHelper.AssertQueryString(db,
@"SELECT dependency_filename FROM content_layout_serialized_file_dependencies_view
- WHERE serialized_file_index = 2 AND position = 1",
+ WHERE serialized_file_index = 6 AND position = 1",
"Library/unity default resources", "dependencies on built-in entries resolve to their path");
SQLTestHelper.AssertQueryString(db,
- @"SELECT filename FROM content_layout_loadable_objects_view
- WHERE asset_path = 'Assets/ScriptableObjects/ContentDirectoryRoot.asset'",
- "c0152db4dd710be51b2decb997325f34.cf", "the loadable view resolves the containing file");
+ @"SELECT filename FROM content_layout_loadable_objects_view WHERE loadable_index = 2",
+ "eb3abd5ab5b0d790980fe9e9df872484.cf", "the loadable view resolves the containing file");
SQLTestHelper.AssertQueryString(db,
@"SELECT dependency_filename FROM content_layout_serialized_file_dependencies_view
- WHERE serialized_file_index = 5 AND position = 1",
- "86d71ff2bb38e064697257d35d6421b8.cf", "the dependencies view resolves target filenames");
+ WHERE serialized_file_index = 10 AND position = 1",
+ "79bdcb3e9bc659d3519594280240cd1b.cf", "the dependencies view resolves target filenames");
}
[Test]
@@ -188,12 +207,10 @@ AND NOT EXISTS (SELECT 1 FROM objects o WHERE o.serialized_file = f.serialized_f
"SELECT COUNT(*) FROM content_layout_loadable_objects_view WHERE object IS NULL", 0,
"every loadable should resolve to an analyzed object");
SQLTestHelper.AssertQueryString(db,
- @"SELECT name FROM content_layout_loadable_objects_view
- WHERE asset_path = 'Assets/ScriptableObjects/ContentDirectoryRoot.asset'",
+ "SELECT name FROM content_layout_loadable_objects_view WHERE is_root_asset = 1",
"ContentDirectoryRoot", "the root loadable resolves to the root ScriptableObject");
SQLTestHelper.AssertQueryString(db,
- @"SELECT type FROM content_layout_loadable_objects_view
- WHERE asset_path = 'Assets/ScriptableObjects/ContentDirectoryRoot.asset'",
+ "SELECT type FROM content_layout_loadable_objects_view WHERE is_root_asset = 1",
"MonoBehaviour", "ScriptableObjects are serialized as MonoBehaviour");
// The layout resolves the .cfid placeholder references, so the only dangling targets
@@ -227,25 +244,6 @@ WHERE EXISTS (SELECT 1 FROM objects o WHERE o.serialized_file = f.serialized_fil
"both loadable scenes should link to analyzed content files");
}
- // Runs a command capturing stderr, where analyze prints its warnings.
- private static async Task<(int exitCode, string stdErr)> RunCapturingStdErr(params string[] args)
- {
- using var sw = new StringWriter();
- var currentError = Console.Error;
- int exitCode;
- try
- {
- Console.SetError(sw);
- exitCode = await Program.Main(args);
- }
- finally
- {
- Console.SetError(currentError);
- }
-
- return (exitCode, sw.ToString());
- }
-
[Test]
public async Task Analyze_ContentDirectoryWithoutLayout_WarnsAndRecordsDanglingRefs()
{
@@ -253,7 +251,7 @@ public async Task Analyze_ContentDirectoryWithoutLayout_WarnsAndRecordsDanglingR
"Data", "LeadingEdgeBuilds", "ContentDirectory");
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
- var (exitCode, stdErr) = await RunCapturingStdErr("analyze", contentDirectory, "-o", databasePath);
+ var (exitCode, stdErr) = await ConsoleTestHelper.RunCapturingStdErr("analyze", contentDirectory, "-o", databasePath);
Assert.AreEqual(0, exitCode, "a ContentDirectory without its layout is still analyzable");
Assert.That(stdErr, Does.Contain("without its ContentLayout.json"),
@@ -279,7 +277,7 @@ public async Task Analyze_MultipleContentDirectories_Fails()
"Data", "contentdirectory-zstd");
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
- var (exitCode, stdErr) = await RunCapturingStdErr(
+ var (exitCode, stdErr) = await ConsoleTestHelper.RunCapturingStdErr(
"analyze", contentDirectory, otherBuild, "-o", databasePath);
Assert.AreEqual(1, exitCode, "analyzing two different ContentDirectory builds must fail");
@@ -295,7 +293,7 @@ public async Task Analyze_ContentDirectoryWithWrongLayout_Fails()
"Data", "contentdirectory-zstd");
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
- var (exitCode, stdErr) = await RunCapturingStdErr(
+ var (exitCode, stdErr) = await ConsoleTestHelper.RunCapturingStdErr(
"analyze", otherBuild, m_ContentLayoutPath, "-o", databasePath);
Assert.AreEqual(1, exitCode, "a layout that does not match the build must not be used");
@@ -310,12 +308,12 @@ public async Task Analyze_MultipleLayoutCandidates_SelectsTheMatchingHash()
var staleFolder = Path.Combine(m_TestOutputFolder, "stale_layout");
Directory.CreateDirectory(staleFolder);
File.WriteAllText(Path.Combine(staleFolder, "ContentLayout.json"),
- "{\"Version\":2,\"BuildManifestHash\":\"deadbeefdeadbeefdeadbeefdeadbeef\"}");
+ "{\"Version\":3,\"BuildManifestHash\":\"deadbeefdeadbeefdeadbeefdeadbeef\"}");
var contentDirectory = Path.Combine(TestContext.CurrentContext.TestDirectory,
"Data", "LeadingEdgeBuilds", "ContentDirectory");
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
- var (exitCode, stdErr) = await RunCapturingStdErr(
+ var (exitCode, stdErr) = await ConsoleTestHelper.RunCapturingStdErr(
"analyze", staleFolder, m_ContentLayoutPath, contentDirectory, "-o", databasePath);
Assert.AreEqual(0, exitCode);
@@ -323,7 +321,7 @@ public async Task Analyze_MultipleLayoutCandidates_SelectsTheMatchingHash()
using var db = SQLTestHelper.OpenDatabase(databasePath);
SQLTestHelper.AssertQueryString(db, "SELECT build_manifest_hash FROM content_layout",
- "baff06b928d147276f2245dd3b19216a", "the matching layout should be the imported one");
+ "e320fc78984f8430afa90a591fd02004", "the matching layout should be the imported one");
}
[Test]
@@ -336,7 +334,7 @@ public async Task Analyze_SubsetOfContentDirectoryWithLayout_ResolvesReferences(
"Data", "LeadingEdgeBuilds", "ContentDirectory");
var subsetFolder = Path.Combine(m_TestOutputFolder, "subset");
Directory.CreateDirectory(subsetFolder);
- const string rootFile = "c0152db4dd710be51b2decb997325f34.cf";
+ const string rootFile = "eb3abd5ab5b0d790980fe9e9df872484.cf";
File.Copy(Path.Combine(contentDirectory, rootFile), Path.Combine(subsetFolder, rootFile));
File.Copy(Path.Combine(contentDirectory, "BuildManifestHash.txt"),
Path.Combine(subsetFolder, "BuildManifestHash.txt"));
@@ -368,11 +366,11 @@ public async Task Analyze_SubsetWithLayoutButNoHashFile_Fails()
"Data", "LeadingEdgeBuilds", "ContentDirectory");
var subsetFolder = Path.Combine(m_TestOutputFolder, "subset_no_hash");
Directory.CreateDirectory(subsetFolder);
- const string rootFile = "c0152db4dd710be51b2decb997325f34.cf";
+ const string rootFile = "eb3abd5ab5b0d790980fe9e9df872484.cf";
File.Copy(Path.Combine(contentDirectory, rootFile), Path.Combine(subsetFolder, rootFile));
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
- var (exitCode, stdErr) = await RunCapturingStdErr(
+ var (exitCode, stdErr) = await ConsoleTestHelper.RunCapturingStdErr(
"analyze", subsetFolder, m_ContentLayoutPath, "-o", databasePath);
Assert.AreEqual(1, exitCode);
@@ -387,22 +385,11 @@ public async Task FindRefs_ContentDirectoryWithLayout_WalksCrossFileChain()
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
Assert.AreEqual(0, await Program.Main(new string[] { "analyze", m_ContentLayoutPath, contentDirectory, "-o", databasePath }));
- using var sw = new StringWriter();
- var currentOut = Console.Out;
- int exitCode;
- try
- {
- Console.SetOut(sw);
- exitCode = await Program.Main(new string[]
- { "find-refs", databasePath, "-n", "SerializationDemo", "-t", "MonoBehaviour", "--stdout" });
- }
- finally
- {
- Console.SetOut(currentOut);
- }
+ var (exitCode, stdOut) = await ConsoleTestHelper.RunCapturingStdOut(
+ "find-refs", databasePath, "-n", "SerializationDemo", "-t", "MonoBehaviour", "--stdout");
Assert.AreEqual(0, exitCode);
- Assert.That(sw.ToString(), Does.Contain("ContentDirectoryRoot"),
+ Assert.That(stdOut, Does.Contain("ContentDirectoryRoot"),
"the chain from the root asset should be found across content files");
}
@@ -466,7 +453,7 @@ public async Task Analyze_MultipleContentLayoutsWithoutBuild_Fails()
File.Copy(m_ContentLayoutPath, Path.Combine(folderB, "ContentLayout.json"));
var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
- var (exitCode, stdErr) = await RunCapturingStdErr(
+ var (exitCode, stdErr) = await ConsoleTestHelper.RunCapturingStdErr(
"analyze", folderA, folderB, "-o", databasePath);
Assert.AreEqual(1, exitCode, "multiple layouts without build content cannot be disambiguated");
diff --git a/UnityDataTool.Tests/AnalyzeContentLayoutV2Tests.cs b/UnityDataTool.Tests/AnalyzeContentLayoutV2Tests.cs
new file mode 100644
index 0000000..2f1ecb9
--- /dev/null
+++ b/UnityDataTool.Tests/AnalyzeContentLayoutV2Tests.cs
@@ -0,0 +1,151 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Data.Sqlite;
+using NUnit.Framework;
+
+namespace UnityDataTools.UnityDataTool.Tests;
+
+#pragma warning disable NUnit2005, NUnit2006
+
+// Tests the import of a version 2 ContentLayout.json (Unity 6.6) into the content_layout* tables
+// (issue #131). v2 files are upgraded to the current model on import: hash-based references become
+// indices and the stable ids lose their ".cfid" extension, so both versions produce the same
+// schema. The v2-only source data (asset path, source lfid) lands in extra columns that only exist
+// in a database imported from a v2 file. Runs against the archived v2 layout of the LeadingEdge
+// reference build in TestCommon/Data/ContentLayoutVersions/v2 (its build content is not archived,
+// so this is a layout-only analyze).
+public class AnalyzeContentLayoutV2Tests
+{
+ private string m_TestOutputFolder;
+ private string m_ContentLayoutPath;
+
+ [OneTimeSetUp]
+ public void OneTimeSetup()
+ {
+ m_TestOutputFolder = Path.Combine(TestContext.CurrentContext.TestDirectory, "content_layout_v2_test_folder");
+ m_ContentLayoutPath = Path.Combine(TestContext.CurrentContext.TestDirectory,
+ "Data", "ContentLayoutVersions", "v2", "ContentLayout.json");
+ Directory.CreateDirectory(m_TestOutputFolder);
+ Directory.SetCurrentDirectory(m_TestOutputFolder);
+ }
+
+ [TearDown]
+ public void Teardown()
+ {
+ SqliteConnection.ClearAllPools();
+ var outputFolder = new DirectoryInfo(m_TestOutputFolder);
+ outputFolder.EnumerateFiles().ToList().ForEach(f => f.Delete());
+ outputFolder.EnumerateDirectories().ToList().ForEach(d => d.Delete(true));
+ }
+
+ [Test]
+ public async Task Analyze_V2ContentLayout_ImportsUpgradedLayoutTables()
+ {
+ var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
+
+ Assert.AreEqual(0, await Program.Main(new string[] { "analyze", m_ContentLayoutPath, "-o", databasePath }));
+ using var db = SQLTestHelper.OpenDatabase(databasePath);
+
+ // The original file version is recorded, not the version it was upgraded to.
+ SQLTestHelper.AssertQueryInt(db, "SELECT version FROM content_layout", 2,
+ "the original schema version should be recorded");
+ SQLTestHelper.AssertQueryString(db, "SELECT build_manifest_hash FROM content_layout",
+ "baff06b928d147276f2245dd3b19216a", "the BuildManifestHash should be recorded");
+
+ // Row counts of each table, matching the json content.
+ SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM content_layout_serialized_files", 14,
+ "one row per SerializedFiles entry");
+ SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM content_layout_source_assets", 16,
+ "one row per source asset per file");
+ SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM content_layout_loadable_objects", 3,
+ "one row per LoadableObjectIds entry");
+ SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM content_layout_binary_artifacts", 18,
+ "one row per BinaryArtifacts entry");
+
+ // The v2 ".cfid" extension is stripped so stable_id is uniform across layout versions.
+ // The built-in entry keeps its human-readable path.
+ SQLTestHelper.AssertQueryString(db,
+ "SELECT stable_id FROM content_layout_serialized_files WHERE file_index = 5",
+ "52b43dad178849b42ac753005736e7bb", "stable_id should not carry the .cfid extension");
+ SQLTestHelper.AssertQueryString(db,
+ "SELECT stable_id FROM content_layout_serialized_files WHERE file_index = 0",
+ "Library/unity default resources", "the built-in entry should be at index 0");
+
+ // The v2 ContentHash is resolved to the index of the matching contentfile artifact.
+ SQLTestHelper.AssertQueryString(db,
+ @"SELECT ba.content_hash FROM content_layout_serialized_files f
+ INNER JOIN content_layout_binary_artifacts ba ON ba.artifact_index = f.artifact_index
+ WHERE f.file_index = 5",
+ "c0152db4dd710be51b2decb997325f34", "the ContentHash becomes a reference to its artifact");
+ SQLTestHelper.AssertQueryInt(db,
+ "SELECT COUNT(*) FROM content_layout_serialized_files WHERE is_builtin = 1 AND artifact_index IS NOT NULL", 0,
+ "built-in entries have no artifact");
+
+ // The v2 ObjectIdHash references are resolved to loadable indices during import
+ // (json LoadableDependencies for file 4 reference the loadables at indices 0 and 2).
+ SQLTestHelper.AssertQueryString(db,
+ @"SELECT GROUP_CONCAT(loadable_index) FROM (
+ SELECT loadable_index FROM content_layout_loadable_dependencies
+ WHERE serialized_file_index = 4 ORDER BY loadable_index)",
+ "0,2", "hash-based loadable dependencies resolve to loadable indices");
+
+ // The loadables keep their json array position as the key. lfid is the id of the object
+ // in its output file (the v2 OutputLFID); the v2 source lfid is preserved separately.
+ SQLTestHelper.AssertQueryString(db,
+ "SELECT asset_path FROM content_layout_loadable_objects WHERE loadable_index = 1",
+ "Assets/ScriptableObjects/ContentDirectoryRoot.asset", "v2 layouts record the loadable's asset path");
+ SQLTestHelper.AssertQueryString(db,
+ "SELECT lfid FROM content_layout_loadable_objects WHERE loadable_index = 1",
+ "-775554941117088049", "lfid holds the output-file lfid");
+ SQLTestHelper.AssertQueryString(db,
+ "SELECT source_lfid FROM content_layout_loadable_objects WHERE loadable_index = 1",
+ "11400000", "v2 layouts record the source lfid");
+
+ // The single RootAssets entry names the ContentDirectoryRoot loadable; its 1-based
+ // position lands in is_root_asset.
+ SQLTestHelper.AssertQueryString(db,
+ "SELECT asset_path FROM content_layout_loadable_objects WHERE is_root_asset = 1",
+ "Assets/ScriptableObjects/ContentDirectoryRoot.asset",
+ "ContentDirectoryRoot is the only root asset");
+ SQLTestHelper.AssertQueryInt(db,
+ "SELECT COUNT(*) FROM content_layout_loadable_objects WHERE is_root_asset = 0", 2,
+ "the other loadables are not roots");
+
+ // The v2-only columns appear in the loadables view as well.
+ SQLTestHelper.AssertQueryString(db,
+ @"SELECT filename FROM content_layout_loadable_objects_view
+ WHERE asset_path = 'Assets/ScriptableObjects/ContentDirectoryRoot.asset'",
+ "c0152db4dd710be51b2decb997325f34.cf", "the loadable view resolves the containing file");
+
+ // Layout-only analyze still links every non-built-in entry to a placeholder row.
+ SQLTestHelper.AssertQueryInt(db,
+ "SELECT COUNT(*) FROM content_layout_serialized_files WHERE is_builtin = 0 AND serialized_file IS NULL", 0,
+ "a layout-only analyze links every entry to a placeholder row");
+
+ // The views derive filenames through the artifact link.
+ SQLTestHelper.AssertQueryString(db,
+ "SELECT filename FROM content_layout_serialized_files_view WHERE file_index = 5",
+ "c0152db4dd710be51b2decb997325f34.cf", "filenames are derived from the linked artifact");
+ SQLTestHelper.AssertQueryString(db,
+ "SELECT filename FROM content_layout_serialized_files_view WHERE is_builtin = 1",
+ "Library/unity default resources", "built-in entries show their path as the filename");
+ }
+
+ [Test]
+ public async Task Analyze_V1ContentLayout_ImportsNothing()
+ {
+ var layoutFolder = Path.Combine(m_TestOutputFolder, "v1_layout");
+ Directory.CreateDirectory(layoutFolder);
+ File.WriteAllText(Path.Combine(layoutFolder, "ContentLayout.json"),
+ "{\"Version\":1,\"BuildManifestHash\":\"deadbeefdeadbeefdeadbeefdeadbeef\"}");
+ var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder);
+
+ var (exitCode, stdErr) = await ConsoleTestHelper.RunCapturingStdErr("analyze", layoutFolder, "-o", databasePath);
+
+ Assert.AreEqual(1, exitCode, "a layout-only analyze of an unsupported version has nothing to import");
+ Assert.That(stdErr, Does.Contain("Unsupported ContentLayout.json version 1"));
+ Assert.That(stdErr, Does.Contain("versions 2 and 3"), "the supported versions should be named");
+ }
+}
diff --git a/UnityDataTool.Tests/BuildReportTests.cs b/UnityDataTool.Tests/BuildReportTests.cs
index 396ee0b..4c1e927 100644
--- a/UnityDataTool.Tests/BuildReportTests.cs
+++ b/UnityDataTool.Tests/BuildReportTests.cs
@@ -24,7 +24,7 @@ public void OneTimeSetup()
m_TestOutputFolder = Path.Combine(TestContext.CurrentContext.TestDirectory, "test_folder");
m_TestDataFolder = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "BuildReports");
var leadingEdge = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "LeadingEdgeBuilds");
- m_ContentDirectoryReport = Path.Combine(leadingEdge, "BuildReport-ContentDirectory", "f64157fb08bb9f645971d39c1203bd03.buildreport");
+ m_ContentDirectoryReport = Path.Combine(leadingEdge, "BuildReport-ContentDirectory", "f096bba0b9ce4c44194edd738d64b9df.buildreport");
m_AssetBundleReport = Path.Combine(leadingEdge, "BuildReport-AssetBundles", "LastBuild.buildreport");
Directory.CreateDirectory(m_TestOutputFolder);
Directory.SetCurrentDirectory(m_TestOutputFolder);
@@ -622,9 +622,9 @@ public async Task Analyze_BuildReport_ContentDirectory_ContainsNewSummaryColumns
"Unexpected build_content_options");
// The session GUID matches the report's GUID-based filename.
SQLTestHelper.AssertQueryString(db, "SELECT build_session_guid FROM build_reports",
- "f64157fb08bb9f645971d39c1203bd03", "Unexpected build_session_guid");
+ "f096bba0b9ce4c44194edd738d64b9df", "Unexpected build_session_guid");
SQLTestHelper.AssertQueryString(db, "SELECT build_manifest_hash FROM build_reports",
- "baff06b928d147276f2245dd3b19216a", "Unexpected build_manifest_hash");
+ "e320fc78984f8430afa90a591fd02004", "Unexpected build_manifest_hash");
// No build profile was active: the path is present-but-empty, and the all-zero GUID -> NULL.
SQLTestHelper.AssertQueryString(db, "SELECT build_profile_path FROM build_reports",
"", "Expected empty build_profile_path");
@@ -686,7 +686,7 @@ public async Task Analyze_BuildReport_ContentDirectory_ContainsContentSummary()
"Unexpected number of asset stats rows");
// Spot-check the cross-build stats.
- SQLTestHelper.AssertQueryInt(db, "SELECT serialized_file_size FROM build_report_content_summary", 158832,
+ SQLTestHelper.AssertQueryInt(db, "SELECT serialized_file_size FROM build_report_content_summary", 99584,
"Unexpected serialized_file_size");
SQLTestHelper.AssertQueryInt(db, "SELECT object_count FROM build_report_content_summary", 28,
"Unexpected object_count");
@@ -749,7 +749,7 @@ public async Task Analyze_BuildReport_MultipleReports_ContentSummaryJoinsCorrect
// Each report's type stats resolve to a build report in the matching serialized file.
SQLTestHelper.AssertQueryInt(db,
@"SELECT COUNT(*) FROM build_report_content_type_stats_view
- WHERE build_report_filename = 'f64157fb08bb9f645971d39c1203bd03.buildreport'
+ WHERE build_report_filename = 'f096bba0b9ce4c44194edd738d64b9df.buildreport'
AND type_name = 'Cubemap'",
1, "Expected the ContentDirectory report's Cubemap type stat");
diff --git a/UnityDataTool.Tests/ConsoleTestHelper.cs b/UnityDataTool.Tests/ConsoleTestHelper.cs
new file mode 100644
index 0000000..f0e13da
--- /dev/null
+++ b/UnityDataTool.Tests/ConsoleTestHelper.cs
@@ -0,0 +1,53 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace UnityDataTools.UnityDataTool.Tests;
+
+///
+/// Helpers for running UnityDataTool commands while capturing their console output.
+///
+public static class ConsoleTestHelper
+{
+ ///
+ /// Runs a command capturing stderr, where analyze prints its warnings and errors.
+ ///
+ public static async Task<(int exitCode, string stdErr)> RunCapturingStdErr(params string[] args)
+ {
+ using var sw = new StringWriter();
+ var currentError = Console.Error;
+ int exitCode;
+ try
+ {
+ Console.SetError(sw);
+ exitCode = await Program.Main(args);
+ }
+ finally
+ {
+ Console.SetError(currentError);
+ }
+
+ return (exitCode, sw.ToString());
+ }
+
+ ///
+ /// Runs a command capturing stdout.
+ ///
+ public static async Task<(int exitCode, string stdOut)> RunCapturingStdOut(params string[] args)
+ {
+ using var sw = new StringWriter();
+ var currentOut = Console.Out;
+ int exitCode;
+ try
+ {
+ Console.SetOut(sw);
+ exitCode = await Program.Main(args);
+ }
+ finally
+ {
+ Console.SetOut(currentOut);
+ }
+
+ return (exitCode, sw.ToString());
+ }
+}
diff --git a/UnityDataTool.Tests/DumpTests.cs b/UnityDataTool.Tests/DumpTests.cs
index d077ab9..4e309d7 100644
--- a/UnityDataTool.Tests/DumpTests.cs
+++ b/UnityDataTool.Tests/DumpTests.cs
@@ -31,7 +31,7 @@ public void OneTimeSetup()
m_NoTypeTreeSerializedFilePath = Path.Combine(m_TestDataFolder, "PlayerNoTypeTree", "level0");
m_NoTypeTreeArchivePath = Path.Combine(m_TestDataFolder, "AssetBundleTypeTreeVariations", "AssetBundle-NoTypeTree", "small.bundle");
m_SerializationDemoBundlePath = Path.Combine(m_TestDataFolder, "LeadingEdgeBuilds", "AssetBundles", "serializationdemo");
- m_ContentDirectoryBuildReportPath = Path.Combine(m_TestDataFolder, "LeadingEdgeBuilds", "BuildReport-ContentDirectory", "f64157fb08bb9f645971d39c1203bd03.buildreport");
+ m_ContentDirectoryBuildReportPath = Path.Combine(m_TestDataFolder, "LeadingEdgeBuilds", "BuildReport-ContentDirectory", "f096bba0b9ce4c44194edd738d64b9df.buildreport");
}
[Test]
@@ -536,9 +536,9 @@ public async Task Dump_Stdout_BuildReport_PrintsGuidAndHash128AsHexStrings()
var output = sw.ToString();
- Assert.That(output, Does.Contain("buildSessionGUID (GUID) f64157fb08bb9f645971d39c1203bd03"));
+ Assert.That(output, Does.Contain("buildSessionGUID (GUID) f096bba0b9ce4c44194edd738d64b9df"));
Assert.That(output, Does.Contain("sourceAssetGUID (GUID) 162c015549f8733449ac70ae78ad3aa5"));
- Assert.That(output, Does.Contain("buildManifestHash (Hash128) baff06b928d147276f2245dd3b19216a"));
+ Assert.That(output, Does.Contain("buildManifestHash (Hash128) e320fc78984f8430afa90a591fd02004"));
// The individual fields of these compound types are no longer dumped.
Assert.That(output, Does.Not.Contain("data[0] (unsigned int)"));