Skip to content

8351073: [macos] jpackage produces invalid Java runtime DMG bundles #26173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

sashamatveev
Copy link
Member

@sashamatveev sashamatveev commented Jul 7, 2025

Re-submission of #25314 after merge conflict was resolved. Old PR will be closed.

All comments are addressed from old PR. Merge conflict was significant, so it is like new fix.

Fixed jpackage to produce valid Java runtimes based on description below:

Definitions:

  • JDK bundle defined as bundle which contains "Contents/Home", "Contents/MacOS/libjli.dylib" and "Contents/Info.plist".
  • Signed JDK bundle contains all files as JDK bundle + "Contents/_CodeSignature".
  • JDK image defined as content of "Contents/Home".
  • Signed JDK image does not exist, since it cannot be signed as bundle.

jpackage output based on input:

  1. "--runtime-image" points to unsigned JDK bundle and --mac-sign is not provided:
  • jpackage will copy all files as is from provided path and run ad-hoc codesign.
  1. "--runtime-image" points to unsigned JDK bundle and --mac-sign is provided:
  • jpackage will copy all files as is from provided path and run codesign with appropriate certificate based on same logic as we do for application image.
  1. "--runtime-image" points to signed JDK bundle and --mac-sign is not provided:
  • jpackage will copy all files as is from provided path including "Contents/_CodeSignature" to preserve signing.
  1. "--runtime-image" points to signed JDK bundle and --mac-sign is provided:
  • jpackage will copy all files as is from provided path including "Contents/_CodeSignature" and will re-sign bundle with appropriate certificate.
  1. "--runtime-image" points to JDK image and --mac-sign is not provided:
  • jpackage will check for libjli.dylib presence in "lib" folder.
  • Create JDK bundle by putting all files from provided path to "Contents/Home", libjli.dylib from "lib" to "Contents/MacOS/libjli.dylib" and create default "Contents/Info.plist" similar to what we do for runtime in application image.
  • Ad-hoc signing will done.
  1. "--runtime-image" points to JDK image and --mac-sign is provided:
  • 2 first steps from 5 and certificate signing will be done.

Additional changes:

  • The bundle's top directory name will have the ".jdk" suffix.

Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8351073: [macos] jpackage produces invalid Java runtime DMG bundles (Bug - P4)

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/26173/head:pull/26173
$ git checkout pull/26173

Update a local copy of the PR:
$ git checkout pull/26173
$ git pull https://git.openjdk.org/jdk.git pull/26173/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 26173

View PR using the GUI difftool:
$ git pr show -t 26173

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/26173.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jul 7, 2025

👋 Welcome back almatvee! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Jul 7, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot added the rfr Pull request is ready for review label Jul 7, 2025
@openjdk
Copy link

openjdk bot commented Jul 7, 2025

@sashamatveev The following label will be automatically applied to this pull request:

  • core-libs

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added the core-libs core-libs-dev@openjdk.org label Jul 7, 2025
@mlbridge
Copy link

mlbridge bot commented Jul 7, 2025

Webrevs

@mlbridge
Copy link

mlbridge bot commented Jul 7, 2025

Mailing list message from Michael Hall on core-libs-dev:

What is the .jdk extension preceded by?

A normal system jdk is like jdk-23.jdk

Maybe something like jpkg-23.jdk to indicate the source of the jre image is jpakcage? Also ensuring it doesn?t conflict with a normally distributed one.

On Jul 7, 2025, at 5:23?PM, Alexander Matveev <almatvee at openjdk.org> wrote:

Additional changes:
- The bundle's top directory name will have the ".jdk" suffix.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/core-libs-dev/attachments/20250707/6114d1a6/attachment-0001.htm>

@sashamatveev
Copy link
Member Author

What is the .jdk extension preceded by?
{NAME}-{VERSION}.jdk, where {NAME} is value of --name and {VERSION} is value of --version.

Comment on lines -37 to +38
return RUNTIME_PACKAGE_LAYOUT;
return RuntimeLayout.DEFAULT;

Choose a reason for hiding this comment

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

Why is this change? It looks wrong. The layout of the output bundle should have the "Contents" folder. This is how it was before the change.

Comment on lines +91 to +101
/**
* Gets the source directory of this application if available or an empty
* {@link Optional} instance.
* <p>
* Source directory is a directory with the applications's classes and other
* resources.
*
* @return the source directory of this application
*/
Optional<Path> runtimeImage();

Choose a reason for hiding this comment

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

What is "the source directory of this application"?
Why is the method that returns a path to the source directory called runtimeImage()?

// External runtime image should be R/O unless it is runtime installer
// on macOS. On macOS runtime image will be signed ad-hoc or with
// real certificate when creating runtime installers.
return !(cmd.isRuntime() && TKit.isOSX());
Copy link
Member

@alexeysemenyukoracle alexeysemenyukoracle Jul 7, 2025

Choose a reason for hiding this comment

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

I guess the directory referenced from the --runtime-image option will not be signed if it is a JDK image, not a JDK bundle. So, this switch turns off verification in cases when it should be done.

My interpretation of the PR description is that if the -runtime-image option references a JDK bundle, its contents will be copied, and the copy will be signed optionally. This makes this switch redundant.

// Make sure we have valid runtime image.
if (!isRuntimeImageJDKBundle(runtimeImage)
&& !isRuntimeImageJDKImage(runtimeImage)) {
throw new ConfigException(
Copy link
Member

@alexeysemenyukoracle alexeysemenyukoracle Jul 8, 2025

Choose a reason for hiding this comment

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

  1. Can be simplified:
throw new ConfigException(
                    I18N.format("message.runtime-image-invalid", runtimeImage),
                    I18N.getString("message.runtime-image-invalid.advice"));
  1. This validation should be a part of MacPackageBuilder.create().

.get();
return IOUtils.exists(jli);
} catch (IOException | NoSuchElementException ex) {
Log.verbose(ex);

Choose a reason for hiding this comment

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

So if jpackage doesn't know if the given path is a JDK image or a JDK bundle, it will proceed with packaging anyway. This doesn't look right. In general, most, if not all, of the existing constructions like:

} catch (Exception ex) {
    Log.verbose(ex);
    ...
}

are wrong. Let's not add new ones.

private String validatedBundleIdentifier() throws ConfigException {
public String validatedBundleIdentifier() throws ConfigException {

Choose a reason for hiding this comment

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

This method is private on purpose. It should not be used outside of the MacApplicationBuilder class. If you need to get the valid bundle identifier, create MacApplication instance and call MacApplication.bundleIdentifier() on it.

Comment on lines +48 to +76
default boolean isRuntimeImageJDKImage() {
if (isRuntimeInstaller()) {
Path runtimeImage = app().runtimeImage().orElseThrow();
Path p = runtimeImage.resolve("Contents/Home");
return !Files.exists(p);
}

return false;
}

// Returns true if signing is requested or JDK bundle is not signed
// or JDK image is provided.
default boolean isRuntimeJDKBundleNeedSigning() {
if (!isRuntimeInstaller()) {
return false;
}

if (app().sign()) {
return true;
}

if (isRuntimeImageJDKImage()) {
return true;
} else {
Path runtimeImage = app().runtimeImage().orElseThrow();
Path p = runtimeImage.resolve("Contents/_CodeSignature");
return !Files.exists(p);
}
}

Choose a reason for hiding this comment

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

Default interface methods should operate on values returned by other interface methods. These new functions involve filesystem operations. They probably don't belong to the interface.

* jpackagerTest keychain with
* always allowed access to this keychain for user which runs test.
* note:
* "jpackage.openjdk.java.net" can be over-ridden by systerm property

Choose a reason for hiding this comment

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

systerm -> system

);

MacHelper.withExplodedDmg(dummyCMD, dmgImage -> {
if (dmgImage.endsWith(dummyCMD.name() + ".jdk")) {

Choose a reason for hiding this comment

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

Can this test ever return false?


MacHelper.withExplodedDmg(dummyCMD, dmgImage -> {
if (dmgImage.endsWith(dummyCMD.name() + ".jdk")) {
Executor.of("cp", "-R")

Choose a reason for hiding this comment

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

Why noit use FileUtils.copyRecursive()?

public static void test(boolean useJDKBundle,
SigningBase.CertIndex JDKBundleCert,
SigningBase.CertIndex signCert) throws Exception {
final int JDKBundleCertIndex = JDKBundleCert.value();

Choose a reason for hiding this comment

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

JDKBundleCertIndex -> jdkBundleCertIndex?

(signCertIndex != SigningBase.CertIndex.INVALID_INDEX.value());

new PackageTest()
.forTypes(PackageType.MAC)

Choose a reason for hiding this comment

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

.forTypes(PackageType.MAC) is redundant. The test will only be executed on macOS

}
}

private static Path getRuntimeImagePath(boolean useJDKBundle,
Copy link
Member

@alexeysemenyukoracle alexeysemenyukoracle Jul 8, 2025

Choose a reason for hiding this comment

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

The name of the function is misleading. It doesn't get the runtime image. It creates it.

It returns a path to a JDK bundle or a JDK image depending on its arguments. So the "RuntimeImagePath" part of its name is also misleading.


ex.execute();

JPackageCommand dummyCMD = new JPackageCommand();

Choose a reason for hiding this comment

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

Why do you need a dummyCMD? You can configure a normal JPackageCommand command:

var cmd = new JPackageCommand().useToolProvider(true).dumpOutput(true).addArguments(...);

testSpec().noAppDesc().nativeType()
.addArgs("--runtime-image", Token.INVALID_MAC_JDK_BUNDLE.token())
.error("message.runtime-image-invalid", JPackageCommand.cannedArgument(cmd -> {
return Path.of(cmd.getArgumentValue("--runtime-image")).toAbsolutePath();

Choose a reason for hiding this comment

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

Why do we need an absolute path in error messages? Wouldn't it make more sense to have the value of --runtime-image parameter as is in error messages?

INVALID_MAC_JDK_BUNDLE(cmd -> {
// Missing "Contents/MacOS/libjli.dylib"
try {
final Path runtimePath = TKit.createTempDirectory("invalidJDKBundle");

Choose a reason for hiding this comment

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

The name "invalid-jdk-bundle" fits better in dash-case naming scheme for path names in jpackage tests.

<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>

Choose a reason for hiding this comment

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

I'm surprised there is no standard way to override this default value. Is it not important?

Choose a reason for hiding this comment

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

What is the origin of this file?

@@ -308,9 +363,16 @@ private static void writeAppInfoPlist(
.saveToFile(infoPlistFile);
}

private static void sign(AppImageBuildEnv<MacApplication, MacApplicationLayout> env) throws IOException {

Choose a reason for hiding this comment

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

I don't understand why individual signing functions are needed for the runtime and application bundles. They look identical to me.

@@ -271,6 +310,22 @@ private static void writeRuntimeInfoPlist(MacApplication app, BuildEnv env, Path
.saveToFile(runtimeRootDirectory.resolve("Contents/Info.plist"));
}

private static void writeRuntimeBundleInfoPlist(MacApplication app, BuildEnv env, Path runtimeRootDirectory) throws IOException {

Choose a reason for hiding this comment

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

This function is almost a duplicate of writeRuntimeInfoPlist(). They can be merged together with branching:

if (app.asApplicationLayout().isPresent()) {
    // This is application bundle
} else {
    // This is runtime bundle
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core-libs core-libs-dev@openjdk.org rfr Pull request is ready for review
Development

Successfully merging this pull request may close these issues.

2 participants