Skip to content

Commit 32e5fa0

Browse files
authored
Merge branch 'eclipse-pde:master' into issue-1626_githubLinkInPlugInSpy
2 parents e05b897 + dd6dca3 commit 32e5fa0

File tree

6 files changed

+109
-23
lines changed

6 files changed

+109
-23
lines changed

.github/copilot-instructions.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,28 @@ mvn clean verify -Dmaven.repo.local=$WORKSPACE/.m2/repository \
8282

8383
**Docs:** `docs/API_Tools.md` for comprehensive API Tools documentation.
8484

85+
## OSGi Semantic Versioning
86+
87+
**PDE follows OSGi semantic versioning principles** (https://docs.osgi.org/whitepaper/semantic-versioning/):
88+
89+
**Version Format:** `major.minor.micro.qualifier`
90+
- **Major:** Breaking changes (incompatible API changes)
91+
- **Minor:** Backward-compatible API additions
92+
- **Micro:** Backward-compatible bug fixes
93+
- **Qualifier:** Build metadata (e.g., timestamps, commit hashes)
94+
95+
**Version Ranges for Dependencies:**
96+
- Use version ranges to specify compatible dependency versions
97+
- **Inclusive lower bound:** `[1.2.3,2.0.0)` includes 1.2.3
98+
- **Exclusive upper bound:** `[1.2.3,2.0.0)` excludes 2.0.0
99+
- **Best practice:** Lower bound inclusive, upper bound at next major (exclusive)
100+
- Example: For version `1.2.3`, use `[1.2.3,2.0.0)` to accept any 1.x version
101+
- Example: For version `2.5.0`, use `[2.5.0,3.0.0)` to accept any 2.x version
102+
103+
**Why exclude next major?** Major version changes indicate breaking changes, so dependencies should not automatically upgrade across major versions.
104+
105+
**PDE Support:** PDE manifest editor and dialogs should help users create proper version ranges following these principles when adding or updating bundle/package dependencies.
106+
85107
## Key Notes
86108

87109
**Tycho:** Eclipse-specific Maven extension managing OSGi dependencies, P2 repos, Eclipse metadata (MANIFEST.MF, plugin.xml, feature.xml).

ds/org.eclipse.pde.ds.tests/META-INF/MANIFEST.MF

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ Bundle-Activator: org.eclipse.pde.internal.ds.tests.Activator
77
Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)",
88
org.eclipse.pde.core;bundle-version="[3.3.0,4.0.0)",
99
org.eclipse.pde.ds.core;bundle-version="[1.0.0,2.0.0)",
10-
org.eclipse.text;bundle-version="[3.3.0,4.0.0)"
10+
org.eclipse.text;bundle-version="[3.3.0,4.0.0)",
11+
junit-jupiter-api;bundle-version="[5.14.0,6.0.0)",
12+
junit-jupiter-engine;bundle-version="[5.14.0,6.0.0)",
13+
junit-platform-launcher;bundle-version="[1.14.0,2.0.0)",
14+
junit-platform-suite-api;bundle-version="[1.14.0,2.0.0)",
15+
junit-platform-suite-commons;bundle-version="[1.14.0,2.0.0)",
16+
junit-platform-suite-engine;bundle-version="[1.14.0,2.0.0)"
1117
Bundle-ActivationPolicy: lazy
1218
Bundle-RequiredExecutionEnvironment: JavaSE-17
1319
Bundle-Localization: plugin
1420
Bundle-Vendor: %providerName
1521
Export-Package: org.eclipse.pde.internal.ds.tests;x-internal:=true
16-
Import-Package: org.junit.jupiter.api;version="[5.13.0,6.0.0)",
17-
org.junit.platform.suite.api;version="[1.13.0,2.0.0)"
1822
Eclipse-BundleShape: dir
1923
Automatic-Module-Name: org.eclipse.pde.ds.tests

e4tools/tests/org.eclipse.e4.tools.compatibility.migration.tests/META-INF/MANIFEST.MF

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ Require-Bundle: org.eclipse.swt;bundle-version="[3.110.0,4.0.0)",
1616
junit-jupiter-api;bundle-version="[5.14.0,6.0.0)",
1717
junit-jupiter-engine;bundle-version="[5.14.0,6.0.0)",
1818
junit-platform-launcher;bundle-version="[1.14.0,2.0.0)",
19-
junit-platform-suite-api;bundle-version="[1.14.0,2.0.0)"
19+
junit-platform-suite-api;bundle-version="[1.14.0,2.0.0)",
20+
junit-platform-suite-commons;bundle-version="[1.14.0,2.0.0)",
21+
junit-platform-suite-engine;bundle-version="[1.14.0,2.0.0)"
2022
Bundle-ActivationPolicy: lazy

e4tools/tests/org.eclipse.e4.tools.persistence.tests/META-INF/MANIFEST.MF

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,7 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.29.0",
1717
junit-jupiter-api;bundle-version="[5.14.0,6.0.0)",
1818
junit-jupiter-engine;bundle-version="[5.14.0,6.0.0)",
1919
junit-platform-launcher;bundle-version="[1.14.0,2.0.0)",
20-
junit-platform-suite-api;bundle-version="[1.14.0,2.0.0)"
20+
junit-platform-suite-api;bundle-version="[1.14.0,2.0.0)",
21+
junit-platform-suite-commons;bundle-version="[1.14.0,2.0.0)",
22+
junit-platform-suite-engine;bundle-version="[1.14.0,2.0.0)"
2123
Bundle-ActivationPolicy: lazy

ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/JUnitLaunchRequirements.java

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@
3737

3838
public class JUnitLaunchRequirements {
3939

40-
public static final String JUNIT4_JDT_RUNTIME_PLUGIN = "org.eclipse.jdt.junit4.runtime"; //$NON-NLS-1$
41-
public static final String JUNIT5_JDT_RUNTIME_PLUGIN = "org.eclipse.jdt.junit5.runtime"; //$NON-NLS-1$
42-
public static void addRequiredJunitRuntimePlugins(ILaunchConfiguration configuration, Map<String, List<IPluginModelBase>> allBundles, Map<IPluginModelBase, String> allModels) throws CoreException {
40+
private static final String PDE_JUNIT_RUNTIME = "org.eclipse.pde.junit.runtime"; //$NON-NLS-1$
41+
private static final String JUNIT4_JDT_RUNTIME_PLUGIN = "org.eclipse.jdt.junit4.runtime"; //$NON-NLS-1$
42+
private static final String JUNIT5_JDT_RUNTIME_PLUGIN = "org.eclipse.jdt.junit5.runtime"; //$NON-NLS-1$
43+
44+
public static void addRequiredJunitRuntimePlugins(ILaunchConfiguration configuration, Map<String, List<IPluginModelBase>> collectedModels, Map<IPluginModelBase, String> startLevelMap) throws CoreException {
4345
Collection<String> runtimePlugins = getRequiredJunitRuntimeEclipsePlugins(configuration);
44-
Set<BundleDescription> addedRuntimeBundles = addAbsentRequirements(runtimePlugins, allBundles, allModels);
46+
Set<BundleDescription> addedRuntimeBundles = addAbsentRequirements(runtimePlugins, collectedModels, startLevelMap);
4547
Set<BundleDescription> runtimeRequirements = DependencyManager.findRequirementsClosure(addedRuntimeBundles);
46-
addAbsentRequirements(runtimeRequirements, allBundles, allModels);
48+
addAbsentRequirements(runtimeRequirements, collectedModels, startLevelMap);
4749
}
4850

4951
@SuppressWarnings("restriction")
@@ -52,40 +54,42 @@ public static Collection<String> getRequiredJunitRuntimeEclipsePlugins(ILaunchCo
5254
if (testKind.isNull()) {
5355
return List.of();
5456
}
55-
List<String> plugins = new ArrayList<>();
56-
plugins.add("org.eclipse.pde.junit.runtime"); //$NON-NLS-1$
5757
switch (testKind.getId()) {
5858
case org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT3_TEST_KIND_ID -> {
59+
return List.of(PDE_JUNIT_RUNTIME);
5960
} // Nothing to add for JUnit-3
60-
case org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT4_TEST_KIND_ID -> plugins.add(JUNIT4_JDT_RUNTIME_PLUGIN);
61-
case org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT5_TEST_KIND_ID -> plugins.add(JUNIT5_JDT_RUNTIME_PLUGIN);
61+
case org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT4_TEST_KIND_ID -> {
62+
return List.of(PDE_JUNIT_RUNTIME,JUNIT4_JDT_RUNTIME_PLUGIN);
63+
}
64+
case org.eclipse.jdt.internal.junit.launcher.TestKindRegistry.JUNIT5_TEST_KIND_ID -> {
65+
return List.of(PDE_JUNIT_RUNTIME, JUNIT5_JDT_RUNTIME_PLUGIN);
66+
}
6267
default -> throw new IllegalArgumentException("Unsupported junit test kind: " + testKind.getId()); //$NON-NLS-1$
6368
}
64-
return plugins;
6569
}
6670

67-
private static Set<BundleDescription> addAbsentRequirements(Collection<String> requirements, Map<String, List<IPluginModelBase>> allBundles, Map<IPluginModelBase, String> allModels) throws CoreException {
71+
private static Set<BundleDescription> addAbsentRequirements(Collection<String> requirements, Map<String, List<IPluginModelBase>> collectedModels, Map<IPluginModelBase, String> startLevelMap) throws CoreException {
6872
Set<BundleDescription> addedRequirements = new LinkedHashSet<>();
6973
for (String id : requirements) {
70-
List<IPluginModelBase> models = allBundles.computeIfAbsent(id, k -> new ArrayList<>());
74+
List<IPluginModelBase> models = collectedModels.computeIfAbsent(id, k -> new ArrayList<>());
7175
if (models.stream().noneMatch(p -> p.getBundleDescription().isResolved())) {
7276
IPluginModelBase model = findRequiredPluginInTargetOrHost(PluginRegistry.findModel(id), plugins -> plugins.max(PDECore.VERSION), id);
7377
models.add(model);
74-
BundleLauncherHelper.addDefaultStartingBundle(allModels, model);
78+
BundleLauncherHelper.addDefaultStartingBundle(startLevelMap, model);
7579
addedRequirements.add(model.getBundleDescription());
7680
}
7781
}
7882
return addedRequirements;
7983
}
8084

81-
private static void addAbsentRequirements(Set<BundleDescription> requirements, Map<String, List<IPluginModelBase>> allBundles, Map<IPluginModelBase, String> allModels) throws CoreException {
85+
private static void addAbsentRequirements(Set<BundleDescription> requirements, Map<String, List<IPluginModelBase>> collectedModels, Map<IPluginModelBase, String> startLevelMap) throws CoreException {
8286
for (BundleRevision bundle : requirements) {
8387
String id = bundle.getSymbolicName();
84-
List<IPluginModelBase> models = allBundles.computeIfAbsent(id, k -> new ArrayList<>());
88+
List<IPluginModelBase> models = collectedModels.computeIfAbsent(id, k -> new ArrayList<>());
8589
if (models.stream().map(IPluginModelBase::getBundleDescription).noneMatch(b -> b.isResolved() && b.getVersion().equals(bundle.getVersion()))) {
8690
IPluginModelBase model = findRequiredPluginInTargetOrHost(PluginRegistry.findModel(bundle), plgs -> plgs.filter(p -> p.getBundleDescription() == bundle).findFirst(), id);
8791
models.add(model);
88-
BundleLauncherHelper.addDefaultStartingBundle(allModels, model);
92+
BundleLauncherHelper.addDefaultStartingBundle(startLevelMap, model);
8993
}
9094
}
9195
}

ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/parts/PluginVersionPart.java

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ protected void buttonSelected(Button button, int index) {
8282
IStructuredSelection selection = getTableViewer().getStructuredSelection();
8383
if (selection.size() == 1) {
8484
String version = getVersion(selection.getFirstElement());
85-
setVersion(version, ""); //$NON-NLS-1$
85+
// Compute upper bound following OSGi semantic versioning
86+
// Preserve existing upper bound if present and major version hasn't changed
87+
String upperBound = computeUpperBound(version, getCurrentUpperBound());
88+
setVersion(version, upperBound);
8689
} else {
8790
// plug-ins come back in a sorted order so we assume min/max
8891
String minVersion;
@@ -105,7 +108,9 @@ protected void buttonSelected(Button button, int index) {
105108
maxVersion = poMax.getVersion();
106109

107110
}
108-
setVersion(minVersion, maxVersion);
111+
// Compute upper bound based on max version following OSGi semantic versioning
112+
String upperBound = computeUpperBound(maxVersion, getCurrentUpperBound());
113+
setVersion(minVersion, upperBound);
109114
}
110115
}
111116

@@ -117,6 +122,53 @@ private String getVersion(Object firstElement) {
117122
PackageObject po = (PackageObject) firstElement;
118123
return po.getVersion();
119124
}
125+
126+
/**
127+
* Gets the current upper bound from the existing version range, if any.
128+
*
129+
* @return the current upper bound version, or null if none exists
130+
*/
131+
private Version getCurrentUpperBound() {
132+
if (fVersionRange != null) {
133+
return fVersionRange.getRight();
134+
}
135+
return null;
136+
}
137+
138+
/**
139+
* Computes the upper bound for a version range following OSGi semantic versioning.
140+
* If an existing upper bound is present, it will be adjusted based on the new lower bound's major version.
141+
* Otherwise, a new upper bound excluding the next major version is created.
142+
*
143+
* @param newLowerBound the new lower bound version
144+
* @param existingUpperBound the existing upper bound (may be null)
145+
* @return the upper bound version (next major version as a string)
146+
*/
147+
private String computeUpperBound(String newLowerBound, Version existingUpperBound) {
148+
try {
149+
Version lowerVersion = Version.parseVersion(newLowerBound);
150+
int newMajor = lowerVersion.getMajor();
151+
152+
if (existingUpperBound != null) {
153+
// Existing upper bound present - adjust it if major version changed
154+
int existingUpperMajor = existingUpperBound.getMajor();
155+
156+
// If the new lower bound has the same major version as the existing upper bound,
157+
// keep the existing upper bound
158+
if (newMajor < existingUpperMajor) {
159+
return existingUpperBound.toString();
160+
}
161+
// If the new lower bound's major version changed, update to next major
162+
}
163+
164+
// No existing upper bound or major version changed - compute next major version
165+
Version nextMajor = new Version(newMajor + 1, 0, 0);
166+
return nextMajor.toString();
167+
} catch (IllegalArgumentException e) {
168+
// If version parsing fails, return empty string to maintain backward compatibility
169+
return ""; //$NON-NLS-1$
170+
}
171+
}
120172
}
121173

122174
private static class PluginVersionContentProvider implements IStructuredContentProvider {

0 commit comments

Comments
 (0)