fix: replace global needRebuild() with project-scoped rebuild APIs#1300
fix: replace global needRebuild() with project-scoped rebuild APIs#1300joaodinissf wants to merge 1 commit intodsldevkit:masterfrom
Conversation
cd9b3aa to
dd4b31f
Compare
dd4b31f to
ba39b55
Compare
BuildContext.needRebuild() called IncrementalProjectBuilder.needRebuild() which sets a global rebuildRequested flag on Eclipse's BuildManager, causing ALL projects in the build cycle to be rebuilt -- not just the current project. This was redundant because the DDK's RebuildingXtextBuilder already handles generated-source reprocessing via an internal rebuild loop (up to 2 extra iterations in doBuild()). The global call was a leftover from a 2014 workaround (Eclipse Bug #452399, Comment dsldevkit#8) that was superseded by the internal loop approach (Comment dsldevkit#10) but never removed when the DDK was open-sourced in 2016. It caused cascading builder re-invocations across the workspace dependency graph, resulting in unrelated projects being fully rebuilt when editing files in completely unrelated projects. Changes: - Remove builder.needRebuild() from the no-arg needRebuild(), keeping only the internal rebuildRequired flag - Override needRebuild(IProject) using the modern Eclipse 3.17+ APIs (triggerRequestProjectRebuild / triggerRequestProjectsRebuild) instead of relying on the IBuildContext default method that falls through to the deprecated global needRebuild(). This matches the upstream Xtext 2.27+ pattern (PR eclipse-xtext/xtext#1821, Eclipse Bug 579082). The DDK now has two complementary rebuild mechanisms: 1. Internal loop: immediate reprocessing within the same build() invocation (up to 2 extra iterations) 2. Project-scoped Eclipse API: safety net if the loop is exhausted, without cascading to unrelated projects Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ba39b55 to
068faeb
Compare
|
| Date | Event |
|---|---|
| Nov 2014 | Bug #452399: Bernhard Buss posts initial workaround using builder.needRebuild() + rememberLastBuiltState() (Comment #8) |
| Nov 2014 | Same bug: Bernhard posts better solution -- internal rebuild loop using workspace ElementTree diffing (Comment #10) |
| Nov 2016 | DDK open-sourced (commit 14b48aa) with both mechanisms. The builder.needRebuild() was a leftover from the earlier workaround. |
| Mar 2022 | Eclipse Bug #579082: needRebuild() identified as cause of O(n²) build behavior. Eclipse 3.17 adds requestProjectRebuild() / requestProjectsRebuild(). |
| Mar 2022 | Xtext PR #1821: Xtext 2.27 deprecates needRebuild(), adds needRebuild(IProject) with project-scoped semantics. |
| Apr 2026 | This PR: vestigial builder.needRebuild() removed after 10 years; needRebuild(IProject) implemented with upstream pattern. |
Related Issues
| Issue | Description |
|---|---|
| Eclipse Bug 579082 | Slow autobuild with Xtext projects; needRebuild() causes global rebuild |
| Xtext #1339 | File generation causes global rebuild of all build configs |
| Xtext #1761 | needRebuild() in pollQueuedBuildData() stops Eclipse build |
| Xtext #1820 | Request for fine-granular needRebuild per project |
4. Eclipse needRebuild() API Reference
| Method | Since | Scope | Mechanism |
|---|---|---|---|
needRebuild() |
2.1 | ALL projects | Sets global rebuildRequested boolean on BuildManager |
requestProjectRebuild(boolean) |
3.17 | Current project only | Writes to per-project restartBuildImmediately map |
requestProjectsRebuild(Collection) |
3.17 | Specified projects only | Adds to projectsToRebuild set |
The DDK targets Eclipse 4.34 (well above 3.17), so the modern project-scoped APIs are available directly. XtextBuilder (since Xtext 2.27) wraps these as triggerRequestProjectRebuild() and triggerRequestProjectsRebuild(IProject).
5. Standard Xtext 2.42 vs DDK Comparison
| Feature | Xtext 2.42 | DDK (before) | DDK (after this PR) |
|---|---|---|---|
needRebuild() |
Deprecated; calls builder.needRebuild() |
rebuildRequired + builder.needRebuild() |
rebuildRequired only |
needRebuild(IProject) |
triggerRequestProjectRebuild() (scoped) |
Not overridden (default fallthrough) | triggerRequestProjectRebuild() (scoped) |
| Internal rebuild loop | None -- relies on Eclipse re-invocation | Yes, up to 2 extra iterations | Yes, up to 2 extra iterations |
doBuild() API |
4-param (since 2.18) | 3-param (deprecated) | 3-param (deprecated) |
BuildData constructor |
7-param (since 2.27) | 4-param (deprecated) | 4-param (deprecated) |
ClosedProjectsQueue |
Integrated | Missing | Missing |
isSourceLevelURI() |
Tracks actual URIs | Always returns true |
Always returns true |
ensureBuilderStateLoaded() |
Called | Not called | Not called |
Empty toBeBuilt guard |
No | Yes (DDK optimization) | Yes |
Key insight: Standard Xtext has no internal rebuild loop, so it needs Eclipse re-invocation to handle generated sources. The DDK replaced this with its own loop -- but never removed the Eclipse-level needRebuild().
6. Remaining Modernization Opportunities
Not part of this PR. Listed for future reference:
| Item | Since | Risk | Priority | Description |
|---|---|---|---|---|
doBuild() 4-param API |
2.18 | Medium | High | Deprecated 3-param override; Xtext logs warning every build |
BuildData 7-param constructor |
2.27 | Low | Follows from above | Adds indexingOnly, rebuildTrigger, removedProjects |
ClosedProjectsQueue |
2.18 | Low-Med | Medium | Stale index entries from closed/deleted projects |
ensureBuilderStateLoaded() |
2.26 | Low | Low | Graceful handling of builder state deserialization failures |
isSourceLevelURI() |
2.9 | Low | Low | Always returns true; should track actual workspace URIs |
pollQueuedBuildData() |
2.19 | HIGH | Skip | Intentionally skipped -- DDK uses MonitoredClusteringBuilderState |
Summary
IncrementalProjectBuilder.needRebuild()call fromBuildContext.needRebuild()that triggered global workspace rebuilds every time a builder participant generated a fileneedRebuild(IProject)using the modern Eclipse 3.17+ project-scoped APIs (triggerRequestProjectRebuild/triggerRequestProjectsRebuild), matching the upstream Xtext 2.27+ patternRoot Cause
The DDK's
BuildContext.needRebuild()calledbuilder.needRebuild()— anIncrementalProjectBuildermethod that sets a globalrebuildRequestedflag on Eclipse'sBuildManager, causing all projects in the build cycle to be rebuilt (not just the current project). This was redundant because the DDK'sRebuildingXtextBuilderalready handles generated-source reprocessing via an internal rebuild loop (up to 2 extra iterations indoBuild()).The call was a leftover from a 2014 workaround (Eclipse Bug #452399, Comment #8) that was superseded by the internal loop approach (Comment #10) but never removed when the DDK was open-sourced in 2016.
Additionally, the DDK's
BuildContextdid not overrideneedRebuild(IProject)(added in Xtext 2.27, PR #1821), so all callers using the modern API fell through to the deprecated no-arg version via the interface default method.What Changes
Commit 1: Remove
builder.needRebuild()from the no-argneedRebuild(), keeping only the internalrebuildRequiredflag.Commit 2: Override
needRebuild(IProject)with project-scoped Eclipse APIs:builder.triggerRequestProjectRebuild()(rebuilds only this project)builder.triggerRequestProjectsRebuild(project)(rebuilds only that project)This gives the DDK two complementary rebuild mechanisms:
build()invocationCommit 3: Analysis document (
DDK-BUILDER-ANALYSIS.md) with full root cause analysis, historical context, architecture reference, and remaining modernization roadmap.References
needRebuild()identified as cause of O(n²) build behaviorRebuildingXtextBuilderneedRebuild(IProject)Test plan
-Dorg.eclipse.core.resources.debug=true) and compare builder invocation counts before/after🤖 Generated with Claude Code