Last active
March 31, 2024 23:19
-
-
Save thebeardphantom/3e44e5078d49075300c6b0b497293cad to your computer and use it in GitHub Desktop.
Sorts imports of an AssemblyDefinition asset in Unity.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define POSTPROCESSOR_ENABLED | |
using Newtonsoft.Json.Linq; | |
using System; | |
using System.IO; | |
using System.Linq; | |
using UnityEditor; | |
using UnityEditorInternal; | |
using Debug = UnityEngine.Debug; | |
/// <summary> | |
/// A post-processor that cleans up assembly definitions in the Unity Editor. | |
/// </summary> | |
public class AssemblyDefinitionCleanupPostProcessor : AssetPostprocessor | |
{ | |
#region Types | |
/// <summary> | |
/// Represents a reference in an Assembly Definition file. | |
/// </summary> | |
private readonly struct Reference : IComparable<Reference>, IEquatable<Reference> | |
{ | |
#region Fields | |
/// <summary> | |
/// Represents the original text of a reference in an Assembly Definition file. | |
/// </summary> | |
public readonly string OriginalText; | |
/// <summary> | |
/// Represents a reference to an Assembly Definition. | |
/// </summary> | |
private readonly string _name; | |
#endregion | |
#region Constructors | |
public Reference(string originalText) | |
{ | |
OriginalText = originalText; | |
const string GUID_PREFIX = "GUID:"; | |
if (originalText.StartsWith(GUID_PREFIX)) | |
{ | |
var guid = originalText[GUID_PREFIX.Length..]; | |
var path = AssetDatabase.GUIDToAssetPath(guid); | |
var asset = AssetDatabase.LoadAssetAtPath<AssemblyDefinitionAsset>(path); | |
var jObject = JObject.Parse(asset.text); | |
_name = jObject.TryGetValue("name", out var token) ? token.Value<string>() : asset.name; | |
} | |
else | |
{ | |
_name = originalText; | |
} | |
} | |
#endregion | |
#region Methods | |
/// <inheritdoc /> | |
public int CompareTo(Reference other) | |
{ | |
return string.Compare(_name, other._name, StringComparison.OrdinalIgnoreCase); | |
} | |
/// <inheritdoc /> | |
public bool Equals(Reference other) | |
{ | |
return OriginalText == other.OriginalText; | |
} | |
/// <inheritdoc /> | |
public override bool Equals(object obj) | |
{ | |
return obj is Reference other && Equals(other); | |
} | |
/// <inheritdoc /> | |
public override int GetHashCode() | |
{ | |
return OriginalText == null ? 0 : OriginalText.GetHashCode(); | |
} | |
#endregion | |
} | |
#endregion | |
#region Methods | |
[MenuItem("CONTEXT/AssemblyDefinitionImporter/Sort Imports/By Name")] | |
private static void SortByNameTest(MenuCommand cmd) | |
{ | |
var importer = (AssemblyDefinitionImporter)cmd.context; | |
if (SortImports(importer)) | |
{ | |
AssetDatabase.ImportAsset(importer.assetPath, ImportAssetOptions.ForceUpdate); | |
} | |
} | |
/// <summary> | |
/// Sorts the imports in an Assembly Definition file by name. | |
/// </summary> | |
/// <param name="importer">The AssetImporter representing the Assembly Definition file.</param> | |
/// <returns> | |
/// Returns true if the imports were sorted successfully. | |
/// Returns false if there were no changes made or if the file could not be made editable. | |
/// </returns> | |
private static bool SortImports(AssetImporter importer) | |
{ | |
var assetPath = importer.assetPath; | |
var jsonOld = File.ReadAllText(assetPath); | |
var jObject = JObject.Parse(jsonOld); | |
const string REFERENCES_PROPERTY_NAME = "references"; | |
if (!jObject.TryGetValue(REFERENCES_PROPERTY_NAME, out var token)) | |
{ | |
return false; | |
} | |
var references = token.Select(r => new Reference(r.Value<string>())).ToArray(); | |
var referencesSorted = references.OrderBy(r => r).ToArray(); | |
if (references.SequenceEqual(referencesSorted)) | |
{ | |
return false; | |
} | |
var results = referencesSorted.Select(r => r.OriginalText); | |
token = JToken.FromObject(results); | |
jObject[REFERENCES_PROPERTY_NAME] = token; | |
var jsonNew = jObject.ToString(); | |
if (!AssetDatabase.MakeEditable(assetPath)) | |
{ | |
Debug.LogWarning($"Asmdef '{assetPath}' required sorting but could not be made editable."); | |
return false; | |
} | |
File.WriteAllText(assetPath, jsonNew); | |
return true; | |
} | |
/// <summary> | |
/// Checks if the given imported asset should be processed. | |
/// </summary> | |
/// <param name="importedAsset">The path of the imported asset.</param> | |
private static bool ShouldProcessAsset(string importedAsset) | |
{ | |
return importedAsset.StartsWith("Assets") && importedAsset.EndsWith(".asmdef"); | |
} | |
#if POSTPROCESSOR_ENABLED | |
private static void OnPostprocessAllAssets( | |
string[] importedAssets, | |
string[] deletedAssets, | |
string[] movedAssets, | |
string[] movedFromAssetPaths) | |
{ | |
using (new AssetDatabase.AssetEditingScope()) | |
{ | |
foreach (var importedAsset in importedAssets) | |
{ | |
if (!ShouldProcessAsset(importedAsset)) | |
{ | |
continue; | |
} | |
var assetImporter = AssetImporter.GetAtPath(importedAsset); | |
SortImports(assetImporter); | |
} | |
} | |
} | |
#endif | |
#endregion | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment