Skip to content

Detect dependency cycles formed through Import-Package - #2408

Draft
vogella wants to merge 3 commits into
eclipse-pde:masterfrom
vogella:import-package-dependencies
Draft

Detect dependency cycles formed through Import-Package#2408
vogella wants to merge 3 commits into
eclipse-pde:masterfrom
vogella:import-package-dependencies

Conversation

@vogella

@vogella vogella commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

The imports of a plug-in read from the state were computed while the model was loaded, which happens before the state is resolved: PDEState creates the models in its constructor and PluginModelManager resolves the state afterwards, once the workspace bundles have been added. At that point BundleDescription.getResolvedImports() is still empty, so the bundles behind the Import-Package header were dropped and that empty result was kept for the lifetime of the model, while getRequiredBundles() needs no resolved state and kept working. As a result the MANIFEST.MF editor's "Look for cycles in the dependency graph" action silently ignored every cycle that closes through package wiring.

Those imports are now computed on demand: until the state is resolved they are recomputed on each access, which preserves today's result for early callers, and afterwards they are computed once and kept. Only models read from a BundleDescription are affected and those are the read-only target models, so a model being edited cannot lose its imports.

Draft because I could not verify it locally: the test harness here installs the released org.eclipse.pde.core instead of the reactor build (visible in target/work/configuration/config.ini), regardless of -pl, -am or the javac profile, so local results say nothing about a change in that bundle. The three added tests are expected to fail without this change and pass with it, and I would like CI to confirm that. Stacked on #2405 and #2406, so it shows their commits until those are merged.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

Test Results

  129 files  ± 0    129 suites  ±0   36m 1s ⏱️ +43s
3 533 tests +10  3 479 ✅ +10   54 💤 ±0  0 ❌ ±0 
9 402 runs  +30  9 272 ✅ +30  130 💤 ±0  0 ❌ ±0 

Results for commit e99b3a1. ± Comparison against base commit 509c57f.

♻️ This comment has been updated with latest results.

vogella added 3 commits August 2, 2026 22:27
Cover the cases the dependency-cycle finder behind the MANIFEST.MF
editor's "Look for cycles in the dependency graph" action handles today:
no cycle, a direct mutual dependency, a three-bundle cycle, two separate
cycles through the root, independence from the order in which the root
declares its dependencies, and a cycle beside the root that must not hide
the cycle through it.

The tests build dummy target bundles and resolve them through
PluginRegistry, exercising the production code path. Registered in
AllPDECoreTests.
DependencyLoopFinder kept a shared list of plug-ins that had been visited
without yielding a loop and skipped them on every later path. Whether a
plug-in yields a loop depends on the path taken to reach it: a branch that
ends in a cycle not passing through the root adds the plug-ins it visited
to that list, so a cycle reachable only through another dependency of the
root is never reported. Which cycles get lost depends on the order of the
Require-Bundle entries.

Replace the list with a prune that does not depend on the path: only
plug-ins that are reachable from the root and lead back to it can sit on a
cycle through the root, and that property is a plain graph reachability
question. Plug-ins outside that set are skipped, which also keeps the
common case of a plug-in without any cycle cheap, and the remaining search
enumerates the cycles without dropping any. The number of reported loops
is capped, as the search runs on the UI thread.

Resolved dependencies are cached for the duration of one search, since the
search visits a plug-in once per path leading to it.
The imports of a plug-in read from the state were computed while the model
was loaded, which happens before the state is resolved: PDEState creates
the models in its constructor and PluginModelManager resolves the state
afterwards, once the workspace bundles have been added. At that point
BundleDescription.getResolvedImports() is still empty, so the bundles
behind the Import-Package header were dropped and the empty result was
kept for the lifetime of the model. getRequiredBundles() does not need a
resolved state, which is why only Require-Bundle edges survived.

Compute those imports on demand instead. Until the state is resolved they
are recomputed on every access, which keeps the previous result for early
callers, and once it is resolved they are computed once and kept. Only
models read from a BundleDescription are affected, and those are the
read-only target models, so a model being edited cannot lose its imports.

As a result the MANIFEST.MF editor's "Look for cycles in the dependency
graph" action reports cycles that close through package wiring instead of
silently ignoring them.
@vogella
vogella force-pushed the import-package-dependencies branch from 1698798 to e99b3a1 Compare August 2, 2026 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant