diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java index 67aa56a65a6..13bf7503f36 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java @@ -53,11 +53,13 @@ import static jdk.jpackage.internal.model.StandardPackageType.MAC_DMG; import static jdk.jpackage.internal.model.StandardPackageType.MAC_PKG; import static jdk.jpackage.internal.util.function.ExceptionBox.toUnchecked; +import java.math.BigInteger; import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.function.UnaryOperator; import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo; import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException; import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector; @@ -65,6 +67,7 @@ import jdk.jpackage.internal.cli.OptionValue; import jdk.jpackage.internal.cli.Options; import jdk.jpackage.internal.cli.StandardFaOption; import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.DottedVersion; import jdk.jpackage.internal.model.ApplicationLaunchers; import jdk.jpackage.internal.model.ExternalApplication; import jdk.jpackage.internal.model.FileAssociation; @@ -219,24 +222,10 @@ final class MacFromOptions { if (isRuntimeInstaller(options) && !APP_VERSION.containsIn(options)) { // User didn't explicitly specify the version on the command line. jpackage derived it from the input. // In this case it should ensure the derived value is valid MacOS version. - var ver = normalizeVersion(app.version()); - if (!ver.equals(app.version())) { - Log.verbose(I18N.format("message.version-normalized", - ver, app.version())); - } - - app = new Application.Stub( - app.name(), - app.description(), - ver, - app.vendor(), - app.copyright(), - app.srcDir(), - app.contentDirs(), - app.imageLayout(), - app.runtimeBuilder(), - app.launchers(), - app.extraAppImageFileData()); + UnaryOperator versionNormalizer = version -> { + return normalizeVersion(version); + }; + app = ApplicationBuilder.normalizeVersion(app, app.version(), versionNormalizer); } final var appBuilder = new MacApplicationBuilder(app); @@ -359,10 +348,10 @@ final class MacFromOptions { // macOS requires 1, 2 or 3 components version string. // When reading from release file it can be 1 or 3 or maybe more. // We always normalize to 3 components. - String[] components = version.split("\\."); + DottedVersion ver = DottedVersion.greedy(version); + BigInteger[] components = ver.getComponents(); if (components.length >= 4) { - components = version.split("\\.", 4); - return String.join(".", Arrays.copyOf(components, components.length - 1)); + return ver.toComponentsStringWithPadding(3); } return version; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java index 6896668ffdc..aa3b14a8237 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,6 +35,7 @@ import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.UnaryOperator; import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLaunchers; @@ -227,6 +228,28 @@ final class ApplicationBuilder { } } + static Application normalizeVersion(Application app, String version, + UnaryOperator versionNormalizer) { + final var ver = versionNormalizer.apply(version); + if (!ver.equals(app.version())) { + Log.verbose(I18N.format("message.version-normalized", + ver, app.version())); + } + + return new Application.Stub( + app.name(), + app.description(), + ver, + app.vendor(), + app.copyright(), + app.srcDir(), + app.contentDirs(), + app.imageLayout(), + app.runtimeBuilder(), + app.launchers(), + app.extraAppImageFileData()); + } + static Launcher overrideLauncherStartupInfo(Launcher launcher, LauncherStartupInfo startupInfo) { return new Launcher.Stub( launcher.name(), diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java index c9f6c59b7ea..d61ee5afe5a 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java @@ -45,6 +45,8 @@ import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; import static jdk.jpackage.internal.cli.StandardOption.RESOURCE_DIR; import static jdk.jpackage.internal.cli.StandardOption.VENDOR; +import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.Arrays; import java.util.List; @@ -54,6 +56,7 @@ import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.util.RuntimeImageUtils; import jdk.jpackage.internal.util.RuntimeVersionReader; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLaunchers; @@ -176,12 +179,18 @@ final class FromOptions { appBuilder.appImageLayout(runtimeLayout); if (!APP_VERSION.containsIn(options)) { // Version is not specified explicitly. Try to get it from the release file. - RuntimeVersionReader.readVersion(PREDEFINED_RUNTIME_IMAGE.getFrom(options)) - .ifPresent(version -> { - appBuilder.version(version); - Log.verbose(I18N.format("message.release-version", - version, PREDEFINED_RUNTIME_IMAGE.getFrom(options))); - }); + final Path releaseFile = RuntimeImageUtils.getReleaseFilePath( + PREDEFINED_RUNTIME_IMAGE.getFrom(options)); + try { + RuntimeVersionReader.readVersion(releaseFile) + .ifPresent(version -> { + appBuilder.version(version); + Log.verbose(I18N.format("message.release-version", + version, PREDEFINED_RUNTIME_IMAGE.getFrom(options))); + }); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } } } else { appBuilder.appImageLayout(appLayout); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ModuleInfo.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ModuleInfo.java index 590321d5e86..65d6d4e22c2 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ModuleInfo.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ModuleInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,7 +35,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Properties; -import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.util.RuntimeImageUtils; record ModuleInfo(String name, Optional version, Optional mainClass, Optional location) { @@ -61,18 +61,7 @@ record ModuleInfo(String name, Optional version, Optional mainCl // is linked in the runtime by simply analysing the data // of `release` file. - final Path releaseFile; - if (!OperatingSystem.isMacOS()) { - releaseFile = cookedRuntime.resolve("release"); - } else { - // On Mac `cookedRuntime` can be runtime root or runtime home. - Path runtimeHome = cookedRuntime.resolve("Contents/Home"); - if (!Files.isDirectory(runtimeHome)) { - runtimeHome = cookedRuntime; - } - releaseFile = runtimeHome.resolve("release"); - } - + final Path releaseFile = RuntimeImageUtils.getReleaseFilePath(cookedRuntime); try (Reader reader = Files.newBufferedReader(releaseFile)) { Properties props = new Properties(); props.load(reader); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/DottedVersion.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/DottedVersion.java index b49cbb025e9..3f869c7e4e6 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/DottedVersion.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/DottedVersion.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -224,6 +224,16 @@ public final class DottedVersion { return Stream.of(components).map(BigInteger::toString).collect(Collectors.joining(".")); } + public String toComponentsStringWithPadding(int numberOfComponents) { + if (components.length >= numberOfComponents) { + return Stream.of(components).map(BigInteger::toString) + .limit(numberOfComponents).collect(Collectors.joining(".")); + } else { + return toComponentsString() + .concat(".0".repeat(numberOfComponents - components.length)); + } + } + public BigInteger[] getComponents() { return components; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RuntimeImageUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RuntimeImageUtils.java new file mode 100644 index 00000000000..00aa1187b80 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RuntimeImageUtils.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.internal.util; + +import java.nio.file.Files; +import java.nio.file.Path; +import jdk.internal.util.OperatingSystem; + +public final class RuntimeImageUtils { + + public static Path getReleaseFilePath(Path runtimePath) { + final Path releaseFile; + if (!OperatingSystem.isMacOS()) { + releaseFile = runtimePath.resolve("release"); + } else { + // On Mac `runtimePath` can be runtime root or runtime home. + Path runtimeHome = runtimePath.resolve("Contents/Home"); + if (!Files.isDirectory(runtimeHome)) { + runtimeHome = runtimePath; + } + releaseFile = runtimeHome.resolve("release"); + } + + return releaseFile; + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RuntimeVersionReader.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RuntimeVersionReader.java index bd81d47e75f..310fd9f4685 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RuntimeVersionReader.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RuntimeVersionReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,53 +32,21 @@ import java.nio.file.Path; import java.util.Optional; import java.util.Properties; -import jdk.jpackage.internal.Log; -import jdk.internal.util.OperatingSystem; - public final class RuntimeVersionReader { - public static Optional readVersion(Path runtimeImage) { - Optional releasePath = getRuntimeReleaseFile(runtimeImage); - Optional releaseVersion = Optional.empty(); - - if (releasePath.isPresent()) { - try (Reader reader = Files.newBufferedReader(releasePath.get())) { - Properties props = new Properties(); - props.load(reader); - String version = props.getProperty("JAVA_VERSION"); - if (version != null) { - version = version.replaceAll("^\"|\"$", ""); - } - releaseVersion = Optional.ofNullable(version); - } catch (IOException ex) { - Log.verbose(ex); - } - }; - - return releaseVersion; - } - - private static Optional checkReleaseFilePath(Path releaseFilePath) { - if (Files.isRegularFile(releaseFilePath)) { - return Optional.of(releaseFilePath); - } else { + public static Optional readVersion(Path releaseFilePath) throws IOException { + if (!Files.isRegularFile(releaseFilePath)) { return Optional.empty(); } - } - private static Optional getRuntimeReleaseFile(Path runtimeImage) { - Optional releasePath = Optional.empty(); - - // Try special case for macOS first ("Contents/Home/release"). - if (OperatingSystem.isMacOS()) { - releasePath = checkReleaseFilePath(runtimeImage.resolve("Contents/Home/release")); + try (Reader reader = Files.newBufferedReader(releaseFilePath)) { + Properties props = new Properties(); + props.load(reader); + String version = props.getProperty("JAVA_VERSION"); + if (version != null) { + version = version.replaceAll("^\"|\"$", ""); + } + return Optional.ofNullable(version); } - - // Try root for all platforms including macOS if releasePath is not set - if (releasePath.isEmpty()) { - releasePath = checkReleaseFilePath(runtimeImage.resolve("release")); - } - - return releasePath; } } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromOptions.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromOptions.java index 4c16cf35369..ed4dead02e8 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromOptions.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromOptions.java @@ -43,10 +43,13 @@ import static jdk.jpackage.internal.cli.StandardOption.WIN_UPDATE_URL; import static jdk.jpackage.internal.cli.StandardOption.WIN_UPGRADE_UUID; import static jdk.jpackage.internal.model.StandardPackageType.WIN_MSI; +import java.math.BigInteger; import java.util.Arrays; +import java.util.function.UnaryOperator; import jdk.jpackage.internal.cli.Options; import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.DottedVersion; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.model.WinApplication; import jdk.jpackage.internal.model.WinExePackage; @@ -83,24 +86,10 @@ final class WinFromOptions { if (isRuntimeInstaller(options) && !APP_VERSION.containsIn(options)) { // User didn't explicitly specify the version on the command line. jpackage derived it from the input. // In this case it should ensure the derived value is valid Windows version. - var ver = normalizeVersion(app.version()); - if (!ver.equals(app.version())) { - Log.verbose(I18N.format("message.version-normalized", - ver, app.version())); - } - - app = new Application.Stub( - app.name(), - app.description(), - ver, - app.vendor(), - app.copyright(), - app.srcDir(), - app.contentDirs(), - app.imageLayout(), - app.runtimeBuilder(), - app.launchers(), - app.extraAppImageFileData()); + UnaryOperator versionNormalizer = version -> { + return normalizeVersion(version); + }; + app = ApplicationBuilder.normalizeVersion(app, app.version(), versionNormalizer); } return WinApplication.create(app); @@ -147,14 +136,11 @@ final class WinFromOptions { // Windows requires 2 or 4 components version string. // When reading from release file it can be 1 or 3 or maybe more. // We always normalize to 4 components. - String[] components = version.split("\\."); - if (components.length == 1) { - return version.concat(".0.0.0"); - } else if (components.length == 3) { - return version.concat(".0"); - } else if (components.length >= 5) { - components = version.split("\\.", 5); - return String.join(".", Arrays.copyOf(components, components.length - 1)); + DottedVersion ver = DottedVersion.greedy(version); + BigInteger[] components = ver.getComponents(); + if (components.length == 1 || components.length == 3 || + components.length >= 5) { + return ver.toComponentsStringWithPadding(4); } return version; diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java index d28047a6ef1..e990f2b1597 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java @@ -63,6 +63,7 @@ import java.util.spi.ToolProvider; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import jdk.jpackage.internal.util.RuntimeImageUtils; import jdk.jpackage.internal.util.RuntimeVersionReader; import jdk.jpackage.internal.util.function.ExceptionBox; import jdk.jpackage.internal.util.function.ThrowingConsumer; @@ -248,26 +249,27 @@ public class JPackageCommand extends CommandArguments { } public String version() { - if (isRuntime()) { - String appVersion = getArgumentValue("--app-version"); - if (appVersion != null) { - return appVersion; - } else { - Optional releaseVersion = RuntimeVersionReader - .readVersion(Path.of(getArgumentValue("--runtime-image"))); - if (releaseVersion.isPresent()) { - if (TKit.isWindows()) { - return WindowsHelper.getNormalizedVersion(this, releaseVersion.get()); - } else if (TKit.isOSX()) { - return MacHelper.getNormalizedVersion(this, releaseVersion.get()); - } - - return releaseVersion.get(); + return Optional.ofNullable(getArgumentValue("--app-version")).or(() -> { + if (isRuntime()) { + final Path releaseFile = RuntimeImageUtils.getReleaseFilePath( + Path.of(getArgumentValue("--runtime-image"))); + try { + return RuntimeVersionReader.readVersion(releaseFile).map(releaseVersion -> { + if (TKit.isWindows()) { + return WindowsHelper.getNormalizedVersion(releaseVersion); + } else if (TKit.isOSX()) { + return MacHelper.getNormalizedVersion(releaseVersion); + } else { + return releaseVersion; + } + }); + } catch (IOException ex) { + throw new UncheckedIOException(ex); } + } else { + return Optional.empty(); } - } - - return getArgumentValue("--app-version", () -> "1.0"); + }).orElse("1.0"); } public String name() { @@ -315,10 +317,6 @@ public class JPackageCommand extends CommandArguments { } public JPackageCommand setFakeRuntime() { - return setFakeRuntime(Optional.empty()); - } - - public JPackageCommand setFakeRuntime(Optional version) { verifyMutable(); ThrowingConsumer createBulkFile = path -> { @@ -347,19 +345,6 @@ public class JPackageCommand extends CommandArguments { // an error by PackageTest. createBulkFile.accept(fakeRuntimeDir.resolve(Path.of("lib", "bulk"))); - // Create release file with version if provided - version.ifPresent(ver -> { - Properties props = new Properties(); - props.setProperty("JAVA_VERSION", ver); - try (Writer writer = Files.newBufferedWriter(fakeRuntimeDir.resolve("release"))) { - props.store(writer, null); - } catch (IOException ex) { - TKit.trace(String.format( - "Failed to create [%s] file: %s", - fakeRuntimeDir.resolve("release"), ex)); - } - }); - cmd.setArgumentValue("--runtime-image", fakeRuntimeDir); }); diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java index 1cb5129456a..b2020bab67c 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java @@ -42,6 +42,7 @@ import java.io.UncheckedIOException; import java.lang.constant.ClassDesc; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.LinkOption; @@ -72,6 +73,7 @@ import jdk.jpackage.internal.util.PListReader; import jdk.jpackage.internal.util.PathUtils; import jdk.jpackage.internal.util.RetryExecutor; import jdk.jpackage.internal.util.XmlUtils; +import jdk.jpackage.internal.model.DottedVersion; import jdk.jpackage.internal.util.function.ThrowingConsumer; import jdk.jpackage.internal.util.function.ThrowingSupplier; import jdk.jpackage.test.MacSign.CertificateRequest; @@ -761,14 +763,13 @@ public final class MacHelper { cmd.packageType().getSuffix()); } - static String getNormalizedVersion(JPackageCommand cmd, String version) { - cmd.verifyIsOfType(PackageType.MAC); + static String getNormalizedVersion(String version) { // macOS requires 1, 2 or 3 components version string. // We always normalize to 3 components. - String[] components = version.split("\\."); + DottedVersion ver = DottedVersion.greedy(version); + BigInteger[] components = ver.getComponents(); if (components.length >= 4) { - components = version.split("\\.", 4); - return String.join(".", Arrays.copyOf(components, components.length - 1)); + return ver.toComponentsStringWithPadding(3); } return version; diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java index 8f27645e042..b8231a67c63 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.lang.ref.SoftReference; import java.lang.reflect.Method; +import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; @@ -44,6 +45,7 @@ import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; +import jdk.jpackage.internal.model.DottedVersion; import jdk.jpackage.internal.util.function.ThrowingRunnable; import jdk.jpackage.test.PackageTest.PackageHandlers; @@ -55,18 +57,14 @@ public class WindowsHelper { cmd.packageType().getSuffix()); } - static String getNormalizedVersion(JPackageCommand cmd, String version) { - cmd.verifyIsOfType(PackageType.WINDOWS); + static String getNormalizedVersion(String version) { // Windows requires 2 or 4 components version string. // We always normalize to 4 components. - String[] components = version.split("\\."); - if (components.length == 1) { - return version.concat(".0.0.0"); - } else if (components.length == 3) { - return version.concat(".0"); - } else if (components.length >= 5) { - components = version.split("\\.", 5); - return String.join(".", Arrays.copyOf(components, components.length - 1)); + DottedVersion ver = DottedVersion.greedy(version); + BigInteger[] components = ver.getComponents(); + if (components.length == 1 || components.length == 3 || + components.length >= 5) { + return ver.toComponentsStringWithPadding(4); } return version; diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/model/DottedVersionTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/model/DottedVersionTest.java index 885ea31f726..68100b8b3d3 100644 --- a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/model/DottedVersionTest.java +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/model/DottedVersionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,26 +41,30 @@ public class DottedVersionTest { public record TestConfig(String input, Function createVersion, String expectedSuffix, - int expectedComponentCount, String expectedToComponent) { + int expectedComponentCount, String expectedToComponent, int numberOfComponents) { TestConfig(String input, Type type, int expectedComponentCount, String expectedToComponent) { - this(input, type.createVersion, "", expectedComponentCount, expectedToComponent); + this(input, type.createVersion, "", expectedComponentCount, expectedToComponent, -1); } TestConfig(String input, Type type, int expectedComponentCount) { - this(input, type.createVersion, "", expectedComponentCount, input); + this(input, type.createVersion, "", expectedComponentCount, input, -1); + } + + TestConfig(String input, Type type, String expectedToComponent, int numberOfComponents) { + this(input, type.createVersion, "", -1, expectedToComponent, numberOfComponents); } static TestConfig greedy(String input, int expectedComponentCount, String expectedToComponent) { - return new TestConfig(input, Type.GREEDY.createVersion, "", expectedComponentCount, expectedToComponent); + return new TestConfig(input, Type.GREEDY.createVersion, "", expectedComponentCount, expectedToComponent, -1); } static TestConfig greedy(String input, int expectedComponentCount) { - return new TestConfig(input, Type.GREEDY.createVersion, "", expectedComponentCount, input); + return new TestConfig(input, Type.GREEDY.createVersion, "", expectedComponentCount, input, -1); } static TestConfig lazy(String input, String expectedSuffix, int expectedComponentCount, String expectedToComponent) { - return new TestConfig(input, Type.LAZY.createVersion, expectedSuffix, expectedComponentCount, expectedToComponent); + return new TestConfig(input, Type.LAZY.createVersion, expectedSuffix, expectedComponentCount, expectedToComponent, -1); } } @@ -114,6 +118,29 @@ public class DottedVersionTest { return data; } + @ParameterizedTest + @MethodSource + public void testComponentsStringWithPadding(TestConfig cfg) { + var dv = cfg.createVersion.apply(cfg.input()); + assertEquals(cfg.expectedToComponent(), + dv.toComponentsStringWithPadding(cfg.numberOfComponents())); + } + + private static List testComponentsStringWithPadding() { + List data = new ArrayList<>(); + for (var type : Type.values()) { + data.addAll(List.of( + new TestConfig("1", type, "1.0.0.0", 4), + new TestConfig("1.2", type, "1.2.0.0", 4), + new TestConfig("1.2.3", type, "1.2.3.0", 4), + new TestConfig("1.2.3.4", type, "1.2.3.4", 4), + new TestConfig("1.2.3.4.5", type, "1.2.3.4", 4) + )); + } + + return data; + } + record InvalidVersionTestSpec(String version, String invalidComponent) { public InvalidVersionTestSpec { Objects.requireNonNull(version); diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/RuntimeVersionReaderTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/RuntimeVersionReaderTest.java new file mode 100644 index 00000000000..4b207163c25 --- /dev/null +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/RuntimeVersionReaderTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.internal.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.Properties; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class RuntimeVersionReaderTest { + + @Test + public void test_release_file_with_version(@TempDir Path workdir) { + final Optional version; + try { + version = RuntimeVersionReader.readVersion( + createPropFileWithValue(workdir, "JAVA_VERSION", "27.1.2")); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + assertTrue(version.isPresent()); + version.ifPresent(ver -> { + assertEquals("27.1.2", version.get()); + }); + } + + @Test + public void test_release_file_without_version(@TempDir Path workdir) { + final Optional version; + try { + version = RuntimeVersionReader.readVersion( + createPropFileWithValue(workdir, "JDK_VERSION", "27.1.2")); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + assertFalse(version.isPresent()); + } + + @Test + public void test_release_file_invalid_input(@TempDir Path workdir) { + final Optional version; + try { + version = RuntimeVersionReader.readVersion(workdir); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + assertFalse(version.isPresent()); + } + + private Path createPropFileWithValue(Path workdir, String name, String value) + throws IOException { + Path releaseFile = workdir.resolve("release"); + Properties props = new Properties(); + props.setProperty(name, "\"" + value + "\""); + try (Writer writer = Files.newBufferedWriter(releaseFile)) { + props.store(writer, null); + } + + return releaseFile; + } + + +} diff --git a/test/jdk/tools/jpackage/share/RuntimePackageTest.java b/test/jdk/tools/jpackage/share/RuntimePackageTest.java index e8a9f926a82..57e07757f53 100644 --- a/test/jdk/tools/jpackage/share/RuntimePackageTest.java +++ b/test/jdk/tools/jpackage/share/RuntimePackageTest.java @@ -23,17 +23,22 @@ import static jdk.internal.util.OperatingSystem.LINUX; import static jdk.internal.util.OperatingSystem.MACOS; +import static jdk.internal.util.OperatingSystem.WINDOWS; import static jdk.jpackage.test.TKit.assertFalse; import static jdk.jpackage.test.TKit.assertTrue; +import java.io.IOException; +import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; import java.util.Optional; +import java.util.Properties; import java.util.function.Predicate; import java.util.function.Supplier; import jdk.jpackage.test.Annotations.Parameter; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.JPackageStringBundle; import jdk.jpackage.test.LinuxHelper; import jdk.jpackage.test.MacHelper; import jdk.jpackage.test.PackageTest; @@ -79,15 +84,15 @@ import jdk.jpackage.test.TKit; */ public class RuntimePackageTest { - @Test - public static void test() { - init().run(); - } + // @Test + // public static void test() { + // init().run(); + // } - @Test(ifOS = MACOS) - public static void testFromBundle() { - init(MacHelper::createRuntimeBundle).run(); - } + // @Test(ifOS = MACOS) + // public static void testFromBundle() { + // init(MacHelper::createRuntimeBundle).run(); + // } @Test(ifOS = LINUX) @Parameter("/usr") @@ -98,27 +103,66 @@ public class RuntimePackageTest { .run(); } - @Test - public static void testName() { - // Test that jpackage can derive package name from the path to runtime image. - init() - .addInitializer(cmd -> cmd.removeArgumentWithValue("--name")) - // Don't attempt to install this package as it may have an odd name derived from - // the runtime image path. Say, on Linux for `--runtime-image foo/bar/sed` - // command line jpackage will create a package named 'sed' that will conflict - // with the default 'sed' package. - .run(Action.CREATE_AND_UNPACK); - } + // @Test + // public static void testName() { + // // Test that jpackage can derive package name from the path to runtime image. + // init() + // .addInitializer(cmd -> cmd.removeArgumentWithValue("--name")) + // // Don't attempt to install this package as it may have an odd name derived from + // // the runtime image path. Say, on Linux for `--runtime-image foo/bar/sed` + // // command line jpackage will create a package named 'sed' that will conflict + // // with the default 'sed' package. + // .run(Action.CREATE_AND_UNPACK); + // } @Test - @Parameter("27") - @Parameter("27.1") - @Parameter("27.1.2") - @Parameter("27.1.2.3") - @Parameter("27.1.2.3.4") - public static void testReleaseFileVersion(String version) { - init() - .addInitializer(cmd -> cmd.setFakeRuntime(Optional.of(version))) + // 27 + @Parameter(value = {"27", ""}, ifOS = {LINUX, MACOS}) + @Parameter(value = {"27", "27.0.0.0"}, ifOS = WINDOWS) + // 27.1 + @Parameter(value = {"27.1", ""}) + // 27.1.2 + @Parameter(value = {"27.1.2", ""}, ifOS = {LINUX, MACOS}) + @Parameter(value = {"27.1.2", "27.1.2.0"}, ifOS = WINDOWS) + // 27.1.2.3 + @Parameter(value = {"27.1.2.3", ""}, ifOS = {LINUX, WINDOWS}) + @Parameter(value = {"27.1.2.3", "27.1.2"}, ifOS = MACOS) + // 27.1.2.3.4 + @Parameter(value = {"27.1.2.3.4", ""}, ifOS = LINUX) + @Parameter(value = {"27.1.2.3.4", "27.1.2"}, ifOS = MACOS) + @Parameter(value = {"27.1.2.3.4", "27.1.2.3"}, ifOS = WINDOWS) + public static void testReleaseFileVersion(String version, String normalizedVersion) { + new PackageTest() + .addInitializer(cmd -> { + // Remove --input parameter from jpackage command line as we don't + // create input directory in the test and jpackage fails + // if --input references non existant directory. + cmd.removeArgumentWithValue("--input"); + + cmd.setFakeRuntime(); + + // Execute prerequisite actions, so fake runtime gets created + cmd.executePrerequisiteActions(); + + // Create release file with version in fake runtime + Path runtimeImage = Path.of(cmd.getArgumentValue("--runtime-image")); + Path releaseFile = runtimeImage.resolve("release"); + Properties props = new Properties(); + props.setProperty("JAVA_VERSION", "\"" + version + "\""); + try (Writer writer = Files.newBufferedWriter(releaseFile)) { + props.store(writer, null); + } + + // Validate output + cmd.validateOutput(JPackageStringBundle.MAIN + .cannedFormattedString("message.release-version", + version, runtimeImage.toString())); + if (!normalizedVersion.isEmpty()) { + cmd.validateOutput(JPackageStringBundle.MAIN + .cannedFormattedString("message.version-normalized", + normalizedVersion, version)); + } + }) // Just create package. It is enough to verify version in bundle name. .run(Action.CREATE); }