Unity Assembly Definition Files Tutorial (.asmdef)
Assembly Definitions are a new way Unity allows us to organize the code by creating separate assemblies and specifying dependencies between them. It first appeared as preview in Unity 2017.3 and became more functional in later releases. Assembly Definitions can also be called asmdefs for short.
In this tutorial let’s look at the basics and most common errors that may happen.
Why use Assembly Definitions
One of the first benefits you can notice, is that only the changed assemblies are rebuilt, instead of the whole project (although that wasn’t necessarily the case for the initial version, as compilation would take longer instead).
They allow us to better structure the code and control the dependencies it has. If you follow some pattern and want to avoid accidental references, for example, from Model to View, with assemblies it will not compile instead of potentially being missed.
Instead of doing #if conditions all over the code, you can have a separate assembly that is only compiled for specific platforms or only when some Scripting Define Symbols are specified.
If you would like to use Unity Package Manager in the future, code inside packages is required to have an Assembly Definition. And creating assemblies beforehand and controlling the dependencies will ensure the code can be easily extracted at a later point.
How to create an Assembly Definition
To create a new asmdef open a folder in Unity Project View (which can be opened with Window → General → Project)
Right-click there and select ‘Create → Assembly Definition’.
This will create a new .asmdef file and make all scripts in this folder and any subfolders to belong to the newly created assembly. And now you will be able to change its settings in the assembly.
Naming the Assembly
By default, naming Assembly Definitions should be done the same way you would name namespaces – use folder names from the root folder of the project, but exclude the Assets (although not each folder should contain a separate asmdef). And prefix this with your studio or project name.
So if you have a folder Assets/DungeonGenerator/Editor you would name it something like Game.DungeonGenerator.Editor.asmdef
You can also look how Unity names assemblies in their packages.
Auto Referenced
I also often uncheck the “Auto Referenced” which adds this assembly as a reference to default Unity assemblies (like Assembly-CSharp & Assembly-CSharp-firstpass) because when you don’t have any code outside your own assemblies, this is unneeded and may even increase build times.
The exception is Editor assemblies because they aren’t always needed to be referenced to use.
Dependencies
If code inside this folder needs some dependencies, we’ll need to add them in the ‘Assembly Definition References’. So you can’t reference anything that isn’t an assembly definition itself, except DLLs, they are globally visible for some reason.
It is important to note that you can’t use cyclic dependencies with asmdefs, if A references B, B will not be able to reference A. So you will need one-directional dependencies, but that may be a good thing.
If, for example, you decide to create one huge assembly for your project and start extracting parts from it, you need to extract the most basic things first so that your new code does not depend back on the huge assembly. And if you’ll need something from the big assembly, try extracting that part first instead.
Unsafe code in Assemblies
If before your code could have used unsafe, now you will need to specify it in each specific assembly definition that requires it.
To enable it, just toggle the checkbox ’unsafe code’ in the .asmdef inspector.
Editor folder and Assembly Definitions
Magical nature of Editor folders does not work well with asmdefs, because they are still counted as a part of your higher level assembly, and so for each Editor folder, you will need to have a separate .asmdef file with Editor platform selected.
It is common to name Editor assemblies by using the name of the non-Editor assembly and adding ‘.Editor’ at the end.
When converting big projects with a lot of Editor folders it better to automate the process instead of doing it by hand. So here is the script that can automatically add asm defs in all Editor folders. The resulting asmdef names maybe not what you want, so you can change how editorAssembly.name is chosen.
Editor Code (click to expand)
1 using System.Collections.Generic;
2 using System.IO;
3 using UnityEditor;
4 using UnityEngine;
5
6 #pragma warning disable 0649
7 class AssemblyDefinitionType
8 {
9 public string name;
10 public List references;
11 public List includePlatforms;
12 public List excludePlatforms;
13 }
14 #pragma warning restore 0649
15
16 public static class AssemblyDefinitionHelper
17 {
18 [MenuItem("Tools/Create Editor Assembly Definition Files")] public static void TurnOnAssembly()
19 {
20 CreateEditorFiles(new DirectoryInfo(Application.dataPath), null);
21 AssetDatabase.Refresh();
22 }
23
24 private static List _editorAssemblySiblings;
25 private static int _duplicateNum = 0;
26
27 static void CreateEditorFiles(DirectoryInfo dir, FileInfo parentAssembly)
28 {
29 FileInfo[] files = dir.GetFiles();
30 foreach (FileInfo file in files)
31 {
32 if (file.Extension == ".asmdef")
33 {
34 parentAssembly = file;
35 _editorAssemblySiblings = new List();
36 _duplicateNum = 1;
37 }
38 }
39
40 if (parentAssembly != null)
41 {
42 foreach (var subdir in dir.GetDirectories())
43 {
44 if (subdir.Name != "Editor")
45 {
46 continue;
47 }
48
49 var editorAssembly = JsonUtility.FromJson(File.ReadAllText(parentAssembly.FullName));
50 editorAssembly.references.Clear(); // do not reference what parent assembly referenced
51 editorAssembly.references.Add(editorAssembly.name); // add parent assembly as a reference
52
53 editorAssembly.name = editorAssembly.name + "." + dir.Name + ".Editor";
54 if (_editorAssemblySiblings.Contains(editorAssembly.name))
55 {
56 editorAssembly.name += _duplicateNum;
57 _duplicateNum += 1;
58 Debug.LogError("Duplicate name, adding numbers at the end: " + editorAssembly.name);
59 }
60 _editorAssemblySiblings.Add(editorAssembly.name);
61 editorAssembly.includePlatforms = new List {"Editor"};
62 File.WriteAllText(Path.Combine(subdir.FullName, editorAssembly.name + ".asmdef"), JsonUtility.ToJson(editorAssembly));
63
64 }
65 }
66
67 foreach (var subdir in dir.GetDirectories())
68 {
69 if (subdir.Name != "Editor")
70 CreateEditorFiles(subdir, parentAssembly);
71 }
72 }
73 }
It is probably better to backup your project before running the script.
It requires a root assembly to work.
To use it – just run the “Tools -> Create Editor Assembly Definition Files”
After it finished, you may still need to fix some references by hand, for example, if one Editor folder depended on another one, this script will not fix this automatically. And maybe fix some assembly names, in case duplicates happened.
Converting Plugins
When you are converting your project to the asmdef way, you will probably want to add dependencies on some third-party code. Some plugins are already providing an .asmdef out of the box, but if the don’t – I normally just add one asmdef to the root folder (plus a separate to the Editor folder), it takes a few moments and will probably not add any difficulty when updating to new versions of the plugin, except to switch to official asmdef.
Assembly hierarchy & transitive dependencies
By transitive dependency, I just mean the dependency of a dependency.
So what happens when you have three assembly definitions: A, B and C, and the depend like this: A→B→C.
‘A’ can use types from B, but if B returns types from C, then what? Well, you actually wouldn’t be able to use C from A without referencing C in both B and A.
A→B→C
↳C
Common Errors with Assembly Definitions
Missing an assembly reference
The error looks similar to this one:
Probably the type you are trying to use is not in the same Assembly. To fix this you will need to add a dependency to the assembly containing the type you are using.
If you open the Inspector for the script that is not compiling, you can see a ‘Definition File’. If you look up the file that is missing a reference, and the file that contains the type you need – you will know what assembly needs a reference, and what one to specify as a dependency.
Assembly has reference to non-existent assembly
This probably just means that you reference an assembly that was removed, renamed, or there is some kind of typo. Try removing this dependency by selecting the broken reference, pressing the ‘-’, and then Apply.
This may lead to ‘Missing an assembly reference’ errors, that should be fixed by adding the correct dependency.
Assembly with cyclic references detected
This normally means that dependencies you have do form a loop, for example, if A references B, and B references A (A ⇄ B) – that would be a cyclic reference which cannot compile.
To fix it you need to break the cycle in some way.
For example, if you can extract a part of A, that B uses, to a separate assembly, even if both A and B require it, it will no longer be a cycle.
No visible errors, but doesn’t compile or run
Try deleting the Library/ScriptAssemblies folder, to trigger full recompilation and be able to see the error.
Exception: No pending assemblies queued for compilation and no compilers running. Compilation will never finish.
Try to change your root assemblies, for example, by changing its platforms, to trigger the recompilation and show the actual error. If this doesn’t help – you could also try deleting Library/ScriptAssemblies or reimporting all assets (Assets → Reimport All)
Mobile or PC builds fail
Look at the build log for messages like these:
This can be caused by the reference assembly not being compiled due to errors or not having any scripts associated with it. Note text
So, as it says, it may be caused by some compilation errors you can search for in the same build log, or if you reference an empty assembly. Empty assemblies work in the Editor, but cause troubles with the build. You can either delete this .asmdef and references to it or just add some empty script.