mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
8375054: Removed "signed" property from jpackage app image file
Reviewed-by: almatvee
This commit is contained in:
parent
a90c7eee6f
commit
f7be1dcf29
@ -47,6 +47,7 @@ import jdk.jpackage.internal.model.ApplicationLayout;
|
|||||||
import jdk.jpackage.internal.model.Launcher;
|
import jdk.jpackage.internal.model.Launcher;
|
||||||
import jdk.jpackage.internal.model.MacApplication;
|
import jdk.jpackage.internal.model.MacApplication;
|
||||||
import jdk.jpackage.internal.model.RuntimeLayout;
|
import jdk.jpackage.internal.model.RuntimeLayout;
|
||||||
|
import jdk.jpackage.internal.util.MacBundle;
|
||||||
import jdk.jpackage.internal.util.PathUtils;
|
import jdk.jpackage.internal.util.PathUtils;
|
||||||
import jdk.jpackage.internal.util.Result;
|
import jdk.jpackage.internal.util.Result;
|
||||||
import jdk.jpackage.internal.util.function.ExceptionBox;
|
import jdk.jpackage.internal.util.function.ExceptionBox;
|
||||||
|
|||||||
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -30,8 +30,8 @@ import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT;
|
|||||||
import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasJliLib;
|
import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasJliLib;
|
||||||
import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasNoBinDir;
|
import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasNoBinDir;
|
||||||
import static jdk.jpackage.internal.cli.StandardBundlingOperation.SIGN_MAC_APP_IMAGE;
|
import static jdk.jpackage.internal.cli.StandardBundlingOperation.SIGN_MAC_APP_IMAGE;
|
||||||
import static jdk.jpackage.internal.cli.StandardOption.ICON;
|
|
||||||
import static jdk.jpackage.internal.cli.StandardOption.APPCLASS;
|
import static jdk.jpackage.internal.cli.StandardOption.APPCLASS;
|
||||||
|
import static jdk.jpackage.internal.cli.StandardOption.ICON;
|
||||||
import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_CATEGORY;
|
import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_CATEGORY;
|
||||||
import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_IMAGE_SIGN_IDENTITY;
|
import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_IMAGE_SIGN_IDENTITY;
|
||||||
import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_STORE;
|
import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_STORE;
|
||||||
@ -52,11 +52,13 @@ import static jdk.jpackage.internal.model.StandardPackageType.MAC_PKG;
|
|||||||
import static jdk.jpackage.internal.util.function.ExceptionBox.toUnchecked;
|
import static jdk.jpackage.internal.util.function.ExceptionBox.toUnchecked;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo;
|
import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo;
|
||||||
import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException;
|
import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException;
|
||||||
import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector;
|
import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector;
|
||||||
|
import jdk.jpackage.internal.cli.OptionValue;
|
||||||
import jdk.jpackage.internal.cli.Options;
|
import jdk.jpackage.internal.cli.Options;
|
||||||
import jdk.jpackage.internal.cli.StandardFaOption;
|
import jdk.jpackage.internal.cli.StandardFaOption;
|
||||||
import jdk.jpackage.internal.model.ApplicationLaunchers;
|
import jdk.jpackage.internal.model.ApplicationLaunchers;
|
||||||
@ -71,6 +73,7 @@ import jdk.jpackage.internal.model.MacPackage;
|
|||||||
import jdk.jpackage.internal.model.MacPkgPackage;
|
import jdk.jpackage.internal.model.MacPkgPackage;
|
||||||
import jdk.jpackage.internal.model.PackageType;
|
import jdk.jpackage.internal.model.PackageType;
|
||||||
import jdk.jpackage.internal.model.RuntimeLayout;
|
import jdk.jpackage.internal.model.RuntimeLayout;
|
||||||
|
import jdk.jpackage.internal.util.MacBundle;
|
||||||
import jdk.jpackage.internal.util.Result;
|
import jdk.jpackage.internal.util.Result;
|
||||||
import jdk.jpackage.internal.util.function.ExceptionBox;
|
import jdk.jpackage.internal.util.function.ExceptionBox;
|
||||||
|
|
||||||
@ -276,16 +279,12 @@ final class MacFromOptions {
|
|||||||
|
|
||||||
final var builder = new MacPackageBuilder(createPackageBuilder(options, app.app(), type));
|
final var builder = new MacPackageBuilder(createPackageBuilder(options, app.app(), type));
|
||||||
|
|
||||||
app.externalApp()
|
for (OptionValue<Path> ov : List.of(PREDEFINED_APP_IMAGE, PREDEFINED_RUNTIME_IMAGE)) {
|
||||||
.map(ExternalApplication::extra)
|
ov.findIn(options)
|
||||||
.flatMap(MAC_SIGN::findIn)
|
.flatMap(MacBundle::fromPath)
|
||||||
.ifPresent(builder::predefinedAppImageSigned);
|
.map(MacPackagingPipeline::isSigned)
|
||||||
|
.ifPresent(builder::predefinedAppImageSigned);
|
||||||
PREDEFINED_RUNTIME_IMAGE.findIn(options)
|
}
|
||||||
.map(MacBundle::new)
|
|
||||||
.filter(MacBundle::isValid)
|
|
||||||
.map(MacBundle::isSigned)
|
|
||||||
.ifPresent(builder::predefinedAppImageSigned);
|
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -76,6 +76,8 @@ import jdk.jpackage.internal.model.MacPackage;
|
|||||||
import jdk.jpackage.internal.model.Package;
|
import jdk.jpackage.internal.model.Package;
|
||||||
import jdk.jpackage.internal.model.PackageType;
|
import jdk.jpackage.internal.model.PackageType;
|
||||||
import jdk.jpackage.internal.util.FileUtils;
|
import jdk.jpackage.internal.util.FileUtils;
|
||||||
|
import jdk.jpackage.internal.util.MacBundle;
|
||||||
|
import jdk.jpackage.internal.util.PListReader;
|
||||||
import jdk.jpackage.internal.util.PathUtils;
|
import jdk.jpackage.internal.util.PathUtils;
|
||||||
import jdk.jpackage.internal.util.function.ThrowingConsumer;
|
import jdk.jpackage.internal.util.function.ThrowingConsumer;
|
||||||
|
|
||||||
@ -178,13 +180,10 @@ final class MacPackagingPipeline {
|
|||||||
builder.task(MacCopyAppImageTaskID.COPY_RUNTIME_JLILIB)
|
builder.task(MacCopyAppImageTaskID.COPY_RUNTIME_JLILIB)
|
||||||
.appImageAction(MacPackagingPipeline::copyJliLib).add();
|
.appImageAction(MacPackagingPipeline::copyJliLib).add();
|
||||||
|
|
||||||
final var predefinedRuntimeBundle = Optional.of(
|
|
||||||
new MacBundle(p.predefinedAppImage().orElseThrow())).filter(MacBundle::isValid);
|
|
||||||
|
|
||||||
// Don't create ".package" file.
|
// Don't create ".package" file.
|
||||||
disabledTasks.add(MacCopyAppImageTaskID.COPY_PACKAGE_FILE);
|
disabledTasks.add(MacCopyAppImageTaskID.COPY_PACKAGE_FILE);
|
||||||
|
|
||||||
if (predefinedRuntimeBundle.isPresent()) {
|
if (MacBundle.fromPath(p.predefinedAppImage().orElseThrow()).isPresent()) {
|
||||||
// The input runtime image is a macOS bundle.
|
// The input runtime image is a macOS bundle.
|
||||||
// Disable all alterations of the input bundle, but keep the signing enabled.
|
// Disable all alterations of the input bundle, but keep the signing enabled.
|
||||||
disabledTasks.addAll(List.of(MacCopyAppImageTaskID.values()));
|
disabledTasks.addAll(List.of(MacCopyAppImageTaskID.values()));
|
||||||
@ -195,7 +194,7 @@ final class MacPackagingPipeline {
|
|||||||
.appImageAction(MacPackagingPipeline::writeRuntimeInfoPlist).add();
|
.appImageAction(MacPackagingPipeline::writeRuntimeInfoPlist).add();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (predefinedRuntimeBundle.map(MacBundle::isSigned).orElse(false) && !((MacPackage)p).app().sign()) {
|
if (((MacPackage)p).predefinedAppImageSigned().orElse(false) && !((MacPackage)p).app().sign()) {
|
||||||
// The input runtime is a signed bundle; explicit signing is not requested for the package.
|
// The input runtime is a signed bundle; explicit signing is not requested for the package.
|
||||||
// Disable the signing, i.e. don't re-sign the input bundle.
|
// Disable the signing, i.e. don't re-sign the input bundle.
|
||||||
disabledTasks.add(MacCopyAppImageTaskID.COPY_SIGN);
|
disabledTasks.add(MacCopyAppImageTaskID.COPY_SIGN);
|
||||||
@ -279,6 +278,30 @@ final class MacPackagingPipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean isSigned(MacBundle bundle) {
|
||||||
|
|
||||||
|
var result = toSupplier(Executor.of(
|
||||||
|
"/usr/sbin/spctl",
|
||||||
|
"-vv",
|
||||||
|
"--raw",
|
||||||
|
"--assess",
|
||||||
|
"--type", "exec",
|
||||||
|
bundle.root().toString()).setQuiet(true).saveOutput(true).binaryOutput()::execute).get();
|
||||||
|
|
||||||
|
switch (result.getExitCode()) {
|
||||||
|
case 0, 3 -> {
|
||||||
|
// These exit codes are accompanied with valid plist xml.
|
||||||
|
return toSupplier(() -> {
|
||||||
|
return new PListReader(result.byteStdout()).findValue("assessment:originator").isPresent();
|
||||||
|
}).get();
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
// Likely to be an "a sealed resource is missing or invalid" error.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void copyAppImage(MacPackage pkg, AppImageLayout srcAppImage,
|
private static void copyAppImage(MacPackage pkg, AppImageLayout srcAppImage,
|
||||||
AppImageLayout dstAppImage) throws IOException {
|
AppImageLayout dstAppImage) throws IOException {
|
||||||
|
|
||||||
@ -286,7 +309,7 @@ final class MacPackagingPipeline {
|
|||||||
|
|
||||||
final Optional<MacBundle> srcMacBundle;
|
final Optional<MacBundle> srcMacBundle;
|
||||||
if (pkg.isRuntimeInstaller()) {
|
if (pkg.isRuntimeInstaller()) {
|
||||||
srcMacBundle = MacBundle.fromAppImageLayout(srcAppImage);
|
srcMacBundle = macBundleFromAppImageLayout(srcAppImage);
|
||||||
} else {
|
} else {
|
||||||
srcMacBundle = Optional.empty();
|
srcMacBundle = Optional.empty();
|
||||||
}
|
}
|
||||||
@ -297,7 +320,7 @@ final class MacPackagingPipeline {
|
|||||||
try {
|
try {
|
||||||
FileUtils.copyRecursive(
|
FileUtils.copyRecursive(
|
||||||
inputBundle.root(),
|
inputBundle.root(),
|
||||||
MacBundle.fromAppImageLayout(dstAppImage).orElseThrow().root(),
|
macBundleFromAppImageLayout(dstAppImage).orElseThrow().root(),
|
||||||
LinkOption.NOFOLLOW_LINKS);
|
LinkOption.NOFOLLOW_LINKS);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new UncheckedIOException(ex);
|
throw new UncheckedIOException(ex);
|
||||||
@ -415,7 +438,7 @@ final class MacPackagingPipeline {
|
|||||||
|
|
||||||
final var app = env.app();
|
final var app = env.app();
|
||||||
|
|
||||||
final var infoPlistFile = MacBundle.fromAppImageLayout(env.resolvedLayout()).orElseThrow().infoPlistFile();
|
final var infoPlistFile = macBundleFromAppImageLayout(env.resolvedLayout()).orElseThrow().infoPlistFile();
|
||||||
|
|
||||||
Log.verbose(I18N.format("message.preparing-info-plist", PathUtils.normalizedAbsolutePathString(infoPlistFile)));
|
Log.verbose(I18N.format("message.preparing-info-plist", PathUtils.normalizedAbsolutePathString(infoPlistFile)));
|
||||||
|
|
||||||
@ -468,7 +491,7 @@ final class MacPackagingPipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Runnable signAction = () -> {
|
final Runnable signAction = () -> {
|
||||||
AppImageSigner.createSigner(app, codesignConfigBuilder.create()).accept(MacBundle.fromAppImageLayout(env.resolvedLayout()).orElseThrow());
|
AppImageSigner.createSigner(app, codesignConfigBuilder.create()).accept(macBundleFromAppImageLayout(env.resolvedLayout()).orElseThrow());
|
||||||
};
|
};
|
||||||
|
|
||||||
app.signingConfig().flatMap(AppImageSigningConfig::keychain).map(Keychain::new).ifPresentOrElse(keychain -> {
|
app.signingConfig().flatMap(AppImageSigningConfig::keychain).map(Keychain::new).ifPresentOrElse(keychain -> {
|
||||||
@ -550,7 +573,7 @@ final class MacPackagingPipeline {
|
|||||||
|
|
||||||
private static MacBundle runtimeBundle(AppImageBuildEnv<MacApplication, AppImageLayout> env) {
|
private static MacBundle runtimeBundle(AppImageBuildEnv<MacApplication, AppImageLayout> env) {
|
||||||
if (env.app().isRuntime()) {
|
if (env.app().isRuntime()) {
|
||||||
return MacBundle.fromAppImageLayout(env.resolvedLayout()).orElseThrow();
|
return macBundleFromAppImageLayout(env.resolvedLayout()).orElseThrow();
|
||||||
} else {
|
} else {
|
||||||
return new MacBundle(((MacApplicationLayout)env.resolvedLayout()).runtimeRootDirectory());
|
return new MacBundle(((MacApplicationLayout)env.resolvedLayout()).runtimeRootDirectory());
|
||||||
}
|
}
|
||||||
@ -595,6 +618,22 @@ final class MacPackagingPipeline {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Optional<MacBundle> macBundleFromAppImageLayout(AppImageLayout layout) {
|
||||||
|
final var root = layout.rootDirectory();
|
||||||
|
final var bundleSubdir = root.relativize(layout.runtimeDirectory());
|
||||||
|
final var contentsDirname = Path.of("Contents");
|
||||||
|
var bundleRoot = root;
|
||||||
|
for (int i = 0; i != bundleSubdir.getNameCount(); i++) {
|
||||||
|
var nameComponent = bundleSubdir.getName(i);
|
||||||
|
if (contentsDirname.equals(nameComponent)) {
|
||||||
|
return Optional.of(new MacBundle(bundleRoot));
|
||||||
|
} else {
|
||||||
|
bundleRoot = bundleRoot.resolve(nameComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private record TaskContextProxy(TaskContext delegate, boolean forApp, boolean copyAppImage) implements TaskContext {
|
private record TaskContextProxy(TaskContext delegate, boolean forApp, boolean copyAppImage) implements TaskContext {
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -28,7 +28,6 @@ import static java.util.stream.Collectors.joining;
|
|||||||
import static java.util.stream.Collectors.toUnmodifiableMap;
|
import static java.util.stream.Collectors.toUnmodifiableMap;
|
||||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_APP_STORE;
|
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_APP_STORE;
|
||||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_MAIN_CLASS;
|
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_MAIN_CLASS;
|
||||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_SIGNED;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -96,9 +95,6 @@ public interface MacApplication extends Application, MacApplicationMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum ExtraAppImageFileField {
|
public enum ExtraAppImageFileField {
|
||||||
SIGNED(MAC_SIGNED, app -> {
|
|
||||||
return Optional.of(Boolean.toString(app.sign()));
|
|
||||||
}),
|
|
||||||
APP_STORE(MAC_APP_STORE, app -> {
|
APP_STORE(MAC_APP_STORE, app -> {
|
||||||
return Optional.of(Boolean.toString(app.appStore()));
|
return Optional.of(Boolean.toString(app.appStore()));
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -73,6 +73,7 @@ final class OptionSpecBuilder<T> {
|
|||||||
valuePattern = other.valuePattern;
|
valuePattern = other.valuePattern;
|
||||||
converterBuilder = other.converterBuilder.copy();
|
converterBuilder = other.converterBuilder.copy();
|
||||||
validatorBuilder = other.validatorBuilder.copy();
|
validatorBuilder = other.validatorBuilder.copy();
|
||||||
|
validator = other.validator;
|
||||||
|
|
||||||
if (other.arrayDefaultValue != null) {
|
if (other.arrayDefaultValue != null) {
|
||||||
arrayDefaultValue = Arrays.copyOf(other.arrayDefaultValue, other.arrayDefaultValue.length);
|
arrayDefaultValue = Arrays.copyOf(other.arrayDefaultValue, other.arrayDefaultValue.length);
|
||||||
@ -135,10 +136,20 @@ final class OptionSpecBuilder<T> {
|
|||||||
scope,
|
scope,
|
||||||
OptionSpecBuilder.this.mergePolicy().orElse(MergePolicy.CONCATENATE),
|
OptionSpecBuilder.this.mergePolicy().orElse(MergePolicy.CONCATENATE),
|
||||||
defaultArrayOptionalValue(),
|
defaultArrayOptionalValue(),
|
||||||
Optional.of(arryValuePattern()),
|
Optional.of(arrayValuePattern()),
|
||||||
OptionSpecBuilder.this.description().orElse(""));
|
OptionSpecBuilder.this.description().orElse(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optional<? extends Validator<T, RuntimeException>> createValidator() {
|
||||||
|
return Optional.ofNullable(validator).or(() -> {
|
||||||
|
if (validatorBuilder.hasValidatingMethod()) {
|
||||||
|
return Optional.of(validatorBuilder.create());
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
OptionSpecBuilder<T> tokenizer(String splitRegexp) {
|
OptionSpecBuilder<T> tokenizer(String splitRegexp) {
|
||||||
Objects.requireNonNull(splitRegexp);
|
Objects.requireNonNull(splitRegexp);
|
||||||
return tokenizer(str -> {
|
return tokenizer(str -> {
|
||||||
@ -162,11 +173,13 @@ final class OptionSpecBuilder<T> {
|
|||||||
|
|
||||||
OptionSpecBuilder<T> validatorExceptionFormatString(String v) {
|
OptionSpecBuilder<T> validatorExceptionFormatString(String v) {
|
||||||
validatorBuilder.formatString(v);
|
validatorBuilder.formatString(v);
|
||||||
|
validator = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionSpecBuilder<T> validatorExceptionFormatString(UnaryOperator<String> mutator) {
|
OptionSpecBuilder<T> validatorExceptionFormatString(UnaryOperator<String> mutator) {
|
||||||
validatorBuilder.formatString(mutator.apply(validatorBuilder.formatString().orElse(null)));
|
validatorBuilder.formatString(mutator.apply(validatorBuilder.formatString().orElse(null)));
|
||||||
|
validator = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +195,7 @@ final class OptionSpecBuilder<T> {
|
|||||||
|
|
||||||
OptionSpecBuilder<T> validatorExceptionFactory(OptionValueExceptionFactory<? extends RuntimeException> v) {
|
OptionSpecBuilder<T> validatorExceptionFactory(OptionValueExceptionFactory<? extends RuntimeException> v) {
|
||||||
validatorBuilder.exceptionFactory(v);
|
validatorBuilder.exceptionFactory(v);
|
||||||
|
validator = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,18 +239,27 @@ final class OptionSpecBuilder<T> {
|
|||||||
|
|
||||||
OptionSpecBuilder<T> validator(Predicate<T> v) {
|
OptionSpecBuilder<T> validator(Predicate<T> v) {
|
||||||
validatorBuilder.predicate(v::test);
|
validatorBuilder.predicate(v::test);
|
||||||
|
validator = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("overloads")
|
@SuppressWarnings("overloads")
|
||||||
OptionSpecBuilder<T> validator(Consumer<T> v) {
|
OptionSpecBuilder<T> validator(Consumer<T> v) {
|
||||||
validatorBuilder.consumer(v::accept);
|
validatorBuilder.consumer(v::accept);
|
||||||
|
validator = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("overloads")
|
@SuppressWarnings("overloads")
|
||||||
OptionSpecBuilder<T> validator(UnaryOperator<Validator.Builder<T, RuntimeException>> mutator) {
|
OptionSpecBuilder<T> validator(UnaryOperator<Validator.Builder<T, RuntimeException>> mutator) {
|
||||||
validatorBuilder = mutator.apply(validatorBuilder);
|
validatorBuilder = mutator.apply(validatorBuilder);
|
||||||
|
validator = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionSpecBuilder<T> validator(Validator<T, RuntimeException> v) {
|
||||||
|
validatorBuilder.predicate(null).consumer(null);
|
||||||
|
validator = Objects.requireNonNull(v);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,6 +270,7 @@ final class OptionSpecBuilder<T> {
|
|||||||
|
|
||||||
OptionSpecBuilder<T> withoutValidator() {
|
OptionSpecBuilder<T> withoutValidator() {
|
||||||
validatorBuilder.predicate(null).consumer(null);
|
validatorBuilder.predicate(null).consumer(null);
|
||||||
|
validator = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,14 +447,6 @@ final class OptionSpecBuilder<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Validator<T, ? extends RuntimeException>> createValidator() {
|
|
||||||
if (validatorBuilder.hasValidatingMethod()) {
|
|
||||||
return Optional.of(validatorBuilder.create());
|
|
||||||
} else {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private OptionValueConverter<T[]> createArrayConverter() {
|
private OptionValueConverter<T[]> createArrayConverter() {
|
||||||
final var newBuilder = converterBuilder.copy();
|
final var newBuilder = converterBuilder.copy();
|
||||||
newBuilder.tokenizer(Optional.ofNullable(arrayTokenizer).orElse(str -> {
|
newBuilder.tokenizer(Optional.ofNullable(arrayTokenizer).orElse(str -> {
|
||||||
@ -440,7 +456,7 @@ final class OptionSpecBuilder<T> {
|
|||||||
return newBuilder.createArray();
|
return newBuilder.createArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String arryValuePattern() {
|
private String arrayValuePattern() {
|
||||||
final var elementValuePattern = OptionSpecBuilder.this.valuePattern().orElseThrow();
|
final var elementValuePattern = OptionSpecBuilder.this.valuePattern().orElseThrow();
|
||||||
if (arrayValuePatternSeparator == null) {
|
if (arrayValuePatternSeparator == null) {
|
||||||
return elementValuePattern;
|
return elementValuePattern;
|
||||||
@ -468,6 +484,7 @@ final class OptionSpecBuilder<T> {
|
|||||||
private String valuePattern;
|
private String valuePattern;
|
||||||
private OptionValueConverter.Builder<T> converterBuilder = OptionValueConverter.build();
|
private OptionValueConverter.Builder<T> converterBuilder = OptionValueConverter.build();
|
||||||
private Validator.Builder<T, RuntimeException> validatorBuilder = Validator.build();
|
private Validator.Builder<T, RuntimeException> validatorBuilder = Validator.build();
|
||||||
|
private Validator<T, RuntimeException> validator;
|
||||||
|
|
||||||
private T[] arrayDefaultValue;
|
private T[] arrayDefaultValue;
|
||||||
private String arrayValuePatternSeparator;
|
private String arrayValuePatternSeparator;
|
||||||
|
|||||||
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -169,15 +169,6 @@ public final class StandardAppImageFileOption {
|
|||||||
.mutate(setPlatformScope(OperatingSystem.MACOS))
|
.mutate(setPlatformScope(OperatingSystem.MACOS))
|
||||||
.toOptionValueBuilder().id(StandardOption.MAC_APP_STORE.id()).create();
|
.toOptionValueBuilder().id(StandardOption.MAC_APP_STORE.id()).create();
|
||||||
|
|
||||||
/**
|
|
||||||
* Is an application image is signed. macOS-only.
|
|
||||||
*/
|
|
||||||
public static final OptionValue<Boolean> MAC_SIGNED = booleanOption("signed")
|
|
||||||
.inScope(AppImageFileOptionScope.APP)
|
|
||||||
.mutate(setPlatformScope(OperatingSystem.MACOS))
|
|
||||||
.toOptionValueBuilder().id(StandardOption.MAC_SIGN.id()).create();
|
|
||||||
|
|
||||||
|
|
||||||
public static final class InvalidOptionValueException extends RuntimeException {
|
public static final class InvalidOptionValueException extends RuntimeException {
|
||||||
|
|
||||||
InvalidOptionValueException(String str, Throwable t) {
|
InvalidOptionValueException(String str, Throwable t) {
|
||||||
|
|||||||
@ -233,6 +233,12 @@ public final class StandardOption {
|
|||||||
.mutate(createOptionSpecBuilderMutator((b, context) -> {
|
.mutate(createOptionSpecBuilderMutator((b, context) -> {
|
||||||
if (context.os() == OperatingSystem.MACOS) {
|
if (context.os() == OperatingSystem.MACOS) {
|
||||||
b.description("help.option.app-image" + resourceKeySuffix(context.os()));
|
b.description("help.option.app-image" + resourceKeySuffix(context.os()));
|
||||||
|
var directoryValidator = b.createValidator().orElseThrow();
|
||||||
|
var macBundleValidator = b
|
||||||
|
.validatorExceptionFormatString("error.parameter-not-mac-bundle")
|
||||||
|
.validator(StandardValidator.IS_VALID_MAC_BUNDLE)
|
||||||
|
.createValidator().orElseThrow();
|
||||||
|
b.validator(Validator.and(directoryValidator, macBundleValidator));
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.create();
|
.create();
|
||||||
|
|||||||
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -38,6 +38,7 @@ import java.util.function.Consumer;
|
|||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import jdk.jpackage.internal.cli.Validator.ValidatingConsumerException;
|
import jdk.jpackage.internal.cli.Validator.ValidatingConsumerException;
|
||||||
import jdk.jpackage.internal.util.FileUtils;
|
import jdk.jpackage.internal.util.FileUtils;
|
||||||
|
import jdk.jpackage.internal.util.MacBundle;
|
||||||
|
|
||||||
final public class StandardValidator {
|
final public class StandardValidator {
|
||||||
|
|
||||||
@ -138,6 +139,10 @@ final public class StandardValidator {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static Predicate<Path> IS_VALID_MAC_BUNDLE = path -> {
|
||||||
|
return MacBundle.fromPath(path).isPresent();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
public static final class DirectoryListingIOException extends RuntimeException {
|
public static final class DirectoryListingIOException extends RuntimeException {
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -24,20 +24,55 @@
|
|||||||
*/
|
*/
|
||||||
package jdk.jpackage.internal.cli;
|
package jdk.jpackage.internal.cli;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
interface Validator<T, U extends Exception> {
|
interface Validator<T, U extends Exception> {
|
||||||
|
|
||||||
List<U> validate(OptionName optionName, ParsedValue<T> optionValue);
|
List<U> validate(OptionName optionName, ParsedValue<T> optionValue);
|
||||||
|
|
||||||
default Validator<T, ? extends Exception> andThen(Validator<T, ? extends Exception> after) {
|
default Validator<T, ? extends Exception> and(Validator<T, ? extends Exception> after) {
|
||||||
return reduce(this, after);
|
Objects.requireNonNull(after);
|
||||||
|
var before = this;
|
||||||
|
return (optionName, optionValue) -> {
|
||||||
|
return Stream.concat(
|
||||||
|
before.validate(optionName, optionValue).stream(),
|
||||||
|
after.validate(optionName, optionValue).stream()
|
||||||
|
).toList();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default Validator<T, ? extends Exception> or(Validator<T, ? extends Exception> after) {
|
||||||
|
Objects.requireNonNull(after);
|
||||||
|
var before = this;
|
||||||
|
return (optionName, optionValue) -> {
|
||||||
|
var bErrors = before.validate(optionName, optionValue);
|
||||||
|
if (bErrors.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
var aErrors = after.validate(optionName, optionValue);
|
||||||
|
if (aErrors.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stream.concat(bErrors.stream(), aErrors.stream()).toList();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static <T, U extends Exception> Validator<T, U> and(Validator<T, U> first, Validator<T, U> second) {
|
||||||
|
return (Validator<T, U>)first.and(second);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static <T, U extends Exception> Validator<T, U> or(Validator<T, U> first, Validator<T, U> second) {
|
||||||
|
return (Validator<T, U>)first.or(second);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -251,15 +286,4 @@ interface Validator<T, U extends Exception> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
private static <T> Validator<T, ? extends Exception> reduce(Validator<T, ? extends Exception>... validators) {
|
|
||||||
@SuppressWarnings("varargs")
|
|
||||||
var theValidators = List.of(validators);
|
|
||||||
return (optionName, optionValue) -> {
|
|
||||||
return theValidators.stream().map(validator -> {
|
|
||||||
return validator.validate(optionName, optionValue);
|
|
||||||
}).flatMap(Collection::stream).map(Exception.class::cast).toList();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,6 +75,7 @@ error.parameter-not-directory=The value "{0}" provided for parameter {1} is not
|
|||||||
error.parameter-not-empty-directory=The value "{0}" provided for parameter {1} is not an empty directory or non existent path
|
error.parameter-not-empty-directory=The value "{0}" provided for parameter {1} is not an empty directory or non existent path
|
||||||
error.parameter-not-url=The value "{0}" provided for parameter {1} is not a valid URL
|
error.parameter-not-url=The value "{0}" provided for parameter {1} is not a valid URL
|
||||||
error.parameter-not-launcher-shortcut-dir=The value "{0}" provided for parameter {1} is not a valid shortcut startup directory
|
error.parameter-not-launcher-shortcut-dir=The value "{0}" provided for parameter {1} is not a valid shortcut startup directory
|
||||||
|
error.parameter-not-mac-bundle=The value "{0}" provided for parameter {1} is not a valid macOS bundle
|
||||||
error.path-parameter-ioexception=I/O error accessing path value "{0}" of parameter {1}
|
error.path-parameter-ioexception=I/O error accessing path value "{0}" of parameter {1}
|
||||||
error.parameter-add-launcher-malformed=The value "{0}" provided for parameter {1} does not match the pattern <name>=<file path>
|
error.parameter-add-launcher-malformed=The value "{0}" provided for parameter {1} does not match the pattern <name>=<file path>
|
||||||
error.parameter-add-launcher-not-file=The value of path to a property file "{0}" provided for additional launcher "{1}" is not a valid file path
|
error.parameter-add-launcher-not-file=The value of path to a property file "{0}" provided for additional launcher "{1}" is not a valid file path
|
||||||
|
|||||||
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -23,54 +23,49 @@
|
|||||||
* questions.
|
* questions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package jdk.jpackage.internal;
|
package jdk.jpackage.internal.util;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import jdk.jpackage.internal.model.AppImageLayout;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstraction of macOS Application bundle.
|
* An abstraction of macOS Application bundle.
|
||||||
*
|
*
|
||||||
* @see <a href="https://en.wikipedia.org/wiki/Bundle_(macOS)#Application_bundles">https://en.wikipedia.org/wiki/Bundle_(macOS)#Application_bundles</a>
|
* @see <a href="https://en.wikipedia.org/wiki/Bundle_(macOS)#Application_bundles">https://en.wikipedia.org/wiki/Bundle_(macOS)#Application_bundles</a>
|
||||||
*/
|
*/
|
||||||
record MacBundle(Path root) {
|
public record MacBundle(Path root) {
|
||||||
|
|
||||||
MacBundle {
|
public MacBundle {
|
||||||
Objects.requireNonNull(root);
|
Objects.requireNonNull(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isValid() {
|
public boolean isValid() {
|
||||||
return Files.isDirectory(contentsDir()) && Files.isDirectory(macOsDir()) && Files.isRegularFile(infoPlistFile());
|
return Files.isDirectory(contentsDir()) && Files.isDirectory(macOsDir()) && Files.isRegularFile(infoPlistFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isSigned() {
|
public Path contentsDir() {
|
||||||
return Files.isDirectory(contentsDir().resolve("_CodeSignature"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Path contentsDir() {
|
|
||||||
return root.resolve("Contents");
|
return root.resolve("Contents");
|
||||||
}
|
}
|
||||||
|
|
||||||
Path homeDir() {
|
public Path homeDir() {
|
||||||
return contentsDir().resolve("Home");
|
return contentsDir().resolve("Home");
|
||||||
}
|
}
|
||||||
|
|
||||||
Path macOsDir() {
|
public Path macOsDir() {
|
||||||
return contentsDir().resolve("MacOS");
|
return contentsDir().resolve("MacOS");
|
||||||
}
|
}
|
||||||
|
|
||||||
Path resourcesDir() {
|
public Path resourcesDir() {
|
||||||
return contentsDir().resolve("Resources");
|
return contentsDir().resolve("Resources");
|
||||||
}
|
}
|
||||||
|
|
||||||
Path infoPlistFile() {
|
public Path infoPlistFile() {
|
||||||
return contentsDir().resolve("Info.plist");
|
return contentsDir().resolve("Info.plist");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Optional<MacBundle> fromPath(Path path) {
|
public static Optional<MacBundle> fromPath(Path path) {
|
||||||
var bundle = new MacBundle(path);
|
var bundle = new MacBundle(path);
|
||||||
if (bundle.isValid()) {
|
if (bundle.isValid()) {
|
||||||
return Optional.of(bundle);
|
return Optional.of(bundle);
|
||||||
@ -78,20 +73,4 @@ record MacBundle(Path root) {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Optional<MacBundle> fromAppImageLayout(AppImageLayout layout) {
|
|
||||||
final var root = layout.rootDirectory();
|
|
||||||
final var bundleSubdir = root.relativize(layout.runtimeDirectory());
|
|
||||||
final var contentsDirname = Path.of("Contents");
|
|
||||||
var bundleRoot = root;
|
|
||||||
for (int i = 0; i != bundleSubdir.getNameCount(); i++) {
|
|
||||||
var nameComponent = bundleSubdir.getName(i);
|
|
||||||
if (contentsDirname.equals(nameComponent)) {
|
|
||||||
return Optional.of(new MacBundle(bundleRoot));
|
|
||||||
} else {
|
|
||||||
bundleRoot = bundleRoot.resolve(nameComponent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@ import org.w3c.dom.Document;
|
|||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
public record AppImageFile(String mainLauncherName, Optional<String> mainLauncherClassName,
|
public record AppImageFile(String mainLauncherName, Optional<String> mainLauncherClassName,
|
||||||
String version, boolean macSigned, boolean macAppStore, Map<String, Map<String, String>> launchers) {
|
String version, boolean macAppStore, Map<String, Map<String, String>> launchers) {
|
||||||
|
|
||||||
public static Path getPathInAppImage(Path appImageDir) {
|
public static Path getPathInAppImage(Path appImageDir) {
|
||||||
return ApplicationLayout.platformAppImage()
|
return ApplicationLayout.platformAppImage()
|
||||||
@ -66,7 +66,7 @@ public record AppImageFile(String mainLauncherName, Optional<String> mainLaunche
|
|||||||
}
|
}
|
||||||
|
|
||||||
public AppImageFile(String mainLauncherName, Optional<String> mainLauncherClassName) {
|
public AppImageFile(String mainLauncherName, Optional<String> mainLauncherClassName) {
|
||||||
this(mainLauncherName, mainLauncherClassName, "1.0", false, false, Map.of(mainLauncherName, Map.of()));
|
this(mainLauncherName, mainLauncherClassName, "1.0", false, Map.of(mainLauncherName, Map.of()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppImageFile(String mainLauncherName, String mainLauncherClassName) {
|
public AppImageFile(String mainLauncherName, String mainLauncherClassName) {
|
||||||
@ -103,10 +103,6 @@ public record AppImageFile(String mainLauncherName, Optional<String> mainLaunche
|
|||||||
xml.writeEndElement();
|
xml.writeEndElement();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
xml.writeStartElement("signed");
|
|
||||||
xml.writeCharacters(Boolean.toString(macSigned));
|
|
||||||
xml.writeEndElement();
|
|
||||||
|
|
||||||
xml.writeStartElement("app-store");
|
xml.writeStartElement("app-store");
|
||||||
xml.writeCharacters(Boolean.toString(macAppStore));
|
xml.writeCharacters(Boolean.toString(macAppStore));
|
||||||
xml.writeEndElement();
|
xml.writeEndElement();
|
||||||
@ -140,10 +136,6 @@ public record AppImageFile(String mainLauncherName, Optional<String> mainLaunche
|
|||||||
var mainLauncherClassName = Optional.ofNullable(xPath.evaluate(
|
var mainLauncherClassName = Optional.ofNullable(xPath.evaluate(
|
||||||
"/jpackage-state/main-class/text()", doc));
|
"/jpackage-state/main-class/text()", doc));
|
||||||
|
|
||||||
var macSigned = Optional.ofNullable(xPath.evaluate(
|
|
||||||
"/jpackage-state/signed/text()", doc)).map(
|
|
||||||
Boolean::parseBoolean).orElse(false);
|
|
||||||
|
|
||||||
var macAppStore = Optional.ofNullable(xPath.evaluate(
|
var macAppStore = Optional.ofNullable(xPath.evaluate(
|
||||||
"/jpackage-state/app-store/text()", doc)).map(
|
"/jpackage-state/app-store/text()", doc)).map(
|
||||||
Boolean::parseBoolean).orElse(false);
|
Boolean::parseBoolean).orElse(false);
|
||||||
@ -171,7 +163,6 @@ public record AppImageFile(String mainLauncherName, Optional<String> mainLaunche
|
|||||||
mainLauncherName,
|
mainLauncherName,
|
||||||
mainLauncherClassName,
|
mainLauncherClassName,
|
||||||
version,
|
version,
|
||||||
macSigned,
|
|
||||||
macAppStore,
|
macAppStore,
|
||||||
Collections.unmodifiableMap(launchers));
|
Collections.unmodifiableMap(launchers));
|
||||||
|
|
||||||
|
|||||||
@ -1385,7 +1385,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
|||||||
if (!isImagePackageType() && hasArgument("--app-image")) {
|
if (!isImagePackageType() && hasArgument("--app-image")) {
|
||||||
// Build native macOS package from an external app image.
|
// Build native macOS package from an external app image.
|
||||||
// If the external app image is signed, ".jpackage.xml" file should be kept, otherwise removed.
|
// If the external app image is signed, ".jpackage.xml" file should be kept, otherwise removed.
|
||||||
return AppImageFile.load(Path.of(getArgumentValue("--app-image"))).macSigned();
|
return MacHelper.isBundleSigned(Path.of(getArgumentValue("--app-image")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1406,13 +1406,8 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
|||||||
final AppImageFile aif = AppImageFile.load(rootDir);
|
final AppImageFile aif = AppImageFile.load(rootDir);
|
||||||
|
|
||||||
if (TKit.isOSX()) {
|
if (TKit.isOSX()) {
|
||||||
boolean expectedValue = MacHelper.appImageSigned(this);
|
var expectedValue = hasArgument("--mac-app-store");
|
||||||
boolean actualValue = aif.macSigned();
|
var actualValue = aif.macAppStore();
|
||||||
TKit.assertEquals(expectedValue, actualValue,
|
|
||||||
"Check for unexpected value of <signed> property in app image file");
|
|
||||||
|
|
||||||
expectedValue = hasArgument("--mac-app-store");
|
|
||||||
actualValue = aif.macAppStore();
|
|
||||||
TKit.assertEquals(expectedValue, actualValue,
|
TKit.assertEquals(expectedValue, actualValue,
|
||||||
"Check for unexpected value of <app-store> property in app image file");
|
"Check for unexpected value of <app-store> property in app image file");
|
||||||
}
|
}
|
||||||
@ -1437,7 +1432,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
|||||||
} else {
|
} else {
|
||||||
if (TKit.isOSX() && hasArgument("--app-image")) {
|
if (TKit.isOSX() && hasArgument("--app-image")) {
|
||||||
String appImage = getArgumentValue("--app-image");
|
String appImage = getArgumentValue("--app-image");
|
||||||
if (AppImageFile.load(Path.of(appImage)).macSigned()) {
|
if (MacHelper.isBundleSigned(Path.of(appImage))) {
|
||||||
assertFileNotInAppImage(lookupPath);
|
assertFileNotInAppImage(lookupPath);
|
||||||
} else {
|
} else {
|
||||||
assertFileInAppImage(lookupPath);
|
assertFileInAppImage(lookupPath);
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import static jdk.jpackage.internal.util.PListWriter.writeStringArray;
|
|||||||
import static jdk.jpackage.internal.util.PListWriter.writeStringOptional;
|
import static jdk.jpackage.internal.util.PListWriter.writeStringOptional;
|
||||||
import static jdk.jpackage.internal.util.XmlUtils.initDocumentBuilder;
|
import static jdk.jpackage.internal.util.XmlUtils.initDocumentBuilder;
|
||||||
import static jdk.jpackage.internal.util.XmlUtils.toXmlConsumer;
|
import static jdk.jpackage.internal.util.XmlUtils.toXmlConsumer;
|
||||||
|
import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction;
|
||||||
import static jdk.jpackage.internal.util.function.ThrowingRunnable.toRunnable;
|
import static jdk.jpackage.internal.util.function.ThrowingRunnable.toRunnable;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@ -45,6 +46,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.LinkOption;
|
import java.nio.file.LinkOption;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -59,14 +61,13 @@ import java.util.function.BiFunction;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import javax.xml.stream.XMLStreamException;
|
import javax.xml.stream.XMLStreamException;
|
||||||
import javax.xml.stream.XMLStreamWriter;
|
import javax.xml.stream.XMLStreamWriter;
|
||||||
import javax.xml.xpath.XPathConstants;
|
import javax.xml.xpath.XPathConstants;
|
||||||
import javax.xml.xpath.XPathFactory;
|
import javax.xml.xpath.XPathFactory;
|
||||||
import jdk.jpackage.internal.util.FileUtils;
|
import jdk.jpackage.internal.util.FileUtils;
|
||||||
|
import jdk.jpackage.internal.util.MacBundle;
|
||||||
import jdk.jpackage.internal.util.PListReader;
|
import jdk.jpackage.internal.util.PListReader;
|
||||||
import jdk.jpackage.internal.util.PathUtils;
|
import jdk.jpackage.internal.util.PathUtils;
|
||||||
import jdk.jpackage.internal.util.RetryExecutor;
|
import jdk.jpackage.internal.util.RetryExecutor;
|
||||||
@ -89,8 +90,8 @@ public final class MacHelper {
|
|||||||
// See JDK-8373105. "hdiutil" does not handle such cases very good.
|
// See JDK-8373105. "hdiutil" does not handle such cases very good.
|
||||||
final var mountRoot = TKit.createTempDirectory("mountRoot");
|
final var mountRoot = TKit.createTempDirectory("mountRoot");
|
||||||
|
|
||||||
// Explode DMG assuming this can require interaction, thus use `yes`.
|
// Explode the DMG assuming this can require interaction if the DMG has a license, thus use `yes`.
|
||||||
final var attachStdout = Executor.of("sh", "-c", String.join(" ",
|
final var attachExec = Executor.of("sh", "-c", String.join(" ",
|
||||||
"yes",
|
"yes",
|
||||||
"|",
|
"|",
|
||||||
"/usr/bin/hdiutil",
|
"/usr/bin/hdiutil",
|
||||||
@ -99,14 +100,34 @@ public final class MacHelper {
|
|||||||
"-mountroot", PathUtils.normalizedAbsolutePathString(mountRoot),
|
"-mountroot", PathUtils.normalizedAbsolutePathString(mountRoot),
|
||||||
"-nobrowse",
|
"-nobrowse",
|
||||||
"-plist"
|
"-plist"
|
||||||
)).saveOutput().storeOutputInFiles().executeAndRepeatUntilExitCode(0, 10, 6).stdout();
|
)).saveOutput().storeOutputInFiles().binaryOutput();
|
||||||
|
|
||||||
|
final var attachResult = attachExec.executeAndRepeatUntilExitCode(0, 10, 6);
|
||||||
|
|
||||||
final Path mountPoint;
|
final Path mountPoint;
|
||||||
|
|
||||||
boolean mountPointInitialized = false;
|
boolean mountPointInitialized = false;
|
||||||
try {
|
try {
|
||||||
|
byte[] stdout = attachResult.byteStdout();
|
||||||
|
|
||||||
|
// If the DMG has a license, it will be printed to the stdout before the plist content.
|
||||||
|
// All bytes before the XML declaration of the plist must be skipped.
|
||||||
|
// We need to find the location of the {'<', '?', 'x', 'm', 'l'} byte array
|
||||||
|
// (the XML declaration) in the captured binary stdout.
|
||||||
|
// Instead of crafting an ad-hoc function that operates on byte arrays,
|
||||||
|
// we will convert the byte array into a String instance using
|
||||||
|
// an 8-bit character set (ISO-8859-1) and use the standard String#indexOf().
|
||||||
|
var startPlistIndex = new String(stdout, StandardCharsets.ISO_8859_1).indexOf("<?xml");
|
||||||
|
|
||||||
|
byte[] plistXml;
|
||||||
|
if (startPlistIndex > 0) {
|
||||||
|
plistXml = Arrays.copyOfRange(stdout, startPlistIndex, stdout.length);
|
||||||
|
} else {
|
||||||
|
plistXml = stdout;
|
||||||
|
}
|
||||||
|
|
||||||
// One of "dict" items of "system-entities" array property should contain "mount-point" string property.
|
// One of "dict" items of "system-entities" array property should contain "mount-point" string property.
|
||||||
mountPoint = readPList(attachStdout).queryArrayValue("system-entities", false)
|
mountPoint = readPList(plistXml).queryArrayValue("system-entities", false)
|
||||||
.map(PListReader.class::cast)
|
.map(PListReader.class::cast)
|
||||||
.map(dict -> {
|
.map(dict -> {
|
||||||
return dict.findValue("mount-point");
|
return dict.findValue("mount-point");
|
||||||
@ -117,7 +138,7 @@ public final class MacHelper {
|
|||||||
} finally {
|
} finally {
|
||||||
if (!mountPointInitialized) {
|
if (!mountPointInitialized) {
|
||||||
TKit.trace("Unexpected plist file missing `system-entities` array:");
|
TKit.trace("Unexpected plist file missing `system-entities` array:");
|
||||||
attachStdout.forEach(TKit::trace);
|
attachResult.toCharacterResult(attachExec.charset(), false).stdout().forEach(TKit::trace);
|
||||||
TKit.trace("Done");
|
TKit.trace("Done");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,19 +189,13 @@ public final class MacHelper {
|
|||||||
|
|
||||||
public static PListReader readPList(Path path) {
|
public static PListReader readPList(Path path) {
|
||||||
TKit.assertReadableFileExists(path);
|
TKit.assertReadableFileExists(path);
|
||||||
return ThrowingSupplier.toSupplier(() -> readPList(Files.readAllLines(
|
return readPList(toFunction(Files::readAllBytes).apply(path));
|
||||||
path))).get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PListReader readPList(List<String> lines) {
|
public static PListReader readPList(byte[] xml) {
|
||||||
return readPList(lines.stream());
|
return ThrowingSupplier.toSupplier(() -> {
|
||||||
}
|
return new PListReader(xml);
|
||||||
|
}).get();
|
||||||
public static PListReader readPList(Stream<String> lines) {
|
|
||||||
return ThrowingSupplier.toSupplier(() -> new PListReader(lines
|
|
||||||
// Skip leading lines before xml declaration
|
|
||||||
.dropWhile(Pattern.compile("\\s?<\\?xml\\b.+\\?>").asPredicate().negate())
|
|
||||||
.collect(Collectors.joining()).getBytes(StandardCharsets.UTF_8))).get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, String> flatMapPList(PListReader plistReader) {
|
public static Map<String, String> flatMapPList(PListReader plistReader) {
|
||||||
@ -265,13 +280,13 @@ public final class MacHelper {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var runtimeImage = Optional.ofNullable(cmd.getArgumentValue("--runtime-image")).map(Path::of);
|
var runtimeImageBundle = Optional.ofNullable(cmd.getArgumentValue("--runtime-image")).map(Path::of).flatMap(MacBundle::fromPath);
|
||||||
var appImage = Optional.ofNullable(cmd.getArgumentValue("--app-image")).map(Path::of);
|
var appImage = Optional.ofNullable(cmd.getArgumentValue("--app-image")).map(Path::of);
|
||||||
|
|
||||||
if (cmd.isRuntime() && Files.isDirectory(runtimeImage.orElseThrow().resolve("Contents/_CodeSignature"))) {
|
if (cmd.isRuntime() && runtimeImageBundle.map(MacHelper::isBundleSigned).orElse(false)) {
|
||||||
// If the predefined runtime is a signed bundle, bundled image should be signed too.
|
// If the predefined runtime is a signed bundle, bundled image should be signed too.
|
||||||
return true;
|
return true;
|
||||||
} else if (appImage.map(AppImageFile::load).map(AppImageFile::macSigned).orElse(false)) {
|
} else if (appImage.map(MacHelper::isBundleSigned).orElse(false)) {
|
||||||
// The external app image is signed, so the app image is signed too.
|
// The external app image is signed, so the app image is signed too.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -301,6 +316,14 @@ public final class MacHelper {
|
|||||||
}).run();
|
}).run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean isBundleSigned(Path bundleRoot) {
|
||||||
|
return isBundleSigned(MacBundle.fromPath(bundleRoot).orElseThrow(IllegalArgumentException::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isBundleSigned(MacBundle bundle) {
|
||||||
|
return MacSignVerify.findSpctlSignOrigin(MacSignVerify.SpctlType.EXEC, bundle.root(), true).isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
private static void createFaPListFragmentFromFaProperties(JPackageCommand cmd, XMLStreamWriter xml)
|
private static void createFaPListFragmentFromFaProperties(JPackageCommand cmd, XMLStreamWriter xml)
|
||||||
throws XMLStreamException, IOException {
|
throws XMLStreamException, IOException {
|
||||||
|
|
||||||
@ -383,7 +406,7 @@ public final class MacHelper {
|
|||||||
|
|
||||||
var predefinedAppImage = Path.of(Optional.ofNullable(cmd.getArgumentValue("--app-image")).orElseThrow(IllegalArgumentException::new));
|
var predefinedAppImage = Path.of(Optional.ofNullable(cmd.getArgumentValue("--app-image")).orElseThrow(IllegalArgumentException::new));
|
||||||
|
|
||||||
var plistPath = ApplicationLayout.macAppImage().resolveAt(predefinedAppImage).contentDirectory().resolve("Info.plist");
|
var plistPath = MacBundle.fromPath(predefinedAppImage).orElseThrow().infoPlistFile();
|
||||||
|
|
||||||
try (var plistStream = Files.newInputStream(plistPath)) {
|
try (var plistStream = Files.newInputStream(plistPath)) {
|
||||||
var plist = new PListReader(initDocumentBuilder().parse(plistStream));
|
var plist = new PListReader(initDocumentBuilder().parse(plistStream));
|
||||||
|
|||||||
@ -22,7 +22,6 @@
|
|||||||
*/
|
*/
|
||||||
package jdk.jpackage.test;
|
package jdk.jpackage.test;
|
||||||
|
|
||||||
import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier;
|
|
||||||
import static jdk.jpackage.test.MacSign.DigestAlgorithm.SHA256;
|
import static jdk.jpackage.test.MacSign.DigestAlgorithm.SHA256;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -30,10 +29,8 @@ import java.security.cert.X509Certificate;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HexFormat;
|
import java.util.HexFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import jdk.jpackage.internal.util.PListReader;
|
import jdk.jpackage.internal.util.PListReader;
|
||||||
import jdk.jpackage.test.MacSign.CertificateHash;
|
import jdk.jpackage.test.MacSign.CertificateHash;
|
||||||
@ -66,7 +63,7 @@ public final class MacSignVerify {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Set to "null" if the sign origin is not found, instead of bailing out with an exception.
|
// Set to "null" if the sign origin is not found, instead of bailing out with an exception.
|
||||||
// Let is fail in the following TKit.assertEquals() call with a proper log message.
|
// Let it fail in the following TKit.assertEquals() call with a proper log message.
|
||||||
var signOrigin = findSpctlSignOrigin(SpctlType.EXEC, bundleRoot).orElse(null);
|
var signOrigin = findSpctlSignOrigin(SpctlType.EXEC, bundleRoot).orElse(null);
|
||||||
|
|
||||||
TKit.assertEquals(certRequest.name(), signOrigin,
|
TKit.assertEquals(certRequest.name(), signOrigin,
|
||||||
@ -92,10 +89,14 @@ public final class MacSignVerify {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<PListReader> findEntitlements(Path path) {
|
public static Optional<PListReader> findEntitlements(Path path) {
|
||||||
final var exec = Executor.of("/usr/bin/codesign", "-d", "--entitlements", "-", "--xml", path.toString()).saveOutput().dumpOutput();
|
final var exec = Executor.of(
|
||||||
|
"/usr/bin/codesign",
|
||||||
|
"-d",
|
||||||
|
"--entitlements", "-",
|
||||||
|
"--xml", path.toString()).saveOutput().dumpOutput().binaryOutput();
|
||||||
final var result = exec.execute();
|
final var result = exec.execute();
|
||||||
var xml = result.stdout();
|
var xml = result.byteStdout();
|
||||||
if (xml.isEmpty()) {
|
if (xml.length == 0) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
} else {
|
} else {
|
||||||
return Optional.of(MacHelper.readPList(xml));
|
return Optional.of(MacHelper.readPList(xml));
|
||||||
@ -135,17 +136,33 @@ public final class MacSignVerify {
|
|||||||
public static final String ADHOC_SIGN_ORIGIN = "-";
|
public static final String ADHOC_SIGN_ORIGIN = "-";
|
||||||
|
|
||||||
public static Optional<String> findSpctlSignOrigin(SpctlType type, Path path) {
|
public static Optional<String> findSpctlSignOrigin(SpctlType type, Path path) {
|
||||||
final var exec = Executor.of("/usr/sbin/spctl", "-vv", "--raw", "--assess", "--type", type.value(), path.toString()).saveOutput().discardStderr();
|
return findSpctlSignOrigin(type, path, false);
|
||||||
final var result = exec.executeWithoutExitCodeCheck();
|
}
|
||||||
TKit.assertTrue(Set.of(0, 3).contains(result.getExitCode()),
|
|
||||||
String.format("Check exit code of command %s is either 0 or 3", exec.getPrintableCommandLine()));
|
public static Optional<String> findSpctlSignOrigin(SpctlType type, Path path, boolean acceptBrokenSignature) {
|
||||||
return toSupplier(() -> {
|
final var exec = Executor.of(
|
||||||
try {
|
"/usr/sbin/spctl",
|
||||||
return Optional.of(new PListReader(String.join("", result.getOutput()).getBytes()).queryValue("assessment:originator"));
|
"-vv",
|
||||||
} catch (NoSuchElementException ex) {
|
"--raw",
|
||||||
return Optional.<String>empty();
|
"--assess",
|
||||||
|
"--type", type.value(),
|
||||||
|
path.toString()).saveOutput().discardStderr().binaryOutput();
|
||||||
|
Executor.Result result;
|
||||||
|
if (acceptBrokenSignature) {
|
||||||
|
result = exec.executeWithoutExitCodeCheck();
|
||||||
|
switch (result.getExitCode()) {
|
||||||
|
case 0, 3 -> {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
// No plist XML to process.
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).get();
|
} else {
|
||||||
|
result = exec.execute(0, 3);
|
||||||
|
}
|
||||||
|
return MacHelper.readPList(result.byteStdout()).findValue("assessment:originator");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<String> findCodesignSignOrigin(Path path) {
|
public static Optional<String> findCodesignSignOrigin(Path path) {
|
||||||
|
|||||||
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -29,7 +29,6 @@ import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_AS_S
|
|||||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_NAME;
|
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_NAME;
|
||||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LINUX_LAUNCHER_SHORTCUT;
|
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LINUX_LAUNCHER_SHORTCUT;
|
||||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_APP_STORE;
|
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_APP_STORE;
|
||||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_SIGNED;
|
|
||||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.WIN_LAUNCHER_DESKTOP_SHORTCUT;
|
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.WIN_LAUNCHER_DESKTOP_SHORTCUT;
|
||||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.WIN_LAUNCHER_MENU_SHORTCUT;
|
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.WIN_LAUNCHER_MENU_SHORTCUT;
|
||||||
import static jdk.jpackage.internal.cli.StandardOption.APPCLASS;
|
import static jdk.jpackage.internal.cli.StandardOption.APPCLASS;
|
||||||
@ -514,7 +513,6 @@ public class AppImageFileTest {
|
|||||||
"<main-class>Foo</main-class>",
|
"<main-class>Foo</main-class>",
|
||||||
"<y/>",
|
"<y/>",
|
||||||
"<x>property-x</x>",
|
"<x>property-x</x>",
|
||||||
"<signed>true</signed>",
|
|
||||||
"<app-store>False</app-store>",
|
"<app-store>False</app-store>",
|
||||||
"<add-launcher name='add-launcher'>",
|
"<add-launcher name='add-launcher'>",
|
||||||
" <description>Quick brown fox</description>",
|
" <description>Quick brown fox</description>",
|
||||||
@ -546,8 +544,7 @@ public class AppImageFileTest {
|
|||||||
.addExtra(WIN_LAUNCHER_MENU_SHORTCUT, new LauncherShortcut(LauncherShortcutStartupDirectory.APP_DIR)).commit()).create());
|
.addExtra(WIN_LAUNCHER_MENU_SHORTCUT, new LauncherShortcut(LauncherShortcutStartupDirectory.APP_DIR)).commit()).create());
|
||||||
|
|
||||||
testCases.add(builder.os(OperatingSystem.MACOS).expect(appBuilder.get().commit()
|
testCases.add(builder.os(OperatingSystem.MACOS).expect(appBuilder.get().commit()
|
||||||
.addExtra(MAC_APP_STORE, false)
|
.addExtra(MAC_APP_STORE, false)).create());
|
||||||
.addExtra(MAC_SIGNED, true)).create());
|
|
||||||
|
|
||||||
return testCases;
|
return testCases;
|
||||||
}
|
}
|
||||||
@ -580,7 +577,6 @@ public class AppImageFileTest {
|
|||||||
"<main-class>OverwrittenMain</main-class>",
|
"<main-class>OverwrittenMain</main-class>",
|
||||||
"<main-class>Main</main-class>",
|
"<main-class>Main</main-class>",
|
||||||
"<x>property-x</x>",
|
"<x>property-x</x>",
|
||||||
"<signed>true</signed>",
|
|
||||||
"<add-launcher name='service-launcher' service='true'>",
|
"<add-launcher name='service-launcher' service='true'>",
|
||||||
" <linux-shortcut><nested>foo</nested></linux-shortcut>",
|
" <linux-shortcut><nested>foo</nested></linux-shortcut>",
|
||||||
" <description>service-launcher description</description>",
|
" <description>service-launcher description</description>",
|
||||||
|
|||||||
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -34,6 +34,8 @@ import java.util.function.Consumer;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
import jdk.jpackage.internal.cli.Validator.ParsedValue;
|
||||||
|
import jdk.jpackage.internal.cli.Validator.ValidatorException;
|
||||||
import jdk.jpackage.test.JUnitUtils;
|
import jdk.jpackage.test.JUnitUtils;
|
||||||
|
|
||||||
final class TestUtils {
|
final class TestUtils {
|
||||||
@ -152,6 +154,31 @@ final class TestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static final class RecordingValidator<T, U extends Exception> implements Validator<T, U> {
|
||||||
|
|
||||||
|
RecordingValidator(Validator<T, U> validator) {
|
||||||
|
this.validator = Objects.requireNonNull(validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<U> validate(OptionName optionName, ParsedValue<T> optionValue) {
|
||||||
|
counter++;
|
||||||
|
return validator.validate(optionName, optionValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int counter() {
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetCounter() {
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Validator<T, U> validator;
|
||||||
|
private int counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private record RecordingExceptionFactory(OptionValueExceptionFactory<? extends RuntimeException> factory,
|
private record RecordingExceptionFactory(OptionValueExceptionFactory<? extends RuntimeException> factory,
|
||||||
Consumer<OptionFailure> sink) implements OptionValueExceptionFactory<RuntimeException> {
|
Consumer<OptionFailure> sink) implements OptionValueExceptionFactory<RuntimeException> {
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -32,10 +32,11 @@ import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import jdk.jpackage.internal.cli.TestUtils.TestException;
|
import jdk.jpackage.internal.cli.TestUtils.TestException;
|
||||||
|
import jdk.jpackage.internal.cli.TestUtils.RecordingValidator;
|
||||||
import jdk.jpackage.internal.cli.Validator.ParsedValue;
|
import jdk.jpackage.internal.cli.Validator.ParsedValue;
|
||||||
import jdk.jpackage.internal.cli.Validator.ValidatingConsumerException;
|
import jdk.jpackage.internal.cli.Validator.ValidatingConsumerException;
|
||||||
import jdk.jpackage.internal.cli.Validator.ValidatorException;
|
import jdk.jpackage.internal.cli.Validator.ValidatorException;
|
||||||
@ -187,46 +188,97 @@ public class ValidatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_andThen() {
|
public void test_and() {
|
||||||
|
|
||||||
Function<String, Validator<String, Exception>> createFailingValidator = exceptionMessage -> {
|
|
||||||
Objects.requireNonNull(exceptionMessage);
|
|
||||||
var exceptionFactory = OptionValueExceptionFactory.build().ctor(TestException::new).messageFormatter((_, _) -> {
|
|
||||||
return exceptionMessage;
|
|
||||||
}).create();
|
|
||||||
|
|
||||||
return Validator.<String, Exception>build()
|
|
||||||
.predicate(_ -> false)
|
|
||||||
.formatString("")
|
|
||||||
.exceptionFactory(exceptionFactory).create();
|
|
||||||
};
|
|
||||||
|
|
||||||
Function<Validator<String, ? extends Exception>, List<? extends Exception>> validate = validator -> {
|
Function<Validator<String, ? extends Exception>, List<? extends Exception>> validate = validator -> {
|
||||||
return validator.validate(OptionName.of("a"), ParsedValue.create("str", StringToken.of("str")));
|
return validator.validate(OptionName.of("a"), ParsedValue.create("str", StringToken.of("str")));
|
||||||
};
|
};
|
||||||
|
|
||||||
var pass = Validator.<String, RuntimeException>build().predicate(_ -> true).create();
|
var pass = new RecordingValidator<>(Validator.<String, RuntimeException>build().predicate(_ -> true).create());
|
||||||
|
|
||||||
var foo = createFailingValidator.apply("foo");
|
var foo = failingValidator("foo");
|
||||||
var bar = createFailingValidator.apply("bar");
|
var bar = failingValidator("bar");
|
||||||
var buz = createFailingValidator.apply("buz");
|
var buz = failingValidator("buz");
|
||||||
|
|
||||||
assertExceptionListEquals(List.of(
|
assertExceptionListEquals(List.of(
|
||||||
new TestException("foo"),
|
new TestException("foo"),
|
||||||
new TestException("bar"),
|
new TestException("bar"),
|
||||||
new TestException("buz")
|
new TestException("buz")
|
||||||
), validate.apply(foo.andThen(bar).andThen(pass).andThen(buz)));
|
), validate.apply(foo.and(bar).and(pass).and(buz)));
|
||||||
|
assertEquals(1, pass.counter());
|
||||||
|
|
||||||
|
pass.resetCounter();
|
||||||
assertExceptionListEquals(List.of(
|
assertExceptionListEquals(List.of(
|
||||||
new TestException("bar"),
|
new TestException("bar"),
|
||||||
new TestException("buz"),
|
new TestException("buz"),
|
||||||
new TestException("foo")
|
new TestException("foo")
|
||||||
), validate.apply(pass.andThen(bar).andThen(buz).andThen(foo)));
|
), validate.apply(pass.and(bar).and(buz).and(foo)));
|
||||||
|
assertEquals(1, pass.counter());
|
||||||
|
|
||||||
assertExceptionListEquals(List.of(
|
assertExceptionListEquals(List.of(
|
||||||
new TestException("foo"),
|
new TestException("foo"),
|
||||||
new TestException("foo")
|
new TestException("foo")
|
||||||
), validate.apply(foo.andThen(foo)));
|
), validate.apply(foo.and(foo)));
|
||||||
|
|
||||||
|
pass.resetCounter();
|
||||||
|
assertExceptionListEquals(List.of(
|
||||||
|
), validate.apply(pass.and(pass)));
|
||||||
|
assertEquals(2, pass.counter());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_or() {
|
||||||
|
|
||||||
|
Function<Validator<String, ? extends Exception>, List<? extends Exception>> validate = validator -> {
|
||||||
|
return validator.validate(OptionName.of("a"), ParsedValue.create("str", StringToken.of("str")));
|
||||||
|
};
|
||||||
|
|
||||||
|
var pass = new RecordingValidator<>(Validator.<String, RuntimeException>build().predicate(_ -> true).create());
|
||||||
|
|
||||||
|
var foo = new RecordingValidator<>(failingValidator("foo"));
|
||||||
|
var bar = new RecordingValidator<>(failingValidator("bar"));
|
||||||
|
var buz = new RecordingValidator<>(failingValidator("buz"));
|
||||||
|
|
||||||
|
Runnable resetCounters = () -> {
|
||||||
|
Stream.of(pass, foo, bar, buz).forEach(RecordingValidator::resetCounter);
|
||||||
|
};
|
||||||
|
|
||||||
|
assertExceptionListEquals(List.of(
|
||||||
|
new TestException("foo"),
|
||||||
|
new TestException("bar"),
|
||||||
|
new TestException("buz")
|
||||||
|
), validate.apply(foo.or(bar).or(buz)));
|
||||||
|
assertEquals(1, foo.counter());
|
||||||
|
assertEquals(1, bar.counter());
|
||||||
|
assertEquals(1, buz.counter());
|
||||||
|
|
||||||
|
resetCounters.run();
|
||||||
|
assertExceptionListEquals(List.of(
|
||||||
|
), validate.apply(foo.or(bar).or(pass).or(buz)));
|
||||||
|
assertEquals(1, foo.counter());
|
||||||
|
assertEquals(1, bar.counter());
|
||||||
|
assertEquals(1, pass.counter());
|
||||||
|
assertEquals(0, buz.counter());
|
||||||
|
|
||||||
|
resetCounters.run();
|
||||||
|
assertExceptionListEquals(List.of(
|
||||||
|
), validate.apply(pass.or(bar).or(buz).or(foo)));
|
||||||
|
assertEquals(1, pass.counter());
|
||||||
|
assertEquals(0, bar.counter());
|
||||||
|
assertEquals(0, buz.counter());
|
||||||
|
assertEquals(0, foo.counter());
|
||||||
|
|
||||||
|
resetCounters.run();
|
||||||
|
assertExceptionListEquals(List.of(
|
||||||
|
new TestException("foo"),
|
||||||
|
new TestException("foo")
|
||||||
|
), validate.apply(foo.or(foo)));
|
||||||
|
assertEquals(2, foo.counter());
|
||||||
|
|
||||||
|
resetCounters.run();
|
||||||
|
assertExceptionListEquals(List.of(
|
||||||
|
), validate.apply(pass.or(pass)));
|
||||||
|
assertEquals(1, pass.counter());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ -269,6 +321,17 @@ public class ValidatorTest {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Validator<String, Exception> failingValidator(String exceptionMessage) {
|
||||||
|
var exceptionFactory = OptionValueExceptionFactory.build().ctor(TestException::new).messageFormatter((_, _) -> {
|
||||||
|
return exceptionMessage;
|
||||||
|
}).create();
|
||||||
|
|
||||||
|
return Validator.<String, Exception>build()
|
||||||
|
.predicate(_ -> false)
|
||||||
|
.formatString("")
|
||||||
|
.exceptionFactory(exceptionFactory).create();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static final class FooException extends Exception {
|
static final class FooException extends Exception {
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -26,6 +26,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import jdk.internal.util.OperatingSystem;
|
import jdk.internal.util.OperatingSystem;
|
||||||
|
import jdk.jpackage.internal.util.MacBundle;
|
||||||
import jdk.jpackage.internal.util.XmlUtils;
|
import jdk.jpackage.internal.util.XmlUtils;
|
||||||
import jdk.jpackage.test.Annotations.Parameter;
|
import jdk.jpackage.test.Annotations.Parameter;
|
||||||
import jdk.jpackage.test.Annotations.Test;
|
import jdk.jpackage.test.Annotations.Test;
|
||||||
@ -133,8 +134,7 @@ public class AppImagePackageTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public static void testBadAppImage() throws IOException {
|
public static void testBadAppImage() throws IOException {
|
||||||
Path appImageDir = TKit.createTempDirectory("appimage");
|
Path appImageDir = createInvalidAppImage();
|
||||||
Files.createFile(appImageDir.resolve("foo"));
|
|
||||||
configureBadAppImage(appImageDir).addInitializer(cmd -> {
|
configureBadAppImage(appImageDir).addInitializer(cmd -> {
|
||||||
cmd.removeArgumentWithValue("--name");
|
cmd.removeArgumentWithValue("--name");
|
||||||
}).run(Action.CREATE);
|
}).run(Action.CREATE);
|
||||||
@ -145,8 +145,7 @@ public class AppImagePackageTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public static void testBadAppImage2() throws IOException {
|
public static void testBadAppImage2() throws IOException {
|
||||||
Path appImageDir = TKit.createTempDirectory("appimage");
|
Path appImageDir = createInvalidAppImage();
|
||||||
Files.createFile(appImageDir.resolve("foo"));
|
|
||||||
configureBadAppImage(appImageDir).run(Action.CREATE);
|
configureBadAppImage(appImageDir).run(Action.CREATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,4 +226,19 @@ public class AppImagePackageTest {
|
|||||||
+ TKit.ICON_SUFFIX));
|
+ TKit.ICON_SUFFIX));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Path createInvalidAppImage() throws IOException {
|
||||||
|
Path appImageDir = TKit.createTempDirectory("appimage");
|
||||||
|
if (TKit.isOSX()) {
|
||||||
|
// Create minimal macOS bundle to prevent jpackage bail out early
|
||||||
|
// with "error.parameter-not-mac-bundle" error.
|
||||||
|
var bundle = new MacBundle(appImageDir);
|
||||||
|
Files.createDirectories(bundle.macOsDir());
|
||||||
|
Files.createFile(bundle.infoPlistFile());
|
||||||
|
} else {
|
||||||
|
Files.createFile(appImageDir.resolve("foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return appImageDir;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -643,7 +643,12 @@ public final class ErrorTest {
|
|||||||
.error("message.invalid-identifier", "#1"),
|
.error("message.invalid-identifier", "#1"),
|
||||||
// Bundle for mac app store should not have runtime commands
|
// Bundle for mac app store should not have runtime commands
|
||||||
testSpec().nativeType().addArgs("--mac-app-store", "--jlink-options", "--bind-services")
|
testSpec().nativeType().addArgs("--mac-app-store", "--jlink-options", "--bind-services")
|
||||||
.error("ERR_MissingJLinkOptMacAppStore", "--strip-native-commands")
|
.error("ERR_MissingJLinkOptMacAppStore", "--strip-native-commands"),
|
||||||
|
// Predefined app image must be a valid macOS bundle.
|
||||||
|
testSpec().noAppDesc().nativeType().addArgs("--app-image", Token.EMPTY_DIR.token())
|
||||||
|
.error("error.parameter-not-mac-bundle", JPackageCommand.cannedArgument(cmd -> {
|
||||||
|
return Path.of(cmd.getArgumentValue("--app-image"));
|
||||||
|
}, Token.EMPTY_DIR.token()), "--app-image")
|
||||||
).map(TestSpec.Builder::create).toList());
|
).map(TestSpec.Builder::create).toList());
|
||||||
|
|
||||||
macInvalidRuntime(testCases::add);
|
macInvalidRuntime(testCases::add);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user