LT-22816: Rebuild native objects when an included header changes - #1168
Open
johnml1135 wants to merge 2 commits into
Open
johnml1135 wants to merge 2 commits into
johnml1135 wants to merge 2 commits into
Conversation
GenerateCellarConstants declared its Outputs through a top-level item built from $(dir-fwoutputCommon). That property is set by the Setup target at run time, so the item was evaluated as "/CellarConstants.h". MSBuild never found the output, ran the target on every build, and rewrote the header with identical bytes and a new timestamp. That was harmless while nothing tracked headers. With header dependencies it would rebuild every object that includes the file (43 of 46 in Views) on every build. Inputs and Outputs now sit on the target, where properties expand at run time, as CopyCellarBaseConstants already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The nmake inference rules in Bld/_rule.mak make each object depend on its own source file only. Editing a header rebuilt nothing, the build reported success, and Views.dll stayed stale. A class-layout change rebuilt only some objects and mixed layouts, which corrupts memory. Every compile rule now passes cl /sourceDependencies, which writes a JSON list of the headers the translation unit read. At the end of _targ.mak, Bld/Write-NmakeHeaderDependencies.ps1 turns one product's lists into plain "object : header" lines and _targ.mak includes them, so nmake makes every up-to-date decision itself. - /sourceDependencies rather than /showIncludes: structured output, no parsing of localized compiler text. Needs VS 2019 16.7+. - Full edges, evaluated by nmake at make time, so a header generated earlier in the same build still triggers rebuilds. - The include comes last because nmake's default goal is the first target it reads. - Only repository headers are tracked, and missing ones are dropped, so a deleted header cannot break the build. - An object with no sidecar predates this change and is rebuilt once. A missing PCH forces its producer and consumers the same way. - The include is written to a temporary file and then replaced. - A path containing # fails the build: nmake cannot quote it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1168 +/- ##
==========================================
- Coverage 39.03% 39.02% -0.01%
==========================================
Files 1522 1522
Lines 353035 353035
Branches 40744 40744
==========================================
- Hits 137792 137770 -22
- Misses 185926 185947 +21
- Partials 29317 29318 +1 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Start here: the end of
Bld/_targ.mak, thenBld/Write-NmakeHeaderDependencies.ps1. Two commits, reviewable in order.What it does
Editing a header in Views, FwKernel, Generic or DebugProcs now rebuilds every object that includes it. Before this, nmake's inference rules made each
.objdepend on its own.cpponly. A header edit rebuilt nothing, the build reported success, andViews.dllstayed stale. A class-layout change recompiled some objects and not others, which corrupts memory (hit on #1166 and LT-22674).Every compile now passes
cl /sourceDependencies. For each product, the helper turns those JSON lists into plain"obj" : "header"lines and_targ.makincludes them, so nmake still makes every up-to-date decision.The unknown: does this slow every build down?
It doesn't. A no-change native build rebuilds 0 objects and takes 21-45 s. The first commit fixes the one thing that would have made it slow:
GenerateCellarConstantsdeclaredOutputsthrough an item evaluated before$(dir-fwoutputCommon)was set. The target ran on every build and rewroteCellarConstants.hwith identical bytes, which with header tracking would recompile 43 Views objects every time.Where to look
build:, as the comment says.U1050, and a path containing#fails with a message.Not here
Fragmentsitem (mkall.targets:39-40); it gets its own ticket./Yc//YuPCH builds. No shipped product compiles a PCH through these rules; the PCH handling is covered by a synthetic test only.Verification
build.ps1 -CommentHygiene -TokenHygieneis clean, and so is a clean native build. TestViews passes 309/0/0. Header-touch checks are in the accordion. Release wasn't built locally. To check it yourself:.\build.ps1 -Project Build/Src/NativeBuild/NativeBuild.csproj(Get-Item Src/views/lib/LayoutCache.h).LastWriteTime = Get-DateUniscribeEngine.obj,UniscribeSegment.objandVwTextBoxes.objshould be the only fresh objects underObj/Debug/Views.Next: approve, or tell me to split the
CellarConstantsfix into its own PR.Reading this a year from now: start here
The working briefs and reviews for this branch stayed outside the tree. This body is the record of why the design looks the way it does. The Jira is LT-22816; the original report is LT-22674 comment 2.
Decisions, and why
/sourceDependencies, not/showIncludes. It produces structured JSON per translation unit, with no parsing of localized compiler text. It needs MSVC 16.7+, and VS 2022/v143 is already the minimum._targ.mak. All six makefiles that include_rule.makinclude_targ.makexactly once, after definingbuild:. That gives one helper run per nmake invocation, whatever entry point started it.Include/(ICU) headers are tracked. Views gets 13,307 edges. Excluding them would leave objects stale after an ICU bump.File.Replace, so nmake never reads a partial file.Paths not taken
.vcxproj, which tracks includes itself. The largest change, and it reordersFieldWorks.proj.TestViewswhile the makefile setstestViews.Its performance motivation (a 122 s no-change build) turned out to be the
CellarConstantsrewrite recompiling 46 objects, not the size of the include._rule.mak. That made the first object in the include nmake's default goal, so a build compiled that one object and nothing else. The first implementation had this too; its stale-only edges hid it.Evidence
Debug,
build.ps1 -Project Build/Src/NativeBuild/NativeBuild.csproj, comparing object timestamps before and after:Src/views/lib/LgUnicodeCollateInit.hLgUnicodeCollater.objSrc/views/lib/LayoutCache.hViews.dllrelinksSrc/views/Main.hVwEnv.obj.source-dependencies.jsonVwEnv.objonce, then 0Src/Kernel/CellarConstants.vm.hBefore the
mkall.targetsfix, a detailed MSBuild log showedOutput file "/CellarConstants.h" does not exist, and three consecutive builds rewrote the file with the same SHA-256.The helper produces identical output under Windows PowerShell 5.1 and PowerShell 7, and exits 1 on error. A synthetic product covers a missing sidecar, a missing PCH (producer and consumer forced),
$in a path (doubled), and#in a path (rejected).Two independent read-only reviews were run: one on the first design and one on the final diff. Every valid finding is addressed.
Preflight review details
Code Review Summary
Branch: LT-22816-native-header-deps
Base: main
Date: 2026-09-30
Review model: Claude Opus 5.5 (synthesis); two independent luna-6 (gpt-6-luna, xhigh) read-only reviews
Files changed: 4
Overview
Purpose (author): implement generated header dependencies for the nmake native build
(LT-22816), so a header edit rebuilds every object that includes it, and explain the
rationale.
The nmake inference rules in
Bld/_rule.makmade each object depend only on its own.cpp. Every compile now writes acl /sourceDependenciessidecar. At the end ofBld/_targ.mak,Bld/Write-NmakeHeaderDependencies.ps1turns each product's sidecarsinto plain
obj : headeredges, and nmake makes every up-to-date decision.Making this work exposed a separate bug that predates the branch:
GenerateCellarConstantsdeclared its
Outputsthrough an item evaluated before$(dir-fwoutputCommon)was set. Thetarget ran on every build and rewrote
CellarConstants.hwith identical bytes, which withheader tracking would recompile 43 Views objects on every build. It's fixed in its own
commit.
The first implementation (luna-6) decided which objects were stale in the helper and
cached the answer. The first review showed that design missed headers generated later
in the same build, and missed builds that skip DebugProcs. Its performance motivation was
also confounded by the CellarConstants bug. It was replaced with full edges per product.
That exposed a latent bug in both versions: nmake is invoked with no target, so an
include placed before
build:became the default goal. The include now sits at the endof
_targ.mak.Contract/API Changes
None to product code, COM or exported surfaces. Build-system contract: native makefiles
now require MSVC 16.7+ (
/sourceDependencies); VS 2022/v143 is already the minimum.Each native product writes
*.obj.source-dependencies.jsonandheader-dependencies.makunder
Obj/<Config>/<Product>/.Findings
Critical - Must address before merge
None.
Important - Should address before merge
an object with no sidecar depends on an always-out-of-date target and rebuilds once)
build (fixed during review: full edges, evaluated by nmake at make time; verified by
touching
CellarConstants.vm.h, which rebuilt 43 objects in the same build)the cache was removed; the include is written through a temp file and
File.Replace)consumers are forced; covered by a synthetic sidecar test)
build:became nmake's default goal (found while verifying,fixed: include moved to the end of
_targ.mak)Minor - Consider
Views.makor test-makefile runs skipped the refresh;testViewscasingmismatch (fixed: runs for every product, with no product-name gate)
^#escaping is wrong inside quotes (fixed: a path containing#fails thebuild with a clear message;
$is doubled)Replace)_rule.mak(fixed)_rule.makhad mixed line endings (fixed: CRLF working copy, LF in index asthe repo stores it)
Avalonia detail views lose row filters; LibPalaso rolled back(not in thisbranch: artifacts of a two-dot
git diff origin/main..HEADafter main moved; themerge-base diff has 4 files)
Fragmentsitem inmkall.targets:39-40uses$(dir-fwdistfiles)before
Setupsets it (out of scope; author chose to file a separate Jira ticket)Required Validation / Evidence
build.ps1 -CommentHygiene -TokenHygiene: clean, 0 errors.build.ps1 -Cleanof the native project: succeeds from scratch.test.ps1 -SkipManaged -TestProject TestViews: 309/0/0.LgUnicodeCollateInit.h: rebuildsLgUnicodeCollater.objonly.LayoutCache.h: rebuilds exactly UniscribeEngine, UniscribeSegment andVwTextBoxes, and relinks Views.dll.
VwEnvsidecar:VwEnv.objrebuilds once, then never again.CellarConstants.vm.h: the header regenerates and 43 objects rebuild in thesame build; the next build rebuilds 0.
error, surfaced by nmake as
U1050./Yc//YuPCH path: no shipped product compiles a PCH through these rules;covered only by a synthetic sidecar test.
Positive Observations
no staleness logic.
Include/(ICU) headers are tracked deliberately: an ICU bump now rebuildsits consumers. Views has 13,307 edges; no-change native builds take 21-39 s. That
wasn't measured against main under identical conditions.
CopyCellarBaseConstantsalready uses, andthe warning comment in
SetupInclude.targets:319.Interview Notes
all-headers guard and a
.vcxprojmove.findings are fixed above).
Fragmentsevaluation-order bug as a separate ticket.Suggested Review Focus
Bld/_targ.maktail: helper call,!INCLUDE, and why it must be last.Write-NmakeHeaderDependencies.ps1: the force target for missing sidecars and PCH.Build/mkall.targets: GenerateCellarConstantsInputs/Outputs.🤖 Generated with Claude Code
This change is