Speed up Unity editmode asset audits (#7263)

This commit is contained in:
2026-06-15 10:53:08 -07:00
committed by GitHub
parent 52bee7a092
commit 827d07e4af
2 changed files with 69 additions and 42 deletions
@@ -47,7 +47,14 @@ namespace eagle0.Tests {
var violations = new List<string>();
foreach (var path in TextFilesToScan(projectRoot)) {
var lines = File.ReadAllLines(path);
var text = File.ReadAllText(path);
if (!PostProcessingPatterns.Any(
pattern => text.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) >=
0)) {
continue;
}
var lines = text.Split('\n');
for (var i = 0; i < lines.Length; i++) {
foreach (var pattern in PostProcessingPatterns) {
if (lines[i].IndexOf(pattern, StringComparison.OrdinalIgnoreCase) < 0) {
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
@@ -50,6 +51,9 @@ namespace eagle0.Tests {
"Assets/TextMesh Pro/",
};
private static readonly Regex SerializedGuidRegex =
new(@"\bguid:\s*([0-9a-f]{32})\b", RegexOptions.IgnoreCase);
[Test]
public void ProductionAssetsDoNotReferenceBuiltInOnlyShaders() {
var projectRoot = Directory.GetParent(Application.dataPath).FullName;
@@ -59,22 +63,15 @@ namespace eagle0.Tests {
foreach (var assetPath in ProductionReferencePaths(projectRoot)) {
var text = File.ReadAllText(Path.Combine(projectRoot, assetPath));
foreach (var shader in riskyShaders.Values) {
if (text.IndexOf(shader.Guid, StringComparison.OrdinalIgnoreCase) < 0) {
continue;
foreach (var guid in SerializedGuids(text)) {
if (riskyShaders.TryGetValue(guid, out var shader)) {
violations.Add($"{assetPath}: shader {shader.Path}");
}
violations.Add($"{assetPath}: shader {shader.Path}");
}
foreach (var material in riskyMaterials.Values) {
if (text.IndexOf(material.Guid, StringComparison.OrdinalIgnoreCase) < 0) {
continue;
if (riskyMaterials.TryGetValue(guid, out var material)) {
violations.Add(
$"{assetPath}: material {material.Path} uses shader {material.ShaderPath}");
}
violations.Add(
$"{assetPath}: material {material.Path} uses shader {material.ShaderPath}");
}
}
@@ -105,12 +102,11 @@ namespace eagle0.Tests {
}
private static Dictionary<string, ShaderRecord> RiskyShaderGuids(string projectRoot) {
return AssetDatabase.GetAllAssetPaths()
.Where(IsShaderLikePath)
return AssetFilePaths(projectRoot, new[] { "Assets/" }, ShaderLikeExtensions)
.Where(path => !IsAllowedRiskyShaderPath(path))
.Where(path => FileContainsAnyPattern(projectRoot, path, BuiltInOnlyPatterns))
.Select(path => new ShaderRecord {
Guid = AssetDatabase.AssetPathToGUID(path),
Guid = AssetGuid(projectRoot, path),
Path = path,
})
.Where(record => !string.IsNullOrEmpty(record.Guid))
@@ -121,22 +117,24 @@ namespace eagle0.Tests {
string projectRoot,
IReadOnlyDictionary<string, ShaderRecord> riskyShaders) {
var results = new Dictionary<string, MaterialRecord>(StringComparer.OrdinalIgnoreCase);
foreach (var guid in AssetDatabase.FindAssets("t:Material")) {
var path = AssetDatabase.GUIDToAssetPath(guid);
foreach (var path in AssetFilePaths(
projectRoot,
new[] { "Assets/" },
new[] { ".mat" })) {
var fullPath = Path.Combine(projectRoot, path);
if (!File.Exists(fullPath)) { continue; }
var text = File.ReadAllText(fullPath);
foreach (var shader in riskyShaders.Values) {
if (text.IndexOf(shader.Guid, StringComparison.OrdinalIgnoreCase) < 0) {
continue;
}
foreach (var guid in SerializedGuids(text)) {
if (!riskyShaders.TryGetValue(guid, out var shader)) { continue; }
results[guid] = new MaterialRecord {
Guid = guid,
var materialGuid = AssetGuid(projectRoot, path);
if (string.IsNullOrEmpty(materialGuid)) { continue; }
results[materialGuid] = new MaterialRecord {
Guid = materialGuid,
Path = path,
ShaderPath = shader.Path,
};
break;
}
}
@@ -144,13 +142,7 @@ namespace eagle0.Tests {
}
private static IEnumerable<string> ProductionReferencePaths(string projectRoot) {
return AssetDatabase.GetAllAssetPaths()
.Where(IsProductionReferencePath)
.Where(path => ReferenceScanExtensions.Contains(
Path.GetExtension(path),
StringComparer.OrdinalIgnoreCase))
.Where(path => File.Exists(Path.Combine(projectRoot, path)))
.OrderBy(path => path, StringComparer.Ordinal);
return AssetFilePaths(projectRoot, ProductionReferenceRoots, ReferenceScanExtensions);
}
private static IEnumerable<string> ProductionMaterialPaths() {
@@ -160,11 +152,9 @@ namespace eagle0.Tests {
.OrderBy(path => path, StringComparer.Ordinal);
}
private static bool IsShaderLikePath(string assetPath) {
return assetPath.StartsWith("Assets/", StringComparison.Ordinal) &&
ShaderLikeExtensions.Contains(
Path.GetExtension(assetPath),
StringComparer.OrdinalIgnoreCase);
private static bool IsAllowedRiskyShaderPath(string assetPath) {
return AllowedRiskyShaderRoots.Any(
root => assetPath.StartsWith(root, StringComparison.Ordinal));
}
private static bool IsProductionReferencePath(string assetPath) {
@@ -172,9 +162,32 @@ namespace eagle0.Tests {
root => assetPath.StartsWith(root, StringComparison.Ordinal));
}
private static bool IsAllowedRiskyShaderPath(string assetPath) {
return AllowedRiskyShaderRoots.Any(
root => assetPath.StartsWith(root, StringComparison.Ordinal));
private static IEnumerable<string> AssetFilePaths(
string projectRoot,
IEnumerable<string> assetRoots,
IReadOnlyCollection<string> extensions) {
return assetRoots.Select(root => Path.Combine(projectRoot, root))
.Where(Directory.Exists)
.SelectMany(
root => Directory
.EnumerateFiles(root, "*", SearchOption.AllDirectories))
.Where(path => extensions.Contains(
Path.GetExtension(path),
StringComparer.OrdinalIgnoreCase))
.Select(path => RelativeAssetPath(projectRoot, path))
.OrderBy(path => path, StringComparer.Ordinal);
}
private static string AssetGuid(string projectRoot, string assetPath) {
var metaPath = Path.Combine(projectRoot, assetPath + ".meta");
if (!File.Exists(metaPath)) { return null; }
var match = SerializedGuidRegex.Match(File.ReadAllText(metaPath));
return match.Success ? match.Groups[1].Value : null;
}
private static string RelativeAssetPath(string projectRoot, string path) {
return path.Substring(projectRoot.Length + 1).Replace(Path.DirectorySeparatorChar, '/');
}
private static bool
@@ -187,6 +200,13 @@ namespace eagle0.Tests {
pattern => text.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) >= 0);
}
private static IEnumerable<string> SerializedGuids(string text) {
var seenGuids = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (Match match in SerializedGuidRegex.Matches(text)) {
if (seenGuids.Add(match.Groups[1].Value)) { yield return match.Groups[1].Value; }
}
}
private struct ShaderRecord {
public string Guid;
public string Path;