Skip to content

Do not follow generic requirements when computing the classpath#2256

Merged
laeubi merged 2 commits intoeclipse-pde:masterfrom
laeubi:pr-2254-cyclic
Mar 17, 2026
Merged

Do not follow generic requirements when computing the classpath#2256
laeubi merged 2 commits intoeclipse-pde:masterfrom
laeubi:pr-2254-cyclic

Conversation

@laeubi
Copy link
Copy Markdown
Contributor

@laeubi laeubi commented Mar 12, 2026

OSGi uses a generic Provide-Capability/Require-Capability model that
allows to declare requirements beyond the classic Import-Package and
Require-Bunlde. A common example is a bundle that provides a DS
component and needs an extender to actually construct the component and
inject the services. Another is a logging API that needs a provider.

These are especially needed because in such scenario a provider/consumer
do not know each other and should not depend directly (only loosely
coupled). Currently such providers are still pulled in even though they
are not contribute to the compilation classpath as they are completely
invisible at runtime - there is a wiring but no classloader access
granted.

This now adds a new option to the DependencyManager to exclude generic
requirements from the computation of the dependency closure and the
RequiredPluginsClasspathContainer is using that option to avoid follow
such generic wiring contracts.

Includes the testcase from PR #2254

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 12, 2026

Test Results

  147 files  ±0    147 suites  ±0   36m 9s ⏱️ - 2m 50s
3 495 tests +2  3 441 ✅ +2   54 💤 ±0  0 ❌ ±0 
9 306 runs  +3  9 176 ✅ +3  130 💤 ±0  0 ❌ ±0 

Results for commit bf4ca14. ± Comparison against base commit 693ecb5.

♻️ This comment has been updated with latest results.

TestUtils.waitForJobs("ClasspathResolutionTest.testRequiredPluginsViaCapabilityForFragment", 200, 30_000);

List<String> requiredPluginContainers = getRequiredPluginContainerEntries(projectConsumer);
assertThat(requiredPluginContainers).contains("A1_0_0");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check seems to be causing problems now, but the important check is the one below. Should it simply be removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange it runs locally if I execute it alone but fails when running the full test-suite... I will take a look.. maybe simply rename the project would already help.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming does not change anything... but it reproducible succeeds when executed alone and fails when executed with all tests...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that the "loadTargetPlatform" of the other test somehow broke the test. It then finds the host but the host is not resolved. So it seems that somehow even though tried it does not.

Looking at the test itself it more is about system packages from target platorms so I will no try to extract your testcase in a separate class instead...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ptziegler I have now extracted this into "real" projects making it easier to investigate/debug.

The problem is that the setup seem to have some (intermediate) problem and the target state complains:

java.lang.AssertionError: 
Expecting empty but was: [Constraints from the fragment conflict with the host: Require-Capability: some.test.capability]
	at org.eclipse.pde.core.tests.internal.classpath.ClasspathResolutionTest2.testRequiredPluginsViaCapabilityForFragment(ClasspathResolutionTest2.java:802)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.eclipse.pde.ui.tests.runtime.TestUtils$1.evaluate(TestUtils.java:267)
	at org.eclipse.pde.ui.tests.runtime.TestUtils$1.evaluate(TestUtils.java:267)

interestingly one can make it work by making some changes to the EE and then switch back.

This happens because the ordering of resolve is important here... so importing the host first and then the *fragment triggers the error.

I'll check if we can avoid that... to me it seems if one asks for an incremental resolve of a fragment the host must also be part of that set...

Copy link
Copy Markdown
Member

@HannesWell HannesWell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This totally makes sense to me and it should prevent problematic cycles (at least those that don't lead to cycle-errors on a workspace project level too).

However I have alternative suggestions for the specific implementation.

Comment on lines +687 to +688
Set<BundleDescription> closure = DependencyManager.findRequirementsClosure(added,
INCLUDE_OPTIONAL_DEPENDENCIES);
INCLUDE_OPTIONAL_DEPENDENCIES, EXCLUDE_REQUIRED_CAPABILITY);
Copy link
Copy Markdown
Member

@HannesWell HannesWell Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact we already have a facility in PDE that computes what you want to obtain here:

Suggested change
Set<BundleDescription> closure = DependencyManager.findRequirementsClosure(added,
INCLUDE_OPTIONAL_DEPENDENCIES);
INCLUDE_OPTIONAL_DEPENDENCIES, EXCLUDE_REQUIRED_CAPABILITY);
Set<BundleDescription> closure = BuildDependencyCollector.collectBuildRelevantDependencies(added)

It's currently an own implementation, but I have just opened a PR that migrates it to use the DependencyManager (after the change was just on my computer for a long time):

In fact you should already be able to use that code now, even without my PR.
Then you don't have to modify DependencyManager at all and introduce a new constant for it.

And then further down below the system bundle does not have to be filtered anymore.

Comment on lines +255 to +267
BundleCapability capability = wire.getCapability();
if (excludeGenericRequiredCapability) {
String namespace = capability.getNamespace();
if (!BundleNamespace.BUNDLE_NAMESPACE.equals(namespace)
&& !PackageNamespace.PACKAGE_NAMESPACE.equals(namespace)
&& !HostNamespace.HOST_NAMESPACE.equals(namespace)
&& !ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE.equals(namespace)) {
// If it is not one of the framework namespaces it is a
// generic one see https://docs.osgi.org/specification/osgi.core/8.0.0/framework.namespaces.html
continue;
}
}
BundleRevision provider = capability.getRevision();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For that particular change I have a counter proposal in:

This doesn't require a new constant within the DependencyManager, which is also quite specific to PDE respectively the build time requirement. In general I would like to keep DependencyManager generic in the hope that it could be up-streamed to OSGi in some form at some day.

Plus I'm not sure if it's necessary to follow EE-namespace wires. See also

OSGi uses a generic Provide-Capability/Require-Capability model that
allows to declare requirements beyond the classic Import-Package and
Require-Bunlde. A common example is a bundle that provides a DS
component and needs an extender to actually construct the component and
inject the services. Another is a logging API that needs a provider.

These are especially needed because in such scenario a provider/consumer
do not know each other and should not depend directly (only loosely
coupled). Currently such providers are still pulled in even though they
are not contribute to the compilation classpath as they are completely
invisible at runtime - there is a wiring but no classloader access
granted.

This now only follows build time requirements to avoid the situation.
@laeubi laeubi merged commit 79b22ee into eclipse-pde:master Mar 17, 2026
19 checks passed
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.

3 participants