diff --git a/.agents/skills/uloop-compile-check/SKILL.md b/.agents/skills/uloop-compile-check/SKILL.md new file mode 100644 index 000000000..c86024e23 --- /dev/null +++ b/.agents/skills/uloop-compile-check/SKILL.md @@ -0,0 +1,123 @@ +--- +name: uloop-compile-check +description: "Compile C# with the Unity-bundled Roslyn compiler without launching or contacting the Unity Editor; reports errors and warnings only." +--- + +# uloop compile-check + +Compile the project's C# offline, without starting Unity or sending anything to a running Editor. +It replays the response files Unity wrote during its last build with +the C# compiler bundled in the Editor install; the output lands in `Library/uloop/compile-check/` +and is never loaded by Unity. + +## When to use + +- You want to know whether the code compiles, without starting the Editor or waiting for an import + and a domain reload, and the result need not be live in the Editor yet. + +## When not to use + +- You want the change reflected in the Editor (play mode, tests, a tool call): run `uloop compile`. +- You just added or removed an `.asmdef`, added a reference to one, or changed scripting defines: + run `uloop compile` once first; the response files describe the previous build only, so the run + is refused with `COMPILE_CHECK_UNITY_BUILD_REQUIRED`. +- The project has no `Library` yet (a fresh clone, or an Editor that has never opened it): there is + no build to replay, so the run is refused with the same code. Let Unity build once, with + `uloop launch` or headless (` -batchmode -nographics -quit -projectPath `). + After that one build, compile-check works without the Editor until an `.asmdef` or build-setting + change makes it ask for another (see Limitations). + +## Usage + +```bash +uloop compile-check +uloop compile-check --all --project-path /path/to/project +``` + +By default it compiles the assemblies whose sources changed since the last Unity build, plus every +assembly that references one of them. Independent assemblies compile at the same time (`--jobs` +caps how many; `--jobs 1` is sequential). An assembly whose inputs are exactly as the previous run +read them is not compiled again: its recorded diagnostics are reported, errors included. `--all` +compiles every assembly from scratch, the way out if a reused result ever looks wrong. + +Once compile-check has compiled a large assembly in full without errors, an edit that keeps every +declaration is checked on its changed files only, in a few seconds, and the assembly is listed in +`FastCheckedAssemblies`. Compiled in full instead (`FastPathFallbacks` says why when the check was +tried): the first run after a Unity build; a run that adds, removes or renames a `.cs` file, or +changes more than 30 files; an edit that changes a declaration (a `using` directive counts); the +first run after a full compile that had errors; and an assembly compiled only because one it +references changed, which records no baseline. A change to the public surface also compiles the +dependents that can see it: those that write a changed name, or all of them when no name carries the +change; `SurfaceChecks` says which and why. Details and times: `references/full-compiles.md`. + +Full compiles run the project's analyzers with the response file's flags, `-warnaserror` included. +The changed-files check skips the assembly-wide rules in `FastChecks[].UncheckedCodes`, so run once +with `--full` before finishing: it compiles in full what differs from its last full compile and +reuses the rest; right after a run that compiled in full it takes about a second. When warnings are +errors, such a rule (CS0414, for example) can fail that run only. A run that stops on declaration +errors (an unknown type, say) reports neither body errors nor analyzer diagnostics: fix them and run +again. + +## Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `--all` | flag | Compile every assembly instead of only the changed ones and their dependents | +| `--full` | flag | Compile changed assemblies in full instead of checking only their changed files | +| `--editor-version ` | string | Use this Unity Editor version's compiler instead of ProjectVersion.txt | +| `--jobs ` | number | Compile up to N assemblies at once (default: half the CPUs, at least 1; `1` compiles sequentially) | +| `--max-depth ` | number | Search depth when locating the project (default: 3, -1 for unlimited) | +| `--project-path ` | string | Target another Unity project instead of the current directory | + +## Output + +A JSON payload; an empty list means the case did not occur in this run: + +- `Success`: whether the compile produced no errors +- `ErrorCount`, `WarningCount`: totals across every compiled and reused assembly +- `Errors`, `Warnings`: each with `Message`, `Code`, `File`, `Line`, `Column`, `Assembly` +- `CompiledAssemblies`: compiled by this run, in dependency order +- `ReusedAssemblies`: reported from the previous run's result, without compiling +- `BlockedAssemblies`: not compiled because an assembly they reference has errors, as in Unity's + build; fix those and run again +- `FastCheckedAssemblies`: checked on their changed files only +- `FastChecks`: per fast-checked assembly, `ChangedFiles`, `UncheckedCodes` (rules only `--full` runs), + `GeneratorsNotRerun`, and `Reused` (the same edit's previous check, reported again) +- `FastPathFallbacks`: `Assembly` and `Reason` for each assembly compiled in full instead +- `FastPathBaselineNotes`: `Assembly` and `Reason` for each successful full compile that left no + baseline for the next check +- `SurfaceChecks`: per assembly others reference that this run compiled, replayed or fast-checked + without errors: `Kept`, or the `ChangedNames` a dependent must write to be affected (`Unnamed` + says why no name tells the change), and the compiled `Dependents` it reached, each with the + `Name` that selected it +- `SkippedAssemblies`: how many were left out because nothing they can see changed +- `ResponseFileSet`, `ProjectRoot`: the Bee build the run replayed and the resolved project root +- `Message`: one-line summary +- `NextActions`: the next step (fix and rerun, `--full` for the skipped rules, or `uloop compile` to + make the result live); empty when nothing changed + +The process exits 1 when `ErrorCount` is greater than zero, and when the command itself fails, such +as with `COMPILE_CHECK_UNITY_BUILD_REQUIRED`; then no payload is printed and the error, with its +`ErrorCode`, goes to stderr. One compile-check runs per project at a time; a second waits up to +five minutes, then fails with `COMPILE_CHECK_PROJECT_BUSY`. Run it once more rather than retrying in +a loop. + +## Limitations + +- It replays the last Unity build's response files. A new or deleted `.asmdef`, a changed `.asmdef` + reference, an `.asmdef` that stopped building for the Editor, or a newly required precompiled + reference stops the run and asks for `uloop compile`. +- Build settings are compared against a record the first run after a Unity build writes: a + removed scripting define, a changed `csc.rsp`, `defineConstraints`, `versionDefines`, + `packages-lock.json` or `manifest.json` stops the run, and a scripting define the last build lacks + stops it when `ProjectSettings.asset` was saved after that build. A change made before that first + run (other than an added define) is not seen, so run `uloop compile` after one. Under a Unity 6 + build profile that overrides Player Settings or carries its own defines, only the global + ProjectSettings defines are compared. +- A reference removed from an `.asmdef` stops the run too; the four references Unity adds on its own + are allowed, and a reference that matches no `.asmdef` in the project is not judged. +- A new `.cs` file outside every `.asmdef` goes to `Assembly-CSharp` or a sibling by Unity's path + rules (`Editor` folders, `Assets/Plugins`); if the last build did not produce that assembly, the run + stops and asks for `uloop compile`. A new script in a package folder without an `.asmdef` is not seen. +- A generator that reads a referenced assembly is not rerun for a change in that assembly; run + `uloop compile` after one. diff --git a/.agents/skills/uloop-compile-check/references/full-compiles.md b/.agents/skills/uloop-compile-check/references/full-compiles.md new file mode 100644 index 000000000..a671cf125 --- /dev/null +++ b/.agents/skills/uloop-compile-check/references/full-compiles.md @@ -0,0 +1,85 @@ +# When compile-check compiles in full + +Read this when a run took longer than you expected, when an assembly shows up in +`FastPathFallbacks`, or when you want to know what the next edit will cost. + +The changed-files check applies to assemblies with at least 300 sources, once compile-check has +compiled that assembly in full without errors and kept a baseline, and at most 30 of its files +changed. Smaller assemblies are always compiled in full. + +## What an edit costs + +| Edit | What runs | Typical time | +|------|-----------|--------------| +| A body-only edit of up to 30 files in a large assembly with a baseline | Changed-files check | A few seconds | +| A body-only edit of more than 30 files | Full compile (`FastPathFallbacks`: too many changed files) | As for a full compile | +| A new, deleted or renamed `.cs` file | Full compile of the assembly | About Unity's compile of it, without the import and the domain reload | +| A declaration change (a member, a signature, a `using` directive) | Full compile, plus the dependents that can see the change | The assembly, then its dependents | +| The first run after a Unity build | Full compile of every changed assembly | As for a full compile | +| The first run after a full compile that had errors | Full compile (no baseline was recorded) | As for a full compile | +| An assembly compiled only because a reference changed | Full compile now, and again on its next body-only edit (it records no baseline) | As for a full compile | +| `--full` | In full: whatever differs from its last full compile - the assemblies fast-checked since, the dependents their rebuilt reference assemblies reach, and anything edited since the last run; the rest reused | As for a full compile of those; about a second right after a run that compiled in full | +| `--all` | Every assembly | The whole build | + +## Dependents + +When an assembly compiles in full, its public surface (its reference assembly) is compared with the +one from the last Unity build, not with the previous compile-check run; a surface that differs from +Unity's keeps compiling its dependents until Unity builds again. Only the dependents that write a +name the change touches are compiled; adding a member counts the type's name among the changed +names. A change a dependent can feel without writing any name is unnamed and compiles every +dependent: a new base type or interface, an operator, an added abstract member, a member C# binds by +pattern (`GetEnumerator`, `Deconstruct` and the like), among others. The output's `SurfaceChecks` +lists, per assembly that others reference, the verdict, the changed names or the unnamed reason, and +the compiled dependents the change reached, with the name that selected each. + +An assembly starts once every assembly it references that this run also compiles has finished. An +assembly selected for its own sources, or a dependent already known to need compiling, starts +sooner: as soon as every assembly it references that was selected for its own sources has finished. +The dependents it references that are still compiling are assumed to keep Unity's public surface; +when one of them turns out to have changed it, the assembly is compiled again after they finish, at +most twice per run. A dependent whose recorded result may still apply once those finish waits for +them instead. + +## Baselines and `no baseline` reasons + +A full compile that succeeds records the baseline the next changed-files check is made against. When +there is none, `FastPathFallbacks[].Reason` says why: + +- `no baseline: no full compile of this assembly is on record`: no full compile has run yet, or an + input changed while it ran (`FastPathBaselineNotes` of that run says which). +- `no baseline: the last full compile (at