Repository navigation
Conversation
The nmake rules in Bld rebuild an object only when its own source file changes. A pulled header change therefore leaves every unedited file that includes it compiled against the old header. After #1166 grew LayoutPassCache, VwTextBoxes.obj kept the old size while the Uniscribe objects used the new one, and loading a project hit the Vector AssertValid assert in Debug builds. build.ps1 now deletes Obj/<Config>/<Module> for Views, FwKernel, Generic, or DebugProcs when a header or makefile that module builds from is newer than its oldest object, so nmake compiles all of it. Fixture tests run under PowerShell 7 and 5.1 in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The compiler's program-database server can hold a module's .pdb open between builds. Deleting the module's Obj folder stopped at that file and failed the build, possibly with objects still in place. Deletion now carries on past locked files. A leftover .pdb or .idb prints a warning, since every object is gone and the module still rebuilds completely; any other leftover file still fails the build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CopyDlls copies restored LCM .idh files and ICU headers into the tree during the MSBuild traversal, after build.ps1 had already run the check. A build that applied a package update could still mix old and new objects until the next build. A RemoveStaleNativeObjects target in mkall.targets now runs the check after CopyDlls and before DebugProcs, the first native module. It also covers MSBuild runs that do not go through build.ps1, so the build.ps1 call is gone. 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 #1174 +/- ##
==========================================
- Coverage 39.30% 39.30% -0.01%
==========================================
Files 1523 1523
Lines 353047 353117 +70
Branches 40750 40761 +11
==========================================
+ Hits 138782 138805 +23
- Misses 185001 185040 +39
- Partials 29264 29272 +8 🚀 New features to boost your workflow:
|
Views includes Src/AppCore/Res/AfAppRes.h and Src/Cellar/FwXml.h by relative path, which bypasses the makefile include list, so neither folder was watched. Views, FwKernel and Generic also include headers generated into Output/<Config>/Common, such as CellarConstants.h, whose source model lives in the LCM package outside the tree. The check now also watches those folders and runs after GenerateCellarConstants. bldinc.h is skipped: it holds only version stamps that local builds regenerate daily. GenerateCellarConstants rewrote CellarConstants.h on every build, since its Outputs item used a property set only at run time and never matched an existing file. It now generates into Obj and copies only changed content, so the header's date marks a real change. A new test fails when a module's sources include a header from an unlisted folder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thejambi
marked this pull request as draft
October 2, 2026 16:27
Contributor
Author
|
Closing, duplicates #1168 |
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:
Remove-NativeObjDirsWithStaleInputsinBuild/Agent/FwBuildHelpers.psm1, then theRemoveStaleNativeObjectstarget inBuild/mkall.targetsthat calls it.Before the first native module builds, MSBuild now deletes
Obj/<Config>/<Module>for Views, FwKernel, Generic or DebugProcs when any header or makefile that module compiles against, including headers generated intoOutput/<Config>/Common, is newer than its oldest.obj. nmake then compiles the whole module.Why. The rules in
Bld/_rule.makrebuild an object only when its own.cppchanges, never when an included header does. After #1166 addedNfcOffsetMaptoLayoutPassCache, an incremental Debug build recompiledUniscribeSegment.objbut kept a 2026-09-02VwTextBoxes.obj, whoseParaBuilderembeds the old, smallerLayoutPassCache. Loading a project then tripped theVector.h:309AssertValidassert. Any existing Debug tree that pulled #1166 is exposed; CI builds clean, so it never is.Will it rebuild everything every time? No. Once a module rebuilds, every object is newer than every header and the check goes quiet. It is a no-op on CI and after
-Clean, and skipped withSkipNative. The cost is deliberate coarseness: one header edit rebuilds its whole module.Where to look
GenerateCellarConstantsrewroteCellarConstants.hon every build (its Outputs item used a property set only at run time). It now generates intoObjand copies only changed content; otherwise this check would rebuild Views every build.CopyDlls(restored LCM.idhand ICU headers) andGenerateCellarConstants, andDebugProcsdepends on the target.Get-NativeMakefileModules. A test fails when a module's sources include a header from an unlisted folder by path; folders reached through a makefile'sUI=line are checked by eye.bldinc.his deliberately not watched: it holds only version stamps that change daily.Not here: real header-dependency tracking (
/sourceDependenciesin_rule.mak), the file-precise fix.Verification. On a real tree, updating
LayoutCache.h's modified time made MSBuild log[INFO] Views: Src\views\lib\LayoutCache.h ... removing Obj\Debug\Viewsand rebuild only Views. After the generated-header change, one build rebuilt Views, FwKernel and Generic once; the next flagged nothing, andCellarConstants.hkept its date.NativeObjStaleness.Tests.ps1(13 fixture cases plus folder and include checks) passes under Windows PowerShell 5.1, with mutation checks failing as expected. CI ran the tests under PowerShell 7 and 5.1 on the previous push, all green.Next: approve, or point at a folder list that looks wrong.
Reading this a year from now -- start here
This branch had no design documents; the reasoning below is the only record of why the check is shaped the way it is. If the native modules have moved to real MSBuild C++ projects (which track headers through
.tlogfiles) orBld/_rule.makemits compiler-generated dependency files, this check is redundant and theRemoveStaleNativeObjectstarget and its helper can be deleted.Decisions, and why
Src/viewsrecursively would pull inSrc/views/Testheaders, which only the MSBuild-built test projects include.CopyDllsandGenerateCellarConstants.CopyDlls(Build/PackageRestore.targets) is where restored.idhand ICU headers land in the tree,GenerateCellarConstantswritesCellarConstants.h, andDebugProcsis the first native module every native target path goes through. Hooking between them sees package bumps and model changes, and covers MSBuild runs that skipbuild.ps1.Output/<Config>/Common. Views, FwKernel and Generic includeCellarConstants.h,CellarBaseConstants.h,FwKernelTlb.handViewsTlb.hfrom there.CellarConstants.hcomes fromMasterLCModel.xmlin the LCM package, so no source-tree file changes when the model does.GenerateCellarConstants. Fixing the Outputs path would make the target incremental by timestamp, but NuGet keeps package file dates (beta0178'sMasterLCModel.xmlis dated06:43:48.000), so a newer model could look older than the header and be skipped. The target regenerates every build intoObj/<Config>/CellarConstantsand copies only when content differs.bldinc.h. It holds only version stamps (BUILD_NUMBER,BUILDLABEL) that local builds regenerate daily. Watching it would rebuild Generic and Views on the first build of each day for a version-string change that alters no layout..pdb/.idbleftovers are tolerated. The compiler's program-database server can hold a debug database open between builds. A leftover debug database does not change what gets compiled; a leftover object or precompiled header would.Reversals
build.ps1, just before the MSBuild traversal. That missed headers copied byCopyDllsduring the same build, so a package bump could still mix objects until the next build, and direct MSBuild runs skipped the check entirely. The third commit moves it intoBuild/mkall.targets;build.ps1is unchanged against main.UI=line. Review found that Views reachesSrc/AppCore/Res/AfAppRes.handSrc/Cellar/FwXml.hby relative path and never watched generated headers. The fourth commit adds those folders, the generated-header folder, and a test that fails on any path-qualified include into an unlisted folder.Paths not taken
CL_OPTS/DEFS, the multiple PCH setups, MIDL and proxy/stub steps and custom link steps for every module. Worth doing only as part of a wider native-build modernization.XAmpleCOMWrapper.vcxprojis the in-repo precedent./sourceDependenciestoBld/_rule.mak, convert the JSON to nmake rules with a small script, and!INCLUDEthe result. One central change and file-precise, but it needs handling for deleted headers, PCH dependencies, generated MIDL headers and atomic writes. The natural next step if native churn continues..mak. Rejected; they rot.testViews.makandtestGenericLib.makalready carry partial hand-written lists.UI=line instead of hard-coding folders. Rejected for now; parsing nmake macros is more fragile than a short list that changes rarely, and it would still miss path-qualified includes, which the include test covers.Evidence
Src/views/lib/LayoutCache.h(content unchanged),./build.ps1logged from MSBuild:[INFO] Views: Src\views\lib\LayoutCache.h (2026-10-01 17:30) is newer than autopch\ViewsGlobals.obj (2026-10-01 16:55); removing Obj\Debug\Views so it rebuilds completely.Only Views was flagged, every Views object was recompiled, the build passed, and a second wrapper run printed nothing.[INFO]for Views, FwKernel and Generic, each flagged byOutput\Debug\Common\CellarConstants.h (2026-10-01 19:30), a copy the old target had rewritten. The next build flagged nothing;CellarConstants.hstayed at 19:30:31 while the staged copy inObj/Debug/CellarConstantswas regenerated at 19:38:34. DebugProcs, which does not watch that folder, was untouched.Copycondition was checked in a scratch MSBuild project with file contents holding quotes,$(),@()and%(): missing destination copies, identical content skips, changed content copies.Build/Agent/NativeObjStaleness.Tests.ps1builds throwaway trees with chosen timestamps. Cases: header newer than oldest object; sharedIncludeheader newer; makefile newer; generated header newer; another configuration's generated header newer (no-op); onlybldinc.hnewer (no-op); all objects newer (no-op); only a.cppnewer (no-op); header in an unlisted subfolder (no-op); no object folder; folder without objects; locked.pdbskipped with every object removed; locked.objfails the build. It also checks every real module's listed folders exist, and that every path-qualified include in a scanned file lands in a listed folder.Src\AppCore\ResandSrc\Cellarfrom Views fails the include check for both; disabling thebldinc.hskip fails its case.CopyDllscopiesSrc/Kernel/*.idhandInclude/unicode/*.hwithSkipUnchangedFiles.Obj/Debug/Views/autopch/VwTextBoxes.objdated 2026-09-02;UniscribeSegment.obj,UniscribeEngine.objandSrc/views/lib/LayoutCache.hdated 2026-10-01.Preflight review details
Code Review Summary
Branch: native-stale-header-objs
Base: main
Date: 2026-10-01
Review model: Claude Opus 5.5 (Claude Code)
Files changed: 5
Overview
Author's purpose: incremental native builds silently reuse objects compiled
against old headers (this caused the
Vector.h:309assert after #1166). Makethe build detect that and force a full rebuild of the affected native module.
The nmake rules in
Bld/_rule.makonly map.cppto.obj, so a headerchange rebuilds nothing that merely includes it. After #1166 added an
NfcOffsetMapmember toLayoutPassCache,Obj/Debug/Views/autopch/VwTextBoxes.obj(2026-09-02) still embedded the old, smaller
LayoutPassCacheinParaBuilder, while the rebuiltUniscribeSegment.objwrote past it. Thebranch adds
Remove-NativeObjDirsWithStaleInputs, which deletesObj/<Config>/<Module>for Views, FwKernel, Generic, or DebugProcs when anyheader or makefile in that module's include folders is newer than its oldest
object. A new
RemoveStaleNativeObjectstarget inBuild/mkall.targetsrunsit after
CopyDlls(which copies restored LCM and ICU headers) and beforethe first
Maketask. The check is deliberately coarse (whole-modulerebuild) and self-healing (once rebuilt, every object is newer than every
header).
Developer-only build tooling: no Jira ticket needed.
Contract/API Changes
Build/Agent/FwBuildHelpers.psm1exports two new functions:Get-NativeMakefileModulesandRemove-NativeObjDirsWithStaleInputs.Build/Agent/Remove-StaleNativeObjects.ps1(-RepoRoot,-Configuration; exits 1 when objects could not be removed).Build/mkall.targetsadds targetRemoveStaleNativeObjects;DebugProcsnow depends on it. It is skipped when
SkipNativeis true. Any nativebuild may now delete
Obj/<Config>/<Module>folders before compiling.build.ps1is unchanged..github/workflows/CI.ymladds two steps that run the new fixture testsunder PowerShell 7 and Windows PowerShell 5.1.
Findings
Critical - Must address before merge
None.
Important - Should address before merge
Package restore ran after the check.
CopyDllsinBuild/PackageRestore.targetscopies LCM.idhfiles intoSrc/KernelandICU headers into
Include/unicodeinside the MSBuild traversal, after theoriginal
build.ps1call site. (fixed during review: the check moved intoBuild/mkall.targetsasRemoveStaleNativeObjects, which depends onCopyDllsand runs beforeDebugProcs. This also covers MSBuild runs thatdo not go through
build.ps1. The author first accepted this as adocumented limitation; the hook point surfaced while verifying the PR text
and the author chose to fold it in.)
Views missed path-included headers (DevinReview, after the PR
opened).
Src/views/Main.hincludesSrc/AppCore/Res/AfAppRes.handSrc/Cellar/FwXml.hby relative path, bypassing the makefileUI=list.(fixed: both folders added to Views; a new test fails when any scanned
file's path-qualified include lands in an unlisted folder.)
Generated headers were not watched (DevinReview). Views includes
CellarConstants.hfromOutput/<Config>/Common, generated from the LCMpackage's
MasterLCModel.xmlafter the check ran. (fixed: the check runsafter
GenerateCellarConstantsand watchesOutput/<Config>/CommonforViews, FwKernel and Generic, skipping version-only
bldinc.h. Thereviewer's fix alone would have rebuilt Views every build, because
GenerateCellarConstantsrewrote the header every build: its Outputs itemused
$(dir-fwoutputCommon), set only at run time. The target nowgenerates into
Objand copies only changed content.)Minor - Consider
.pdbcould abort the build.Remove-Item -ErrorAction Stopstopped at the first locked file, possibly leaving objects behind.
(fixed during review: deletion continues past errors; only a leftover file
other than
.pdb/.idbfails the build, otherwise a warning is printed.Two fixture tests cover a locked
.pdband a locked.obj.)Folder lists are hand-maintained.(Accepted: directory-level listsGet-NativeMakefileModulesmirrorseach makefile's
UI=include line; the fixture test only checks the foldersexist, not that the list is complete.
change rarely, and parsing nmake syntax would be more fragile.)
Required Validation / Evidence
Build/Agent/NativeObjStaleness.Tests.ps1under Windows PowerShell 5.1:13 fixture cases plus the real-module folder and include checks pass.
Views, FwKernel and Generic once; the next flagged nothing, and
CellarConstants.hkept its date while its staged copy was regenerated.Copycondition checked in a scratch MSBuild project(missing, identical and changed destination; contents with quotes and
$()/@()/%()).cases; disabling the lock filter fails the locked-object case; dropping
Src\AppCore\ResandSrc\Cellarfails the include check; disabling thebldinc.hskip fails its case.Build/Agent/powershell-compat.ps1: clean for 5.1 and 7.0.Build/Agent/comment-hygiene.ps1: clean../build.ps1 -CommentHygiene -TokenHygiene: passed after each change.Src/views/lib/LayoutCache.h(content unchanged),./build.ps1logged[INFO] Views: Src\views\lib\LayoutCache.h (2026-10-01 17:30) is newer than autopch\ViewsGlobals.obj (2026-10-01 16:55); removing Obj\Debug\Views so it rebuilds completely.from inside MSBuild. Only Views was flagged, everyViews object was recompiled (oldest now 17:40), the build passed, and a
second run of the wrapper was a no-op.
Src/Kernel/*.idhandInclude/unicode/*.hare copied withSkipUnchangedFiles, so they do notcause a rebuild on every build.
the new CI step is the first PowerShell 7 run.
Positive Observations
track headers and are untouched.
-Clean(emptyObj), so CI behavior is unchanged.*.Tests.ps1style and rununder both engines in CI.
Interview Notes
CopyDllshook point was found, author chose to fold the fix in beforeopening the PR.
.pdbabort and accept thehand-maintained folder lists.
In-Review Quality Check
Remove-NativeObjDirsWithStaleInputsplus two fixture tests(
locked-debug-database-is-skipped,locked-object-fails-the-build).build.ps1to theRemoveStaleNativeObjectstarget inBuild/mkall.targets, via the newBuild/Agent/Remove-StaleNativeObjects.ps1wrapper../build.ps1 -CommentHygiene -TokenHygienere-run after each change.
Suggested Review Focus
Get-NativeMakefileModulesagainst eachmakefile's
UI=line.Objfolder is acceptable rebuild costfor a single header edit.
RemoveStaleNativeObjectsplacement: afterCopyDlls, beforeDebugProcs.🤖 Generated with Claude Code
This change is