Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %bundleName
Bundle-SymbolicName: org.eclipse.equinox.p2.publisher.eclipse;singleton:=true
Bundle-Version: 1.6.800.qualifier
Bundle-Version: 1.6.900.qualifier
Bundle-Activator: org.eclipse.pde.internal.publishing.Activator
Bundle-ActivationPolicy: lazy
Bundle-Vendor: %providerName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.eclipse.equinox.frameworkadmin.BundleInfo;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.p2.metadata.ArtifactKey;
import org.eclipse.equinox.internal.p2.metadata.InstallableUnit;
import org.eclipse.equinox.internal.p2.metadata.ProvidedCapability;
import org.eclipse.equinox.internal.p2.publisher.Messages;
import org.eclipse.equinox.internal.p2.publisher.eclipse.GeneratorBundleInfo;
Expand Down Expand Up @@ -78,6 +79,43 @@ public class BundlesAction extends AbstractPublisherAction {

public static final String INSTALL_SOURCE_FILTER = String.format("(%s=true)", FILTER_PROPERTY_INSTALL_SOURCE); //$NON-NLS-1$

/**
* Prefix for profile properties that disable <code>Require-Capability</code>
* requirements on bundles published by this action. The full property name is
* formed by appending a dot and the OSGi namespace, for example:
* <ul>
* <li><code>org.eclipse.equinox.p2.disable.require.capability.osgi.ee</code>
* disables <code>osgi.ee</code> requirements</li>
* <li><code>org.eclipse.equinox.p2.disable.require.capability.my.cap</code>
* disables <code>my.cap</code> requirements</li>
* </ul>
* When a namespace-specific property is set to <code>"true"</code> on a
* profile, only requirements in that namespace are ignored during installation.
* This can be useful to break cyclic dependencies in build scenarios.
* Requirements are active by default (property absent or not equal to
* <code>"true"</code>).
*
* @see #getFilterPropertyForNamespace(String)
*/
public static final String FILTER_PROPERTY_DISABLE_REQUIRE_CAPABILITY = "org.eclipse.equinox.p2.disable.require.capability"; //$NON-NLS-1$

/**
* Returns the profile property name that disables <code>Require-Capability</code>
* requirements in the given OSGi namespace. Setting this property to
* <code>"true"</code> on a profile causes requirements in that namespace to be
* ignored during installation.
*
* @param namespace the OSGi namespace (e.g. {@code "osgi.ee"}, {@code "my.cap"})
* @return the full profile property name for the given namespace
*/
public static String getFilterPropertyForNamespace(String namespace) {
return FILTER_PROPERTY_DISABLE_REQUIRE_CAPABILITY + '.' + namespace;
}

private static IMatchExpression<IInstallableUnit> createRequireCapabilityFilter(String namespace) {
return InstallableUnit.parseFilter("(!(" + getFilterPropertyForNamespace(namespace) + "=true))"); //$NON-NLS-1$ //$NON-NLS-2$
}

/**
* A suffix used to match a bundle IU to its source
*/
Expand Down Expand Up @@ -443,8 +481,8 @@ protected void addRequirement(List<IRequirement> reqsDeps, GenericSpecification
boolean greedy = isGreedy(directives);
int minCard = getMinCardinality(directives);
int maxCard = getMaxCardinality(directives);
IRequirement requireCap = MetadataFactory.createRequirement(namespace, capFilter, null, minCard, maxCard,
greedy);
IRequirement requireCap = MetadataFactory.createRequirement(namespace, capFilter,
createRequireCapabilityFilter(namespace), minCard, maxCard, greedy);
reqsDeps.add(requireCap);
}

Expand All @@ -459,8 +497,8 @@ protected void addRequirement(List<IRequirement> reqsDeps, GenericSpecification
boolean greedy = isGreedy(directives);
int minCard = getMinCardinality(directives);
int maxCard = getMaxCardinality(directives);
IRequirement requireCap = MetadataFactory.createRequirement(namespace, capFilter, null, minCard, maxCard,
greedy, bd.getSymbolicName());
IRequirement requireCap = MetadataFactory.createRequirement(namespace, capFilter,
createRequireCapabilityFilter(namespace), minCard, maxCard, greedy, bd.getSymbolicName());
reqsDeps.add(requireCap);
}

Expand Down
2 changes: 1 addition & 1 deletion bundles/org.eclipse.equinox.p2.tests/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ Require-Bundle: org.eclipse.equinox.frameworkadmin,
org.eclipse.ant.core;bundle-version="3.2.200",
org.apache.ant;bundle-version="1.7.1",
org.eclipse.equinox.p2.transport.ecf;bundle-version="1.0.0",
org.eclipse.equinox.p2.publisher.eclipse;bundle-version="1.0.0",
org.eclipse.equinox.p2.publisher.eclipse;bundle-version="1.6.900",
org.eclipse.equinox.p2.operations;bundle-version="2.1.0",
org.eclipse.equinox.p2.ui.sdk.scheduler,
org.eclipse.equinox.p2.artifact.repository;bundle-version="[1.3.0,2.0.0)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.equinox.p2.tests.publisher.actions.AbstractPublisherActionTest;
import org.eclipse.equinox.p2.tests.publisher.actions.AccumulateConfigDataActionTest;
import org.eclipse.equinox.p2.tests.publisher.actions.AdviceFileParserTest;
import org.eclipse.equinox.p2.tests.publisher.actions.BundlesActionRequireCapabilityFilterTest;
import org.eclipse.equinox.p2.tests.publisher.actions.BundlesActionTest;
import org.eclipse.equinox.p2.tests.publisher.actions.CategoryPublisherTest;
import org.eclipse.equinox.p2.tests.publisher.actions.ConfigCUsActionTest;
Expand All @@ -46,7 +47,8 @@

@Suite
@SelectClasses({ AbstractPublisherActionTest.class, AccumulateConfigDataActionTest.class,
AdviceFileParserTest.class, ANYConfigCUsActionTest.class, BundlesActionTest.class, CategoryPublisherTest.class,
AdviceFileParserTest.class, ANYConfigCUsActionTest.class, BundlesActionRequireCapabilityFilterTest.class,
BundlesActionTest.class, CategoryPublisherTest.class,
ConfigCUsActionTest.class, ContextRepositoryTest.class, DefaultCUsActionTest.class,
EquinoxExecutableActionTest.class, EquinoxLauncherCUActionTest.class, FeaturesActionTest.class,
JREActionTest.class, LocalizationTests.class, LocalUpdateSiteActionTest.class, ChecksumGenerationTest.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/*******************************************************************************
* Copyright (c) 2026 Eclipse contributors and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which accompanies this distribution,
* and is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.equinox.p2.tests.publisher.actions;

import java.io.File;
import java.util.Collection;
import org.eclipse.equinox.internal.p2.metadata.IRequiredCapability;
import org.eclipse.equinox.internal.p2.metadata.RequiredPropertiesMatch;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.IRequirement;
import org.eclipse.equinox.p2.metadata.MetadataFactory;
import org.eclipse.equinox.p2.metadata.MetadataFactory.InstallableUnitDescription;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.metadata.expression.IMatchExpression;
import org.eclipse.equinox.p2.publisher.PublisherInfo;
import org.eclipse.equinox.p2.publisher.eclipse.BundlesAction;
import org.eclipse.equinox.p2.tests.AbstractProvisioningTest;
import org.eclipse.equinox.spi.p2.publisher.PublisherHelper;

/**
* Tests that {@code Require-Capability} requirements published by
* {@link BundlesAction} carry a namespace-specific environment filter that is
* active by default and can be disabled by setting the profile property
* {@code org.eclipse.equinox.p2.disable.require.capability.<namespace>} to
* {@code "true"}.
*/
public class BundlesActionRequireCapabilityFilterTest extends AbstractProvisioningTest {

private static final String TEST_BUNDLE = "testData/BundlesActionRequireCapabilityFilterTest/testRC";

private final IInstallableUnit bundleIU;

@Override
protected void setUp() throws Exception {
super.setUp();
File testData = getTestData("testRC", TEST_BUNDLE);
bundleIU = BundlesAction.createBundleIU(BundlesAction.createBundleDescription(testData), null,
new PublisherInfo());
}

/**
* A {@code Require-Capability} requirement must carry an env filter whose
* property name is the namespace-specific property.
*/
public void testRequireCapabilityHasEnvFilter() {
IRequirement req = findRequiredPropertiesMatchByNamespace(bundleIU.getRequirements(), "my.cap");
assertNotNull("my.cap Require-Capability requirement missing", req);

IMatchExpression<IInstallableUnit> filter = req.getFilter();
assertNotNull("Require-Capability requirement should have an env filter", filter);
// filter.toString() renders the parameter as "$0"; the LDAP filter string is in getParameters()[0]
assertTrue("Env filter should contain the namespace-specific property name",
filter.getParameters()[0].toString()
.contains(BundlesAction.getFilterPropertyForNamespace("my.cap")));
}

/**
* The env filter must evaluate to {@code true} when the disable property is
* absent, so that the requirement is enforced by default.
*/
public void testRequireCapabilityFilterActiveByDefault() {
IRequirement req = findRequiredPropertiesMatchByNamespace(bundleIU.getRequirements(), "my.cap");
assertNotNull("my.cap Require-Capability requirement missing", req);

IInstallableUnit profileIU = createProfileIU();
assertTrue("Require-Capability requirement should be active when property is absent",
req.getFilter().isMatch(profileIU));
}

/**
* Setting the namespace-specific property to {@code "true"} must disable only
* requirements in that namespace and leave other namespaces active.
*/
public void testRequireCapabilityFilterDisabledByNamespaceProperty() {
IRequirement myCapReq = findRequiredPropertiesMatchByNamespace(bundleIU.getRequirements(), "my.cap");
IRequirement osgiEeReq = findRequiredPropertiesMatchByNamespace(bundleIU.getRequirements(), "osgi.ee");
assertNotNull("my.cap Require-Capability requirement missing", myCapReq);
assertNotNull("osgi.ee requirement missing", osgiEeReq);

IInstallableUnit profileIU = createProfileIU("my.cap");
assertFalse("my.cap requirement should be disabled when its property is set to true",
myCapReq.getFilter().isMatch(profileIU));
assertTrue("osgi.ee requirement must remain active when only my.cap property is set",
osgiEeReq.getFilter().isMatch(profileIU));
}

/**
* Setting the old global property (without namespace suffix) must NOT disable
* any requirement, since the filter is now namespace-specific.
*/
public void testOldGlobalPropertyHasNoEffect() {
IRequirement myCapReq = findRequiredPropertiesMatchByNamespace(bundleIU.getRequirements(), "my.cap");
IRequirement osgiEeReq = findRequiredPropertiesMatchByNamespace(bundleIU.getRequirements(), "osgi.ee");
assertNotNull("my.cap Require-Capability requirement missing", myCapReq);
assertNotNull("osgi.ee requirement missing", osgiEeReq);

InstallableUnitDescription desc = new InstallableUnitDescription();
desc.setId("test.profile.global");
desc.setVersion(Version.create("1.0.0"));
// Set only the unsuffixed (old) global property — this should have no effect
desc.setProperty(BundlesAction.FILTER_PROPERTY_DISABLE_REQUIRE_CAPABILITY, "true");
IInstallableUnit profileIU = MetadataFactory.createInstallableUnit(desc);

assertTrue("my.cap requirement must remain active when only global property is set (no namespace suffix)",
myCapReq.getFilter().isMatch(profileIU));
assertTrue("osgi.ee requirement must remain active when only global property is set (no namespace suffix)",
osgiEeReq.getFilter().isMatch(profileIU));
}

/**
* Requirements synthesised from {@code Bundle-RequiredExecutionEnvironment}
* are emitted as {@code osgi.ee} {@code Require-Capability} entries and must
* carry the namespace-specific env filter.
*/
public void testBREERequirementHasEnvFilter() {
IRequirement req = findRequiredPropertiesMatchByNamespace(bundleIU.getRequirements(), "osgi.ee");
assertNotNull("osgi.ee requirement (from BREE) missing", req);

IMatchExpression<IInstallableUnit> filter = req.getFilter();
assertNotNull("osgi.ee requirement should have an env filter", filter);
// filter.toString() renders the parameter as "$0"; the LDAP filter string is in getParameters()[0]
assertTrue("Env filter of osgi.ee requirement should contain the namespace-specific property name",
filter.getParameters()[0].toString()
.contains(BundlesAction.getFilterPropertyForNamespace("osgi.ee")));
}

/**
* Setting the {@code osgi.ee}-specific property to {@code "true"} must disable
* the BREE-derived {@code osgi.ee} requirement.
*/
public void testBREERequirementFilterDisabledByNamespaceProperty() {
IRequirement req = findRequiredPropertiesMatchByNamespace(bundleIU.getRequirements(), "osgi.ee");
assertNotNull("osgi.ee requirement (from BREE) missing", req);

IInstallableUnit profileIU = createProfileIU("osgi.ee");
assertFalse("osgi.ee requirement should be disabled when its property is set to true",
req.getFilter().isMatch(profileIU));
}

/**
* {@code Import-Package} requirements must NOT carry the env filter because
* they are not emitted via the {@code Require-Capability} code path.
*/
public void testImportPackageHasNoEnvFilter() {
IRequiredCapability req = findRequiredCapabilityByNamespace(bundleIU.getRequirements(),
PublisherHelper.CAPABILITY_NS_JAVA_PACKAGE);
assertNotNull("java.package (Import-Package) requirement missing", req);
assertNull("Import-Package requirement must not have an env filter", req.getFilter());
}

/**
* {@code Require-Bundle} requirements must NOT carry the env filter because
* they are not emitted via the {@code Require-Capability} code path.
*/
public void testRequireBundleHasNoEnvFilter() {
IRequiredCapability req = findRequiredCapabilityByNamespace(bundleIU.getRequirements(),
BundlesAction.CAPABILITY_NS_OSGI_BUNDLE);
assertNotNull("osgi.bundle (Require-Bundle) requirement missing", req);
assertNull("Require-Bundle requirement must not have an env filter", req.getFilter());
}

// --- helpers ---

/**
* Creates a profile IU with the given namespaces disabled. At least one
* property is always set so that {@code InstallableUnit.getMember("properties")}
* returns a non-null map (required for {@code IMatchExpression.isMatch()} to
* work correctly).
*/
private static IInstallableUnit createProfileIU(String... disabledNamespaces) {
InstallableUnitDescription desc = new InstallableUnitDescription();
desc.setId("test.profile");
desc.setVersion(Version.create("1.0.0"));
// Always set a dummy property to initialize the properties map; without it
// InstallableUnit.getMember("properties") returns null and filter.isMatch()
// short-circuits to false regardless of the filter expression.
desc.setProperty("p2.test.profile", "active");
for (String namespace : disabledNamespaces) {
desc.setProperty(BundlesAction.getFilterPropertyForNamespace(namespace), "true");
}
return MetadataFactory.createInstallableUnit(desc);
}

private static IRequirement findRequiredPropertiesMatchByNamespace(Collection<IRequirement> requirements,
String namespace) {
for (IRequirement req : requirements) {
if (req instanceof RequiredPropertiesMatch rpm
&& namespace.equals(RequiredPropertiesMatch.extractNamespace(rpm.getMatches()))) {
return req;
}
}
return null;
}

private static IRequiredCapability findRequiredCapabilityByNamespace(Collection<IRequirement> requirements,
String namespace) {
for (IRequirement req : requirements) {
if (req instanceof IRequiredCapability rc && namespace.equals(rc.getNamespace())) {
return rc;
}
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: testRC
Bundle-SymbolicName: testRC
Bundle-Version: 1.0.0
Bundle-RequiredExecutionEnvironment: JavaSE-11
Require-Capability: my.cap;filter:="(my.attr=my.value)"
Import-Package: org.osgi.framework
Require-Bundle: some.other.bundle
2 changes: 1 addition & 1 deletion features/org.eclipse.equinox.p2.extras.feature/feature.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<feature
id="org.eclipse.equinox.p2.extras.feature"
label="%featureName"
version="1.4.3200.qualifier"
version="1.4.3300.qualifier"
provider-name="%providerName"
license-feature="org.eclipse.license"
license-feature-version="0.0.0">
Expand Down
Loading