diff --git a/application/org.openjdk.jmc.feature.flightrecorder/feature.xml b/application/org.openjdk.jmc.feature.flightrecorder/feature.xml
index 7e252a851..f5d1b8eac 100644
--- a/application/org.openjdk.jmc.feature.flightrecorder/feature.xml
+++ b/application/org.openjdk.jmc.feature.flightrecorder/feature.xml
@@ -1,6 +1,6 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/releng/platform-definitions/platform-definition-2025-12/pom.xml b/releng/platform-definitions/platform-definition-2025-12/pom.xml
new file mode 100644
index 000000000..64eb141d3
--- /dev/null
+++ b/releng/platform-definitions/platform-definition-2025-12/pom.xml
@@ -0,0 +1,47 @@
+
+
+
+ 4.0.0
+
+ org.openjdk.jmc
+ platform-definitions
+ ${revision}${changelist}
+
+ platform-definition-2025-12
+ eclipse-target-definition
+
+ ${project.basedir}/../../../configuration
+
+
diff --git a/releng/platform-definitions/platform-definition-2026-03/pom.xml b/releng/platform-definitions/platform-definition-2026-03/pom.xml
new file mode 100644
index 000000000..9a2c511f1
--- /dev/null
+++ b/releng/platform-definitions/platform-definition-2026-03/pom.xml
@@ -0,0 +1,47 @@
+
+
+
+ 4.0.0
+
+ org.openjdk.jmc
+ platform-definitions
+ ${revision}${changelist}
+
+ platform-definition-2026-03
+ eclipse-target-definition
+
+ ${project.basedir}/../../../configuration
+
+
diff --git a/releng/platform-definitions/pom.xml b/releng/platform-definitions/pom.xml
index fbea747af..7648840f8 100644
--- a/releng/platform-definitions/pom.xml
+++ b/releng/platform-definitions/pom.xml
@@ -44,6 +44,7 @@
${project.basedir}/../../configuration
+ platform-definition-2025-12
platform-definition-2025-09
platform-definition-2025-06
platform-definition-2025-03
diff --git a/releng/third-party/pom.xml b/releng/third-party/pom.xml
index e9b2e78a2..727cff935 100644
--- a/releng/third-party/pom.xml
+++ b/releng/third-party/pom.xml
@@ -59,7 +59,7 @@
1.10.4
2.2.2
2.0.0
- 12.1.0
+ 12.1.4
1.3.7
2.5.1
2.27.2
@@ -121,24 +121,26 @@
org.adoptopenjdk:jemmy-swt:${jemmy.version}
+
org.eclipse.jetty.websocket:jetty-websocket-jetty-server:${jetty.version}
-
-
- org.eclipse.jetty.ee9:jetty-ee9-servlet:${jetty.version}
-
-
- org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-servlet:${jetty.version}
+ true
+
+ org.eclipse.jetty.websocket.server
+ org.eclipse.jetty.websocket.server.*
+
+ org.eclipse.jetty.http,org.eclipse.jetty.http.pathmap,org.eclipse.jetty.server,org.eclipse.jetty.server.handler,org.eclipse.jetty.util,org.eclipse.jetty.util.component,org.eclipse.jetty.util.thread,org.eclipse.jetty.websocket.api,org.eclipse.jetty.websocket.common,org.eclipse.jetty.websocket.core,org.eclipse.jetty.websocket.core.exception,org.eclipse.jetty.websocket.core.server,*
+
org.eclipse.jetty.websocket:jetty-websocket-jetty-api:${jetty.version}
-
- org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jetty-api:${jetty.version}
-
-
- org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jetty-server:${jetty.version}
-
org.eclipse.jetty:jetty-security:${jetty.version}
diff --git a/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/CreatePlatform.java b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/CreatePlatform.java
new file mode 100644
index 000000000..e66e4b9b1
--- /dev/null
+++ b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/CreatePlatform.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2026, Datadog, Inc. All rights reserved.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * The contents of this file are subject to the terms of either the Universal Permissive License
+ * v 1.0 as shown at https://oss.oracle.com/licenses/upl
+ *
+ * or the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
+ * and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials provided with
+ * the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.openjdk.jmc.releng.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Creates a new platform-definition-<version>/ directory under a platform-definitions root,
+ * generating pom.xml and the .target file from templates and registering the new module in the
+ * parent pom.xml. Unit versions are resolved automatically: bundles served from the Eclipse update
+ * site are pulled fresh via {@link ListVersions}, and bundles from other locations (the local p2
+ * site and the Babel archive) are inherited from the most recent existing platform definition.
+ */
+public class CreatePlatform {
+
+ private static final String POM_TEMPLATE_RESOURCE = "templates/pom.xml.template";
+ private static final String TARGET_TEMPLATE_RESOURCE = "templates/platform-definition.target.template";
+ private static final String PROJECT_TEMPLATE_RESOURCE = "templates/.project.template";
+
+ private static final Pattern VERSION_PATTERN = Pattern.compile("\\d{4}-\\d{2}");
+ private static final Pattern PLATFORM_DIR_PATTERN = Pattern.compile("platform-definition-(\\d{4}-\\d{2})");
+ private static final Pattern LOCATION_PATTERN = Pattern.compile("]*>(.*?)", Pattern.DOTALL);
+ private static final Pattern UNIT_PATTERN = Pattern.compile("");
+ private static final Pattern REPOSITORY_PATTERN = Pattern.compile("");
+ private static final Pattern ECLIPSE_RELEASE_PATTERN = Pattern
+ .compile("https://download\\.eclipse\\.org/releases/\\d{4}-\\d{2}/?");
+ private static final Pattern UNIT_VERSION_PLACEHOLDER = Pattern.compile("\\$\\{([^}]+)\\.version\\}");
+ private static final Pattern ANY_PLACEHOLDER = Pattern.compile("\\$\\{[^}]+\\}");
+
+ public static void main(String[] args) throws IOException {
+ if (args.length != 2) {
+ System.out.println("Usage: java CreatePlatform ");
+ System.out.println(" e.g. java CreatePlatform 2025-12 releng/platform-definitions");
+ System.exit(2);
+ }
+ String eclipseVersion = args[0];
+ Path root = Paths.get(args[1]).toAbsolutePath().normalize();
+
+ if (!VERSION_PATTERN.matcher(eclipseVersion).matches()) {
+ System.err.println("Eclipse version must be in YYYY-MM form, e.g. 2025-12. Got: " + eclipseVersion);
+ System.exit(2);
+ }
+ if (!Files.isDirectory(root)) {
+ System.err.println("Platform-definitions root not found or not a directory: " + root);
+ System.exit(2);
+ }
+
+ Path platformDir = root.resolve("platform-definition-" + eclipseVersion);
+ Path pomFile = platformDir.resolve("pom.xml");
+ Path targetFile = platformDir.resolve("platform-definition-" + eclipseVersion + ".target");
+ Path projectFile = platformDir.resolve(".project");
+ Path parentPom = root.resolve("pom.xml");
+
+ if (Files.exists(platformDir) && Files.isDirectory(platformDir) && hasContent(platformDir)) {
+ System.err.println("Platform definition already exists: " + platformDir);
+ System.err.println("Use UpdateVersions to refresh an existing platform definition.");
+ System.exit(1);
+ }
+
+ Path baseline = findLatestPlatformTarget(root, eclipseVersion);
+ if (baseline == null) {
+ System.err.println("No prior platform-definition-YYYY-MM/ found in " + root
+ + " to inherit non-Eclipse bundle versions from.");
+ System.exit(1);
+ }
+ System.out.println("Inheriting non-Eclipse bundle versions from: " + baseline);
+
+ Map baselineVersions = new HashMap<>();
+ Set eclipseManagedIds = new HashSet<>();
+ parseBaselineTarget(baseline, baselineVersions, eclipseManagedIds);
+
+ System.out.println("Fetching unit versions for Eclipse " + eclipseVersion + "...");
+ Map eclipseFresh = ListVersions.getNewVersions(eclipseVersion);
+ if (eclipseFresh.isEmpty()) {
+ System.err.println("No versions retrieved for Eclipse " + eclipseVersion + ". Aborting.");
+ System.exit(1);
+ }
+
+ Map resolved = new HashMap<>(baselineVersions);
+ for (String id : eclipseManagedIds) {
+ String fresh = eclipseFresh.get(id);
+ if (fresh != null) {
+ resolved.put(id, fresh);
+ }
+ }
+
+ String year = String.valueOf(LocalDate.now().getYear());
+ String pomContent = applyTopLevelPlaceholders(loadTemplate(POM_TEMPLATE_RESOURCE), eclipseVersion, year);
+ String targetContent = applyTopLevelPlaceholders(loadTemplate(TARGET_TEMPLATE_RESOURCE), eclipseVersion, year);
+ String projectContent = applyTopLevelPlaceholders(loadTemplate(PROJECT_TEMPLATE_RESOURCE), eclipseVersion, year);
+ targetContent = substituteUnitVersions(targetContent, resolved);
+
+ List unresolved = findUnresolvedPlaceholders(targetContent);
+ if (!unresolved.isEmpty()) {
+ System.err.println("Could not resolve all placeholders in target template:");
+ for (String p : unresolved) {
+ System.err.println(" " + p);
+ }
+ System.err.println("Add the missing bundle to a prior platform definition or update the template.");
+ System.exit(1);
+ }
+
+ boolean parentNeedsModule = Files.exists(parentPom) && !parentPomHasModule(parentPom, eclipseVersion);
+
+ List plan = new ArrayList<>();
+ plan.add("Create directory: " + platformDir);
+ plan.add("Create file: " + projectFile);
+ plan.add("Create file: " + pomFile);
+ plan.add("Create file: " + targetFile);
+ if (parentNeedsModule) {
+ plan.add("Add module to: " + parentPom + " (platform-definition-" + eclipseVersion
+ + ")");
+ }
+
+ System.out.println();
+ System.out.println("Plan:");
+ for (String line : plan) {
+ System.out.println(" " + line);
+ }
+ System.out.println();
+
+ if (!Prompts.yesNo("Proceed? [y/N]: ")) {
+ System.out.println("Aborted. No files written.");
+ return;
+ }
+
+ Files.createDirectories(platformDir);
+ Files.write(projectFile, projectContent.getBytes(StandardCharsets.UTF_8));
+ Files.write(pomFile, pomContent.getBytes(StandardCharsets.UTF_8));
+ Files.write(targetFile, targetContent.getBytes(StandardCharsets.UTF_8));
+ System.out.println("Wrote " + projectFile);
+ System.out.println("Wrote " + pomFile);
+ System.out.println("Wrote " + targetFile);
+
+ if (parentNeedsModule) {
+ addModuleToParentPom(parentPom, eclipseVersion);
+ System.out.println("Updated " + parentPom);
+ }
+ }
+
+ private static boolean hasContent(Path dir) throws IOException {
+ try (var stream = Files.list(dir)) {
+ return stream.findAny().isPresent();
+ }
+ }
+
+ private static Path findLatestPlatformTarget(Path root, String excludeVersion) throws IOException {
+ String latest = null;
+ Path latestTarget = null;
+ try (var stream = Files.list(root)) {
+ for (Path p : (Iterable) stream::iterator) {
+ if (!Files.isDirectory(p)) {
+ continue;
+ }
+ Matcher m = PLATFORM_DIR_PATTERN.matcher(p.getFileName().toString());
+ if (!m.matches()) {
+ continue;
+ }
+ String ver = m.group(1);
+ if (ver.equals(excludeVersion)) {
+ continue;
+ }
+ Path tgt = p.resolve("platform-definition-" + ver + ".target");
+ if (!Files.exists(tgt)) {
+ continue;
+ }
+ if (latest == null || ver.compareTo(latest) > 0) {
+ latest = ver;
+ latestTarget = tgt;
+ }
+ }
+ }
+ return latestTarget;
+ }
+
+ private static void parseBaselineTarget(Path baseline, Map versions, Set eclipseManagedIds)
+ throws IOException {
+ String content = new String(Files.readAllBytes(baseline), StandardCharsets.UTF_8);
+ Matcher locM = LOCATION_PATTERN.matcher(content);
+ while (locM.find()) {
+ String block = locM.group(1);
+ boolean isEclipse = false;
+ Matcher repoM = REPOSITORY_PATTERN.matcher(block);
+ if (repoM.find()) {
+ isEclipse = ECLIPSE_RELEASE_PATTERN.matcher(repoM.group(1)).matches();
+ }
+ Matcher unitM = UNIT_PATTERN.matcher(block);
+ while (unitM.find()) {
+ String id = unitM.group(1);
+ String ver = unitM.group(2);
+ versions.put(id, ver);
+ if (isEclipse) {
+ eclipseManagedIds.add(id);
+ }
+ }
+ }
+ }
+
+ private static String loadTemplate(String resourceName) throws IOException {
+ try (InputStream in = CreatePlatform.class.getResourceAsStream(resourceName)) {
+ if (in == null) {
+ throw new IOException("Template resource not found on classpath: " + resourceName);
+ }
+ return new String(in.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ }
+
+ private static String applyTopLevelPlaceholders(String template, String eclipseVersion, String year) {
+ return template.replace("${ECLIPSE_VERSION}", eclipseVersion).replace("${COPYRIGHT_YEAR}", year);
+ }
+
+ private static String substituteUnitVersions(String content, Map versions) {
+ StringBuffer sb = new StringBuffer();
+ Matcher m = UNIT_VERSION_PLACEHOLDER.matcher(content);
+ while (m.find()) {
+ String id = m.group(1);
+ String ver = versions.get(id);
+ if (ver == null) {
+ // Leave the placeholder in place; the unresolved-check below will report it.
+ m.appendReplacement(sb, Matcher.quoteReplacement(m.group()));
+ } else {
+ m.appendReplacement(sb, Matcher.quoteReplacement(ver));
+ }
+ }
+ m.appendTail(sb);
+ return sb.toString();
+ }
+
+ private static List findUnresolvedPlaceholders(String content) {
+ List out = new ArrayList<>();
+ Matcher m = ANY_PLACEHOLDER.matcher(content);
+ while (m.find()) {
+ out.add(m.group());
+ }
+ return out;
+ }
+
+ static boolean parentPomHasModule(Path parentPom, String eclipseVersion) throws IOException {
+ String content = new String(Files.readAllBytes(parentPom), StandardCharsets.UTF_8);
+ return content.contains("platform-definition-" + eclipseVersion + "");
+ }
+
+ static void addModuleToParentPom(Path parentPom, String eclipseVersion) throws IOException {
+ String content = new String(Files.readAllBytes(parentPom), StandardCharsets.UTF_8);
+ Pattern firstModule = Pattern.compile("(?m)^([ \\t]*)platform-definition-\\d{4}-\\d{2}\\s*\\R");
+ Matcher m = firstModule.matcher(content);
+ String moduleEntry = "platform-definition-" + eclipseVersion + "";
+ String updated;
+ if (m.find()) {
+ String indent = m.group(1);
+ updated = content.substring(0, m.start()) + indent + moduleEntry + System.lineSeparator()
+ + content.substring(m.start());
+ } else {
+ Pattern modulesOpen = Pattern.compile("(?m)^([ \\t]*)\\s*\\R");
+ Matcher mo = modulesOpen.matcher(content);
+ if (!mo.find()) {
+ throw new IOException("Could not find in parent pom: " + parentPom);
+ }
+ String indent = mo.group(1) + "\t";
+ updated = content.substring(0, mo.end()) + indent + moduleEntry + System.lineSeparator()
+ + content.substring(mo.end());
+ }
+ Files.write(parentPom, updated.getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/util/listversions/ListVersions.java b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/ListVersions.java
similarity index 82%
rename from releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/util/listversions/ListVersions.java
rename to releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/ListVersions.java
index cf831c9c6..78a773b88 100644
--- a/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/util/listversions/ListVersions.java
+++ b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/ListVersions.java
@@ -1,6 +1,6 @@
/*
- * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2025, Datadog, Inc. All rights reserved.
+ * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2026, Datadog, Inc. All rights reserved.
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@@ -31,7 +31,7 @@
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.openjdk.jmc.util.listversions;
+package org.openjdk.jmc.releng.util;
import java.io.IOException;
import java.io.InputStream;
@@ -100,6 +100,9 @@ public static Map getNewVersions(String eclipseVersion) {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
dbFactory.setFeature(XML_PARSER_DISALLOW_DOCTYPE_ATTRIBUTE, true);
dbFactory.setValidating(true);
+ // Eclipse content.jar entries can exceed the default JAXP entity size limit (100k)
+ dbFactory.setAttribute("jdk.xml.maxGeneralEntitySizeLimit", "0");
+ dbFactory.setAttribute("jdk.xml.totalEntitySizeLimit", "0");
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
dBuilder.setErrorHandler(null);
Document compositeDoc = dBuilder.parse(compositeZipStream);
@@ -138,7 +141,10 @@ public static Map getNewVersions(String eclipseVersion) {
Element unitElement = (Element) unitNode;
String id = unitElement.getAttribute("id");
String version = unitElement.getAttribute("version");
- versions.put(id, version);
+ String existing = versions.get(id);
+ if (existing == null || compareOsgiVersions(version, existing) > 0) {
+ versions.put(id, version);
+ }
}
}
}
@@ -147,4 +153,28 @@ public static Map getNewVersions(String eclipseVersion) {
}
return versions;
}
+
+ // OSGi version order: major.minor.micro numerically, then qualifier as string.
+ static int compareOsgiVersions(String a, String b) {
+ String[] aParts = a.split("\\.", 4);
+ String[] bParts = b.split("\\.", 4);
+ for (int i = 0; i < 3; i++) {
+ int ai = i < aParts.length ? parseIntSafe(aParts[i]) : 0;
+ int bi = i < bParts.length ? parseIntSafe(bParts[i]) : 0;
+ if (ai != bi) {
+ return Integer.compare(ai, bi);
+ }
+ }
+ String aq = aParts.length > 3 ? aParts[3] : "";
+ String bq = bParts.length > 3 ? bParts[3] : "";
+ return aq.compareTo(bq);
+ }
+
+ private static int parseIntSafe(String s) {
+ try {
+ return Integer.parseInt(s);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
}
diff --git a/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/Prompts.java b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/Prompts.java
new file mode 100644
index 000000000..b1c311efc
--- /dev/null
+++ b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/Prompts.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2026, Datadog, Inc. All rights reserved.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * The contents of this file are subject to the terms of either the Universal Permissive License
+ * v 1.0 as shown at https://oss.oracle.com/licenses/upl
+ *
+ * or the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
+ * and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials provided with
+ * the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.openjdk.jmc.releng.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+final class Prompts {
+
+ private Prompts() {
+ }
+
+ static boolean yesNo(String prompt) throws IOException {
+ System.out.print(prompt);
+ System.out.flush();
+ String line;
+ if (System.console() != null) {
+ line = System.console().readLine();
+ } else {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
+ line = reader.readLine();
+ }
+ if (line == null) {
+ return false;
+ }
+ String trimmed = line.trim().toLowerCase();
+ return trimmed.equals("y") || trimmed.equals("yes");
+ }
+}
diff --git a/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/UpdateVersions.java b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/UpdateVersions.java
new file mode 100644
index 000000000..144ed180c
--- /dev/null
+++ b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/UpdateVersions.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2024, 2026, Datadog, Inc. All rights reserved.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * The contents of this file are subject to the terms of either the Universal Permissive License
+ * v 1.0 as shown at https://oss.oracle.com/licenses/upl
+ *
+ * or the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
+ * and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials provided with
+ * the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.openjdk.jmc.releng.util;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class UpdateVersions {
+
+ private static final Pattern UNIT_PATTERN = Pattern
+ .compile("");
+ private static final Pattern TARGET_NAME_PATTERN = Pattern.compile("");
+ private static final Pattern LOCATION_PATTERN = Pattern.compile("]*>(.*?)", Pattern.DOTALL);
+ private static final Pattern LOCATION_REPOSITORY_PATTERN = Pattern
+ .compile("");
+ private static final Pattern ECLIPSE_RELEASE_REPO_PATTERN = Pattern
+ .compile("https://download\\.eclipse\\.org/releases/\\d{4}-\\d{2}/?");
+
+ private static final class Change {
+ final String label;
+ final String oldValue;
+ final String newValue;
+
+ Change(String label, String oldValue, String newValue) {
+ this.label = label;
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ if (args.length != 2) {
+ System.out.println("Usage: java UpdateVersions ");
+ System.exit(2);
+ }
+ String eclipseVersion = args[0];
+ String platformDefinitionPath = args[1];
+
+ Path file = Paths.get(platformDefinitionPath);
+ String content = new String(Files.readAllBytes(file), StandardCharsets.UTF_8);
+
+ System.out.println("Fetching unit versions for Eclipse " + eclipseVersion + "...");
+ Map newVersions = ListVersions.getNewVersions(eclipseVersion);
+ if (newVersions.isEmpty()) {
+ System.err.println("No versions retrieved for Eclipse " + eclipseVersion + ". Aborting.");
+ System.exit(1);
+ }
+
+ List changes = new ArrayList<>();
+ String updated = content;
+ updated = collectTargetNameChange(updated, eclipseVersion, changes);
+ updated = collectRepositoryChange(updated, eclipseVersion, changes);
+ updated = collectUnitChanges(updated, newVersions, changes);
+
+ if (changes.isEmpty()) {
+ System.out.println("No updates needed. " + platformDefinitionPath + " is already up to date.");
+ return;
+ }
+
+ printChanges(platformDefinitionPath, changes);
+ if (Prompts.yesNo("Apply these " + changes.size() + " update(s) to " + platformDefinitionPath + "? [y/N]: ")) {
+ Files.write(file, updated.getBytes(StandardCharsets.UTF_8));
+ System.out.println("Wrote updated platform definition to: " + file);
+ } else {
+ System.out.println("Aborted. No changes written.");
+ }
+ }
+
+ private static String collectTargetNameChange(String content, String newEclipseVersion, List changes) {
+ Matcher matcher = TARGET_NAME_PATTERN.matcher(content);
+ if (!matcher.find()) {
+ return content;
+ }
+ String currentVersion = matcher.group(1);
+ if (currentVersion.equals(newEclipseVersion)) {
+ return content;
+ }
+ String oldTag = matcher.group();
+ String newTag = " changes) {
+ Matcher matcher = REPOSITORY_PATTERN.matcher(content);
+ if (!matcher.find()) {
+ return content;
+ }
+ String currentVersion = matcher.group(1);
+ if (currentVersion.equals(newEclipseVersion)) {
+ return content;
+ }
+ String oldRepo = matcher.group();
+ String newRepo = "";
+ changes.add(new Change("repository location", "releases/" + currentVersion + "/",
+ "releases/" + newEclipseVersion + "/"));
+ return content.replace(oldRepo, newRepo);
+ }
+
+ private static String collectUnitChanges(String content, Map newVersions, List changes) {
+ // Only update units that live inside a block whose repository points at the Eclipse
+ // releases update site. Units in other blocks (the local p2 site, the Babel archive) come from
+ // repositories that don't host the same versions, so blindly applying the Eclipse fetch there
+ // would propose bogus and often unresolvable downgrades.
+ StringBuffer sb = new StringBuffer();
+ Matcher locationMatcher = LOCATION_PATTERN.matcher(content);
+ int lastEnd = 0;
+ while (locationMatcher.find()) {
+ sb.append(content, lastEnd, locationMatcher.start());
+ String block = locationMatcher.group();
+ boolean isEclipse = isEclipseReleaseLocation(block);
+ sb.append(isEclipse ? rewriteUnitsInBlock(block, newVersions, changes) : block);
+ lastEnd = locationMatcher.end();
+ }
+ sb.append(content, lastEnd, content.length());
+ return sb.toString();
+ }
+
+ private static boolean isEclipseReleaseLocation(String block) {
+ Matcher repo = LOCATION_REPOSITORY_PATTERN.matcher(block);
+ return repo.find() && ECLIPSE_RELEASE_REPO_PATTERN.matcher(repo.group(1)).matches();
+ }
+
+ private static String rewriteUnitsInBlock(String block, Map newVersions, List changes) {
+ StringBuffer sb = new StringBuffer();
+ Matcher matcher = UNIT_PATTERN.matcher(block);
+ while (matcher.find()) {
+ String id = matcher.group(1);
+ String currentVersion = matcher.group(2);
+ String newVersion = newVersions.get(id);
+ if (newVersion != null && !newVersion.equals(currentVersion)) {
+ String replacement = matcher.group().replace("version=\"" + currentVersion + "\"",
+ "version=\"" + newVersion + "\"");
+ matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
+ changes.add(new Change("unit " + id, currentVersion, newVersion));
+ } else {
+ matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group()));
+ }
+ }
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
+
+ private static void printChanges(String filePath, List changes) {
+ System.out.println();
+ System.out.println("Proposed updates to " + filePath + ":");
+ int labelWidth = 0;
+ for (Change c : changes) {
+ labelWidth = Math.max(labelWidth, c.label.length());
+ }
+ for (Change c : changes) {
+ System.out.printf(" %-" + labelWidth + "s : %s -> %s%n", c.label, c.oldValue, c.newValue);
+ }
+ System.out.println();
+ }
+
+}
diff --git a/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/templates/.project.template b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/templates/.project.template
new file mode 100644
index 000000000..e53fc195d
--- /dev/null
+++ b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/templates/.project.template
@@ -0,0 +1,17 @@
+
+
+ platform-definition-${ECLIPSE_VERSION}
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/templates/platform-definition.target.template b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/templates/platform-definition.target.template
new file mode 100644
index 000000000..5c87e28b1
--- /dev/null
+++ b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/templates/platform-definition.target.template
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/templates/pom.xml.template b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/templates/pom.xml.template
new file mode 100644
index 000000000..f83c8eebe
--- /dev/null
+++ b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/releng/util/templates/pom.xml.template
@@ -0,0 +1,47 @@
+
+
+
+ 4.0.0
+
+ org.openjdk.jmc
+ platform-definitions
+ ${revision}${changelist}
+
+ platform-definition-${ECLIPSE_VERSION}
+ eclipse-target-definition
+
+ ${project.basedir}/../../../configuration
+
+
diff --git a/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/util/listversions/UpdateVersions.java b/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/util/listversions/UpdateVersions.java
deleted file mode 100644
index c7676222c..000000000
--- a/releng/tools/org.openjdk.jmc.util.listversions/src/org/openjdk/jmc/util/listversions/UpdateVersions.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2024, 2025, Datadog, Inc. All rights reserved.
- *
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * The contents of this file are subject to the terms of either the Universal Permissive License
- * v 1.0 as shown at https://oss.oracle.com/licenses/upl
- *
- * or the following license:
- *
- * Redistribution and use in source and binary forms, with or without modification, are permitted
- * provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
- * and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
- * conditions and the following disclaimer in the documentation and/or other materials provided with
- * the distribution.
- *
- * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
- * endorse or promote products derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
- * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
- * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.openjdk.jmc.util.listversions;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class UpdateVersions {
-
- public static void main(String[] args) throws IOException {
- if (args.length != 2) {
- System.out
- .println("Usage: java UpdatePlatformDefinition ");
- System.exit(2);
- }
- String eclipseVersion = args[0];
- String platformDefinitionPath = args[1];
-
- Map newVersions = ListVersions.getNewVersions(eclipseVersion);
- String updatedContent = updatePlatformDefinition(platformDefinitionPath, newVersions, eclipseVersion);
- writePlatformDefinition(platformDefinitionPath, updatedContent);
- }
-
- private static void writePlatformDefinition(String platformDefinitionPath, String updatedContent)
- throws IOException {
- System.out.println("Updated content...");
- System.out.println(updatedContent);
- Path originalFile = Paths.get(platformDefinitionPath);
- Path directory = originalFile.getParent();
- String fileName = originalFile.getFileName().toString();
- String newFileName = "updated_" + fileName;
- Path newFile = directory.resolve(newFileName);
-
- Files.write(newFile, updatedContent.getBytes());
- System.out.println("Updated platform definition written to: " + newFile);
- }
-
- private static String updatePlatformDefinition(String filePath, Map newVersions, String newEclipseVersion)
- throws IOException {
- String content = new String(Files.readAllBytes(Paths.get(filePath)));
- content = updateTargetName(content, newEclipseVersion);
- content = updateRepositoryLocation(content, newEclipseVersion);
-
- // Pattern to match elements with their id and version
- Pattern pattern = Pattern.compile("");
-
- StringBuffer sb = new StringBuffer();
- Matcher matcher = pattern.matcher(content);
-
- while (matcher.find()) {
- String id = matcher.group(1);
- String currentVersion = matcher.group(2);
-
- if (newVersions.containsKey(id)) {
- String newVersion = newVersions.get(id);
- // Replace only the version, keeping everything else the same
- String replacement = matcher.group().replace("version=\"" + currentVersion + "\"",
- "version=\"" + newVersion + "\"");
- matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
- System.out.println("Updated " + id + " from version " + currentVersion + " to " + newVersion);
- } else {
- // If no new version, keep the original
- matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group()));
- }
- }
- matcher.appendTail(sb);
-
- return sb.toString();
- }
-
- private static String updateTargetName(String content, String newEclipseVersion) {
- Pattern pattern = Pattern.compile("");
- Matcher matcher = pattern.matcher(content);
-
- if (matcher.find()) {
- String oldRepository = matcher.group();
- String newRepository = "";
- content = content.replace(oldRepository, newRepository);
- }
-
- return content;
- }
-}