Unity Assembly Definition Files

I’ve been waiting for a good way to split up large Unity codebases into assemblies for a while now. Both for keeping compile times low (for Modbox I was waiting 20+ seconds every recompile), and for better code modularity. It was possible before to do this outside of Unity and just import as a plugin – but that would mean manually compiling the library on every code change. For code modularity I mostly wanted this so that I could set code as Internal to a certain namespace – which is especially important when making a SDK that other devs will use (like the ModboxSDK).

Unity’s new Assembly Definition File system sounded perfect – just drop a file into a folder and have that folder be it’s own assembly. My Modbox compile times went from 20+ seconds to under 3 – and I was able to split up my codebase into 11 projects.

Capture2Capture1

The problem is how Assembly Definition Files ignores Unity’s old ‘magic folders’ system – where anything in a folder called ‘Editor’ will be compiled into the Editor assembly. It’s definitely good for Unity to start moving away from magic folders – but the problem is when switching over a large project (with a lot of Asset Store content) that means a massive refactor to move all the editor scripts to a single folder.

For developing you could just use include Editor folders in assemblies with game code – but then errors will come up when actually trying to build the game (since the built game can’t include the UnityEditor namespace). Or you could make a Editor only assembly for each Editor folder – but for Modbox that would result in making 60+ Editor assemblies. Which both sucks to set up and also massively increases Visual Studio’s start up time.

My way around this at first was to add a editor script to turn all assembly definition files on/off (so only when building the game would they be turned off, and developing can still have super low compile times / code modularity). Only after a few weeks did I notice all the problems with this with Unity’s UI event system (it references a script’s method based on the type, so if the Assembly changes the reference is then missing).

So my new solution was to make a script that would auto generate the Editor assembly definition files and then delete them after the game is built (so I wouldn’t have 60+ editor projects in my VS solution while developing).
In some ways it’s a hacky solution, but it actually behaves similar to how Unity handles the Editor namespace: easily use it during development, but exclude it for actually building.

To generate the Editor assemblies it looks for a ‘parent assembly’, and builds it’s references based on that (also uses it to set the right name). It was able to generate all 60+ Modbox Editor assemblies with the correct references (mostly third party asset store code).

All the code is here (add it as a Editor script):

class AssemblyDefinitionType // type used to load the .asmdef as json
{
    public string name;
    public List references;
    public List includePlatforms;
    public List excludePlatforms;
}

public class AssemblyDefinitionSwitch
{
    [MenuItem("Tools/Create EditorAssemblyDefFiles")]
    public static void TurnOnAssembly()
    {
        CreateEditorFiles(new DirectoryInfo(Application.dataPath), null, false);
        AssetDatabase.Refresh();
    }

    [MenuItem("Tools/Delete EditorAssemblyDefFiles")]
    public static void TurnOffAssembly()
    {
        CreateEditorFiles(new DirectoryInfo(Application.dataPath), null, true);
        AssetDatabase.Refresh();
    }

    static List EditorAssemblySibilings;
    static int duplicateNum = 0;

    static void CreateEditorFiles(DirectoryInfo Dir, FileInfo ParentAssemb, bool Remove)
    {
        FileInfo[] files = Dir.GetFiles();
        foreach (FileInfo file in files)
        {
            if (file.Extension == ".asmdef")
            {
                ParentAssemb = file;
                EditorAssemblySibilings = new List();
                duplicateNum = 1;
            }
        }

        if (ParentAssemb != null)
        {
            DirectoryInfo[] dirs = Dir.GetDirectories();
            foreach (DirectoryInfo subdir in dirs)
            {
                if (subdir.Name == "Editor")
                {
                    FileInfo[] editorfiles = subdir.GetFiles();
                    foreach (FileInfo file in editorfiles)
                    {
                        // Delete old asmdef editor folder files
                        if (file.Extension == ".asmdef")
                        {
                            File.Delete(file.FullName);
                        }
                    }
                    if (!Remove)
                    {
                        // create a assemb file based on the Parent one, only with includePlatforms changed to Editor
                        // assembly name is the parent assembly name + directory that contains the editor folder + "Editor"

                        AssemblyDefinitionType EditorAssemb = JsonUtility.FromJson(File.ReadAllText(ParentAssemb.FullName));
                        EditorAssemb.references.Add(EditorAssemb.name); // add parent assembly as a reference
                        EditorAssemb.references.AddRange(EditorAssemblySibilings); // for subeditor folders to reference the above editor assemblies

                        EditorAssemb.name = "z_" + EditorAssemb.name + Dir.Name + "Editor"; // added z_ to be at bottom of solution explorer
                        if (EditorAssemblySibilings.Contains(EditorAssemb.name))
                        {
                            EditorAssemb.name += duplicateNum;
                            duplicateNum += 1;
                        }
                        EditorAssemblySibilings.Add(EditorAssemb.name);
                        EditorAssemb.includePlatforms = new List();
                        EditorAssemb.includePlatforms.Add("Editor");
                        File.WriteAllText(Path.Combine(subdir.FullName, EditorAssemb.name + ".asmdef"), JsonUtility.ToJson(EditorAssemb));
                    }
                }
            }
        }

        DirectoryInfo[] dirs2 = Dir.GetDirectories();
        foreach (DirectoryInfo subdir in dirs2)
        {
            if (subdir.Name != "Editor")
                CreateEditorFiles(subdir, ParentAssemb, Remove);
        }
    }
}