From 6fba947ffc1324e74f8016bd9772ae2f0f89f7d6 Mon Sep 17 00:00:00 2001 From: Andrew Skowronski Date: Wed, 22 Jul 2026 17:15:14 -0400 Subject: [PATCH 1/5] [#107] Add Unity 6.6 BuildReport Summary fields and ContentSummary tables Part 1: extend build_reports with the new 6.6 BuildSummary fields (build_name, build_content_options, build_session_guid, build_manifest_hash, build_profile_path, build_profile_guid, data_path), left NULL for older reports. Part 2: add on-demand build_report_content_* tables and views for the new ContentSummary object (cross-build stats, per-type stats, per-source-asset stats). --- Analyzer/Properties/Resources.Designer.cs | 6 + Analyzer/Properties/Resources.resx | 3 + Analyzer/Resources/BuildReport.sql | 8 + Analyzer/Resources/ContentSummary.sql | 72 ++++++++ .../SQLite/Handlers/BuildReportHandler.cs | 20 ++- .../SQLite/Handlers/ContentSummaryHandler.cs | 158 +++++++++++++++++ .../Writers/SerializedFileSQLiteWriter.cs | 1 + Analyzer/SerializedObjects/BuildReport.cs | 54 ++++++ Analyzer/SerializedObjects/ContentSummary.cs | 90 ++++++++++ Documentation/buildreport.md | 12 +- UnityBinaryFormat/GuidHelper.cs | 16 ++ UnityDataTool.Tests/BuildReportTests.cs | 165 ++++++++++++++++++ 12 files changed, 602 insertions(+), 3 deletions(-) create mode 100644 Analyzer/Resources/ContentSummary.sql create mode 100644 Analyzer/SQLite/Handlers/ContentSummaryHandler.cs create mode 100644 Analyzer/SerializedObjects/ContentSummary.cs diff --git a/Analyzer/Properties/Resources.Designer.cs b/Analyzer/Properties/Resources.Designer.cs index 57f5822..d9a101a 100644 --- a/Analyzer/Properties/Resources.Designer.cs +++ b/Analyzer/Properties/Resources.Designer.cs @@ -359,5 +359,11 @@ internal static string PackedAssets { return ResourceManager.GetString("PackedAssets", resourceCulture); } } + + internal static string ContentSummary { + get { + return ResourceManager.GetString("ContentSummary", resourceCulture); + } + } } } diff --git a/Analyzer/Properties/Resources.resx b/Analyzer/Properties/Resources.resx index 24d30da..0a616aa 100644 --- a/Analyzer/Properties/Resources.resx +++ b/Analyzer/Properties/Resources.resx @@ -268,4 +268,7 @@ ..\Resources\PackedAssets.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + ..\Resources\ContentSummary.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + diff --git a/Analyzer/Resources/BuildReport.sql b/Analyzer/Resources/BuildReport.sql index d25bc72..79d216e 100644 --- a/Analyzer/Resources/BuildReport.sql +++ b/Analyzer/Resources/BuildReport.sql @@ -15,6 +15,14 @@ CREATE TABLE IF NOT EXISTS build_reports( asset_bundle_options INTEGER, output_path TEXT, crc INTEGER, + -- Fields added in Unity 6.6; left NULL when analyzing reports from older versions. + build_name TEXT, + build_content_options INTEGER, + build_session_guid TEXT, + build_manifest_hash TEXT, + build_profile_path TEXT, + build_profile_guid TEXT, + data_path TEXT, PRIMARY KEY (id) ); diff --git a/Analyzer/Resources/ContentSummary.sql b/Analyzer/Resources/ContentSummary.sql new file mode 100644 index 0000000..9c684f0 --- /dev/null +++ b/Analyzer/Resources/ContentSummary.sql @@ -0,0 +1,72 @@ +CREATE TABLE IF NOT EXISTS build_report_content_summary( + id INTEGER, + serialized_file_size INTEGER, + reused_serialized_file_size INTEGER, + resource_data_size INTEGER, + header_size INTEGER, + serialized_file_count INTEGER, + reused_serialized_file_count INTEGER, + resource_file_count INTEGER, + object_count INTEGER, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS build_report_content_type_stats( + content_summary_id INTEGER NOT NULL, + type INTEGER NOT NULL, + size INTEGER, + object_count INTEGER, + resource_count INTEGER, + PRIMARY KEY (content_summary_id, type), + FOREIGN KEY (content_summary_id) REFERENCES build_report_content_summary(id) +); + +CREATE TABLE IF NOT EXISTS build_report_content_asset_stats( + content_summary_id INTEGER NOT NULL, + source_asset_guid TEXT, + source_asset_path TEXT, + size INTEGER, + object_count INTEGER, + resource_count INTEGER, + FOREIGN KEY (content_summary_id) REFERENCES build_report_content_summary(id) +); + +-- Cross-build statistics with the owning BuildReport resolved. build_report_id is the id of the +-- BuildReport object (type 1125) in the same serialized file as the ContentSummary; it is not +-- stored on the table, so this view computes it the same way build_report_packed_assets_view does. +CREATE VIEW build_report_content_summary_view AS +SELECT + cs.id AS content_summary_id, + br_obj.id AS build_report_id, + sf.name AS build_report_filename, + cs.serialized_file_size, + cs.reused_serialized_file_size, + cs.resource_data_size, + cs.header_size, + cs.serialized_file_count, + cs.reused_serialized_file_count, + cs.resource_file_count, + cs.object_count +FROM build_report_content_summary cs +INNER JOIN objects o ON cs.id = o.id +INNER JOIN serialized_files sf ON o.serialized_file = sf.id +LEFT JOIN objects br_obj ON o.serialized_file = br_obj.serialized_file AND br_obj.type = 1125; + +-- Per-type statistics with the type name resolved (from TypeIdRegistry or TypeTree analysis) and +-- the owning BuildReport, so a single build's type breakdown can be selected by build_report_id. +CREATE VIEW build_report_content_type_stats_view AS +SELECT + cs.id AS content_summary_id, + br_obj.id AS build_report_id, + sf.name AS build_report_filename, + cts.type, + COALESCE(t.name, CAST(cts.type AS TEXT)) AS type_name, + cts.size, + cts.object_count, + cts.resource_count +FROM build_report_content_type_stats cts +INNER JOIN build_report_content_summary cs ON cts.content_summary_id = cs.id +INNER JOIN objects o ON cs.id = o.id +INNER JOIN serialized_files sf ON o.serialized_file = sf.id +LEFT JOIN objects br_obj ON o.serialized_file = br_obj.serialized_file AND br_obj.type = 1125 +LEFT JOIN types t ON cts.type = t.id; diff --git a/Analyzer/SQLite/Handlers/BuildReportHandler.cs b/Analyzer/SQLite/Handlers/BuildReportHandler.cs index e4a4d11..c50b10f 100644 --- a/Analyzer/SQLite/Handlers/BuildReportHandler.cs +++ b/Analyzer/SQLite/Handlers/BuildReportHandler.cs @@ -24,11 +24,13 @@ public void Init(SqliteConnection db) m_InsertCommand.CommandText = @"INSERT INTO build_reports( id, build_type, build_result, platform_name, subtarget, start_time, end_time, total_time_seconds, total_size, build_guid, total_errors, total_warnings, options, asset_bundle_options, - output_path, crc + output_path, crc, build_name, build_content_options, build_session_guid, build_manifest_hash, + build_profile_path, build_profile_guid, data_path ) VALUES( @id, @build_type, @build_result, @platform_name, @subtarget, @start_time, @end_time, @total_time_seconds, @total_size, @build_guid, @total_errors, @total_warnings, @options, @asset_bundle_options, - @output_path, @crc + @output_path, @crc, @build_name, @build_content_options, @build_session_guid, @build_manifest_hash, + @build_profile_path, @build_profile_guid, @data_path )"; m_InsertCommand.Parameters.Add("@id", SqliteType.Integer); @@ -47,6 +49,13 @@ public void Init(SqliteConnection db) m_InsertCommand.Parameters.Add("@asset_bundle_options", SqliteType.Integer); m_InsertCommand.Parameters.Add("@output_path", SqliteType.Text); m_InsertCommand.Parameters.Add("@crc", SqliteType.Integer); + m_InsertCommand.Parameters.Add("@build_name", SqliteType.Text); + m_InsertCommand.Parameters.Add("@build_content_options", SqliteType.Integer); + m_InsertCommand.Parameters.Add("@build_session_guid", SqliteType.Text); + m_InsertCommand.Parameters.Add("@build_manifest_hash", SqliteType.Text); + m_InsertCommand.Parameters.Add("@build_profile_path", SqliteType.Text); + m_InsertCommand.Parameters.Add("@build_profile_guid", SqliteType.Text); + m_InsertCommand.Parameters.Add("@data_path", SqliteType.Text); m_InsertFileCommand = db.CreateCommand(); m_InsertFileCommand.CommandText = @"INSERT INTO build_report_files( @@ -110,6 +119,13 @@ public void Process(Context ctx, long objectId, RandomAccessReader reader, out s m_InsertCommand.Parameters["@asset_bundle_options"].Value = buildReport.AssetBundleOptions; m_InsertCommand.Parameters["@output_path"].Value = buildReport.OutputPath; m_InsertCommand.Parameters["@crc"].Value = buildReport.Crc; + m_InsertCommand.Parameters["@build_name"].Value = (object)buildReport.BuildName ?? DBNull.Value; + m_InsertCommand.Parameters["@build_content_options"].Value = (object)buildReport.BuildContentOptions ?? DBNull.Value; + m_InsertCommand.Parameters["@build_session_guid"].Value = (object)buildReport.BuildSessionGuid ?? DBNull.Value; + m_InsertCommand.Parameters["@build_manifest_hash"].Value = (object)buildReport.BuildManifestHash ?? DBNull.Value; + m_InsertCommand.Parameters["@build_profile_path"].Value = (object)buildReport.BuildProfilePath ?? DBNull.Value; + m_InsertCommand.Parameters["@build_profile_guid"].Value = (object)buildReport.BuildProfileGuid ?? DBNull.Value; + m_InsertCommand.Parameters["@data_path"].Value = (object)buildReport.DataPath ?? DBNull.Value; m_InsertCommand.ExecuteNonQuery(); diff --git a/Analyzer/SQLite/Handlers/ContentSummaryHandler.cs b/Analyzer/SQLite/Handlers/ContentSummaryHandler.cs new file mode 100644 index 0000000..2229e2f --- /dev/null +++ b/Analyzer/SQLite/Handlers/ContentSummaryHandler.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using Microsoft.Data.Sqlite; +using UnityDataTools.Analyzer.SerializedObjects; +using UnityDataTools.FileSystem; +using UnityDataTools.FileSystem.TypeTreeReaders; + +namespace UnityDataTools.Analyzer.SQLite.Handlers; + +// Writes the ContentSummary object (Unity 6.6+). There is at most one per serialized file, so this +// is simpler than PackedAssetsHandler, but it follows the same on-demand schema pattern: the +// build_report_content_* tables and views are created lazily on the first ContentSummary object. +public class ContentSummaryHandler : ISQLiteHandler +{ + private SqliteConnection m_Database; + private bool m_SchemaCreated; + private SqliteCommand m_InsertSummaryCommand; + private SqliteCommand m_InsertTypeStatCommand; + private SqliteCommand m_InsertAssetStatCommand; + private SqliteCommand m_InsertTypeCommand; + + // Type ids already added to the types table, to skip redundant INSERTs across all reports. + private HashSet m_InsertedTypes = new(); + + public void Init(SqliteConnection db) + { + m_Database = db; + + m_InsertSummaryCommand = db.CreateCommand(); + m_InsertSummaryCommand.CommandText = @"INSERT INTO build_report_content_summary( + id, serialized_file_size, reused_serialized_file_size, resource_data_size, header_size, + serialized_file_count, reused_serialized_file_count, resource_file_count, object_count + ) VALUES( + @id, @serialized_file_size, @reused_serialized_file_size, @resource_data_size, @header_size, + @serialized_file_count, @reused_serialized_file_count, @resource_file_count, @object_count + )"; + m_InsertSummaryCommand.Parameters.Add("@id", SqliteType.Integer); + m_InsertSummaryCommand.Parameters.Add("@serialized_file_size", SqliteType.Integer); + m_InsertSummaryCommand.Parameters.Add("@reused_serialized_file_size", SqliteType.Integer); + m_InsertSummaryCommand.Parameters.Add("@resource_data_size", SqliteType.Integer); + m_InsertSummaryCommand.Parameters.Add("@header_size", SqliteType.Integer); + m_InsertSummaryCommand.Parameters.Add("@serialized_file_count", SqliteType.Integer); + m_InsertSummaryCommand.Parameters.Add("@reused_serialized_file_count", SqliteType.Integer); + m_InsertSummaryCommand.Parameters.Add("@resource_file_count", SqliteType.Integer); + m_InsertSummaryCommand.Parameters.Add("@object_count", SqliteType.Integer); + + m_InsertTypeStatCommand = db.CreateCommand(); + m_InsertTypeStatCommand.CommandText = @"INSERT INTO build_report_content_type_stats( + content_summary_id, type, size, object_count, resource_count + ) VALUES( + @content_summary_id, @type, @size, @object_count, @resource_count + )"; + m_InsertTypeStatCommand.Parameters.Add("@content_summary_id", SqliteType.Integer); + m_InsertTypeStatCommand.Parameters.Add("@type", SqliteType.Integer); + m_InsertTypeStatCommand.Parameters.Add("@size", SqliteType.Integer); + m_InsertTypeStatCommand.Parameters.Add("@object_count", SqliteType.Integer); + m_InsertTypeStatCommand.Parameters.Add("@resource_count", SqliteType.Integer); + + m_InsertAssetStatCommand = db.CreateCommand(); + m_InsertAssetStatCommand.CommandText = @"INSERT INTO build_report_content_asset_stats( + content_summary_id, source_asset_guid, source_asset_path, size, object_count, resource_count + ) VALUES( + @content_summary_id, @source_asset_guid, @source_asset_path, @size, @object_count, @resource_count + )"; + m_InsertAssetStatCommand.Parameters.Add("@content_summary_id", SqliteType.Integer); + m_InsertAssetStatCommand.Parameters.Add("@source_asset_guid", SqliteType.Text); + m_InsertAssetStatCommand.Parameters.Add("@source_asset_path", SqliteType.Text); + m_InsertAssetStatCommand.Parameters.Add("@size", SqliteType.Integer); + m_InsertAssetStatCommand.Parameters.Add("@object_count", SqliteType.Integer); + m_InsertAssetStatCommand.Parameters.Add("@resource_count", SqliteType.Integer); + + // Populate the shared types table (INSERT OR IGNORE) so the type-stats view can show type + // names even when the build output and its TypeTrees are not analyzed alongside the report. + m_InsertTypeCommand = db.CreateCommand(); + m_InsertTypeCommand.CommandText = "INSERT OR IGNORE INTO types(id, name) VALUES(@id, @name)"; + m_InsertTypeCommand.Parameters.Add("@id", SqliteType.Integer); + m_InsertTypeCommand.Parameters.Add("@name", SqliteType.Text); + } + + private void EnsureSchema(SqliteTransaction transaction) + { + if (m_SchemaCreated) + return; + + using var command = m_Database.CreateCommand(); + command.Transaction = transaction; + command.CommandText = Properties.Resources.ContentSummary ?? throw new InvalidOperationException("ContentSummary resource not found"); + command.ExecuteNonQuery(); + + m_SchemaCreated = true; + } + + public void Process(Context ctx, long objectId, RandomAccessReader reader, out string name, out long streamDataSize) + { + EnsureSchema(ctx.Transaction); + + var contentSummary = ContentSummary.Read(reader); + + m_InsertSummaryCommand.Transaction = ctx.Transaction; + m_InsertSummaryCommand.Parameters["@id"].Value = objectId; + m_InsertSummaryCommand.Parameters["@serialized_file_size"].Value = (long)contentSummary.SerializedFileSize; + m_InsertSummaryCommand.Parameters["@reused_serialized_file_size"].Value = (long)contentSummary.ReusedSerializedFileSize; + m_InsertSummaryCommand.Parameters["@resource_data_size"].Value = (long)contentSummary.ResourceDataSize; + m_InsertSummaryCommand.Parameters["@header_size"].Value = (long)contentSummary.HeaderSize; + m_InsertSummaryCommand.Parameters["@serialized_file_count"].Value = contentSummary.SerializedFileCount; + m_InsertSummaryCommand.Parameters["@reused_serialized_file_count"].Value = contentSummary.ReusedSerializedFileCount; + m_InsertSummaryCommand.Parameters["@resource_file_count"].Value = contentSummary.ResourceFileCount; + m_InsertSummaryCommand.Parameters["@object_count"].Value = contentSummary.ObjectCount; + m_InsertSummaryCommand.ExecuteNonQuery(); + + foreach (var typeStat in contentSummary.TypeStats) + { + if (m_InsertedTypes.Add(typeStat.Type) && + TypeIdRegistry.TryGetTypeName(typeStat.Type, out var typeName)) + { + m_InsertTypeCommand.Transaction = ctx.Transaction; + m_InsertTypeCommand.Parameters["@id"].Value = typeStat.Type; + m_InsertTypeCommand.Parameters["@name"].Value = typeName; + m_InsertTypeCommand.ExecuteNonQuery(); + } + + m_InsertTypeStatCommand.Transaction = ctx.Transaction; + m_InsertTypeStatCommand.Parameters["@content_summary_id"].Value = objectId; + m_InsertTypeStatCommand.Parameters["@type"].Value = typeStat.Type; + m_InsertTypeStatCommand.Parameters["@size"].Value = (long)typeStat.Size; + m_InsertTypeStatCommand.Parameters["@object_count"].Value = typeStat.ObjectCount; + m_InsertTypeStatCommand.Parameters["@resource_count"].Value = typeStat.ResourceCount; + m_InsertTypeStatCommand.ExecuteNonQuery(); + } + + foreach (var assetStat in contentSummary.AssetStats) + { + m_InsertAssetStatCommand.Transaction = ctx.Transaction; + m_InsertAssetStatCommand.Parameters["@content_summary_id"].Value = objectId; + m_InsertAssetStatCommand.Parameters["@source_asset_guid"].Value = assetStat.SourceAssetGUID; + m_InsertAssetStatCommand.Parameters["@source_asset_path"].Value = assetStat.SourceAssetPath; + m_InsertAssetStatCommand.Parameters["@size"].Value = (long)assetStat.Size; + m_InsertAssetStatCommand.Parameters["@object_count"].Value = assetStat.ObjectCount; + m_InsertAssetStatCommand.Parameters["@resource_count"].Value = assetStat.ResourceCount; + m_InsertAssetStatCommand.ExecuteNonQuery(); + } + + streamDataSize = 0; + name = string.Empty; + } + + public void Finalize(SqliteConnection db) + { + } + + void IDisposable.Dispose() + { + m_InsertSummaryCommand?.Dispose(); + m_InsertTypeStatCommand?.Dispose(); + m_InsertAssetStatCommand?.Dispose(); + m_InsertTypeCommand?.Dispose(); + } +} diff --git a/Analyzer/SQLite/Writers/SerializedFileSQLiteWriter.cs b/Analyzer/SQLite/Writers/SerializedFileSQLiteWriter.cs index 142de79..759701c 100644 --- a/Analyzer/SQLite/Writers/SerializedFileSQLiteWriter.cs +++ b/Analyzer/SQLite/Writers/SerializedFileSQLiteWriter.cs @@ -79,6 +79,7 @@ public class SerializedFileSQLiteWriter : IDisposable { "MonoScript", new MonoScriptHandler() }, { "BuildReport", new BuildReportHandler() }, { "PackedAssets", new PackedAssetsHandler() }, + { "ContentSummary", new ContentSummaryHandler() }, }; // serialized files diff --git a/Analyzer/SerializedObjects/BuildReport.cs b/Analyzer/SerializedObjects/BuildReport.cs index 7113bf9..1b2b865 100644 --- a/Analyzer/SerializedObjects/BuildReport.cs +++ b/Analyzer/SerializedObjects/BuildReport.cs @@ -24,6 +24,14 @@ public class BuildReport public int TotalWarnings { get; init; } public int BuildType { get; init; } public string BuildResult { get; init; } + // Fields added in Unity 6.6; null when analyzing reports from older versions. + public string BuildName { get; init; } + public int? BuildContentOptions { get; init; } + public string BuildSessionGuid { get; init; } + public string BuildManifestHash { get; init; } + public string BuildProfilePath { get; init; } + public string BuildProfileGuid { get; init; } + public string DataPath { get; init; } public List Files { get; init; } public FileListArchiveHelper fileListArchiveHelper { get; init; } @@ -69,6 +77,13 @@ public static BuildReport Read(RandomAccessReader reader) return new BuildReport() { + BuildName = summary.HasChild("buildName") ? summary["buildName"].GetValue() : null, + BuildContentOptions = summary.HasChild("buildContentOptions") ? summary["buildContentOptions"].GetValue() : null, + BuildSessionGuid = ReadOptionalGuid(summary, "buildSessionGUID"), + BuildManifestHash = ReadOptionalHash128(summary, "buildManifestHash"), + BuildProfilePath = summary.HasChild("buildProfilePath") ? summary["buildProfilePath"].GetValue() : null, + BuildProfileGuid = ReadOptionalGuid(summary, "buildProfileGuid"), + DataPath = summary.HasChild("dataPath") ? summary["dataPath"].GetValue() : null, Name = reader["m_Name"].GetValue(), BuildGuid = guidString, PlatformName = summary["platformName"].GetValue(), @@ -114,6 +129,45 @@ static void TrimCommonPathPrefix(List files) } } + // Reads a Unity GUID (4 uints) if present, returning null when the field is absent (older + // Unity) or a default all-zero GUID (e.g. no build profile was active). + static string ReadOptionalGuid(RandomAccessReader summary, string fieldName) + { + if (!summary.HasChild(fieldName)) + return null; + + var guidData = summary[fieldName]; + var guid0 = guidData["data[0]"].GetValue(); + var guid1 = guidData["data[1]"].GetValue(); + var guid2 = guidData["data[2]"].GetValue(); + var guid3 = guidData["data[3]"].GetValue(); + + if ((guid0 | guid1 | guid2 | guid3) == 0) + return null; + + return GuidHelper.FormatUnityGuid(guid0, guid1, guid2, guid3); + } + + // Reads a Unity Hash128 (16 bytes) if present, returning null when the field is absent (older + // Unity) or an all-zero hash (e.g. non-ContentDirectory builds have no manifest hash). + static string ReadOptionalHash128(RandomAccessReader summary, string fieldName) + { + if (!summary.HasChild(fieldName)) + return null; + + var hashData = summary[fieldName]; + var bytes = new byte[16]; + var allZero = true; + for (var i = 0; i < 16; i++) + { + bytes[i] = hashData[$"bytes[{i}]"].GetValue(); + if (bytes[i] != 0) + allZero = false; + } + + return allZero ? null : GuidHelper.FormatUnityHash128(bytes); + } + public static string GetBuildTypeString(int buildType) { return buildType switch diff --git a/Analyzer/SerializedObjects/ContentSummary.cs b/Analyzer/SerializedObjects/ContentSummary.cs new file mode 100644 index 0000000..f4226fc --- /dev/null +++ b/Analyzer/SerializedObjects/ContentSummary.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using UnityDataTools.BinaryFormat; +using UnityDataTools.FileSystem.TypeTreeReaders; + +namespace UnityDataTools.Analyzer.SerializedObjects; + +// A high level summary of the content included in a build, introduced in Unity 6.6. It carries +// cross-build totals plus per-type and per-source-asset breakdowns. See +// https://docs.unity3d.com/6000.6/Documentation/ScriptReference/Build.Reporting.ContentSummary.html +public class ContentSummary +{ + public ulong SerializedFileSize { get; init; } + public ulong ReusedSerializedFileSize { get; init; } + public ulong ResourceDataSize { get; init; } + public ulong HeaderSize { get; init; } + public int SerializedFileCount { get; init; } + public int ReusedSerializedFileCount { get; init; } + public int ResourceFileCount { get; init; } + public int ObjectCount { get; init; } + public List TypeStats { get; init; } + public List AssetStats { get; init; } + + private ContentSummary() { } + + public static ContentSummary Read(RandomAccessReader reader) + { + var typeStats = new List(reader["m_typeStatsList"].GetArraySize()); + foreach (var element in reader["m_typeStatsList"]) + { + typeStats.Add(new TypeStat + { + Type = element["classID"].GetValue(), + Size = element["size"].GetValue(), + ObjectCount = element["objectCount"].GetValue(), + ResourceCount = element["resourceCount"].GetValue() + }); + } + + var assetStats = new List(reader["m_assetStatsList"].GetArraySize()); + foreach (var element in reader["m_assetStatsList"]) + { + var guidData = element["sourceAssetGUID"]; + var guidString = GuidHelper.FormatUnityGuid( + guidData["data[0]"].GetValue(), + guidData["data[1]"].GetValue(), + guidData["data[2]"].GetValue(), + guidData["data[3]"].GetValue()); + + assetStats.Add(new AssetStat + { + SourceAssetGUID = guidString, + SourceAssetPath = element["sourceAssetPath"].GetValue(), + Size = element["size"].GetValue(), + ObjectCount = element["objectCount"].GetValue(), + ResourceCount = element["resourceCount"].GetValue() + }); + } + + return new ContentSummary() + { + SerializedFileSize = reader["m_serializedFileSize"].GetValue(), + ReusedSerializedFileSize = reader["m_reusedSerializedFileSize"].GetValue(), + ResourceDataSize = reader["m_resourceDataSize"].GetValue(), + HeaderSize = reader["m_headerSize"].GetValue(), + SerializedFileCount = reader["m_serializedFileCount"].GetValue(), + ReusedSerializedFileCount = reader["m_reusedSerializedFileCount"].GetValue(), + ResourceFileCount = reader["m_resourceFileCount"].GetValue(), + ObjectCount = reader["m_objectCount"].GetValue(), + TypeStats = typeStats, + AssetStats = assetStats + }; + } +} + +public class TypeStat +{ + public int Type { get; init; } + public ulong Size { get; init; } + public int ObjectCount { get; init; } + public int ResourceCount { get; init; } +} + +public class AssetStat +{ + public string SourceAssetGUID { get; init; } + public string SourceAssetPath { get; init; } + public ulong Size { get; init; } + public int ObjectCount { get; init; } + public int ResourceCount { get; init; } +} diff --git a/Documentation/buildreport.md b/Documentation/buildreport.md index 5c772cf..7d5e0d4 100644 --- a/Documentation/buildreport.md +++ b/Documentation/buildreport.md @@ -12,6 +12,7 @@ The [`analyze`](command-analyze.md) command extracts build report data into dedi * **BuildReport** - The primary object containing build inputs and results * **PackedAssets** - Describes the contents of each SerializedFile, .resS, or .resource file, including type, size, and source asset for each object or resource blob, enabling object-level analysis +* **ContentSummary** (class id 330615474) - A high-level content summary added in Unity 6.6: cross-build totals plus per-type and per-source-asset breakdowns. Present in Unity 6.6+ Player, AssetBundle, and ContentDirectory reports (absent from older reports and scripts-only builds). **Note:** PackedAssets information is not currently written for scenes in the build. @@ -204,13 +205,22 @@ Build report data is stored in the following tables and views: | `build_report_packed_assets` | table | SerializedFile, .resS, or .resource file info. See [PackedAssets](https://docs.unity3d.com/ScriptReference/Build.Reporting.PackedAssets.html) | | `build_report_packed_asset_info` | table | Each object inside a SerializedFile (or data in .resS/.resource files). See [PackedAssetInfo](https://docs.unity3d.com/ScriptReference/Build.Reporting.PackedAssetInfo.html) | | `build_report_source_assets` | table | Source asset GUID and path for each PackedAssetInfo reference | +| `build_report_content_summary` | table | Cross-build content totals (sizes and counts). See [ContentSummary](https://docs.unity3d.com/6000.6/Documentation/ScriptReference/Build.Reporting.ContentSummary.html) | +| `build_report_content_type_stats` | table | Per Unity object type: size, object count, resource count | +| `build_report_content_asset_stats` | table | Per source asset: GUID, path, size, object count, resource count | | `build_report_files_view` | view | All files from all build reports | | `build_report_packed_assets_view` | view | All PackedAssets with their BuildReport, Archive, and SerializedFile | | `build_report_packed_asset_contents_view` | view | All objects and resources tracked in build reports | +| `build_report_content_summary_view` | view | Cross-build content totals with their BuildReport | +| `build_report_content_type_stats_view` | view | Per-type stats with the type name and their BuildReport | The `build_reports` table contains primary build information. Additional tables store detailed content data. Views simplify queries by automatically joining tables, especially when working with multiple build reports. -These tables and views are created on demand, so a database analyzed without any build report does not contain them. The `build_reports`, `build_report_files`, `build_report_archive_contents` group and `build_report_files_view` are created when the first BuildReport object is analyzed; the `build_report_packed_*` tables and views are created when the first PackedAssets object is analyzed (a build report with no PackedAssets objects, such as some scene-only builds, will not have them). +These tables and views are created on demand, so a database analyzed without any build report does not contain them. The `build_reports`, `build_report_files`, `build_report_archive_contents` group and `build_report_files_view` are created when the first BuildReport object is analyzed; the `build_report_packed_*` tables and views are created when the first PackedAssets object is analyzed (a build report with no PackedAssets objects, such as some scene-only builds, will not have them); the `build_report_content_*` tables and views are created when the first ContentSummary object is analyzed (Unity 6.6+ only). + +The new-in-6.6 `build_reports` columns (`build_name`, `build_content_options`, `build_session_guid`, `build_manifest_hash`, `build_profile_path`, `build_profile_guid`, `data_path`) are left NULL when analyzing reports from older Unity versions. + +Because a ContentSummary object does not itself record which build it belongs to, the `build_report_content_*` tables are linked to their BuildReport the same way PackedAssets are: they store the ContentSummary object's own id, and the `build_report_content_summary_view` / `build_report_content_type_stats_view` resolve `build_report_id` via the BuildReport object (type 1125) in the same serialized file. ### Schema Overview diff --git a/UnityBinaryFormat/GuidHelper.cs b/UnityBinaryFormat/GuidHelper.cs index 8bacf69..9f12b14 100644 --- a/UnityBinaryFormat/GuidHelper.cs +++ b/UnityBinaryFormat/GuidHelper.cs @@ -23,6 +23,22 @@ public static string FormatUnityGuid(uint data0, uint data1, uint data2, uint da return new string(result); } + /// + /// Formats a Unity Hash128 (16 bytes) as a 32-character lowercase hex string, matching + /// Unity's Hash128.ToString(). Unlike a GUID, the bytes are emitted in order. + /// + public static string FormatUnityHash128(byte[] bytes) + { + char[] result = new char[32]; + const string hexChars = "0123456789abcdef"; + for (int i = 0; i < 16; i++) + { + result[i * 2] = hexChars[bytes[i] >> 4]; + result[i * 2 + 1] = hexChars[bytes[i] & 0xF]; + } + return new string(result); + } + /// /// Formats a uint32 as 8 hex digits matching Unity's GUIDToString logic. /// Unity's implementation extracts nibbles from most significant to least significant diff --git a/UnityDataTool.Tests/BuildReportTests.cs b/UnityDataTool.Tests/BuildReportTests.cs index 210955a..4adc2d0 100644 --- a/UnityDataTool.Tests/BuildReportTests.cs +++ b/UnityDataTool.Tests/BuildReportTests.cs @@ -14,11 +14,18 @@ public class BuildReportTests private string m_TestOutputFolder; private string m_TestDataFolder; + // Unity 6.6 reference reports, which include the new Summary fields and a ContentSummary object. + private string m_ContentDirectoryReport; + private string m_AssetBundleReport; + [OneTimeSetUp] 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_AssetBundleReport = Path.Combine(leadingEdge, "BuildReport-AssetBundles", "LastBuild.buildreport"); Directory.CreateDirectory(m_TestOutputFolder); Directory.SetCurrentDirectory(m_TestOutputFolder); } @@ -514,4 +521,162 @@ public async Task Analyze_BuildReports_BothReports_ContainsBuildReportFilesData( Assert.AreEqual(0, playerPackedAssetsWithNonNullBundle, "Expected all PackedAssets from Player.buildreport have NULL archive"); } + + // The Unity 6.6 Summary adds several fields (issue #107 Part 1). Verify they land in the new + // build_reports columns for a ContentDirectory report, which populates all of them. + [Test] + public async Task Analyze_BuildReport_ContentDirectory_ContainsNewSummaryColumns() + { + var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder); + + Assert.AreEqual(0, await Program.Main(new[] { "analyze", m_ContentDirectoryReport })); + using var db = SQLTestHelper.OpenDatabase(databasePath); + + SQLTestHelper.AssertQueryString(db, "SELECT build_type FROM build_reports", "ContentDirectory", + "Unexpected build_type"); + SQLTestHelper.AssertQueryString(db, "SELECT build_name FROM build_reports", "ContentDirectory", + "Unexpected build_name"); + SQLTestHelper.AssertQueryInt(db, "SELECT build_content_options FROM build_reports", 32, + "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"); + SQLTestHelper.AssertQueryString(db, "SELECT build_manifest_hash FROM build_reports", + "baff06b928d147276f2245dd3b19216a", "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"); + SQLTestHelper.AssertQueryString(db, "SELECT COALESCE(build_profile_guid, 'NULL') FROM build_reports", + "NULL", "Expected NULL build_profile_guid"); + + var dataPath = SQLTestHelper.QueryString(db, "SELECT data_path FROM build_reports"); + Assert.That(dataPath, Does.Contain("ContentDirectory"), "Unexpected data_path"); + } + + // An AssetBundle build has no build name; that field is present-but-empty in the report. + [Test] + public async Task Analyze_BuildReport_AssetBundle_HasEmptyBuildName() + { + var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder); + + Assert.AreEqual(0, await Program.Main(new[] { "analyze", m_AssetBundleReport })); + using var db = SQLTestHelper.OpenDatabase(databasePath); + + SQLTestHelper.AssertQueryString(db, "SELECT build_name FROM build_reports", "", + "Expected empty build_name for AssetBundle build"); + SQLTestHelper.AssertQueryInt(db, "SELECT build_content_options FROM build_reports", 0, + "Expected zero build_content_options for AssetBundle build"); + } + + // Older Unity reports lack all the new Summary fields; the columns must be NULL and analyze + // must still succeed. + [Test] + public async Task Analyze_BuildReport_OldReport_NewSummaryColumnsAreNull() + { + var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder); + + Assert.AreEqual(0, await Program.Main(new[] { "analyze", Path.Combine(m_TestDataFolder, "Player.buildreport") })); + using var db = SQLTestHelper.OpenDatabase(databasePath); + + SQLTestHelper.AssertQueryInt(db, + @"SELECT COUNT(*) FROM build_reports + WHERE build_name IS NULL AND build_content_options IS NULL + AND build_session_guid IS NULL AND build_manifest_hash IS NULL + AND build_profile_path IS NULL AND build_profile_guid IS NULL AND data_path IS NULL", + 1, "Expected all new Summary columns to be NULL for an old report"); + } + + // issue #107 Part 2: a ContentSummary object (Unity 6.6+) populates the on-demand + // build_report_content_* tables and views. + [Test] + public async Task Analyze_BuildReport_ContentDirectory_ContainsContentSummary() + { + var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder); + + Assert.AreEqual(0, await Program.Main(new[] { "analyze", m_ContentDirectoryReport })); + using var db = SQLTestHelper.OpenDatabase(databasePath); + + SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM build_report_content_summary", 1, + "Expected exactly one ContentSummary row"); + SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM build_report_content_type_stats", 14, + "Unexpected number of type stats rows"); + SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM build_report_content_asset_stats", 15, + "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, + "Unexpected serialized_file_size"); + SQLTestHelper.AssertQueryInt(db, "SELECT object_count FROM build_report_content_summary", 28, + "Unexpected object_count"); + + // Spot-check a specific type stat (Cubemap, class id 89). + SQLTestHelper.AssertQueryInt(db, + "SELECT size FROM build_report_content_type_stats WHERE type = 89", 524532, + "Unexpected size for type 89"); + SQLTestHelper.AssertQueryInt(db, + "SELECT resource_count FROM build_report_content_type_stats WHERE type = 89", 1, + "Unexpected resource_count for type 89"); + + // The type-stats view resolves the class id to a name and the owning build report. + SQLTestHelper.AssertQueryString(db, + "SELECT type_name FROM build_report_content_type_stats_view WHERE type = 89", "Cubemap", + "Expected type_name 'Cubemap' for type 89"); + + var buildReportId = SQLTestHelper.QueryInt(db, "SELECT id FROM objects WHERE type = 1125"); + SQLTestHelper.AssertQueryInt(db, + $"SELECT COUNT(*) FROM build_report_content_type_stats_view WHERE build_report_id != {buildReportId}", 0, + "All type stats should resolve to the single BuildReport object"); + SQLTestHelper.AssertQueryInt(db, + $"SELECT build_report_id FROM build_report_content_summary_view", buildReportId, + "ContentSummary should resolve to the BuildReport object in the same file"); + + // A specific source asset appears in the asset stats. + SQLTestHelper.AssertQueryInt(db, + "SELECT COUNT(*) FROM build_report_content_asset_stats WHERE source_asset_path = 'Assets/Audio/a.mp3'", + 1, "Expected the a.mp3 source asset in asset stats"); + } + + // The ContentSummary tables are created on demand, like PackedAssets: an older report without a + // ContentSummary object must not create them. + [Test] + public async Task Analyze_BuildReport_OldReport_NoContentSummaryTables() + { + var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder); + + Assert.AreEqual(0, await Program.Main(new[] { "analyze", Path.Combine(m_TestDataFolder, "Player.buildreport") })); + using var db = SQLTestHelper.OpenDatabase(databasePath); + + SQLTestHelper.AssertQueryInt(db, + "SELECT COUNT(*) FROM sqlite_master WHERE name LIKE 'build_report_content%'", 0, + "Expected no ContentSummary tables/views for a report without a ContentSummary object"); + } + + // When multiple 6.6 reports are analyzed together, each ContentSummary's rows must resolve back + // to the BuildReport object in its own serialized file. + [Test] + public async Task Analyze_BuildReport_MultipleReports_ContentSummaryJoinsCorrectly() + { + var databasePath = SQLTestHelper.GetDatabasePath(m_TestOutputFolder); + + Assert.AreEqual(0, await Program.Main(new[] { "analyze", m_ContentDirectoryReport, m_AssetBundleReport })); + using var db = SQLTestHelper.OpenDatabase(databasePath); + + SQLTestHelper.AssertQueryInt(db, "SELECT COUNT(*) FROM build_report_content_summary", 2, + "Expected one ContentSummary per report"); + + // 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' + AND type_name = 'Cubemap'", + 1, "Expected the ContentDirectory report's Cubemap type stat"); + + // Every type/summary row resolves to a non-NULL build report. + SQLTestHelper.AssertQueryInt(db, + "SELECT COUNT(*) FROM build_report_content_summary_view WHERE build_report_id IS NULL", 0, + "Every ContentSummary should resolve to a BuildReport"); + SQLTestHelper.AssertQueryInt(db, + "SELECT COUNT(*) FROM build_report_content_type_stats_view WHERE build_report_id IS NULL", 0, + "Every type stat should resolve to a BuildReport"); + } } From 5cc473cfb88a0dd189f6efa07dcde9837e929504 Mon Sep 17 00:00:00 2001 From: Andrew Skowronski <86242170+SkowronskiAndrew@users.noreply.github.com> Date: Thu, 23 Jul 2026 09:25:22 -0400 Subject: [PATCH 2/5] Adjustments to documentation Manual fixes based on review. Co-authored-by: Andrew Skowronski <86242170+SkowronskiAndrew@users.noreply.github.com> --- Documentation/buildreport.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Documentation/buildreport.md b/Documentation/buildreport.md index 7d5e0d4..28e51ae 100644 --- a/Documentation/buildreport.md +++ b/Documentation/buildreport.md @@ -12,9 +12,9 @@ The [`analyze`](command-analyze.md) command extracts build report data into dedi * **BuildReport** - The primary object containing build inputs and results * **PackedAssets** - Describes the contents of each SerializedFile, .resS, or .resource file, including type, size, and source asset for each object or resource blob, enabling object-level analysis -* **ContentSummary** (class id 330615474) - A high-level content summary added in Unity 6.6: cross-build totals plus per-type and per-source-asset breakdowns. Present in Unity 6.6+ Player, AssetBundle, and ContentDirectory reports (absent from older reports and scripts-only builds). +* **ContentSummary** - A high-level content summary added in Unity 6.6: cross-build totals plus per-type and per-source-asset breakdowns. Present in Unity 6.6+ Player, AssetBundle, and content directory reports (absent from older reports and scripts-only builds). -**Note:** PackedAssets information is not currently written for scenes in the build. +**Note:** Prior to Unity 6.6, PackedAssets information was not written for scenes in the build. ## Examples @@ -50,7 +50,7 @@ SELECT build_time_asset_path from build_report_source_assets WHERE build_time_as ## Cross-Referencing with Build Output -For comprehensive analysis, run `analyze` on both the build output **and** the matching build report file. Use a clean build to ensure PackedAssets information is fully populated. +For comprehensive analysis, run `analyze` on both the build output **and** the matching build report file. For Player and AssetBundle builds use a clean build, to ensure PackedAssets information is fully populated. `analyze` accepts multiple path arguments, each of which can be a file or a directory, so you can pass the build output directory together with the build report path (or the directory containing it) in a single command: @@ -216,9 +216,8 @@ Build report data is stored in the following tables and views: The `build_reports` table contains primary build information. Additional tables store detailed content data. Views simplify queries by automatically joining tables, especially when working with multiple build reports. -These tables and views are created on demand, so a database analyzed without any build report does not contain them. The `build_reports`, `build_report_files`, `build_report_archive_contents` group and `build_report_files_view` are created when the first BuildReport object is analyzed; the `build_report_packed_*` tables and views are created when the first PackedAssets object is analyzed (a build report with no PackedAssets objects, such as some scene-only builds, will not have them); the `build_report_content_*` tables and views are created when the first ContentSummary object is analyzed (Unity 6.6+ only). +These tables and views are created on demand, so a database analyzed without any build report does not contain them. The `build_reports`, `build_report_files`, `build_report_archive_contents` group and `build_report_files_view` are created when the first BuildReport object is analyzed; the `build_report_packed_*` tables and views are created when the first PackedAssets object is analyzed (a build report with no PackedAssets objects, such as some scene-only builds, will not have them); the `build_report_content_*` tables and views are created if the file contains a ContentSummary object (Unity 6.6+ only). -The new-in-6.6 `build_reports` columns (`build_name`, `build_content_options`, `build_session_guid`, `build_manifest_hash`, `build_profile_path`, `build_profile_guid`, `data_path`) are left NULL when analyzing reports from older Unity versions. Because a ContentSummary object does not itself record which build it belongs to, the `build_report_content_*` tables are linked to their BuildReport the same way PackedAssets are: they store the ContentSummary object's own id, and the `build_report_content_summary_view` / `build_report_content_type_stats_view` resolve `build_report_id` via the BuildReport object (type 1125) in the same serialized file. From f9c1588e048e893ac793debde19be8ae9c17c2e9 Mon Sep 17 00:00:00 2001 From: Andrew Skowronski Date: Thu, 23 Jul 2026 11:12:50 -0400 Subject: [PATCH 3/5] BuildReport docs - Fill out more information based on Unity 6.6 improvements --- Documentation/buildreport.md | 38 +++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/Documentation/buildreport.md b/Documentation/buildreport.md index 28e51ae..0fd09dd 100644 --- a/Documentation/buildreport.md +++ b/Documentation/buildreport.md @@ -1,8 +1,14 @@ # BuildReport Support -Unity generates a [BuildReport](https://docs.unity3d.com/ScriptReference/Build.Reporting.BuildReport.html) file for Player builds and when building AssetBundles via [BuildPipeline.BuildAssetBundles](https://docs.unity3d.com/ScriptReference/BuildPipeline.BuildAssetBundles.html). Build reports are **not** generated by the [Addressables](addressables-build-reports.md) package or Scriptable Build Pipeline. +Unity generates a [BuildReport](https://docs.unity3d.com/ScriptReference/Build.Reporting.BuildReport.html) file for common build types: -Build reports are written to `Library/LastBuild.buildreport` as Unity SerializedFiles (the same binary format used for build output). UnityDataTool can read this format and extract detailed build information using the same mechanisms as other Unity object types. +* Player builds +* AssetBundles built via [BuildPipeline.BuildAssetBundles](https://docs.unity3d.com/ScriptReference/BuildPipeline.BuildAssetBundles.html) +* For [content directory](https://docs.unity3d.com/6000.6/Documentation/ScriptReference/BuildPipeline.BuildContentDirectory.html) builds, including content directories build through the Addressables package. + +Build reports are **not** generated when doing AssetBundle builds with the [Addressables](addressables-build-reports.md) package or Scriptable Build Pipeline. + +The BuildReport is a Unity Serialized File (the same binary format used for build output). UnityDataTool can read this format and extract detailed build information using the same mechanisms as other Unity object types. ## UnityDataTool Support @@ -12,9 +18,23 @@ The [`analyze`](command-analyze.md) command extracts build report data into dedi * **BuildReport** - The primary object containing build inputs and results * **PackedAssets** - Describes the contents of each SerializedFile, .resS, or .resource file, including type, size, and source asset for each object or resource blob, enabling object-level analysis -* **ContentSummary** - A high-level content summary added in Unity 6.6: cross-build totals plus per-type and per-source-asset breakdowns. Present in Unity 6.6+ Player, AssetBundle, and content directory reports (absent from older reports and scripts-only builds). +* **ContentSummary** - A high-level content summary added in Unity 6.6: cross-build totals plus per-type and per-source-asset breakdowns. Present starting in Unity 6.6+ (absent from older reports and scripts-only builds). + + +### PackedAssets and ContentSummary + +Prior to Unity 6.6: + +* PackedAssets information was not written for scenes in the build. +* There is no ContentSummary object. Tip: The [BuildReportInspector](https://github.com/Unity-Technologies/BuildReportInspector) package has a C# helper that uses the PackedAssets to populate data structures that are similar to the ContentSummary. + +From Unity 6.6: -**Note:** Prior to Unity 6.6, PackedAssets information was not written for scenes in the build. +* PackedAssets is written for Player builds and AssetBundle builds, including scene content +* ContentSummary is written for Player, AssetBundle and content directory builds. +* Content directory builds do not include PackedAssets. This was removed intentionally to reduce build report file sizes, because the ContentSummary contains a more concise summary and the [ContentLayout.json](contentlayout-database.md) has the source mapping information. + +All versions include the BuildReport object, and Analyze works with all variations and imports whatever data is available. ## Examples @@ -60,6 +80,8 @@ UnityDataTool analyze /path/to/build/output /path/to/Library/LastBuild.buildrepo PackedAssets data provides source asset information for each object that isn't available when analyzing only the build output. Objects are listed in the same order as they appear in the output SerializedFile, .resS, or .resource file. +Note: for content directory builds it is more important to match the ContentLayout.json file with the build output rather than the BuildReport. However adding the BuildReport file means additional useful data is populated into the database, but it is more optional. The `--build-history` argument helps find and add both files when analysing a content directory. + ### Database Relationships - Match `build_report_packed_assets` rows to analyzed SerializedFiles using `object_view.serialized_file` and `build_report_packed_assets.path` @@ -86,7 +108,9 @@ UnityDataTool analyze build1.buildreport build2.buildreport ### Unity 6.6 and later -Player and content directory builds record a structured [build history](https://docs.unity3d.com/6000.6/Documentation/ScriptReference/Build.BuildHistory.html) (default location `Library/BuildHistory`). Unity assigns each build its own directory and gives every build report a unique GUID-based filename, so there is no need to copy or rename reports to compare them. Run `analyze` on the entire build history folder, or on specific build report directories: +Player and content directory builds record a structured [build history](https://docs.unity3d.com/6000.6/Documentation/ScriptReference/Build.BuildHistory.html) (default location `Library/BuildHistory`). Unity assigns each build its own directory and gives every build report a unique GUID-based filename, so there is no need to copy or rename reports to compare them. For more details see [Build History File Reference](https://docs.unity3d.com/6000.6/Documentation/Manual/build-history-file-reference.html#buildreport-file). + +Run `analyze` on the entire build history folder, or on specific build report directories: ```bash # Analyze every build in the history @@ -96,10 +120,10 @@ UnityDataTool analyze Library/BuildHistory UnityDataTool analyze Library/BuildHistory/20260504-153912Z-2dd7642e Library/BuildHistory/20260504-153855Z-7aff42f4 ``` -AssetBundle builds are not tracked in the build history; they still write only to `Library/LastBuild.buildreport`. - See the schema sections below for guidance on writing queries that handle multiple build reports correctly. +AssetBundle builds are not tracked in the build history; they still write only to `Library/LastBuild.buildreport`. + ## Alternatives UnityDataTool provides low-level access to build reports. Consider these alternatives for easier or more convenient workflows: From da518b21faa58ee1a806bda640265f928577cffd Mon Sep 17 00:00:00 2001 From: Andrew Skowronski Date: Thu, 23 Jul 2026 11:29:05 -0400 Subject: [PATCH 4/5] [#107] Restructure buildreport.md around build-type/version differences Reorganize the page so its focus is what UnityDataTool extracts and how build reports differ across build types and Unity versions (which the versioned Unity Manual intentionally does not cover): - Add a build-type x data-availability matrix and a "Unity version differences" section (ContentSummary added in 6.6, PackedAssets scene coverage, content directory builds omit PackedAssets, build history location change). - Note ContentSummary is absent for scripts-only/content-reusing Player builds and reflects only rebuilt bundles for incremental AssetBundle builds. - Document the --build-history option for content directory analysis. - Consolidate usage, examples, schema, alternatives, and limitations. --- Documentation/buildreport.md | 373 +++++++++++++++++++---------------- 1 file changed, 200 insertions(+), 173 deletions(-) diff --git a/Documentation/buildreport.md b/Documentation/buildreport.md index 0fd09dd..15c531c 100644 --- a/Documentation/buildreport.md +++ b/Documentation/buildreport.md @@ -1,116 +1,107 @@ # BuildReport Support -Unity generates a [BuildReport](https://docs.unity3d.com/ScriptReference/Build.Reporting.BuildReport.html) file for common build types: +Unity generates a [BuildReport](https://docs.unity3d.com/ScriptReference/Build.Reporting.BuildReport.html) file for its common build types: * Player builds -* AssetBundles built via [BuildPipeline.BuildAssetBundles](https://docs.unity3d.com/ScriptReference/BuildPipeline.BuildAssetBundles.html) -* For [content directory](https://docs.unity3d.com/6000.6/Documentation/ScriptReference/BuildPipeline.BuildContentDirectory.html) builds, including content directories build through the Addressables package. +* AssetBundle builds via [BuildPipeline.BuildAssetBundles](https://docs.unity3d.com/ScriptReference/BuildPipeline.BuildAssetBundles.html) +* [Content directory](https://docs.unity3d.com/6000.6/Documentation/ScriptReference/BuildPipeline.BuildContentDirectory.html) builds (Unity 6.6+), including content directories built through the Addressables package -Build reports are **not** generated when doing AssetBundle builds with the [Addressables](addressables-build-reports.md) package or Scriptable Build Pipeline. +Build reports are **not** generated for AssetBundle builds done with the [Addressables](addressables-build-reports.md) package or the Scriptable Build Pipeline. Those produce a `buildlayout.json` instead — see [Addressables Build Reports](addressables-build-reports.md). -The BuildReport is a Unity Serialized File (the same binary format used for build output). UnityDataTool can read this format and extract detailed build information using the same mechanisms as other Unity object types. +A build report is a Unity SerializedFile (the same binary format used for build output), so UnityDataTool reads it with the same mechanisms as any other Unity object. This page focuses on **what UnityDataTool extracts** and on the **differences between Unity versions**, which the versioned [Unity Manual](https://docs.unity3d.com/6000.6/Documentation/Manual/build-history-file-reference.html#buildreport-file) intentionally does not cover. -## UnityDataTool Support +## What a build report contains -Since build reports are SerializedFiles, you can use [`dump`](command-dump.md) to convert them to text format. +The information in a build report depends on the build type and the Unity version. UnityDataTool imports whatever data is present, so the same commands work across all of them; the tables that get populated differ. -The [`analyze`](command-analyze.md) command extracts build report data into dedicated database tables with custom handlers for: +| Data | Player | AssetBundle | Content Directory | UnityDataTool tables | +|------|:------:|:-----------:|:-----------------:|----------------------| +| Build summary | ✓ | ✓ | ✓ | `build_reports` | +| File list | ✓ | ✓ | ✓ | `build_report_files`, `build_report_archive_contents` | +| PackedAssets (per-object breakdown) | ✓ | ✓ | ✗ | `build_report_packed_*` | +| ContentSummary (aggregate statistics) | ✓ | ✓ | ✓ | `build_report_content_*` | -* **BuildReport** - The primary object containing build inputs and results -* **PackedAssets** - Describes the contents of each SerializedFile, .resS, or .resource file, including type, size, and source asset for each object or resource blob, enabling object-level analysis -* **ContentSummary** - A high-level content summary added in Unity 6.6: cross-build totals plus per-type and per-source-asset breakdowns. Present starting in Unity 6.6+ (absent from older reports and scripts-only builds). +All build types always include the **build summary** and **file list**, so `build_reports` and `build_report_files` are populated for every report. +### Unity version differences -### PackedAssets and ContentSummary +**ContentSummary** is new in Unity 6.6. It provides aggregate statistics (total sizes, counts, and per-type and per-source-asset breakdowns) that are compact compared to the per-object PackedAssets data. Reports from earlier Unity versions contain no ContentSummary object, and the `build_report_content_*` tables are not created for them. -Prior to Unity 6.6: +* It is **not** written for Player builds that reuse content from a previous build (for example [scripts-only builds](https://docs.unity3d.com/6000.6/Documentation/Manual/build-scripts-only.html)). +* For incremental AssetBundle builds it reflects only the AssetBundles that were rebuilt, not those reused from a previous build. -* PackedAssets information was not written for scenes in the build. -* There is no ContentSummary object. Tip: The [BuildReportInspector](https://github.com/Unity-Technologies/BuildReportInspector) package has a C# helper that uses the PackedAssets to populate data structures that are similar to the ContentSummary. +> **Tip:** For pre-6.6 reports, the [BuildReportInspector](https://github.com/Unity-Technologies/BuildReportInspector) package has a C# helper that derives ContentSummary-like statistics from PackedAssets. -From Unity 6.6: +**PackedAssets** behavior changed at Unity 6.6: -* PackedAssets is written for Player builds and AssetBundle builds, including scene content -* ContentSummary is written for Player, AssetBundle and content directory builds. -* Content directory builds do not include PackedAssets. This was removed intentionally to reduce build report file sizes, because the ContentSummary contains a more concise summary and the [ContentLayout.json](contentlayout-database.md) has the source mapping information. +* Before 6.6, PackedAssets data was not written for scene files. +* From 6.6, PackedAssets covers scene content in Player and AssetBundle builds. +* Content directory builds do **not** include PackedAssets. The ContentSummary provides the aggregate view, and [`ContentLayout.json`](contentlayout-database.md) provides the source-asset-to-file mapping, so the larger per-object data was omitted to keep content directory reports small. -All versions include the BuildReport object, and Analyze works with all variations and imports whatever data is available. +**Build report location** also changed at 6.6 — see [Analyzing multiple build reports](#analyzing-multiple-build-reports). -## Examples +The Unity Manual's [Build report file reference](https://docs.unity3d.com/6000.6/Documentation/Manual/build-history-file-reference.html#buildreport-file) documents the full set of build report members and their availability for the current Unity version. -These are some example queries that can be run after running analyze on a build report file. +## What UnityDataTool extracts -1. Show all successful builds recorded in the database. +The [`analyze`](command-analyze.md) command extracts build report data into dedicated database tables using custom handlers for these objects: -``` -SELECT * from build_reports WHERE build_result = "Succeeded" -``` +* **BuildReport** — the primary object, containing the build inputs and results (`build_reports`, `build_report_files`, `build_report_archive_contents`). +* **PackedAssets** — the contents of each SerializedFile, `.resS`, or `.resource` file: the type, size, and source asset for each object or resource blob, enabling object-level analysis (`build_report_packed_*`). +* **ContentSummary** — aggregate content statistics with per-type and per-source-asset breakdowns (`build_report_content_*`, Unity 6.6+). -2. Show information about the data in the build that originates from "Assets/Sprites/Snow.jpg". +These tables are created on demand, so a database analyzed without a build report does not contain them, and a report missing an object (for example a content directory report has no PackedAssets) does not create that object's tables. See the [Database Schema](#database-schema) for the full list and the exact creation rules. -``` -SELECT * -FROM build_report_packed_asset_contents_view -WHERE build_time_asset_path like "Assets/Sprites/Snow.jpg" -``` +Because a build report is a SerializedFile, you can also use [`dump`](command-dump.md) to convert its full contents to text — useful for inspecting data that `analyze` does not import (see [Information not exported](#information-not-exported)). -3. Show the AssetBundles that contain content from "Assets/Sprites/Snow.jpg". +## Analyzing build reports -``` -SELECT DISTINCT archive -FROM build_report_packed_asset_contents_view -WHERE build_time_asset_path like "Assets/Sprites/Snow.jpg" -``` +### A single build report -4. Show all source assets included in the build (excluding C# scripts, e.g. MonoScript objects) +Pass the build report file (or a directory containing it) to `analyze`: -``` -SELECT build_time_asset_path from build_report_source_assets WHERE build_time_asset_path NOT LIKE "%.cs" +```bash +UnityDataTool analyze /path/to/Library/LastBuild.buildreport ``` -## Cross-Referencing with Build Output +### Cross-referencing with build output -For comprehensive analysis, run `analyze` on both the build output **and** the matching build report file. For Player and AssetBundle builds use a clean build, to ensure PackedAssets information is fully populated. - -`analyze` accepts multiple path arguments, each of which can be a file or a directory, so you can pass the build output directory together with the build report path (or the directory containing it) in a single command: +For the most complete analysis, run `analyze` on the build output **and** the matching build report together. `analyze` accepts multiple path arguments, each a file or a directory: ```bash UnityDataTool analyze /path/to/build/output /path/to/Library/LastBuild.buildreport ``` -PackedAssets data provides source asset information for each object that isn't available when analyzing only the build output. Objects are listed in the same order as they appear in the output SerializedFile, .resS, or .resource file. +PackedAssets adds the source asset for each object, which is not available from the build output alone. Objects are listed in the same order they appear in the output SerializedFile, `.resS`, or `.resource` file. For Player and AssetBundle builds, use a clean build so the PackedAssets data is fully populated. -Note: for content directory builds it is more important to match the ContentLayout.json file with the build output rather than the BuildReport. However adding the BuildReport file means additional useful data is populated into the database, but it is more optional. The `--build-history` argument helps find and add both files when analysing a content directory. +For **content directory** builds it is more important to pair the build output with its [`ContentLayout.json`](contentlayout-database.md) than with the build report, because the layout carries the source-asset mapping. Adding the build report still contributes useful summary data, but is more optional. The `--build-history` option locates and adds both files automatically: -### Database Relationships +```bash +UnityDataTool analyze /path/to/ContentDirectory --build-history /path/to/Library/BuildHistory +``` -- Match `build_report_packed_assets` rows to analyzed SerializedFiles using `object_view.serialized_file` and `build_report_packed_assets.path` -- Match `build_report_packed_asset_info` entries to objects in the build output using `object_id` (local file ID) +See the [`analyze` command reference](command-analyze.md) for details. -**Note:** build_report_packed_assets` also record .resS and .resource files. These rows will not match with the serialized_files table. +#### Database relationships -**Note:** The source object's local file ID is not recorded in PackedAssetInfo. While you can identify the source asset (e.g., which Prefab), you cannot directly pinpoint the specific object within that asset. When needed, objects can often be distinguished by name or other properties. +- Match `build_report_packed_assets` rows to analyzed SerializedFiles using `object_view.serialized_file` and `build_report_packed_assets.path`. +- Match `build_report_packed_asset_info` entries to objects in the build output using `object_id` (local file ID). -## Working with Multiple Build Reports +> **Note:** `build_report_packed_assets` also records `.resS` and `.resource` files. Those rows do not match the `serialized_files` table. -Multiple build reports can be imported into the same database if their filenames differ. Pass each report (and any build output directories) as separate path arguments to a single `analyze` command. This enables: -- Comprehensive build history tracking -- Cross-build comparisons -- Identifying duplicated data between Player and AssetBundle builds +> **Note:** The source object's local file ID is not recorded in PackedAssetInfo. You can identify the source asset (for example, which Prefab), but not the specific object within that asset. When needed, objects can often be distinguished by name or other properties. -### Prior to Unity 6.6 +### Analyzing multiple build reports -Each build overwrites `Library/LastBuild.buildreport`. To compare builds, manually collect the report after each build, rename the copies so the filenames are unique (the analyzer keys serialized files by filename), then pass them to `analyze`: +Multiple build reports can be imported into the same database as long as their filenames differ (UnityDataTool keys SerializedFiles by filename). Pass each report, and any build output directories, as separate path arguments to a single `analyze` command. This enables build history tracking, cross-build comparisons, and finding data duplicated between Player and AssetBundle builds. + +**Prior to Unity 6.6**, each build overwrites `Library/LastBuild.buildreport`. To compare builds, collect the report after each build and rename the copies so their filenames are unique, then pass them together: ```bash UnityDataTool analyze build1.buildreport build2.buildreport ``` -### Unity 6.6 and later - -Player and content directory builds record a structured [build history](https://docs.unity3d.com/6000.6/Documentation/ScriptReference/Build.BuildHistory.html) (default location `Library/BuildHistory`). Unity assigns each build its own directory and gives every build report a unique GUID-based filename, so there is no need to copy or rename reports to compare them. For more details see [Build History File Reference](https://docs.unity3d.com/6000.6/Documentation/Manual/build-history-file-reference.html#buildreport-file). - -Run `analyze` on the entire build history folder, or on specific build report directories: +**From Unity 6.6**, Player and content directory builds record a structured [build history](https://docs.unity3d.com/6000.6/Documentation/ScriptReference/Build.BuildHistory.html) (default location `Library/BuildHistory`). Unity gives each build its own directory and a unique GUID-based build report filename, so no copying or renaming is needed. Run `analyze` on the whole history folder or on specific build directories: ```bash # Analyze every build in the history @@ -120,102 +111,47 @@ UnityDataTool analyze Library/BuildHistory UnityDataTool analyze Library/BuildHistory/20260504-153912Z-2dd7642e Library/BuildHistory/20260504-153855Z-7aff42f4 ``` -See the schema sections below for guidance on writing queries that handle multiple build reports correctly. - AssetBundle builds are not tracked in the build history; they still write only to `Library/LastBuild.buildreport`. -## Alternatives - -UnityDataTool provides low-level access to build reports. Consider these alternatives for easier or more convenient workflows: - -### BuildReportInspector Package +For more detail on the build history layout, see the Unity Manual's [Build history file reference](https://docs.unity3d.com/6000.6/Documentation/Manual/build-history-file-reference.html). -View build reports in the Unity Editor using the [BuildReportInspector](https://github.com/Unity-Technologies/BuildReportInspector) package. - -### BuildReport API - -Access build report data programmatically within Unity using the BuildReport API: - -**1. Most recent build:** -Use [BuildPipeline.GetLatestReport()](https://docs.unity3d.com/ScriptReference/Build.Reporting.BuildReport.GetLatestReport.html) - -**2. Build report in Assets folder:** -Load via AssetDatabase API: - -```csharp -using UnityEditor; -using UnityEditor.Build.Reporting; -using UnityEngine; +## Example queries -public class BuildReportInProjectUtility -{ - static public BuildReport LoadBuildReport(string buildReportPath) - { - var report = AssetDatabase.LoadAssetAtPath("Assets/MyBuildReport.buildReport"); +Run these after analyzing a build report file. - if (report == null) - Debug.LogWarning($"Failed to load build report from {buildReportPath}"); +Show all successful builds recorded in the database: - return report; - } -} +```sql +SELECT * FROM build_reports WHERE build_result = 'Succeeded' ``` -**3. Build report outside Assets folder:** -For files in Library or elsewhere, use `InternalEditorUtility`: - - -```csharp -using System; -using System.IO; -using UnityEditor.Build.Reporting; -using UnityEditorInternal; -using UnityEngine; - -public class BuildReportUtility -{ - static public BuildReport LoadBuildReport(string buildReportPath) - { - if (!File.Exists(buildReportPath)) - return null; +Show data in the build that originates from a specific source asset: - try - { - var objs = InternalEditorUtility.LoadSerializedFileAndForget(buildReportPath); - foreach (UnityEngine.Object obj in objs) - { - if (obj is BuildReport) - return obj as BuildReport; - } - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to load build report from {buildReportPath}: {ex.Message}"); - } - return null; - } -} +```sql +SELECT * +FROM build_report_packed_asset_contents_view +WHERE build_time_asset_path LIKE 'Assets/Sprites/Snow.jpg' ``` -### Text Format Access - -Build reports can be output in Unity's pseudo-YAML format using a diagnostic flag: +Show the AssetBundles that contain content from a specific source asset: -![](Diagnostics-TextBasedBuildReport.png) - -Text files are significantly larger than binary. You can also convert to text by moving the binary file into your Unity project (assets default to text format). - -UnityDataTool's `dump` command produces a non-YAML text representation of build report contents. +```sql +SELECT DISTINCT archive +FROM build_report_packed_asset_contents_view +WHERE build_time_asset_path LIKE 'Assets/Sprites/Snow.jpg' +``` -**When to use text formats:** -- Quick extraction of specific information via text processing tools (regex, YAML parsers, etc.) +Show all source assets included in the build, excluding C# scripts (MonoScript objects): -**When to use structured access:** -- Working with full structured data: use UnityDataTool's `analyze` command or Unity's BuildReport API +```sql +SELECT build_time_asset_path FROM build_report_source_assets WHERE build_time_asset_path NOT LIKE '%.cs' +``` -### Addressables Build Reports +Show the content size breakdown by type (Unity 6.6+, requires a ContentSummary): -The Addressables package generates `buildlayout.json` files instead of BuildReport files. While the format and schema differ, they contain similar information. See [Addressables Build Reports](addressables-build-reports.md) for details on importing these files with UnityDataTool. +```sql +SELECT type_name, size, object_count FROM build_report_content_type_stats_view ORDER BY size DESC +``` ## Database Schema @@ -238,36 +174,43 @@ Build report data is stored in the following tables and views: | `build_report_content_summary_view` | view | Cross-build content totals with their BuildReport | | `build_report_content_type_stats_view` | view | Per-type stats with the type name and their BuildReport | -The `build_reports` table contains primary build information. Additional tables store detailed content data. Views simplify queries by automatically joining tables, especially when working with multiple build reports. +The `build_reports` table holds the primary build information; the other tables hold detailed content data. Views simplify queries by joining tables automatically, which matters most when working with multiple build reports. -These tables and views are created on demand, so a database analyzed without any build report does not contain them. The `build_reports`, `build_report_files`, `build_report_archive_contents` group and `build_report_files_view` are created when the first BuildReport object is analyzed; the `build_report_packed_*` tables and views are created when the first PackedAssets object is analyzed (a build report with no PackedAssets objects, such as some scene-only builds, will not have them); the `build_report_content_*` tables and views are created if the file contains a ContentSummary object (Unity 6.6+ only). +These tables and views are created on demand, so a database analyzed without any build report does not contain them: +* The `build_reports`, `build_report_files`, and `build_report_archive_contents` group (and `build_report_files_view`) is created with the first **BuildReport** object. +* The `build_report_packed_*` tables and views are created with the first **PackedAssets** object (a report with none, such as a content directory build, will not have them). +* The `build_report_content_*` tables and views are created with the first **ContentSummary** object (Unity 6.6+ only). -Because a ContentSummary object does not itself record which build it belongs to, the `build_report_content_*` tables are linked to their BuildReport the same way PackedAssets are: they store the ContentSummary object's own id, and the `build_report_content_summary_view` / `build_report_content_type_stats_view` resolve `build_report_id` via the BuildReport object (type 1125) in the same serialized file. +The new-in-6.6 `build_reports` columns (`build_name`, `build_content_options`, `build_session_guid`, `build_manifest_hash`, `build_profile_path`, `build_profile_guid`, `data_path`) are left NULL when analyzing reports from older Unity versions. -### Schema Overview +### Schema overview -Views automatically identify which build report each row belongs to, simplifying multi-report queries. To create custom queries, understand the table relationships: +Views automatically identify which build report each row belongs to, simplifying multi-report queries. To write custom queries, it helps to understand the relationships: -**Primary relationships:** -- `build_reports`: One row per analyzed BuildReport file, corresponding to the BuildReport object in the `objects` table via the `id` column -- `build_report_packed_assets`: Records the `id` of each PackedAssets object. Find the associated BuildReport via the shared `objects.serialized_file` value (PackedAssets are processed independently of BuildReport objects) +**Primary relationships** -**Auxiliary tables:** -- `build_report_files` and `build_report_archive_contents`: Stores the BuildReport object `id` for each row (as `build_report_id`). -- `build_report_packed_asset_info`: Stores the PackedAssets object `id` for each row (as `packed_assets_id`). -- `build_report_source_assets`: Normalized table of distinct source asset GUIDs and paths, linked via `build_report_packed_asset_info.source_asset_id` +- `build_reports`: One row per analyzed BuildReport file, corresponding to the BuildReport object in the `objects` table via the `id` column. +- `build_report_packed_assets`: Records the `id` of each PackedAssets object. Find the associated BuildReport via the shared `objects.serialized_file` value (PackedAssets are processed independently of BuildReport objects). +- `build_report_content_summary`: Records the `id` of the ContentSummary object. Like PackedAssets, a ContentSummary does not record which build it belongs to, so `build_report_content_summary_view` and `build_report_content_type_stats_view` resolve `build_report_id` via the BuildReport object (type 1125) in the same serialized file. -**Note:** BuildReport and PackedAssets objects are also linked in the `refs` table (BuildReport references PackedAssets in its appendices array), but this relationship is not used in built-in views since `refs` table population is optional. +**Auxiliary tables** -**Example: build_report_packed_assets_view** +- `build_report_files` and `build_report_archive_contents`: store the BuildReport object `id` for each row (as `build_report_id`). +- `build_report_packed_asset_info`: stores the PackedAssets object `id` for each row (as `packed_assets_id`). +- `build_report_source_assets`: normalized table of distinct source asset GUIDs and paths, linked via `build_report_packed_asset_info.source_asset_id`. +- `build_report_content_type_stats` and `build_report_content_asset_stats`: store the ContentSummary object `id` for each row (as `content_summary_id`). -This view demonstrates key relationships: -- Finds the BuildReport object (`br_obj`) by type (1125) and shared `serialized_file` with the PackedAssets (`pa`) -- Retrieves the serialized file name from `serialized_files` table (`sf.name`) -- For archive-based builds (AssetBundle, ContentDirectory), retrieves the archive name from `build_report_archive_contents` by matching BuildReport ID and PackedAssets path (`archive` is NULL for Player builds) +> **Note:** BuildReport and PackedAssets objects are also linked in the `refs` table (BuildReport references PackedAssets in its appendices array), but this relationship is not used in the built-in views because `refs` population is optional. -``` +**Example: `build_report_packed_assets_view`** + +This view demonstrates the key relationships: +- Finds the BuildReport object (`br_obj`) by type (1125) and shared `serialized_file` with the PackedAssets (`pa`). +- Retrieves the serialized file name from the `serialized_files` table (`sf.name`). +- For archive-based builds (AssetBundle, ContentDirectory), retrieves the archive name from `build_report_archive_contents` by matching BuildReport ID and PackedAssets path (`archive` is NULL for Player builds). + +```sql CREATE VIEW build_report_packed_assets_view AS SELECT pa.id, @@ -284,9 +227,9 @@ LEFT JOIN objects br_obj ON o.serialized_file = br_obj.serialized_file AND br_ob LEFT JOIN build_report_archive_contents brac ON br_obj.id = brac.build_report_id AND pa.path = brac.archive_content; ``` -### Column Naming +### Column naming -For consistency and clarity, database columns use slightly different names than the BuildReport API: +For consistency and clarity, some database columns use different names than the BuildReport API: | Database Column | BuildReport API | Notes | |-----------------|-----------------|-------| @@ -295,18 +238,102 @@ For consistency and clarity, database columns use slightly different names than | `build_report_packed_asset_info.object_id` | `PackedAssetInfo.fileID` | Local file ID of the object (renamed for consistency with `objects.object_id`) | | `build_report_packed_asset_info.type` | `PackedAssetInfo.classID` | Unity object type as a numeric [Class ID](https://docs.unity3d.com/Manual/ClassIDReference.html) (renamed for consistency with `objects.type`). `build_report_packed_asset_contents_view` exposes this as the type name. | +## Alternatives + +UnityDataTool provides low-level access to build reports. Consider these alternatives for easier or more convenient workflows. + +### BuildReportInspector package + +View build reports in the Unity Editor using the [BuildReportInspector](https://github.com/Unity-Technologies/BuildReportInspector) package. + +### BuildReport API + +Access build report data programmatically within Unity using the BuildReport API. + +**Most recent build:** use [BuildPipeline.GetLatestReport()](https://docs.unity3d.com/ScriptReference/Build.Reporting.BuildReport.GetLatestReport.html). + +**Build report in the Assets folder:** load via the AssetDatabase API: + +```csharp +using UnityEditor; +using UnityEditor.Build.Reporting; +using UnityEngine; + +public class BuildReportInProjectUtility +{ + static public BuildReport LoadBuildReport(string buildReportPath) + { + var report = AssetDatabase.LoadAssetAtPath("Assets/MyBuildReport.buildReport"); + + if (report == null) + Debug.LogWarning($"Failed to load build report from {buildReportPath}"); + + return report; + } +} +``` + +**Build report outside the Assets folder:** for files in Library or elsewhere, use `InternalEditorUtility`: + +```csharp +using System; +using System.IO; +using UnityEditor.Build.Reporting; +using UnityEditorInternal; +using UnityEngine; + +public class BuildReportUtility +{ + static public BuildReport LoadBuildReport(string buildReportPath) + { + if (!File.Exists(buildReportPath)) + return null; + + try + { + var objs = InternalEditorUtility.LoadSerializedFileAndForget(buildReportPath); + foreach (UnityEngine.Object obj in objs) + { + if (obj is BuildReport) + return obj as BuildReport; + } + } + catch (Exception ex) + { + Debug.LogWarning($"Failed to load build report from {buildReportPath}: {ex.Message}"); + } + return null; + } +} +``` + +### Text format access + +Build reports can be output in Unity's pseudo-YAML format using a diagnostic flag: + +![](Diagnostics-TextBasedBuildReport.png) + +Text files are significantly larger than binary. You can also convert to text by moving the binary file into your Unity project (assets default to text format). UnityDataTool's `dump` command produces a non-YAML text representation of build report contents. + +* **Use text formats** for quick extraction of specific information via text-processing tools (regex, YAML parsers, etc.). +* **Use structured access** (`analyze`, or Unity's BuildReport API) for working with the full structured data. + +### Addressables build reports + +The Addressables package generates `buildlayout.json` files instead of BuildReport files. The format and schema differ but the information is similar. See [Addressables Build Reports](addressables-build-reports.md). + ## Limitations -**Duplicate filenames:** Multiple build reports cannot be imported into the same database if they share the same filename. This is a general UnityDataTool limitation that assumes unique SerializedFile names. The BuildReport files in the build history (introduced in Unity 6.6) intentionally have unique file names (incorporating the build session GUID), so analyze does not hit this issue when analyzing multiple entries from the BuildHistory folder. When analyzing multiple AssetBundle build reports, or build reports from older versions of Unity, be sure to assign a unique filename to each. See also issue #36. +**Duplicate filenames:** Multiple build reports cannot be imported into the same database if they share the same filename. This is a general UnityDataTool limitation that assumes unique SerializedFile names. Build history reports (Unity 6.6+) have unique filenames (they incorporate the build session GUID), so analyzing multiple entries from a `BuildHistory` folder does not hit this issue. When analyzing multiple AssetBundle build reports, or reports from older Unity versions, assign a unique filename to each. See also issue #36. -### Information Not Exported +### Information not exported -Currently, only the most useful BuildReport data is extracted to SQL. Additional data may be added as needed: +Currently only the most useful BuildReport data is extracted to SQL. Additional data may be added as needed: -* [Code stripping](https://docs.unity3d.com/ScriptReference/Build.Reporting.BuildReport-strippingInfo.html) appendix (available only for IL2CPP Player builds) -* [ScenesUsingAssets](https://docs.unity3d.com/ScriptReference/Build.Reporting.BuildReport-scenesUsingAssets.html) (available only in detailed build reports for Player builds) -* `BuildReport.m_BuildSteps` array. Supporting this is low priority - SQL is not an ideal representation for this hierarchical data. Starting in Unity 6.6 the build steps are also reported in the BuildLog.jsonl file and Trace Event Profile (TEP) files, which are easy to parse. -* `BuildAssetBundleInfoSet` appendix (undocumented object listing files in each AssetBundle; `build_report_archive_contents` currently derives this from the File list without relying on that data) +* [Code stripping](https://docs.unity3d.com/ScriptReference/Build.Reporting.BuildReport-strippingInfo.html) appendix (IL2CPP Player builds only) +* [ScenesUsingAssets](https://docs.unity3d.com/ScriptReference/Build.Reporting.BuildReport-scenesUsingAssets.html) (detailed Player build reports only) +* `BuildReport.m_BuildSteps` array. Low priority — SQL is not an ideal representation for this hierarchical data. From Unity 6.6 the build steps are also reported in `BuildLog.jsonl` and Trace Event Profile (TEP) files, which are easy to parse. +* `BuildAssetBundleInfoSet` appendix (undocumented object listing files in each AssetBundle; `build_report_archive_contents` derives this from the file list without relying on that data) * Analytics-only appendices (unlikely to be valuable for analysis) -Tip: All this additional BuildReport data can be viewed using the `dump` command. \ No newline at end of file +> **Tip:** All of this additional BuildReport data can be viewed with the `dump` command. From a23142fc81c673a9d3b9c93d63b4519e17efbcb0 Mon Sep 17 00:00:00 2001 From: Andrew Skowronski Date: Thu, 23 Jul 2026 13:03:40 -0400 Subject: [PATCH 5/5] [#107] Update BuildReport Alternatives for Unity 6.6 - Add the built-in Build Analysis window (6.6+) as an alternative UI. - Note the BuildReportInspector package's 6.6 status and its overlap with the built-in window. - Restructure the BuildReport API guidance around build history: BuildHistory. LoadBuildReport for 6.6+ tracked builds, with the GetLatestReport/AssetDatabase/ InternalEditorUtility approaches kept for pre-6.6 and AssetBundle reports. --- Documentation/buildreport.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Documentation/buildreport.md b/Documentation/buildreport.md index 15c531c..df23c7d 100644 --- a/Documentation/buildreport.md +++ b/Documentation/buildreport.md @@ -242,17 +242,25 @@ For consistency and clarity, some database columns use different names than the UnityDataTool provides low-level access to build reports. Consider these alternatives for easier or more convenient workflows. +### Build Analysis window + +Unity 6.6 and later includes a built-in [Build Analysis window](https://docs.unity3d.com/6000.7/Documentation/Manual/build-analysis-window-reference.html) that presents BuildReport information (and the rest of the build history) directly in the Editor. For many cases this is a more convenient UI than the BuildReportInspector package. + ### BuildReportInspector package -View build reports in the Unity Editor using the [BuildReportInspector](https://github.com/Unity-Technologies/BuildReportInspector) package. +The [BuildReportInspector](https://github.com/Unity-Technologies/BuildReportInspector) package renders a build report in the Editor's Inspector. It predates the built-in Build Analysis window, which now covers many of the same needs in Unity 6.6+. For the package's current status and behavior on Unity 6.6, see [New with Unity 6.6](https://github.com/Unity-Technologies/BuildReportInspector/blob/master/com.unity.build-report-inspector/Documentation~/com.unity.build-report-inspector.md#new-with-unity-66). ### BuildReport API -Access build report data programmatically within Unity using the BuildReport API. +Access build report data programmatically within Unity using the BuildReport API. How you obtain a report depends on the Unity version. + +**Unity 6.6 and later (build history):** Player and content directory builds are tracked in the [build history](https://docs.unity3d.com/6000.6/Documentation/ScriptReference/Build.BuildHistory.html). Load any tracked report directly with [BuildHistory.LoadBuildReport](https://docs.unity3d.com/6000.6/Documentation/ScriptReference/Build.BuildHistory.LoadBuildReport.html), which is the simplest way to reach recent builds and to enumerate history. AssetBundle builds are not tracked in the build history, so use the pre-6.6 approaches below for them. + +**Prior to Unity 6.6 (and for AssetBundle reports):** the build history API is not available, so load reports one of these ways. -**Most recent build:** use [BuildPipeline.GetLatestReport()](https://docs.unity3d.com/ScriptReference/Build.Reporting.BuildReport.GetLatestReport.html). +Most recent build: use [BuildPipeline.GetLatestReport()](https://docs.unity3d.com/ScriptReference/Build.Reporting.BuildReport.GetLatestReport.html). -**Build report in the Assets folder:** load via the AssetDatabase API: +Build report in the Assets folder: load via the AssetDatabase API: ```csharp using UnityEditor; @@ -273,7 +281,7 @@ public class BuildReportInProjectUtility } ``` -**Build report outside the Assets folder:** for files in Library or elsewhere, use `InternalEditorUtility`: +Build report outside the Assets folder: for files in Library or elsewhere, use `InternalEditorUtility`: ```csharp using System;