General

Tooling Recipes

Things that are hard to work out from scratch and needed more than once. Paths are macOS and assume the Unity version this project pins (6000.5.4f1).

Decompile the engine

The most reliable way to settle a question about UI Toolkit, URP or any other engine assembly is to read it. Decompiling the whole assembly once and grepping it beats fishing for type names:

dotnet tool install -g ilspycmd      # once; lands in ~/.dotnet/tools, not on PATH
cd "/Applications/Unity/Hub/Editor/6000.5.4f1/Unity.app/Contents/Resources/Scripting/Managed/UnityEngine"
~/.dotnet/tools/ilspycmd -o /tmp/uitk -p UnityEngine.UIElementsModule.dll   # whole assembly, ~1070 files
~/.dotnet/tools/ilspycmd -t UnityEngine.UIElements.MaterialDefinition UnityEngine.UIElementsModule.dll

Type and member names found this way are authoritative; line numbers are not — they shift with the decompiler version, so cite the type and member, never a line.

The shader libraries are plain text and need no decompiling:

  • UI Toolkit’s own shader library: /Applications/Unity/Hub/Editor/6000.5.4f1/Unity.app/Contents/Resources/CGIncludes/Internal/UnityUIE.cginc

  • Shader Graph’s UITK target, useful as a worked example of the vertex contract: …​/BuiltInPackages/com.unity.shadergraph/Editor/Generation/Targets/UITK/

Compile C# while Unity holds the project lock

Bee leaves a full response file per assembly — every define, every reference, every source file — from the last successful build, so Roslyn can be run on it directly without waiting for the editor. Nothing is written into the project: point -out at a scratch path.

cd <project>
csc=/Applications/Unity/Hub/Editor/6000.5.4f1/Unity.app/Contents/Resources/Scripting/DotNetSdk/sdk/8.0.318/Roslyn/bincore/csc.dll
# ...EDbg.dag = editor defines (UNITY_EDITOR), ...P.dag = player defines. Check both.
sed 's#-out:.*#-out:"/tmp/csc/Promethist.LiquidGlass.dll"#; s#-refout:.*#-refout:"/tmp/csc/Promethist.LiquidGlass.ref.dll"#' \
    Library/Bee/artifacts/2000b0aEDbg.dag/Promethist.LiquidGlass.rsp > /tmp/lg.rsp
dotnet "$csc" @/tmp/lg.rsp        # silence means it compiled

Two ways this bites:

  • The source list is from the last build. A file added since will not be in it and has to be appended ("$PWD/<path>", one per line); a deleted file has to be grep -v’d out. Diff the `.rsp against find <dir> -name '*.cs' rather than assuming.

  • Give each define set its own output directory. The player build of an assembly has no UNITY_EDITOR, so it contains none of the assembly’s editor-only internal members. Building both configs to one path leaves the player output in place, and the matching *.Editor assembly then fails against it — with exactly the CS1061 wall described below, from a compile that was fine.

  • Keep the assembly’s own filename and redirect only the directory. Roslyn takes the assembly identity from the output filename, so -out:/tmp/out.dll builds an assembly called out, and [assembly: InternalsVisibleTo("Promethist.Something.Editor")] then does not match — every editor-only internal member reads as missing, surfacing as a wall of plausible-looking CS1061 errors that look exactly like a real break in the code just changed. For the same reason, point an editor assembly’s -r: at the runtime assembly just built rather than at the stale one in Library/Bee/artifacts.

This does not cover shaders, which only the editor compiles.

File encoding

The rules are in Code Style. .gitattributes sets * text=auto eol=lf, so git stores and checks out every text file with LF regardless of core.autocrlf, and leaves files it detects as binary untouched. .editorconfig makes editors write the same bytes git expects; a file that differs from the index only in line endings is what breaks partial-hunk stash, restore and checkout.

Unity-serialized files (.asset, .meta, .prefab, …) get LF, but .editorconfig leaves their final newline and trailing whitespace alone, since Unity writes both (an empty string serializes as `key: `) and would revert any change on the next save.

The URP submodule only has its line endings normalized, and its own .editorconfig stops the root one from applying. Its BOMs and whitespace follow upstream Unity, so that importing a new URP version does not conflict on formatting.