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.MacApplication;
|
||||
import jdk.jpackage.internal.model.RuntimeLayout;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
import jdk.jpackage.internal.util.PathUtils;
|
||||
import jdk.jpackage.internal.util.Result;
|
||||
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.
|
||||
*
|
||||
* 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.validateRuntimeHasNoBinDir;
|
||||
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.ICON;
|
||||
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_STORE;
|
||||
@ -52,11 +52,13 @@ import static jdk.jpackage.internal.model.StandardPackageType.MAC_PKG;
|
||||
import static jdk.jpackage.internal.util.function.ExceptionBox.toUnchecked;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo;
|
||||
import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException;
|
||||
import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector;
|
||||
import jdk.jpackage.internal.cli.OptionValue;
|
||||
import jdk.jpackage.internal.cli.Options;
|
||||
import jdk.jpackage.internal.cli.StandardFaOption;
|
||||
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.PackageType;
|
||||
import jdk.jpackage.internal.model.RuntimeLayout;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
import jdk.jpackage.internal.util.Result;
|
||||
import jdk.jpackage.internal.util.function.ExceptionBox;
|
||||
|
||||
@ -276,16 +279,12 @@ final class MacFromOptions {
|
||||
|
||||
final var builder = new MacPackageBuilder(createPackageBuilder(options, app.app(), type));
|
||||
|
||||
app.externalApp()
|
||||
.map(ExternalApplication::extra)
|
||||
.flatMap(MAC_SIGN::findIn)
|
||||
.ifPresent(builder::predefinedAppImageSigned);
|
||||
|
||||
PREDEFINED_RUNTIME_IMAGE.findIn(options)
|
||||
.map(MacBundle::new)
|
||||
.filter(MacBundle::isValid)
|
||||
.map(MacBundle::isSigned)
|
||||
.ifPresent(builder::predefinedAppImageSigned);
|
||||
for (OptionValue<Path> ov : List.of(PREDEFINED_APP_IMAGE, PREDEFINED_RUNTIME_IMAGE)) {
|
||||
ov.findIn(options)
|
||||
.flatMap(MacBundle::fromPath)
|
||||
.map(MacPackagingPipeline::isSigned)
|
||||
.ifPresent(builder::predefinedAppImageSigned);
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* 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.PackageType;
|
||||
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.function.ThrowingConsumer;
|
||||
|
||||
@ -178,13 +180,10 @@ final class MacPackagingPipeline {
|
||||
builder.task(MacCopyAppImageTaskID.COPY_RUNTIME_JLILIB)
|
||||
.appImageAction(MacPackagingPipeline::copyJliLib).add();
|
||||
|
||||
final var predefinedRuntimeBundle = Optional.of(
|
||||
new MacBundle(p.predefinedAppImage().orElseThrow())).filter(MacBundle::isValid);
|
||||
|
||||
// Don't create ".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.
|
||||
// Disable all alterations of the input bundle, but keep the signing enabled.
|
||||
disabledTasks.addAll(List.of(MacCopyAppImageTaskID.values()));
|
||||
@ -195,7 +194,7 @@ final class MacPackagingPipeline {
|
||||
.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.
|
||||
// Disable the signing, i.e. don't re-sign the input bundle.
|
||||
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,
|
||||
AppImageLayout dstAppImage) throws IOException {
|
||||
|
||||
@ -286,7 +309,7 @@ final class MacPackagingPipeline {
|
||||
|
||||
final Optional<MacBundle> srcMacBundle;
|
||||
if (pkg.isRuntimeInstaller()) {
|
||||
srcMacBundle = MacBundle.fromAppImageLayout(srcAppImage);
|
||||
srcMacBundle = macBundleFromAppImageLayout(srcAppImage);
|
||||
} else {
|
||||
srcMacBundle = Optional.empty();
|
||||
}
|
||||
@ -297,7 +320,7 @@ final class MacPackagingPipeline {
|
||||
try {
|
||||
FileUtils.copyRecursive(
|
||||
inputBundle.root(),
|
||||
MacBundle.fromAppImageLayout(dstAppImage).orElseThrow().root(),
|
||||
macBundleFromAppImageLayout(dstAppImage).orElseThrow().root(),
|
||||
LinkOption.NOFOLLOW_LINKS);
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
@ -415,7 +438,7 @@ final class MacPackagingPipeline {
|
||||
|
||||
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)));
|
||||
|
||||
@ -468,7 +491,7 @@ final class MacPackagingPipeline {
|
||||
}
|
||||
|
||||
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 -> {
|
||||
@ -550,7 +573,7 @@ final class MacPackagingPipeline {
|
||||
|
||||
private static MacBundle runtimeBundle(AppImageBuildEnv<MacApplication, AppImageLayout> env) {
|
||||
if (env.app().isRuntime()) {
|
||||
return MacBundle.fromAppImageLayout(env.resolvedLayout()).orElseThrow();
|
||||
return macBundleFromAppImageLayout(env.resolvedLayout()).orElseThrow();
|
||||
} else {
|
||||
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 {
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -28,7 +28,6 @@ import static java.util.stream.Collectors.joining;
|
||||
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_MAIN_CLASS;
|
||||
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_SIGNED;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
@ -96,9 +95,6 @@ public interface MacApplication extends Application, MacApplicationMixin {
|
||||
}
|
||||
|
||||
public enum ExtraAppImageFileField {
|
||||
SIGNED(MAC_SIGNED, app -> {
|
||||
return Optional.of(Boolean.toString(app.sign()));
|
||||
}),
|
||||
APP_STORE(MAC_APP_STORE, app -> {
|
||||
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.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -73,6 +73,7 @@ final class OptionSpecBuilder<T> {
|
||||
valuePattern = other.valuePattern;
|
||||
converterBuilder = other.converterBuilder.copy();
|
||||
validatorBuilder = other.validatorBuilder.copy();
|
||||
validator = other.validator;
|
||||
|
||||
if (other.arrayDefaultValue != null) {
|
||||
arrayDefaultValue = Arrays.copyOf(other.arrayDefaultValue, other.arrayDefaultValue.length);
|
||||
@ -135,10 +136,20 @@ final class OptionSpecBuilder<T> {
|
||||
scope,
|
||||
OptionSpecBuilder.this.mergePolicy().orElse(MergePolicy.CONCATENATE),
|
||||
defaultArrayOptionalValue(),
|
||||
Optional.of(arryValuePattern()),
|
||||
Optional.of(arrayValuePattern()),
|
||||
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) {
|
||||
Objects.requireNonNull(splitRegexp);
|
||||
return tokenizer(str -> {
|
||||
@ -162,11 +173,13 @@ final class OptionSpecBuilder<T> {
|
||||
|
||||
OptionSpecBuilder<T> validatorExceptionFormatString(String v) {
|
||||
validatorBuilder.formatString(v);
|
||||
validator = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
OptionSpecBuilder<T> validatorExceptionFormatString(UnaryOperator<String> mutator) {
|
||||
validatorBuilder.formatString(mutator.apply(validatorBuilder.formatString().orElse(null)));
|
||||
validator = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -182,6 +195,7 @@ final class OptionSpecBuilder<T> {
|
||||
|
||||
OptionSpecBuilder<T> validatorExceptionFactory(OptionValueExceptionFactory<? extends RuntimeException> v) {
|
||||
validatorBuilder.exceptionFactory(v);
|
||||
validator = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -225,18 +239,27 @@ final class OptionSpecBuilder<T> {
|
||||
|
||||
OptionSpecBuilder<T> validator(Predicate<T> v) {
|
||||
validatorBuilder.predicate(v::test);
|
||||
validator = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("overloads")
|
||||
OptionSpecBuilder<T> validator(Consumer<T> v) {
|
||||
validatorBuilder.consumer(v::accept);
|
||||
validator = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("overloads")
|
||||
OptionSpecBuilder<T> validator(UnaryOperator<Validator.Builder<T, RuntimeException>> mutator) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -247,6 +270,7 @@ final class OptionSpecBuilder<T> {
|
||||
|
||||
OptionSpecBuilder<T> withoutValidator() {
|
||||
validatorBuilder.predicate(null).consumer(null);
|
||||
validator = null;
|
||||
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() {
|
||||
final var newBuilder = converterBuilder.copy();
|
||||
newBuilder.tokenizer(Optional.ofNullable(arrayTokenizer).orElse(str -> {
|
||||
@ -440,7 +456,7 @@ final class OptionSpecBuilder<T> {
|
||||
return newBuilder.createArray();
|
||||
}
|
||||
|
||||
private String arryValuePattern() {
|
||||
private String arrayValuePattern() {
|
||||
final var elementValuePattern = OptionSpecBuilder.this.valuePattern().orElseThrow();
|
||||
if (arrayValuePatternSeparator == null) {
|
||||
return elementValuePattern;
|
||||
@ -468,6 +484,7 @@ final class OptionSpecBuilder<T> {
|
||||
private String valuePattern;
|
||||
private OptionValueConverter.Builder<T> converterBuilder = OptionValueConverter.build();
|
||||
private Validator.Builder<T, RuntimeException> validatorBuilder = Validator.build();
|
||||
private Validator<T, RuntimeException> validator;
|
||||
|
||||
private T[] arrayDefaultValue;
|
||||
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.
|
||||
*
|
||||
* 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))
|
||||
.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 {
|
||||
|
||||
InvalidOptionValueException(String str, Throwable t) {
|
||||
|
||||
@ -233,6 +233,12 @@ public final class StandardOption {
|
||||
.mutate(createOptionSpecBuilderMutator((b, context) -> {
|
||||
if (context.os() == OperatingSystem.MACOS) {
|
||||
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();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -38,6 +38,7 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import jdk.jpackage.internal.cli.Validator.ValidatingConsumerException;
|
||||
import jdk.jpackage.internal.util.FileUtils;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
|
||||
final public class StandardValidator {
|
||||
|
||||
@ -138,6 +139,10 @@ final public class StandardValidator {
|
||||
return true;
|
||||
};
|
||||
|
||||
public static Predicate<Path> IS_VALID_MAC_BUNDLE = path -> {
|
||||
return MacBundle.fromPath(path).isPresent();
|
||||
};
|
||||
|
||||
|
||||
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.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -24,20 +24,55 @@
|
||||
*/
|
||||
package jdk.jpackage.internal.cli;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@FunctionalInterface
|
||||
interface Validator<T, U extends Exception> {
|
||||
|
||||
List<U> validate(OptionName optionName, ParsedValue<T> optionValue);
|
||||
|
||||
default Validator<T, ? extends Exception> andThen(Validator<T, ? extends Exception> after) {
|
||||
return reduce(this, after);
|
||||
default Validator<T, ? extends Exception> and(Validator<T, ? extends Exception> 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-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-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.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
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,54 +23,49 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jpackage.internal;
|
||||
package jdk.jpackage.internal.util;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import jdk.jpackage.internal.model.AppImageLayout;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
record MacBundle(Path root) {
|
||||
public record MacBundle(Path root) {
|
||||
|
||||
MacBundle {
|
||||
public MacBundle {
|
||||
Objects.requireNonNull(root);
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
public boolean isValid() {
|
||||
return Files.isDirectory(contentsDir()) && Files.isDirectory(macOsDir()) && Files.isRegularFile(infoPlistFile());
|
||||
}
|
||||
|
||||
boolean isSigned() {
|
||||
return Files.isDirectory(contentsDir().resolve("_CodeSignature"));
|
||||
}
|
||||
|
||||
Path contentsDir() {
|
||||
public Path contentsDir() {
|
||||
return root.resolve("Contents");
|
||||
}
|
||||
|
||||
Path homeDir() {
|
||||
public Path homeDir() {
|
||||
return contentsDir().resolve("Home");
|
||||
}
|
||||
|
||||
Path macOsDir() {
|
||||
public Path macOsDir() {
|
||||
return contentsDir().resolve("MacOS");
|
||||
}
|
||||
|
||||
Path resourcesDir() {
|
||||
public Path resourcesDir() {
|
||||
return contentsDir().resolve("Resources");
|
||||
}
|
||||
|
||||
Path infoPlistFile() {
|
||||
public Path infoPlistFile() {
|
||||
return contentsDir().resolve("Info.plist");
|
||||
}
|
||||
|
||||
static Optional<MacBundle> fromPath(Path path) {
|
||||
public static Optional<MacBundle> fromPath(Path path) {
|
||||
var bundle = new MacBundle(path);
|
||||
if (bundle.isValid()) {
|
||||
return Optional.of(bundle);
|
||||
@ -78,20 +73,4 @@ record MacBundle(Path root) {
|
||||
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;
|
||||
|
||||
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) {
|
||||
return ApplicationLayout.platformAppImage()
|
||||
@ -66,7 +66,7 @@ public record AppImageFile(String mainLauncherName, Optional<String> mainLaunche
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -103,10 +103,6 @@ public record AppImageFile(String mainLauncherName, Optional<String> mainLaunche
|
||||
xml.writeEndElement();
|
||||
}));
|
||||
|
||||
xml.writeStartElement("signed");
|
||||
xml.writeCharacters(Boolean.toString(macSigned));
|
||||
xml.writeEndElement();
|
||||
|
||||
xml.writeStartElement("app-store");
|
||||
xml.writeCharacters(Boolean.toString(macAppStore));
|
||||
xml.writeEndElement();
|
||||
@ -140,10 +136,6 @@ public record AppImageFile(String mainLauncherName, Optional<String> mainLaunche
|
||||
var mainLauncherClassName = Optional.ofNullable(xPath.evaluate(
|
||||
"/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(
|
||||
"/jpackage-state/app-store/text()", doc)).map(
|
||||
Boolean::parseBoolean).orElse(false);
|
||||
@ -171,7 +163,6 @@ public record AppImageFile(String mainLauncherName, Optional<String> mainLaunche
|
||||
mainLauncherName,
|
||||
mainLauncherClassName,
|
||||
version,
|
||||
macSigned,
|
||||
macAppStore,
|
||||
Collections.unmodifiableMap(launchers));
|
||||
|
||||
|
||||
@ -1385,7 +1385,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
if (!isImagePackageType() && hasArgument("--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.
|
||||
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);
|
||||
|
||||
if (TKit.isOSX()) {
|
||||
boolean expectedValue = MacHelper.appImageSigned(this);
|
||||
boolean actualValue = aif.macSigned();
|
||||
TKit.assertEquals(expectedValue, actualValue,
|
||||
"Check for unexpected value of <signed> property in app image file");
|
||||
|
||||
expectedValue = hasArgument("--mac-app-store");
|
||||
actualValue = aif.macAppStore();
|
||||
var expectedValue = hasArgument("--mac-app-store");
|
||||
var actualValue = aif.macAppStore();
|
||||
TKit.assertEquals(expectedValue, actualValue,
|
||||
"Check for unexpected value of <app-store> property in app image file");
|
||||
}
|
||||
@ -1437,7 +1432,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
} else {
|
||||
if (TKit.isOSX() && hasArgument("--app-image")) {
|
||||
String appImage = getArgumentValue("--app-image");
|
||||
if (AppImageFile.load(Path.of(appImage)).macSigned()) {
|
||||
if (MacHelper.isBundleSigned(Path.of(appImage))) {
|
||||
assertFileNotInAppImage(lookupPath);
|
||||
} else {
|
||||
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.XmlUtils.initDocumentBuilder;
|
||||
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 java.io.ByteArrayInputStream;
|
||||
@ -45,6 +46,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -59,14 +61,13 @@ import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.XMLStreamWriter;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
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.RetryExecutor;
|
||||
@ -89,8 +90,8 @@ public final class MacHelper {
|
||||
// See JDK-8373105. "hdiutil" does not handle such cases very good.
|
||||
final var mountRoot = TKit.createTempDirectory("mountRoot");
|
||||
|
||||
// Explode DMG assuming this can require interaction, thus use `yes`.
|
||||
final var attachStdout = Executor.of("sh", "-c", String.join(" ",
|
||||
// Explode the DMG assuming this can require interaction if the DMG has a license, thus use `yes`.
|
||||
final var attachExec = Executor.of("sh", "-c", String.join(" ",
|
||||
"yes",
|
||||
"|",
|
||||
"/usr/bin/hdiutil",
|
||||
@ -99,14 +100,34 @@ public final class MacHelper {
|
||||
"-mountroot", PathUtils.normalizedAbsolutePathString(mountRoot),
|
||||
"-nobrowse",
|
||||
"-plist"
|
||||
)).saveOutput().storeOutputInFiles().executeAndRepeatUntilExitCode(0, 10, 6).stdout();
|
||||
)).saveOutput().storeOutputInFiles().binaryOutput();
|
||||
|
||||
final var attachResult = attachExec.executeAndRepeatUntilExitCode(0, 10, 6);
|
||||
|
||||
final Path mountPoint;
|
||||
|
||||
boolean mountPointInitialized = false;
|
||||
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.
|
||||
mountPoint = readPList(attachStdout).queryArrayValue("system-entities", false)
|
||||
mountPoint = readPList(plistXml).queryArrayValue("system-entities", false)
|
||||
.map(PListReader.class::cast)
|
||||
.map(dict -> {
|
||||
return dict.findValue("mount-point");
|
||||
@ -117,7 +138,7 @@ public final class MacHelper {
|
||||
} finally {
|
||||
if (!mountPointInitialized) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -168,19 +189,13 @@ public final class MacHelper {
|
||||
|
||||
public static PListReader readPList(Path path) {
|
||||
TKit.assertReadableFileExists(path);
|
||||
return ThrowingSupplier.toSupplier(() -> readPList(Files.readAllLines(
|
||||
path))).get();
|
||||
return readPList(toFunction(Files::readAllBytes).apply(path));
|
||||
}
|
||||
|
||||
public static PListReader readPList(List<String> lines) {
|
||||
return readPList(lines.stream());
|
||||
}
|
||||
|
||||
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 PListReader readPList(byte[] xml) {
|
||||
return ThrowingSupplier.toSupplier(() -> {
|
||||
return new PListReader(xml);
|
||||
}).get();
|
||||
}
|
||||
|
||||
public static Map<String, String> flatMapPList(PListReader plistReader) {
|
||||
@ -265,13 +280,13 @@ public final class MacHelper {
|
||||
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);
|
||||
|
||||
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.
|
||||
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.
|
||||
return true;
|
||||
}
|
||||
@ -301,6 +316,14 @@ public final class MacHelper {
|
||||
}).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)
|
||||
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 plistPath = ApplicationLayout.macAppImage().resolveAt(predefinedAppImage).contentDirectory().resolve("Info.plist");
|
||||
var plistPath = MacBundle.fromPath(predefinedAppImage).orElseThrow().infoPlistFile();
|
||||
|
||||
try (var plistStream = Files.newInputStream(plistPath)) {
|
||||
var plist = new PListReader(initDocumentBuilder().parse(plistStream));
|
||||
|
||||
@ -22,7 +22,6 @@
|
||||
*/
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier;
|
||||
import static jdk.jpackage.test.MacSign.DigestAlgorithm.SHA256;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@ -30,10 +29,8 @@ import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import jdk.jpackage.internal.util.PListReader;
|
||||
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.
|
||||
// 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);
|
||||
|
||||
TKit.assertEquals(certRequest.name(), signOrigin,
|
||||
@ -92,10 +89,14 @@ public final class MacSignVerify {
|
||||
}
|
||||
|
||||
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();
|
||||
var xml = result.stdout();
|
||||
if (xml.isEmpty()) {
|
||||
var xml = result.byteStdout();
|
||||
if (xml.length == 0) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(MacHelper.readPList(xml));
|
||||
@ -135,17 +136,33 @@ public final class MacSignVerify {
|
||||
public static final String ADHOC_SIGN_ORIGIN = "-";
|
||||
|
||||
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();
|
||||
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()));
|
||||
return toSupplier(() -> {
|
||||
try {
|
||||
return Optional.of(new PListReader(String.join("", result.getOutput()).getBytes()).queryValue("assessment:originator"));
|
||||
} catch (NoSuchElementException ex) {
|
||||
return Optional.<String>empty();
|
||||
return findSpctlSignOrigin(type, path, false);
|
||||
}
|
||||
|
||||
public static Optional<String> findSpctlSignOrigin(SpctlType type, Path path, boolean acceptBrokenSignature) {
|
||||
final var exec = Executor.of(
|
||||
"/usr/sbin/spctl",
|
||||
"-vv",
|
||||
"--raw",
|
||||
"--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) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -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.LINUX_LAUNCHER_SHORTCUT;
|
||||
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_MENU_SHORTCUT;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.APPCLASS;
|
||||
@ -514,7 +513,6 @@ public class AppImageFileTest {
|
||||
"<main-class>Foo</main-class>",
|
||||
"<y/>",
|
||||
"<x>property-x</x>",
|
||||
"<signed>true</signed>",
|
||||
"<app-store>False</app-store>",
|
||||
"<add-launcher name='add-launcher'>",
|
||||
" <description>Quick brown fox</description>",
|
||||
@ -546,8 +544,7 @@ public class AppImageFileTest {
|
||||
.addExtra(WIN_LAUNCHER_MENU_SHORTCUT, new LauncherShortcut(LauncherShortcutStartupDirectory.APP_DIR)).commit()).create());
|
||||
|
||||
testCases.add(builder.os(OperatingSystem.MACOS).expect(appBuilder.get().commit()
|
||||
.addExtra(MAC_APP_STORE, false)
|
||||
.addExtra(MAC_SIGNED, true)).create());
|
||||
.addExtra(MAC_APP_STORE, false)).create());
|
||||
|
||||
return testCases;
|
||||
}
|
||||
@ -580,7 +577,6 @@ public class AppImageFileTest {
|
||||
"<main-class>OverwrittenMain</main-class>",
|
||||
"<main-class>Main</main-class>",
|
||||
"<x>property-x</x>",
|
||||
"<signed>true</signed>",
|
||||
"<add-launcher name='service-launcher' service='true'>",
|
||||
" <linux-shortcut><nested>foo</nested></linux-shortcut>",
|
||||
" <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.
|
||||
*
|
||||
* 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.UnaryOperator;
|
||||
import java.util.stream.StreamSupport;
|
||||
import jdk.jpackage.internal.cli.Validator.ParsedValue;
|
||||
import jdk.jpackage.internal.cli.Validator.ValidatorException;
|
||||
import jdk.jpackage.test.JUnitUtils;
|
||||
|
||||
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,
|
||||
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.
|
||||
*
|
||||
* 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.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
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.ValidatingConsumerException;
|
||||
import jdk.jpackage.internal.cli.Validator.ValidatorException;
|
||||
@ -187,46 +188,97 @@ public class ValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_andThen() {
|
||||
|
||||
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();
|
||||
};
|
||||
public void test_and() {
|
||||
|
||||
Function<Validator<String, ? extends Exception>, List<? extends Exception>> validate = validator -> {
|
||||
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 bar = createFailingValidator.apply("bar");
|
||||
var buz = createFailingValidator.apply("buz");
|
||||
var foo = failingValidator("foo");
|
||||
var bar = failingValidator("bar");
|
||||
var buz = failingValidator("buz");
|
||||
|
||||
assertExceptionListEquals(List.of(
|
||||
new TestException("foo"),
|
||||
new TestException("bar"),
|
||||
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(
|
||||
new TestException("bar"),
|
||||
new TestException("buz"),
|
||||
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(
|
||||
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
|
||||
@ -269,6 +321,17 @@ public class ValidatorTest {
|
||||
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 {
|
||||
|
||||
|
||||
@ -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.
|
||||
*
|
||||
* 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.util.function.Predicate;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
import jdk.jpackage.internal.util.XmlUtils;
|
||||
import jdk.jpackage.test.Annotations.Parameter;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
@ -133,8 +134,7 @@ public class AppImagePackageTest {
|
||||
*/
|
||||
@Test
|
||||
public static void testBadAppImage() throws IOException {
|
||||
Path appImageDir = TKit.createTempDirectory("appimage");
|
||||
Files.createFile(appImageDir.resolve("foo"));
|
||||
Path appImageDir = createInvalidAppImage();
|
||||
configureBadAppImage(appImageDir).addInitializer(cmd -> {
|
||||
cmd.removeArgumentWithValue("--name");
|
||||
}).run(Action.CREATE);
|
||||
@ -145,8 +145,7 @@ public class AppImagePackageTest {
|
||||
*/
|
||||
@Test
|
||||
public static void testBadAppImage2() throws IOException {
|
||||
Path appImageDir = TKit.createTempDirectory("appimage");
|
||||
Files.createFile(appImageDir.resolve("foo"));
|
||||
Path appImageDir = createInvalidAppImage();
|
||||
configureBadAppImage(appImageDir).run(Action.CREATE);
|
||||
}
|
||||
|
||||
@ -227,4 +226,19 @@ public class AppImagePackageTest {
|
||||
+ 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"),
|
||||
// Bundle for mac app store should not have runtime commands
|
||||
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());
|
||||
|
||||
macInvalidRuntime(testCases::add);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user