mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-28 16:50:10 +00:00
8371182: [macos] Improve error messages for "invalid mac bundle identifier"
Reviewed-by: almatvee
This commit is contained in:
parent
4408e1c927
commit
274f8e601c
@ -24,6 +24,8 @@
|
||||
*/
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import static jdk.jpackage.internal.cli.StandardValidator.IS_VALID_MAC_BUNDLE_IDENTIFIER;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
@ -37,6 +39,7 @@ import jdk.jpackage.internal.model.AppImageLayout;
|
||||
import jdk.jpackage.internal.model.AppImageSigningConfig;
|
||||
import jdk.jpackage.internal.model.Application;
|
||||
import jdk.jpackage.internal.model.ApplicationLaunchers;
|
||||
import jdk.jpackage.internal.model.ConfigException;
|
||||
import jdk.jpackage.internal.model.ExternalApplication;
|
||||
import jdk.jpackage.internal.model.JPackageException;
|
||||
import jdk.jpackage.internal.model.Launcher;
|
||||
@ -138,20 +141,6 @@ final class MacApplicationBuilder {
|
||||
return MacApplication.create(ApplicationBuilder.overrideAppImageLayout(app, appImageLayout), mixin);
|
||||
}
|
||||
|
||||
static boolean isValidBundleIdentifier(String id) {
|
||||
for (int i = 0; i < id.length(); i++) {
|
||||
char a = id.charAt(i);
|
||||
// We check for ASCII codes first which we accept. If check fails,
|
||||
// check if it is acceptable extended ASCII or unicode character.
|
||||
if ((a >= 'A' && a <= 'Z') || (a >= 'a' && a <= 'z')
|
||||
|| (a >= '0' && a <= '9') || (a == '-' || a == '.')) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void validateAppVersion(Application app) {
|
||||
try {
|
||||
CFBundleVersion.of(app.version());
|
||||
@ -246,8 +235,8 @@ final class MacApplicationBuilder {
|
||||
}
|
||||
|
||||
private String validatedBundleIdentifier(Application app) {
|
||||
final var value = Optional.ofNullable(bundleIdentifier).orElseGet(() -> {
|
||||
return app.mainLauncher()
|
||||
return Optional.ofNullable(bundleIdentifier).orElseGet(() -> {
|
||||
var derivedValue = app.mainLauncher()
|
||||
.flatMap(Launcher::startupInfo)
|
||||
.map(li -> {
|
||||
final var packageName = li.packageName();
|
||||
@ -258,15 +247,23 @@ final class MacApplicationBuilder {
|
||||
}
|
||||
})
|
||||
.orElseGet(app::name);
|
||||
|
||||
if (!IS_VALID_MAC_BUNDLE_IDENTIFIER.test(derivedValue)) {
|
||||
// Derived bundle identifier is invalid. Try to adjust it by dropping all invalid characters.
|
||||
derivedValue = derivedValue.codePoints()
|
||||
.mapToObj(Character::toString)
|
||||
.filter(IS_VALID_MAC_BUNDLE_IDENTIFIER)
|
||||
.collect(Collectors.joining(""));
|
||||
if (!IS_VALID_MAC_BUNDLE_IDENTIFIER.test(derivedValue)) {
|
||||
throw new ConfigException(
|
||||
I18N.format("error.invalid-derived-bundle-identifier"),
|
||||
I18N.format("error.invalid-derived-bundle-identifier.advice"));
|
||||
}
|
||||
}
|
||||
|
||||
Log.verbose(I18N.format("message.derived-bundle-identifier", derivedValue));
|
||||
return derivedValue;
|
||||
});
|
||||
|
||||
if (!isValidBundleIdentifier(value)) {
|
||||
throw I18N.buildConfigException("message.invalid-identifier", value)
|
||||
.advice("message.invalid-identifier.advice")
|
||||
.create();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private String validatedCategory() {
|
||||
|
||||
@ -33,6 +33,8 @@ error.invalid-app-image-runtime-image-bin-dir=Runtime directory {0} in the prede
|
||||
error.invalid-runtime-image-bin-dir=Runtime image "{0}" should not contain "bin" folder
|
||||
error.invalid-runtime-image-bin-dir.advice=Use --strip-native-commands jlink option when generating runtime image used with {0} option
|
||||
error.invalid-app-image-plist-file=Invalid "{0}" file in the predefined application image
|
||||
error.invalid-derived-bundle-identifier=Can't derive a valid bundle identifier from the input data
|
||||
error.invalid-derived-bundle-identifier.advice=Specify bundle identifier with --mac-package-identifier option
|
||||
|
||||
resource.app-info-plist=Application Info.plist
|
||||
resource.app-runtime-info-plist=Embedded Java Runtime Info.plist
|
||||
@ -57,8 +59,7 @@ message.icon-not-icns= The specified icon "{0}" is not an ICNS file and will not
|
||||
message.version-string-too-many-components='app-version' may have between 1 and 3 numbers: 1, 1.2, 1.2.3.
|
||||
message.version-string-first-number-not-zero=The first number in an app-version cannot be zero or negative.
|
||||
message.keychain.error=Unable to get keychain list.
|
||||
message.invalid-identifier=Invalid mac bundle identifier [{0}].
|
||||
message.invalid-identifier.advice=specify identifier with "--mac-package-identifier".
|
||||
message.derived-bundle-identifier=Derived bundle identifier: {0}
|
||||
message.preparing-dmg-setup=Preparing dmg setup: {0}.
|
||||
message.preparing-scripts=Preparing package scripts.
|
||||
message.preparing-distribution-dist=Preparing distribution.dist: {0}.
|
||||
|
||||
@ -58,9 +58,11 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.jpackage.internal.cli.OptionValueExceptionFactory.StandardArgumentsMapper;
|
||||
import jdk.jpackage.internal.model.AppImageBundleType;
|
||||
import jdk.jpackage.internal.model.BundleType;
|
||||
import jdk.jpackage.internal.model.BundlingOperationDescriptor;
|
||||
import jdk.jpackage.internal.model.ConfigException;
|
||||
import jdk.jpackage.internal.model.JPackageException;
|
||||
import jdk.jpackage.internal.model.LauncherShortcut;
|
||||
import jdk.jpackage.internal.model.LauncherShortcutStartupDirectory;
|
||||
@ -352,6 +354,11 @@ public final class StandardOption {
|
||||
|
||||
public static final OptionValue<String> MAC_BUNDLE_IDENTIFIER = stringOption("mac-package-identifier")
|
||||
.valuePattern("package identifier")
|
||||
.validator(StandardValidator.IS_VALID_MAC_BUNDLE_IDENTIFIER)
|
||||
.validatorExceptionFactory(OptionValueExceptionFactory.build((message, cause) -> {
|
||||
return new ConfigException(message, I18N.format("error.parameter-not-mac-bundle-identifier.advice"), cause);
|
||||
}).formatArgumentsTransformer(StandardArgumentsMapper.VALUE_AND_NAME).create())
|
||||
.validatorExceptionFormatString("error.parameter-not-mac-bundle-identifier")
|
||||
.create();
|
||||
|
||||
public static final OptionValue<String> MAC_BUNDLE_SIGNING_PREFIX = stringOption("mac-package-signing-prefix").scope(MAC_SIGNING).create();
|
||||
|
||||
@ -36,11 +36,12 @@ import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import jdk.jpackage.internal.cli.Validator.ValidatingConsumerException;
|
||||
import jdk.jpackage.internal.util.FileUtils;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
|
||||
final public class StandardValidator {
|
||||
public final class StandardValidator {
|
||||
|
||||
private StandardValidator() {
|
||||
}
|
||||
@ -143,6 +144,9 @@ final public class StandardValidator {
|
||||
return MacBundle.fromPath(path).isPresent();
|
||||
};
|
||||
|
||||
// https://developer.apple.com/documentation/BundleResources/Information-Property-List/CFBundleIdentifier
|
||||
public static final Predicate<String> IS_VALID_MAC_BUNDLE_IDENTIFIER = Pattern.compile("[\\p{Alnum}-\\.]+").asMatchPredicate();
|
||||
|
||||
|
||||
public static final class DirectoryListingIOException extends RuntimeException {
|
||||
|
||||
|
||||
@ -88,6 +88,8 @@ error.parameter-not-empty-directory=The value "{0}" provided for parameter {1} i
|
||||
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.parameter-not-mac-bundle-identifier=The value "{0}" provided for parameter {1} is not a valid macOS bundle identifier.
|
||||
error.parameter-not-mac-bundle-identifier.advice=Bundle identifier must be a non-empty string containing only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-), and periods (.)
|
||||
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) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, 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
|
||||
@ -27,6 +27,7 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public record CannedFormattedString(BiFunction<String, Object[], String> formatter, String key, List<Object> args) implements CannedArgument {
|
||||
@ -67,6 +68,23 @@ public record CannedFormattedString(BiFunction<String, Object[], String> formatt
|
||||
}
|
||||
}
|
||||
|
||||
public interface Spec {
|
||||
|
||||
String format();
|
||||
List<Object> modelArgs();
|
||||
|
||||
default CannedFormattedString asCannedFormattedString(Object ... args) {
|
||||
if (args.length != modelArgs().size()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return JPackageStringBundle.MAIN.cannedFormattedString(format(), args);
|
||||
}
|
||||
|
||||
default Pattern asPattern() {
|
||||
return JPackageStringBundle.MAIN.cannedFormattedStringAsPattern(format(), modelArgs().toArray());
|
||||
}
|
||||
}
|
||||
|
||||
private record AddPrefixFormatter(BiFunction<String, Object[], String> formatter) implements BiFunction<String, Object[], String> {
|
||||
|
||||
AddPrefixFormatter {
|
||||
|
||||
@ -1078,6 +1078,53 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public JPackageCommand validateOutput(
|
||||
Class<? extends CannedFormattedString.Spec> messageGroup,
|
||||
Consumer<JPackageOutputValidator> validatorMutator,
|
||||
List<CannedFormattedString> expectedMessages) {
|
||||
|
||||
Objects.requireNonNull(validatorMutator);
|
||||
|
||||
if (!messageGroup.isEnum()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
var messageSpecs = messageGroup.getEnumConstants();
|
||||
|
||||
var expectMessageFormats = expectedMessages.stream().map(CannedFormattedString::key).toList();
|
||||
|
||||
var groupMessageFormats = Stream.of(messageSpecs)
|
||||
.map(CannedFormattedString.Spec::format)
|
||||
.collect(Collectors.toMap(x -> x, x -> x))
|
||||
.keySet();
|
||||
|
||||
if (!groupMessageFormats.containsAll(expectMessageFormats)) {
|
||||
// Expected format strings should be a subset of the group format strings.
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
if (!expectedMessages.isEmpty()) {
|
||||
new JPackageOutputValidator().expectMatchingStrings(expectedMessages).mutate(validatorMutator).applyTo(this);
|
||||
}
|
||||
|
||||
Stream.of(messageSpecs).filter(spec -> {
|
||||
return !expectMessageFormats.contains(spec.format());
|
||||
}).map(CannedFormattedString.Spec::asPattern).map(pattern -> {
|
||||
return TKit.assertTextStream(pattern).negate();
|
||||
}).forEach(validator -> {
|
||||
new JPackageOutputValidator().add(validator).stdoutAndStderr().applyTo(this);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public JPackageCommand validateOutput(
|
||||
Class<? extends CannedFormattedString.Spec> messageGroup,
|
||||
Consumer<JPackageOutputValidator> validatorMutator,
|
||||
CannedFormattedString... expected) {
|
||||
return validateOutput(messageGroup, validatorMutator, List.of(expected));
|
||||
}
|
||||
|
||||
public boolean isWithToolProvider() {
|
||||
return toolProviderSource.toolProvider().isPresent();
|
||||
}
|
||||
|
||||
@ -140,6 +140,11 @@ public final class JPackageOutputValidator {
|
||||
return this;
|
||||
}
|
||||
|
||||
public JPackageOutputValidator mutate(Consumer<JPackageOutputValidator> mutator) {
|
||||
mutator.accept(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void applyTo(JPackageCommand cmd) {
|
||||
toResultConsumer(cmd).ifPresent(cmd::validateResult);
|
||||
}
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import jdk.jpackage.internal.model.ApplicationLaunchers;
|
||||
import jdk.jpackage.internal.model.ConfigException;
|
||||
import jdk.jpackage.internal.model.Launcher;
|
||||
import jdk.jpackage.internal.model.LauncherStartupInfo;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
class MacApplicationBuilderTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"NAME,!fo#o,foo",
|
||||
"NAME,bar,bar",
|
||||
"MAIN_LAUNCHER_CLASSNAME,foo.b$ar.Hello,foo.bar",
|
||||
"MAIN_LAUNCHER_CLASSNAME,Hello$2,Hello2",
|
||||
"NAME,!#,",
|
||||
})
|
||||
void testDerivedBundleIdentifier(ApplicationBuilderProperty type, String value, String expectedBundleIdentifier) {
|
||||
|
||||
var builder = buildApplication();
|
||||
var macBuilder = new MacApplicationBuilder(builder);
|
||||
|
||||
type.applyTo(value, builder, macBuilder);
|
||||
|
||||
if (expectedBundleIdentifier != null) {
|
||||
assertEquals(expectedBundleIdentifier, macBuilder.create().bundleIdentifier());
|
||||
} else {
|
||||
var ex = assertThrowsExactly(ConfigException.class, macBuilder::create);
|
||||
assertEquals(I18N.format("error.invalid-derived-bundle-identifier"), ex.getMessage());
|
||||
assertEquals(I18N.format("error.invalid-derived-bundle-identifier.advice"), ex.getAdvice());
|
||||
}
|
||||
}
|
||||
|
||||
private static ApplicationBuilder buildApplication() {
|
||||
return new ApplicationBuilder().appImageLayout(APPLICATION_LAYOUT);
|
||||
}
|
||||
|
||||
enum ApplicationBuilderProperty {
|
||||
NAME {
|
||||
void applyTo(String value, ApplicationBuilder builder, MacApplicationBuilder macBuilder) {
|
||||
builder.name(Objects.requireNonNull(value));
|
||||
}
|
||||
},
|
||||
MAIN_LAUNCHER_CLASSNAME {
|
||||
void applyTo(String value, ApplicationBuilder builder, MacApplicationBuilder macBuilder) {
|
||||
var startupInfo = new LauncherStartupInfo.Stub(Objects.requireNonNull(value), List.of(), List.of(), List.of());
|
||||
var launcher = new Launcher.Stub(
|
||||
startupInfo.simpleClassName(),
|
||||
Optional.of(startupInfo),
|
||||
List.of(),
|
||||
false,
|
||||
"",
|
||||
Optional.empty(),
|
||||
null,
|
||||
Map.of());
|
||||
builder.launchers(new ApplicationLaunchers(launcher));
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
abstract void applyTo(String value, ApplicationBuilder builder, MacApplicationBuilder macBuilder);
|
||||
}
|
||||
}
|
||||
@ -63,3 +63,11 @@
|
||||
* jdk/jpackage/internal/ActiveKeychainListTest.java
|
||||
* @run junit jdk.jpackage/jdk.jpackage.internal.ActiveKeychainListTest
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Test MacApplicationBuilderTest
|
||||
* @requires (os.family == "mac")
|
||||
* @compile/module=jdk.jpackage -Xlint:all -Werror
|
||||
* jdk/jpackage/internal/MacApplicationBuilderTest.java
|
||||
* @run junit jdk.jpackage/jdk.jpackage.internal.MacApplicationBuilderTest
|
||||
*/
|
||||
|
||||
@ -18,7 +18,6 @@ ErrorTest.test(IMAGE; args-add=[--module, com.foo.bar, --runtime-image, @@JAVA_H
|
||||
ErrorTest.test(IMAGE; args-add=[--module, java.base, --runtime-image, @@JAVA_HOME@@]; errors=[message.error-header+[ERR_NoMainClass]])
|
||||
ErrorTest.test(LINUX_DEB; app-desc=Hello; args-add=[--linux-package-name, #]; errors=[message.error-header+[error.deb-invalid-value-for-package-name, #], message.advice-header+[error.deb-invalid-value-for-package-name.advice]])
|
||||
ErrorTest.test(LINUX_RPM; app-desc=Hello; args-add=[--linux-package-name, #]; errors=[message.error-header+[error.rpm-invalid-value-for-package-name, #], message.advice-header+[error.rpm-invalid-value-for-package-name.advice]])
|
||||
ErrorTest.test(MAC_PKG; app-desc=Hello; args-add=[--mac-package-identifier, #1]; errors=[message.error-header+[message.invalid-identifier, #1], message.advice-header+[message.invalid-identifier.advice]])
|
||||
ErrorTest.test(NATIVE; app-desc=Hello; args-add=[--mac-app-store, --runtime-image, @@JAVA_HOME@@]; errors=[message.error-header+[error.invalid-runtime-image-bin-dir, @@JAVA_HOME@@], message.advice-header+[error.invalid-runtime-image-bin-dir.advice, --mac-app-store]])
|
||||
ErrorTest.test(NATIVE; app-desc=Hello; args-add=[--runtime-image, @@EMPTY_DIR@@]; errors=[message.error-header+[error.invalid-runtime-image-missing-file, @@EMPTY_DIR@@, lib/**/libjli.dylib]])
|
||||
ErrorTest.test(NATIVE; app-desc=Hello; args-add=[--runtime-image, @@INVALID_MAC_RUNTIME_BUNDLE@@]; errors=[message.error-header+[error.invalid-runtime-image-missing-file, @@INVALID_MAC_RUNTIME_BUNDLE@@, Contents/Home/lib/**/libjli.dylib]])
|
||||
|
||||
@ -58,6 +58,7 @@ import jdk.jpackage.internal.cli.StandardOption.LauncherProperty;
|
||||
import jdk.jpackage.internal.model.AppImageBundleType;
|
||||
import jdk.jpackage.internal.model.BundleType;
|
||||
import jdk.jpackage.internal.model.JPackageException;
|
||||
import jdk.jpackage.internal.model.ConfigException;
|
||||
import jdk.jpackage.internal.model.LauncherShortcut;
|
||||
import jdk.jpackage.internal.model.LauncherShortcutStartupDirectory;
|
||||
import jdk.jpackage.internal.util.RootedPath;
|
||||
@ -71,6 +72,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
@ -258,6 +260,40 @@ public class StandardOptionTest extends JUnitAdapter.TestSrcInitializer {
|
||||
}).toList(), result.errors());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
".",
|
||||
"a-b.c",
|
||||
"A",
|
||||
"com.acme.Foo"
|
||||
})
|
||||
void test_MAC_BUNDLE_IDENTIFIER_valid(String id) {
|
||||
|
||||
var spec = StandardOption.MAC_BUNDLE_IDENTIFIER.getSpec();
|
||||
|
||||
var result = spec.convert(spec.name(), StringToken.of(id)).orElseThrow();
|
||||
|
||||
assertEquals(result, id);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"",
|
||||
",",
|
||||
"Hello!"
|
||||
})
|
||||
void test_MAC_BUNDLE_IDENTIFIER_invalid(String id) {
|
||||
|
||||
var spec = StandardOption.MAC_BUNDLE_IDENTIFIER.getSpec();
|
||||
|
||||
var result = spec.convert(spec.name(), StringToken.of(id));
|
||||
|
||||
var ex = assertThrows(ConfigException.class, result::orElseThrow);
|
||||
|
||||
assertEquals(I18N.format("error.parameter-not-mac-bundle-identifier", id, spec.name().formatForCommandLine()), ex.getMessage());
|
||||
assertEquals(I18N.format("error.parameter-not-mac-bundle-identifier.advice"), ex.getAdvice());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(OptionMutatorTest.TestType.class)
|
||||
public void test_pathOptionMutator(OptionMutatorTest.TestType type) {
|
||||
|
||||
@ -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
|
||||
@ -21,11 +21,20 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import jdk.jpackage.test.TKit;
|
||||
import jdk.jpackage.test.MacHelper;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import jdk.jpackage.test.Annotations.ParameterSupplier;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.Annotations.Parameter;
|
||||
import jdk.jpackage.test.CannedFormattedString;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
import jdk.jpackage.test.JPackageOutputValidator;
|
||||
import jdk.jpackage.test.MacHelper;
|
||||
import jdk.jpackage.test.TKit;
|
||||
|
||||
|
||||
/**
|
||||
@ -43,30 +52,198 @@ import jdk.jpackage.test.Annotations.Parameter;
|
||||
* --jpt-run=MacPropertiesTest
|
||||
*/
|
||||
public class MacPropertiesTest {
|
||||
@Test
|
||||
@Parameter("MacPackageNameTest")
|
||||
public void testPackageName(String packageName) {
|
||||
testParameterInAppImage("--mac-package-name", "CFBundleName",
|
||||
packageName);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameter("Foo")
|
||||
public void testPackageIdetifier(String packageId) {
|
||||
testParameterInAppImage("--mac-package-identifier", "CFBundleIdentifier",
|
||||
packageId);
|
||||
@ParameterSupplier
|
||||
public void test(TestSpec spec) {
|
||||
spec.run();
|
||||
}
|
||||
|
||||
private static void testParameterInAppImage(String jpackageParameterName,
|
||||
String plistKeyName, String value) {
|
||||
JPackageCommand cmd = JPackageCommand.helloAppImage()
|
||||
.addArguments(jpackageParameterName, value);
|
||||
public static Collection<Object[]> test() {
|
||||
|
||||
cmd.executeAndAssertHelloAppImageCreated();
|
||||
var testCases = new ArrayList<TestSpec.Builder>();
|
||||
|
||||
var plist = MacHelper.readPListFromAppImage(cmd.outputBundle());
|
||||
testCases.addAll(List.of(
|
||||
TestSpec.build("CFBundleName").addArgs("--mac-package-name", "MacPackageNameTest").expect("MacPackageNameTest"),
|
||||
TestSpec.build("CFBundleIdentifier").addArgs("--mac-package-identifier", "Foo").expect("Foo"),
|
||||
// Should derive from the input data.
|
||||
TestSpec.build("CFBundleIdentifier").appDesc("com.acme.Hello").expect("com.acme").expect(BundleIdentifierMessage.VALUE.asCannedFormattedString("com.acme"))
|
||||
));
|
||||
|
||||
TKit.assertEquals(value, plist.queryValue(plistKeyName), String.format(
|
||||
"Check value of %s plist key", plistKeyName));
|
||||
return testCases.stream().map(TestSpec.Builder::create).map(v -> {
|
||||
return new Object[] {v};
|
||||
}).toList();
|
||||
}
|
||||
|
||||
enum BundleIdentifierMessage implements CannedFormattedString.Spec {
|
||||
VALUE("message.derived-bundle-identifier", "bundle-id"),
|
||||
;
|
||||
|
||||
BundleIdentifierMessage(String key, Object ... args) {
|
||||
this.key = Objects.requireNonNull(key);
|
||||
this.args = List.of(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> modelArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
private final String key;
|
||||
private final List<Object> args;
|
||||
}
|
||||
|
||||
record TestSpec(
|
||||
Optional<String> appDesc,
|
||||
List<String> addArgs,
|
||||
List<String> delArgs,
|
||||
List<CannedFormattedString> expectedTraceMessages,
|
||||
String expectedInfoPlistKeyValue,
|
||||
String infoPlistKey,
|
||||
Optional<Class<? extends CannedFormattedString.Spec>> traceMessagesClass) {
|
||||
|
||||
TestSpec {
|
||||
Objects.requireNonNull(addArgs);
|
||||
Objects.requireNonNull(delArgs);
|
||||
Objects.requireNonNull(expectedTraceMessages);
|
||||
Objects.requireNonNull(expectedInfoPlistKeyValue);
|
||||
Objects.requireNonNull(infoPlistKey);
|
||||
Objects.requireNonNull(traceMessagesClass);
|
||||
}
|
||||
|
||||
void run() {
|
||||
var cmd = appDesc.map(JPackageCommand::helloAppImage).orElseGet(JPackageCommand::helloAppImage)
|
||||
.setFakeRuntime().addArguments(addArgs);
|
||||
|
||||
delArgs.forEach(cmd::removeArgumentWithValue);
|
||||
|
||||
Consumer<JPackageOutputValidator> validatorMutator = validator -> {
|
||||
validator.matchTimestamps().stripTimestamps();
|
||||
};
|
||||
|
||||
traceMessagesClass.ifPresentOrElse(v -> {
|
||||
cmd.validateOutput(v, validatorMutator, expectedTraceMessages);
|
||||
}, () -> {
|
||||
new JPackageOutputValidator()
|
||||
.mutate(validatorMutator)
|
||||
.expectMatchingStrings(expectedTraceMessages)
|
||||
.applyTo(cmd);
|
||||
});
|
||||
|
||||
cmd.executeAndAssertHelloAppImageCreated();
|
||||
|
||||
var plist = MacHelper.readPListFromAppImage(cmd.outputBundle());
|
||||
|
||||
TKit.assertEquals(
|
||||
expectedInfoPlistKeyValue,
|
||||
plist.queryValue(infoPlistKey),
|
||||
String.format("Check value of %s plist key", infoPlistKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var tokens = new ArrayList<String>();
|
||||
|
||||
tokens.add(String.format("%s=>%s", infoPlistKey, expectedInfoPlistKeyValue));
|
||||
|
||||
appDesc.ifPresent(v -> {
|
||||
tokens.add("app-desc=" + v);
|
||||
});
|
||||
|
||||
if (!addArgs.isEmpty()) {
|
||||
tokens.add("args-add=" + addArgs);
|
||||
}
|
||||
|
||||
if (!delArgs.isEmpty()) {
|
||||
tokens.add("args-del=" + delArgs);
|
||||
}
|
||||
|
||||
if (!expectedTraceMessages.isEmpty()) {
|
||||
tokens.add("expect=" + expectedTraceMessages);
|
||||
}
|
||||
|
||||
return tokens.stream().collect(Collectors.joining("; "));
|
||||
}
|
||||
|
||||
static Builder build() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
static Builder build(String infoPlistKey) {
|
||||
return build().infoPlistKey(Objects.requireNonNull(infoPlistKey));
|
||||
}
|
||||
|
||||
static final class Builder {
|
||||
|
||||
TestSpec create() {
|
||||
|
||||
Class<? extends CannedFormattedString.Spec> traceMessagesClass = switch (Objects.requireNonNull(infoPlistKey)) {
|
||||
case "CFBundleIdentifier" -> BundleIdentifierMessage.class;
|
||||
case "CFBundleName" -> null;
|
||||
default -> {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
};
|
||||
|
||||
return new TestSpec(
|
||||
Optional.ofNullable(appDesc),
|
||||
addArgs,
|
||||
delArgs,
|
||||
expectedTraceMessages,
|
||||
expectedInfoPlistKeyValue,
|
||||
infoPlistKey,
|
||||
Optional.ofNullable(traceMessagesClass));
|
||||
}
|
||||
|
||||
Builder appDesc(String v) {
|
||||
appDesc = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder addArgs(List<String> v) {
|
||||
addArgs.addAll(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder addArgs(String... args) {
|
||||
return addArgs(List.of(args));
|
||||
}
|
||||
|
||||
Builder delArgs(List<String> v) {
|
||||
delArgs.addAll(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder delArgs(String... args) {
|
||||
return delArgs(List.of(args));
|
||||
}
|
||||
|
||||
Builder expect(CannedFormattedString traceMessage) {
|
||||
expectedTraceMessages.add(traceMessage);
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder expect(String v) {
|
||||
expectedInfoPlistKeyValue = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder infoPlistKey(String v) {
|
||||
infoPlistKey = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
private String appDesc;
|
||||
private final List<String> addArgs = new ArrayList<>();
|
||||
private final List<String> delArgs = new ArrayList<>();
|
||||
private final List<CannedFormattedString> expectedTraceMessages = new ArrayList<>();
|
||||
private String expectedInfoPlistKeyValue;
|
||||
private String infoPlistKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +46,6 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
import jdk.jpackage.internal.util.RuntimeReleaseFile;
|
||||
@ -58,8 +57,6 @@ import jdk.jpackage.test.CannedFormattedString;
|
||||
import jdk.jpackage.test.ConfigurationTarget;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
import jdk.jpackage.test.JPackageCommand.StandardAssert;
|
||||
import jdk.jpackage.test.JPackageOutputValidator;
|
||||
import jdk.jpackage.test.JPackageStringBundle;
|
||||
import jdk.jpackage.test.JavaAppDesc;
|
||||
import jdk.jpackage.test.MacHelper;
|
||||
import jdk.jpackage.test.PackageTest;
|
||||
@ -270,7 +267,7 @@ public final class AppVersionTest {
|
||||
}).toList();
|
||||
}
|
||||
|
||||
enum Message {
|
||||
enum Message implements CannedFormattedString.Spec {
|
||||
VERSION_FROM_MODULE("message.module-version", "version", "module"),
|
||||
VERSION_FROM_RELEASE_FILE("message.release-version", "version"),
|
||||
VERSION_NORMALIZED("message.version-normalized", "version", "version"),
|
||||
@ -278,24 +275,21 @@ public final class AppVersionTest {
|
||||
|
||||
Message(String key, Object ... args) {
|
||||
this.key = Objects.requireNonNull(key);
|
||||
this.args = args;
|
||||
this.args = List.of(args);
|
||||
}
|
||||
|
||||
CannedFormattedString cannedFormattedString(Object ... args) {
|
||||
return JPackageStringBundle.MAIN.cannedFormattedString(key, args);
|
||||
}
|
||||
|
||||
TKit.TextStreamVerifier negateFind() {
|
||||
var pattern = JPackageStringBundle.MAIN.cannedFormattedStringAsPattern(key, args);
|
||||
return TKit.assertTextStream(pattern).negate();
|
||||
}
|
||||
|
||||
String key() {
|
||||
@Override
|
||||
public String format() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> modelArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
private final String key;
|
||||
private final Object[] args;
|
||||
private final List<Object> args;
|
||||
}
|
||||
|
||||
sealed interface VersionSource {
|
||||
@ -389,16 +383,9 @@ public final class AppVersionTest {
|
||||
}
|
||||
|
||||
void applyTo(JPackageCommand cmd) {
|
||||
Objects.requireNonNull(cmd);
|
||||
new JPackageOutputValidator().expectMatchingStrings(messages).matchTimestamps().stripTimestamps().applyTo(cmd);
|
||||
cmd.version(version);
|
||||
|
||||
var expectMessageKeys = messages.stream().map(CannedFormattedString::key).toList();
|
||||
Stream.of(Message.values()).filter(message -> {
|
||||
return !expectMessageKeys.contains(message.key());
|
||||
}).map(Message::negateFind).forEach(validator -> {
|
||||
new JPackageOutputValidator().add(validator).stdoutAndStderr().applyTo(cmd);
|
||||
});
|
||||
cmd.version(version).validateOutput(Message.class, validator -> {
|
||||
validator.matchTimestamps().stripTimestamps();
|
||||
}, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -436,7 +423,7 @@ public final class AppVersionTest {
|
||||
}
|
||||
|
||||
Builder message(Message message, Object ... args) {
|
||||
return messages(message.cannedFormattedString(args));
|
||||
return messages(message.asCannedFormattedString(args));
|
||||
}
|
||||
|
||||
private String version;
|
||||
|
||||
@ -1015,8 +1015,8 @@ public final class ErrorTest {
|
||||
testSpec().noAppDesc().addArgs("--app-image", Token.APP_IMAGE.token())
|
||||
.error("error.app-image.mac-sign.required"),
|
||||
testSpec().type(PackageType.MAC_PKG).addArgs("--mac-package-identifier", "#1")
|
||||
.error("message.invalid-identifier", "#1")
|
||||
.advice("message.invalid-identifier.advice"),
|
||||
.error("error.parameter-not-mac-bundle-identifier", "#1", "--mac-package-identifier")
|
||||
.advice("error.parameter-not-mac-bundle-identifier.advice"),
|
||||
// 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"),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user