Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save thebeardphantom/3e44e5078d49075300c6b0b497293cad to your computer and use it in GitHub Desktop.
Save thebeardphantom/3e44e5078d49075300c6b0b497293cad to your computer and use it in GitHub Desktop.
Sorts imports of an AssemblyDefinition asset in Unity.
#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