Skip to content

Commit b93bde5

Browse files
7sharp9claude
andcommitted
Update CLAUDE.md and BACKLOG.md to reflect the shipped MSBuild/CLI engineering fixes
Corrects the now-stale claim that the DTB gate removal (Q022) was only applied to a scratch copy, and records the other three fixes (batching, TOML writer, OutputPath bug) in BACKLOG.md's known-engineering-gaps section, matching this repo's existing convention for marking backlog items shipped. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 793bc98 commit b93bde5

2 files changed

Lines changed: 66 additions & 19 deletions

File tree

CLAUDE.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,21 @@ need a spike to justify fixing. Design-time/IDE invisibility, long the biggest n
7979
short of it, and the direct MSBuild/DTB-hook route (item 8) has now been spiked as `Q022` — REVISE,
8080
closing the gap at project load/reload time (confirmed against a real `fsautocomplete`/LSP session, the
8181
first such test in this repo's history) but not during live source editing, since an ordinary edit to
82-
the attributed source file never touches the `.fsproj` a reload is keyed on. Applying the gate-removal
83-
to the real, shared `src/Myriad.Sdk/build/Myriad.Sdk.targets` (Q022 only ever edited a scoped local
84-
copy) remains a real, low-risk, not-yet-applied candidate change in its own right. A second,
82+
the attributed source file never touches the `.fsproj` a reload is keyed on. **The gate removal was
83+
applied for real on 2026-07-17** to the actual shared `src/Myriad.Sdk/build/Myriad.Sdk.targets` (Q022
84+
itself only ever edited a scoped local copy) — re-verified directly against this repo's own test
85+
project via a real DTB invocation (`-p:DesignTimeBuild=true -p:SkipCompilerExecution=true`): codegen
86+
runs, the compiled `.dll`'s mtime never moves, and a repeat DTB call still correctly no-ops. Same
87+
session, three more MSBuild/CLI engineering fixes shipped from `BACKLOG.md`'s known-gaps list, none of
88+
them quartet-shaped: Myriad now runs once per project instead of once per file (a new `--manifest`
89+
CLI mode, since the old per-file rebuild cache was already invalidating every file on any single
90+
change — no real incrementality lost); the project-context TOML writer no longer depends on MSBuild's
91+
implicit `;`-splitting of `Include` attributes to fake multi-line output (the same fragility class
92+
this repo's git history already shows repeated fixes for); and a stray trailing `)` in `Myriad.Sdk.
93+
targets`'s `OutputPath` (a 2022 refactor leftover) that had been silently defeating the up-to-date
94+
check for `MyriadInlineGeneration` files, forcing regeneration on every build, is fixed. All four are
95+
detailed in `BACKLOG.md`'s known-engineering-gaps section and `DEVNOTES.md`; committed as `793bc98`.
96+
A second,
8597
non-type-provider route was also opened in an earlier session: item 18 proposes FSAC itself hosting
8698
Myriad as a live-editing sidecar (modeled loosely on rust-analyzer's out-of-process proc-macro
8799
architecture), and its own cheapest-falsifier precursor question was spiked as `Q021`

experiments/BACKLOG.md

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,22 +1121,57 @@ Surfaced as background findings while building the quartets above, not something
11211121
needs to re-derive. Candidate real fixes, independent of whether the architecture-exploration
11221122
track above goes anywhere:
11231123

1124-
- **Generated code is invisible to the IDE until a real build — partially addressed, see item 8 /
1125-
Q022.** `MyriadSdkGenerateCode` is gated `Condition="'$(DesignTimeBuild)' != 'true'"` and
1126-
`MyriadSdkIncludeCodegenOutputDuringDesignTimeBuild` is an empty target
1127-
(`src/Myriad.Sdk/build/Myriad.Sdk.targets`). Both the type-provider route (Q006, REVISE — structurally
1128-
can't reach a same-project attributed type) and the direct DTB-hook route (`Q022`, REVISE — real
1129-
mechanism, real capability against a literal FSAC process, but only at project load/reload time, not
1130-
live source editing) have now been tried; see item 8 above for the full Q022 account. Removing the
1131-
gate in `src/` for real would be a genuine, well-understood, low-risk fix for the load/reload case
1132-
(Round 1 confirmed it never breaks DTB and the existing rebuild cache still governs cost correctly),
1133-
not yet applied to the shared `src/Myriad.Sdk/build/Myriad.Sdk.targets` itself — that remains a real
1134-
candidate change, separate from and cheaper than any further spike, worth doing regardless of whether
1135-
the live-source-edit half is ever solved.
1136-
- **Codegen runs one cold process per input file.** `MyriadSdkGenerateCode`'s
1137-
`Outputs="%(MyriadCodegen.OutputPath)"` triggers MSBuild's per-item batching, so the target (and
1138-
its `<Exec>`) runs once per file, each paying full JIT + Fantomas-parse startup cost. Fixable
1139-
without touching the plugin API: batch the CLI invocation, or publish it ReadyToRun/AOT.
1124+
- **Generated code is invisible to the IDE until a real build — DTB gate removed for real,
1125+
2026-07-17 (see item 8 / Q022 for the full mechanism account).** `MyriadSdkGenerateCode`'s
1126+
`Condition="'$(DesignTimeBuild)' != 'true'"` gate and the empty
1127+
`MyriadSdkIncludeCodegenOutputDuringDesignTimeBuild` target are both gone from the real, shared
1128+
`src/Myriad.Sdk/build/Myriad.Sdk.targets` — not just the Q022 scratch copy. Re-verified directly
1129+
against this repo's own test project via a real `-p:DesignTimeBuild=true
1130+
-p:SkipCompilerExecution=true` invocation: codegen runs, the compiled `.dll`'s mtime never moves
1131+
(the real F# compiler is never invoked), and a repeat DTB call still correctly no-ops. Still only
1132+
closes the gap at project load/reload time, exactly as Q022 found — an ordinary source-file save
1133+
does not itself re-run codegen in an already-running IDE session; only a project reload does.
1134+
- **Codegen runs one cold process per input file — SHIPPED, 2026-07-17.** `MyriadSdkGenerateCode` now
1135+
runs Myriad exactly once per project (not once per file) via a new `--manifest <file>.toml` CLI
1136+
mode: MSBuild writes one small TOML file (one `[[unit]]` table per attributed file, built in a new
1137+
`_MyriadSdkFlattenParams` target) and a single `<Exec>` processes all of them in one process,
1138+
loading plugins once. This was not a pure size-preserving refactor — the old per-item
1139+
`Outputs="%(MyriadCodegen.OutputPath)"` batching turned out to already be a false promise of
1140+
per-file incrementality: `_MyriadSdkCodeGenInputCache` is one combined hash over every codegen
1141+
input, so editing any single attributed file already forced every other generated file to
1142+
regenerate too (confirmed directly: editing one file caused four unrelated generated files to be
1143+
rewritten). Batching into one process therefore loses no real granularity, only removes N-1
1144+
redundant cold-process launches. Two non-obvious MSBuild traps found only by testing end-to-end,
1145+
not by reasoning about the XML: a `<Target>`'s own `Condition` is evaluated *before* its
1146+
`DependsOnTargets`/`BeforeTargets` chain runs, so a naive `Condition="'@(MyriadCodegen)' != ''"`
1147+
guard always saw an empty list and skipped the whole target, silently — the guard has to live on
1148+
the `<Exec>` task itself instead; and forcing MSBuild to batch a helper target per-item has to key
1149+
off `%(MyriadCodegen.OutputPath)` (guaranteed unique per `<Compile>` item), not
1150+
`%(MyriadCodegen.Identity)` (the *input* file, which several different `<Compile>` items can
1151+
legitimately share via `MyriadFile`) — keying on the wrong one silently merged multiple files'
1152+
`MyriadParams`/`Generators` together. Full account: `DEVNOTES.md`'s "Why one process, not one per
1153+
file" and "A target's `Condition` runs before its own dependencies" sections. All 58 existing tests
1154+
plus `samples/Example` verified passing; committed as `793bc98`.
1155+
- **The project-context TOML writer no longer depends on MSBuild's implicit `;`-splitting of
1156+
`Include` attributes — SHIPPED, 2026-07-17.** `_MyriadContext`'s multi-line TOML arrays
1157+
(`referencePaths`, `compile`, etc.) used to be built as `[;$(_ReferencePaths);]`, relying on
1158+
MSBuild silently splitting that one string into three separate items at the unescaped `;`
1159+
characters so `WriteLinesToFile` would render them across multiple physical lines — the same class
1160+
of fragility this repo's own git history already shows repeated fixes for ("escape with a single
1161+
apostrophe," "try msbuild escaping targets," "fix for windows (again)"). Replaced with explicit
1162+
`%0a`-embedded construction. Verified: item counts round-trip exactly (11 compile items matching 9
1163+
explicit `<Compile>` entries plus MSBuild's 2 auto-generated assembly-info files), and a real
1164+
embedded comma inside a path (`...AssemblyAttributes.fs`) doesn't break parsing, since the
1165+
single-quoted TOML literal-string quoting (unchanged) protects it regardless of the outer
1166+
delimiter mechanism. Committed as `793bc98`.
1167+
- **`Myriad.Sdk.targets`' `OutputPath` for `MyriadInlineGeneration` files had a stray trailing `)`
1168+
FIXED, 2026-07-17.** Left over from a 2022 refactor (`c818a70`) that removed a
1169+
`[System.IO.Path]::GetFullPath(...)` wrapper but left its closing paren behind, producing an
1170+
`OutputPath` metadata value that could never exist on disk. Since that value fed directly into
1171+
MSBuild's own up-to-date check for the target, every build regenerated every
1172+
`MyriadInlineGeneration` file regardless of whether anything had changed — confirmed by a direct
1173+
before/after comparison (4 of 5 vs. 5 of 5 batched items correctly skipped on a no-op rebuild).
1174+
One-character fix; committed as `793bc98`.
11401175
- **No `#line` pragmas in generated output.** Errors in generated code point at the generated
11411176
file, not the source declaration that produced it. Cheap, mechanical fix.
11421177
- **Config lives behind an indirection.** An attribute carries a string key, which is looked up

0 commit comments

Comments
 (0)