mirror of
https://github.com/openjdk/jdk.git
synced 2026-05-10 05:29:48 +00:00
8374839: Improve jpackage information messages
8356116: [macos] Add logging of sign commands in jpackage Reviewed-by: almatvee
This commit is contained in:
parent
b485729859
commit
06e6539ceb
@ -430,7 +430,7 @@ final class DesktopIntegration extends ShellCustomAction {
|
||||
BufferedImage bi = ImageIO.read(path.toFile());
|
||||
return Math.max(bi.getWidth(), bi.getHeight());
|
||||
} catch (IOException e) {
|
||||
Log.verbose(e);
|
||||
Log.trace(e, "Failed to get dimensions of an image at [%s]", path);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -66,16 +66,17 @@ public final class LibProvidersLookup {
|
||||
// Get the list of unique package names.
|
||||
List<String> neededPackages = neededLibs.stream().map(libPath -> {
|
||||
try {
|
||||
List<String> packageNames = packageLookup.apply(libPath).filter(
|
||||
Objects::nonNull).filter(Predicate.not(String::isBlank)).distinct().collect(
|
||||
Collectors.toList());
|
||||
Log.verbose(String.format("%s is provided by %s", libPath, packageNames));
|
||||
List<String> packageNames = packageLookup.apply(libPath)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(Predicate.not(String::isBlank))
|
||||
.distinct()
|
||||
.toList();
|
||||
Log.trace("%s is provided by %s", libPath, packageNames);
|
||||
return packageNames;
|
||||
} catch (IOException ex) {
|
||||
// Ignore and keep going
|
||||
Log.verbose(ex);
|
||||
List<String> packageNames = Collections.emptyList();
|
||||
return packageNames;
|
||||
Log.trace(ex, "Failed to get required packages for [%s]", libPath);
|
||||
return List.<String>of();
|
||||
}
|
||||
}).flatMap(List::stream).sorted().distinct().toList();
|
||||
|
||||
@ -83,10 +84,10 @@ public final class LibProvidersLookup {
|
||||
}
|
||||
|
||||
private static List<Path> getNeededLibsForFile(Path path) throws IOException {
|
||||
final var result = Executor.of(TOOL_LDD, path.toString()).saveOutput().execute();
|
||||
final var result = Executor.of(TOOL_LDD, path.toString()).quiet().saveOutput().execute();
|
||||
|
||||
if (result.getExitCode() != 0) {
|
||||
// objdump failed. This is OK if the tool was applied to not a binary file
|
||||
// ldd failed. This is OK if the tool was applied to not a binary file
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@ -107,7 +108,7 @@ public final class LibProvidersLookup {
|
||||
try {
|
||||
libs = getNeededLibsForFile(path);
|
||||
} catch (IOException ex) {
|
||||
Log.verbose(ex);
|
||||
Log.trace(ex, "Failed to get required libraries for [%s]", path);
|
||||
libs = Collections.emptyList();
|
||||
}
|
||||
return libs;
|
||||
|
||||
@ -43,6 +43,8 @@ import jdk.jpackage.internal.model.BundlingOperationDescriptor;
|
||||
import jdk.jpackage.internal.model.LinuxPackage;
|
||||
import jdk.jpackage.internal.model.PackageType;
|
||||
import jdk.jpackage.internal.model.StandardPackageType;
|
||||
import jdk.jpackage.internal.summary.SummaryAccumulator;
|
||||
import jdk.jpackage.internal.summary.StandardProperty;
|
||||
import jdk.jpackage.internal.util.Result;
|
||||
|
||||
public class LinuxBundlingEnvironment extends DefaultBundlingEnvironment {
|
||||
@ -78,22 +80,26 @@ public class LinuxBundlingEnvironment extends DefaultBundlingEnvironment {
|
||||
|
||||
private static void createDebPackage(Options options, LinuxDebSystemEnvironment sysEnv) {
|
||||
|
||||
var pkg = LinuxFromOptions.createLinuxDebPackage(options, sysEnv);
|
||||
|
||||
createNativePackage(options,
|
||||
LinuxFromOptions.createLinuxDebPackage(options, sysEnv),
|
||||
updateSummary(pkg, OptionUtils.summary(options), sysEnv),
|
||||
buildEnv()::create,
|
||||
LinuxBundlingEnvironment::buildPipeline,
|
||||
(env, pkg, outputDir) -> {
|
||||
(env, _, outputDir) -> {
|
||||
return new LinuxDebPackager(env, pkg, outputDir, sysEnv);
|
||||
});
|
||||
}
|
||||
|
||||
private static void createRpmPackage(Options options, LinuxRpmSystemEnvironment sysEnv) {
|
||||
|
||||
var pkg = LinuxFromOptions.createLinuxRpmPackage(options, sysEnv);
|
||||
|
||||
createNativePackage(options,
|
||||
LinuxFromOptions.createLinuxRpmPackage(options, sysEnv),
|
||||
updateSummary(pkg, OptionUtils.summary(options), sysEnv),
|
||||
buildEnv()::create,
|
||||
LinuxBundlingEnvironment::buildPipeline,
|
||||
(env, pkg, outputDir) -> {
|
||||
(env, _, outputDir) -> {
|
||||
return new LinuxRpmPackager(env, pkg, outputDir, sysEnv);
|
||||
});
|
||||
}
|
||||
@ -113,6 +119,14 @@ public class LinuxBundlingEnvironment extends DefaultBundlingEnvironment {
|
||||
return new BuildEnvFromOptions().predefinedAppImageLayout(APPLICATION_LAYOUT);
|
||||
}
|
||||
|
||||
private static <T extends LinuxPackage> T updateSummary(
|
||||
T pkg, SummaryAccumulator summary, LinuxSystemEnvironment sysEnv) {
|
||||
if (!LinuxSystemEnvironment.isWithRequiredPackagesSearch(sysEnv, pkg)) {
|
||||
summary.put(StandardProperty.LINUX_DISABLE_REQUIRED_PACKAGES_SEARCH);
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
|
||||
private static Result<LinuxSystemEnvironment> adjustPackageArch(LinuxSystemEnvironment sysEnv, StandardPackageType type) {
|
||||
Objects.requireNonNull(sysEnv);
|
||||
Objects.requireNonNull(type);
|
||||
|
||||
@ -108,8 +108,9 @@ final class LinuxDebPackager extends LinuxPackager<LinuxDebPackage> {
|
||||
|
||||
Map<String, String> actualValues = Executor.of(cmdline)
|
||||
.saveOutput(true)
|
||||
.quiet()
|
||||
.executeExpectSuccess()
|
||||
.getOutput().stream()
|
||||
.stdout().stream()
|
||||
.map(line -> line.split(":\\s+", 2))
|
||||
.collect(Collectors.toMap(
|
||||
components -> components[0],
|
||||
@ -149,9 +150,7 @@ final class LinuxDebPackager extends LinuxPackager<LinuxDebPackage> {
|
||||
|
||||
List<String> cmdline = new ArrayList<>();
|
||||
Stream.of(sysEnv.fakeroot(), sysEnv.dpkgdeb()).map(Path::toString).forEach(cmdline::add);
|
||||
if (Log.isVerbose()) {
|
||||
cmdline.add("--verbose");
|
||||
}
|
||||
cmdline.add("--verbose");
|
||||
cmdline.addAll(List.of("-b", env.appImageDir().toString(), debFile.toAbsolutePath().toString()));
|
||||
|
||||
// run dpkg
|
||||
@ -276,8 +275,9 @@ final class LinuxDebPackager extends LinuxPackager<LinuxDebPackage> {
|
||||
var debArch = sysEnv.packageArch().value();
|
||||
|
||||
Executor.of(sysEnv.dpkg().toString(), "-S", file.toString())
|
||||
.quiet()
|
||||
.saveOutput(true).executeExpectSuccess()
|
||||
.getOutput().forEach(line -> {
|
||||
.stdout().forEach(line -> {
|
||||
Matcher matcher = PACKAGE_NAME_REGEX.matcher(line);
|
||||
if (matcher.find()) {
|
||||
String name = matcher.group(1);
|
||||
|
||||
@ -46,8 +46,11 @@ import jdk.jpackage.internal.model.LinuxApplication;
|
||||
import jdk.jpackage.internal.model.LinuxDebPackage;
|
||||
import jdk.jpackage.internal.model.LinuxLauncher;
|
||||
import jdk.jpackage.internal.model.LinuxLauncherMixin;
|
||||
import jdk.jpackage.internal.model.LinuxPackage;
|
||||
import jdk.jpackage.internal.model.LinuxRpmPackage;
|
||||
import jdk.jpackage.internal.model.StandardPackageType;
|
||||
import jdk.jpackage.internal.summary.StandardProperty;
|
||||
import jdk.jpackage.internal.summary.StandardWarning;
|
||||
|
||||
final class LinuxFromOptions {
|
||||
|
||||
@ -84,7 +87,11 @@ final class LinuxFromOptions {
|
||||
|
||||
LINUX_RPM_LICENSE_TYPE.ifPresentIn(options, pkgBuilder::licenseType);
|
||||
|
||||
return pkgBuilder.create();
|
||||
final var pkg = pkgBuilder.create();
|
||||
|
||||
updateSummary(options, pkg);
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
static LinuxDebPackage createLinuxDebPackage(Options options, LinuxDebSystemEnvironment sysEnv) {
|
||||
@ -99,9 +106,11 @@ final class LinuxFromOptions {
|
||||
|
||||
// Show warning if license file is missing
|
||||
if (pkg.licenseFile().isEmpty()) {
|
||||
Log.verbose(I18N.getString("message.debs-like-licenses"));
|
||||
OptionUtils.summary(options).put(StandardWarning.LINUX_DEB_MISSING_LICENSE_FILE);
|
||||
}
|
||||
|
||||
updateSummary(options, pkg);
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
@ -135,4 +144,9 @@ final class LinuxFromOptions {
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private static void updateSummary(Options options, LinuxPackage pkg) {
|
||||
OptionUtils.summary(options).put(StandardProperty.VERSION, pkg.versionWithRelease());
|
||||
OptionUtils.summary(options).put(StandardProperty.LINUX_PACKAGE_NAME, pkg.packageName());
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ record LinuxPackageArch(String value) {
|
||||
}
|
||||
|
||||
private static Result<String> deb() {
|
||||
var exec = Executor.of("dpkg", "--print-architecture").saveOutput(true);
|
||||
var exec = Executor.of("dpkg", "--print-architecture").quiet().saveOutput(true);
|
||||
return Result.of(exec::executeExpectSuccess, IOException.class)
|
||||
.flatMap(LinuxPackageArch::getStdoutFirstLine);
|
||||
}
|
||||
|
||||
@ -24,9 +24,10 @@
|
||||
*/
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import static jdk.jpackage.internal.LinuxSystemEnvironment.isWithRequiredPackagesSearch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -47,7 +48,7 @@ abstract class LinuxPackager<T extends LinuxPackage> implements Consumer<Packagi
|
||||
this.env = Objects.requireNonNull(env);
|
||||
this.pkg = Objects.requireNonNull(pkg);
|
||||
this.outputDir = Objects.requireNonNull(outputDir);
|
||||
this.withRequiredPackagesLookup = sysEnv.soLookupAvailable() && sysEnv.nativePackageType().equals(pkg.type());
|
||||
this.withRequiredPackagesLookup = isWithRequiredPackagesSearch(sysEnv, pkg);
|
||||
|
||||
customActions = List.of(
|
||||
DesktopIntegration.create(env, pkg),
|
||||
@ -135,19 +136,21 @@ abstract class LinuxPackager<T extends LinuxPackage> implements Consumer<Packagi
|
||||
final List<String> neededLibPackages;
|
||||
if (withRequiredPackagesLookup) {
|
||||
neededLibPackages = findRequiredPackages();
|
||||
Log.trace("Runtime requires: %s", neededLibPackages);
|
||||
} else {
|
||||
neededLibPackages = Collections.emptyList();
|
||||
Log.info(I18N.getString("warning.foreign-app-image"));
|
||||
}
|
||||
|
||||
Log.trace("Features of the package require: %s", caPackages);
|
||||
|
||||
// Merge all package lists together.
|
||||
// Filter out empty names, sort and remove duplicates.
|
||||
Stream.of(caPackages, neededLibPackages)
|
||||
requiredPackages = Stream.of(caPackages, neededLibPackages)
|
||||
.flatMap(List::stream)
|
||||
.filter(Predicate.not(String::isEmpty))
|
||||
.sorted().distinct().forEach(requiredPackages::add);
|
||||
.sorted().distinct().toList();
|
||||
|
||||
Log.verbose(String.format("Required packages: %s", requiredPackages));
|
||||
Log.trace("Required packages: %s", requiredPackages);
|
||||
}
|
||||
|
||||
private List<String> findRequiredPackages() throws IOException {
|
||||
@ -160,16 +163,16 @@ abstract class LinuxPackager<T extends LinuxPackage> implements Consumer<Packagi
|
||||
final List<? extends Exception> errors;
|
||||
try {
|
||||
errors = findErrorsInOutputPackage();
|
||||
} catch (Exception ex) {
|
||||
} catch (IOException ex) {
|
||||
// Ignore error as it is not critical. Just report it.
|
||||
Log.verbose(ex);
|
||||
Log.trace(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var ex : errors) {
|
||||
Log.verbose(ex.getLocalizedMessage());
|
||||
Log.progressWarning(ex);
|
||||
if (ex instanceof ConfigException cfgEx) {
|
||||
Log.verbose(cfgEx.getAdvice());
|
||||
Log.progress(cfgEx.getAdvice());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -178,6 +181,6 @@ abstract class LinuxPackager<T extends LinuxPackage> implements Consumer<Packagi
|
||||
protected final T pkg;
|
||||
protected final Path outputDir;
|
||||
private final boolean withRequiredPackagesLookup;
|
||||
private final List<String> requiredPackages = new ArrayList<>();
|
||||
private List<String> requiredPackages;
|
||||
private final List<ShellCustomAction> customActions;
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ final class LinuxRpmPackager extends LinuxPackager<LinuxRpmPackage> {
|
||||
return Executor.of(sysEnv.rpm().toString(),
|
||||
"-q", "--queryformat", "%{name}\\n",
|
||||
"-q", "--whatprovides", file.toString()
|
||||
).saveOutput(true).executeExpectSuccess().getOutput().stream();
|
||||
).saveOutput(true).quiet().executeExpectSuccess().stdout().stream();
|
||||
});
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ final class LinuxRpmPackager extends LinuxPackager<LinuxRpmPackage> {
|
||||
"-qp",
|
||||
"--queryformat", properties.stream().map(e -> String.format("%%{%s}", e.name())).collect(joining("\\n")),
|
||||
outputPackageFile().toString()
|
||||
).saveOutput(true).executeExpectSuccess().getOutput();
|
||||
).saveOutput(true).quiet().executeExpectSuccess().stdout();
|
||||
|
||||
for (int i = 0; i != properties.size(); i++) {
|
||||
Optional.ofNullable(properties.get(i).verifyValue(actualValues.get(i))).ifPresent(errors::add);
|
||||
|
||||
@ -27,8 +27,10 @@ package jdk.jpackage.internal;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import jdk.jpackage.internal.model.LinuxPackage;
|
||||
import jdk.jpackage.internal.model.PackageType;
|
||||
import jdk.jpackage.internal.model.StandardPackageType;
|
||||
import jdk.jpackage.internal.util.CompositeProxy;
|
||||
@ -45,6 +47,11 @@ interface LinuxSystemEnvironment extends SystemEnvironment {
|
||||
});
|
||||
}
|
||||
|
||||
static boolean isWithRequiredPackagesSearch(LinuxSystemEnvironment sysEnv, LinuxPackage pkg) {
|
||||
Objects.requireNonNull(pkg);
|
||||
return sysEnv.soLookupAvailable() && sysEnv.nativePackageType().equals(pkg.type());
|
||||
}
|
||||
|
||||
static Optional<StandardPackageType> detectNativePackageType() {
|
||||
if (Internal.isDebian()) {
|
||||
return Optional.of(StandardPackageType.LINUX_DEB);
|
||||
@ -89,7 +96,7 @@ interface LinuxSystemEnvironment extends SystemEnvironment {
|
||||
// we are just going to run "dpkg -s coreutils" and assume Debian
|
||||
// or derivative if no error is returned.
|
||||
try {
|
||||
Executor.of("dpkg", "-s", "coreutils").executeExpectSuccess();
|
||||
Executor.of("dpkg", "-s", "coreutils").quiet().executeExpectSuccess();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
// just fall thru
|
||||
@ -101,7 +108,7 @@ interface LinuxSystemEnvironment extends SystemEnvironment {
|
||||
// we are just going to run "rpm -q rpm" and assume RPM
|
||||
// or derivative if no error is returned.
|
||||
try {
|
||||
Executor.of("rpm", "-q", "rpm").executeExpectSuccess();
|
||||
Executor.of("rpm", "-q", "rpm").quiet().executeExpectSuccess();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
// just fall thru
|
||||
|
||||
@ -36,6 +36,9 @@ resource.menu-icon=menu icon
|
||||
resource.rpm-spec-file=RPM spec file
|
||||
resource.systemd-unit-file=systemd unit file
|
||||
|
||||
summary.property.linux-package-name=Package name
|
||||
summary.property.linux-required-packages-search=Required packages search
|
||||
|
||||
error.tool-not-found.advice=Please install required packages
|
||||
error.tool-old-version.advice=Please install required packages
|
||||
|
||||
@ -55,9 +58,6 @@ message.ldd-not-available=ldd command not found. Package dependencies will not b
|
||||
message.deb-ldd-not-available.advice=Install "libc-bin" DEB package to get ldd.
|
||||
message.rpm-ldd-not-available.advice=Install "glibc-common" RPM package to get ldd.
|
||||
|
||||
warning.foreign-app-image=Warning: app-image dir not generated by jpackage.
|
||||
message.not-default-bundler-no-dependencies-lookup={0} is not the default package type. Package dependencies will not be generated.
|
||||
|
||||
error.unexpected-package-property=Expected value of "{0}" property is [{1}]. Actual value in output package is [{2}]. Looks like the value of "{0}" property is hardcoded in "{3}" file in the resource directory
|
||||
error.unexpected-package-property.advice=Use [{0}] pattern string instead of hard coded value [{1}] of {2} property in custom "{3}" file
|
||||
error.unexpected-default-package-property.advice=Don''t explicitly set value of "{0}" property in custom "{1}" file
|
||||
|
||||
@ -84,6 +84,14 @@ final class AppImageSigner {
|
||||
|
||||
@Override
|
||||
public boolean test(Path path) {
|
||||
var accepted = testInternal(path);
|
||||
if (!accepted) {
|
||||
Log.trace("Skip signing [%s]", path);
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
|
||||
private boolean testInternal(Path path) {
|
||||
if (!Files.isRegularFile(path) || otherExcludePaths.contains(path)) {
|
||||
return false;
|
||||
}
|
||||
@ -139,7 +147,7 @@ final class AppImageSigner {
|
||||
if (Files.isDirectory(frameworkPath)) {
|
||||
try (var content = Files.list(frameworkPath)) {
|
||||
content.forEach(toConsumer(path -> {
|
||||
codesigners.codesignDir().accept(path);
|
||||
codesigners.codesignMacBundle().accept(path);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -167,14 +175,14 @@ final class AppImageSigner {
|
||||
private static IOException handleCodesignException(MacApplication app, CodesignException ex) {
|
||||
if (!app.contentDirSources().isEmpty()) {
|
||||
// Additional content may cause signing error.
|
||||
Log.fatalError(I18N.getString("message.codesign.failed.reason.app.content"));
|
||||
Log.progressWarning(I18N.getString("message.codesign.failed.reason.app.content"));
|
||||
}
|
||||
|
||||
// Signing might not work without Xcode with command line
|
||||
// developer tools. Show user if Xcode is missing as possible
|
||||
// reason.
|
||||
if (!isXcodeDevToolsInstalled()) {
|
||||
Log.fatalError(I18N.getString("message.codesign.failed.reason.xcode.tools"));
|
||||
Log.progressWarning(I18N.getString("message.codesign.failed.reason.xcode.tools"));
|
||||
}
|
||||
|
||||
return ex.getCause();
|
||||
@ -182,14 +190,14 @@ final class AppImageSigner {
|
||||
|
||||
private static boolean isXcodeDevToolsInstalled() {
|
||||
return Result.of(
|
||||
Executor.of("/usr/bin/xcrun", "--help").setQuiet(true)::executeExpectSuccess,
|
||||
Executor.of("/usr/bin/xcrun", "--help").quiet()::executeExpectSuccess,
|
||||
IOException.class).hasValue();
|
||||
}
|
||||
|
||||
private static void unsign(Path path) throws IOException {
|
||||
// run quietly
|
||||
Executor.of("/usr/bin/codesign", "--remove-signature", path.toString())
|
||||
.setQuiet(true)
|
||||
.quiet()
|
||||
.executeExpectSuccess();
|
||||
}
|
||||
|
||||
@ -201,23 +209,27 @@ final class AppImageSigner {
|
||||
this.codesigners = Objects.requireNonNull(codesigners);
|
||||
}
|
||||
|
||||
private record Codesigners(Consumer<Path> codesignFile, Consumer<Path> codesignExecutableFile, Consumer<Path> codesignDir) implements Consumer<Path> {
|
||||
private record Codesigners(
|
||||
Consumer<Path> codesignFile,
|
||||
Consumer<Path> codesignExecutableFile,
|
||||
Consumer<Path> codesignMacBundle) implements Consumer<Path> {
|
||||
|
||||
Codesigners {
|
||||
Objects.requireNonNull(codesignFile);
|
||||
Objects.requireNonNull(codesignExecutableFile);
|
||||
Objects.requireNonNull(codesignDir);
|
||||
Objects.requireNonNull(codesignMacBundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Path path) {
|
||||
findCodesigner(path).orElseThrow(() -> {
|
||||
return new IllegalArgumentException(String.format("No codesigner for %s path", PathUtils.normalizedAbsolutePathString(path)));
|
||||
return new IllegalArgumentException(String.format("No codesigner for [%s]", PathUtils.normalizedAbsolutePathString(path)));
|
||||
}).accept(path);
|
||||
}
|
||||
|
||||
private Optional<Consumer<Path>> findCodesigner(Path path) {
|
||||
if (Files.isDirectory(path)) {
|
||||
return Optional.of(codesignDir);
|
||||
if (MacBundle.fromPath(path).isPresent()) {
|
||||
return Optional.of(codesignMacBundle);
|
||||
} else if (Files.isRegularFile(path)) {
|
||||
if (Files.isExecutable(path)) {
|
||||
return Optional.of(codesignExecutableFile);
|
||||
@ -233,9 +245,9 @@ final class AppImageSigner {
|
||||
|
||||
final var codesignExecutableFile = Codesign.build(signingCfg::toCodesignArgs).quiet(true).create().asConsumer();
|
||||
final var codesignFile = Codesign.build(signingCfgWithoutEntitlements::toCodesignArgs).quiet(true).create().asConsumer();
|
||||
final var codesignDir = Codesign.build(signingCfg::toCodesignArgs).force(true).create().asConsumer();
|
||||
final var codesignMacBundle = Codesign.build(signingCfg::toCodesignArgs).force(true).create().asConsumer();
|
||||
|
||||
return new Codesigners(codesignFile, codesignExecutableFile, codesignDir);
|
||||
return new Codesigners(codesignFile, codesignExecutableFile, codesignMacBundle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ public final class Codesign {
|
||||
}
|
||||
|
||||
return new Codesign(cmdline, quiet ? exec -> {
|
||||
exec.setQuiet(true);
|
||||
exec.quiet();
|
||||
} : null);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
@ -72,7 +72,7 @@ record Keychain(String name) {
|
||||
// Get the current keychain list
|
||||
final List<String> cmdOutput;
|
||||
try {
|
||||
cmdOutput = Executor.of("/usr/bin/security", "list-keychains").saveOutput(true).executeExpectSuccess().getOutput();
|
||||
cmdOutput = Executor.of("/usr/bin/security", "list-keychains").quiet().saveOutput(true).executeExpectSuccess().stdout();
|
||||
} catch (IOException ex) {
|
||||
throw I18N.buildException().message("message.keychain.error").cause(ex).create(KeychainException::new);
|
||||
}
|
||||
|
||||
@ -25,11 +25,17 @@
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import static jdk.jpackage.internal.cli.StandardValidator.IS_VALID_MAC_BUNDLE_IDENTIFIER;
|
||||
import static java.util.stream.Collectors.groupingBy;
|
||||
import static java.util.stream.Collectors.mapping;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@ -45,6 +51,9 @@ import jdk.jpackage.internal.model.JPackageException;
|
||||
import jdk.jpackage.internal.model.Launcher;
|
||||
import jdk.jpackage.internal.model.MacApplication;
|
||||
import jdk.jpackage.internal.model.MacApplicationMixin;
|
||||
import jdk.jpackage.internal.summary.StandardWarning;
|
||||
import jdk.jpackage.internal.summary.StandardProperty;
|
||||
import jdk.jpackage.internal.summary.SummaryAccumulator;
|
||||
import jdk.jpackage.internal.util.PListReader;
|
||||
import jdk.jpackage.internal.util.Result;
|
||||
import jdk.jpackage.internal.util.RootedPath;
|
||||
@ -64,6 +73,7 @@ final class MacApplicationBuilder {
|
||||
appStore = other.appStore;
|
||||
externalInfoPlistFile = other.externalInfoPlistFile;
|
||||
signingBuilder = other.signingBuilder;
|
||||
summary = other.summary;
|
||||
}
|
||||
|
||||
MacApplicationBuilder icon(Path v) {
|
||||
@ -101,6 +111,11 @@ final class MacApplicationBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
MacApplicationBuilder summary(SummaryAccumulator v) {
|
||||
summary = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Optional<ExternalApplication> externalApplication() {
|
||||
return superBuilder.externalApplication();
|
||||
}
|
||||
@ -117,7 +132,9 @@ final class MacApplicationBuilder {
|
||||
var app = superBuilder.create();
|
||||
|
||||
validateAppVersion(app);
|
||||
validateAppContentDirs(app);
|
||||
summary().ifPresent(s -> {
|
||||
validateAppContentDirs(s, app);
|
||||
});
|
||||
|
||||
final var mixin = new MacApplicationMixin.Stub(
|
||||
validatedIcon(),
|
||||
@ -127,7 +144,14 @@ final class MacApplicationBuilder {
|
||||
appStore,
|
||||
createSigningConfig());
|
||||
|
||||
return MacApplication.create(app, mixin);
|
||||
var macApp = MacApplication.create(app, mixin);
|
||||
|
||||
summary().ifPresent(s -> {
|
||||
s.put(StandardProperty.MAC_BUNDLE_IDENTIFIER, macApp.bundleIdentifier());
|
||||
s.put(StandardProperty.MAC_BUNDLE_NAME, macApp.bundleName());
|
||||
});
|
||||
|
||||
return macApp;
|
||||
}
|
||||
|
||||
static MacApplication overrideAppImageLayout(MacApplication app, AppImageLayout appImageLayout) {
|
||||
@ -149,18 +173,59 @@ final class MacApplicationBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateAppContentDirs(Application app) {
|
||||
app.contentDirSources().stream().filter(rootedPath -> {
|
||||
private static Stream<Path> appContentTopPaths(Application app) {
|
||||
return app.contentDirSources().stream().filter(rootedPath -> {
|
||||
return rootedPath.branch().getNameCount() == 1;
|
||||
}).map(RootedPath::fullPath).forEach(contentDir -> {
|
||||
}).map(RootedPath::fullPath);
|
||||
}
|
||||
|
||||
private static void validateAppContentDirs(SummaryAccumulator summary, Application app) {
|
||||
var warnings = appContentTopPaths(app)
|
||||
.map(NonStandardAppContentWarning::createMapEntry)
|
||||
.flatMap(Optional::stream)
|
||||
.collect(groupingBy(
|
||||
Map.Entry::getKey,
|
||||
mapping(Map.Entry::getValue, toList())
|
||||
)).entrySet().stream()
|
||||
.sorted(Comparator.comparing(
|
||||
Map.Entry::getKey,
|
||||
Comparator.comparing(Enum::ordinal)
|
||||
))
|
||||
.map(Map.Entry::getValue)
|
||||
.flatMap(Collection::stream)
|
||||
.toList();
|
||||
if (!warnings.isEmpty()) {
|
||||
summary.putMultiValue(StandardWarning.MAC_NON_STANDARD_APP_CONTENT, warnings);
|
||||
}
|
||||
}
|
||||
|
||||
private enum NonStandardAppContentWarning {
|
||||
NOT_DIRECTORY("warning.non-standard-app-content.not-dir"),
|
||||
NON_STANDARD_DIRECTOTY_NAME("warning.non-standard-app-content.non-standard-dir-name"),
|
||||
;
|
||||
|
||||
NonStandardAppContentWarning(String formatKey) {
|
||||
this.formatKey = Objects.requireNonNull(formatKey);
|
||||
}
|
||||
|
||||
static Optional<Map.Entry<NonStandardAppContentWarning, String>> createMapEntry(Path contentDir) {
|
||||
if (!Files.isDirectory(contentDir)) {
|
||||
Log.info(I18N.format("warning.app.content.is.not.dir",
|
||||
contentDir));
|
||||
return Optional.of(Map.entry(NOT_DIRECTORY, NOT_DIRECTORY.format(contentDir)));
|
||||
} else if (!CONTENTS_SUB_DIRS.contains(contentDir.getFileName())) {
|
||||
Log.info(I18N.format("warning.non.standard.contents.sub.dir",
|
||||
contentDir));
|
||||
return Optional.of(Map.entry(
|
||||
NON_STANDARD_DIRECTOTY_NAME,
|
||||
NON_STANDARD_DIRECTOTY_NAME.format(contentDir.getFileName(), contentDir)
|
||||
));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String format(Object... formatArgs) {
|
||||
return I18N.format(formatKey, formatArgs);
|
||||
}
|
||||
|
||||
private final String formatKey;
|
||||
}
|
||||
|
||||
private MacApplicationBuilder createCopyForExternalInfoPlistFile() {
|
||||
@ -186,7 +251,10 @@ final class MacApplicationBuilder {
|
||||
}
|
||||
|
||||
if (builder.superBuilder.version().isEmpty()) {
|
||||
plist.findValue("CFBundleVersion").ifPresent(builder.superBuilder::version);
|
||||
plist.findValue("CFBundleVersion").ifPresent(ver -> {
|
||||
Log.trace("Derive bundle version [%s] from [%s] file", ver, externalInfoPlistFile);
|
||||
builder.superBuilder.version(ver);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -227,9 +295,11 @@ final class MacApplicationBuilder {
|
||||
return appName;
|
||||
});
|
||||
|
||||
if (value.length() > MAX_BUNDLE_NAME_LENGTH && (bundleName != null)) {
|
||||
Log.error(I18N.format("message.bundle-name-too-long-warning", "--mac-package-name", value));
|
||||
}
|
||||
summary().ifPresent(s -> {
|
||||
if (value.length() > MAX_BUNDLE_NAME_LENGTH && (bundleName != null)) {
|
||||
s.put(StandardWarning.MAC_BUNDLE_NAME_TOO_LONG, (Object)value);
|
||||
}
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
@ -261,7 +331,7 @@ final class MacApplicationBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
Log.verbose(I18N.format("message.derived-bundle-identifier", derivedValue));
|
||||
Log.trace("Derived bundle identifier: %s", derivedValue);
|
||||
return derivedValue;
|
||||
});
|
||||
}
|
||||
@ -274,6 +344,10 @@ final class MacApplicationBuilder {
|
||||
return Optional.ofNullable(icon).map(LauncherBuilder::validateIcon);
|
||||
}
|
||||
|
||||
private Optional<SummaryAccumulator> summary() {
|
||||
return Optional.ofNullable(summary);
|
||||
}
|
||||
|
||||
private record Defaults(String category) {
|
||||
}
|
||||
|
||||
@ -284,6 +358,7 @@ final class MacApplicationBuilder {
|
||||
private boolean appStore;
|
||||
private Path externalInfoPlistFile;
|
||||
private AppImageSigningConfigBuilder signingBuilder;
|
||||
private SummaryAccumulator summary;
|
||||
|
||||
private final ApplicationBuilder superBuilder;
|
||||
|
||||
|
||||
@ -35,8 +35,11 @@ import static jdk.jpackage.internal.cli.StandardOption.EXIT_AFTER_CONFIGURATION_
|
||||
|
||||
import java.util.Optional;
|
||||
import jdk.jpackage.internal.cli.Options;
|
||||
import jdk.jpackage.internal.model.AppImageBundleType;
|
||||
import jdk.jpackage.internal.model.MacPackage;
|
||||
import jdk.jpackage.internal.model.Package;
|
||||
import jdk.jpackage.internal.summary.StandardProperty;
|
||||
import jdk.jpackage.internal.util.PathUtils;
|
||||
|
||||
public class MacBundlingEnvironment extends DefaultBundlingEnvironment {
|
||||
|
||||
@ -75,6 +78,12 @@ public class MacBundlingEnvironment extends DefaultBundlingEnvironment {
|
||||
|
||||
final var pkg = createSignAppImagePackage(app, env);
|
||||
|
||||
OptionUtils.summary(options).put(StandardProperty.MAC_SIGN_APP_IMAGE_OPERATION,
|
||||
AppImageBundleType.MAC_APP_IMAGE.label(),
|
||||
PathUtils.normalizedAbsolutePath(env.appImageDir()));
|
||||
|
||||
OptionUtils.finalizeAndPrintSummary(options, pkg);
|
||||
|
||||
if (EXIT_AFTER_CONFIGURATION_PHASE.getFrom(options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ public final class MacCertificateUtils {
|
||||
|
||||
return toSupplier(() -> {
|
||||
final var output = Executor.of(args)
|
||||
.setQuiet(true).saveOutput(true).executeExpectSuccess()
|
||||
.quiet().saveOutput(true).executeExpectSuccess()
|
||||
.getOutput();
|
||||
|
||||
final byte[] pemCertificatesBuffer = output.stream()
|
||||
|
||||
@ -32,7 +32,6 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -139,9 +138,7 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
|
||||
|
||||
private void prepareDMGSetupScript() throws IOException {
|
||||
Path dmgSetup = volumeScript();
|
||||
Log.verbose(MessageFormat.format(
|
||||
I18N.getString("message.preparing-dmg-setup"),
|
||||
dmgSetup.toAbsolutePath().toString()));
|
||||
Log.progress(I18N.format("message.preparing-dmg-setup", dmgSetup.toAbsolutePath().toString()));
|
||||
|
||||
// Prepare DMG setup script
|
||||
Map<String, String> data = new HashMap<>();
|
||||
@ -202,10 +199,6 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
|
||||
}
|
||||
}
|
||||
|
||||
private String hdiUtilVerbosityFlag() {
|
||||
return env.verbose() ? "-verbose" : "-quiet";
|
||||
}
|
||||
|
||||
private void buildDMG() throws IOException {
|
||||
boolean copyAppImage = false;
|
||||
|
||||
@ -217,7 +210,7 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
|
||||
Files.createDirectories(protoDMG.getParent());
|
||||
Files.createDirectories(finalDMG.getParent());
|
||||
|
||||
final String hdiUtilVerbosityFlag = hdiUtilVerbosityFlag();
|
||||
final String hdiUtilVerbosityFlag = "-verbose";
|
||||
|
||||
// create temp image
|
||||
try {
|
||||
@ -229,7 +222,9 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
|
||||
"-fs", "HFS+",
|
||||
"-format", "UDRW").executeExpectSuccess();
|
||||
} catch (IOException ex) {
|
||||
Log.verbose(ex); // Log exception
|
||||
Log.trace(ex, "Failed to create a DMG from the entire app image");
|
||||
|
||||
Log.trace("Will create an empty DMG and fill it manually");
|
||||
|
||||
// Creating DMG from entire app image failed, so lets try to create empty
|
||||
// DMG and copy files manually. See JDK-8248059.
|
||||
@ -285,7 +280,7 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
|
||||
.timeout(3, TimeUnit.MINUTES)
|
||||
.executeExpectSuccess();
|
||||
} catch (IOException ex) {
|
||||
Log.verbose(ex);
|
||||
Log.trace(ex, "Failed to set background image");
|
||||
}
|
||||
|
||||
// volume icon
|
||||
@ -317,11 +312,10 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
|
||||
normalizedAbsolutePathString(mountedVolume)
|
||||
).executeExpectSuccess();
|
||||
} catch (IOException ex) {
|
||||
Log.error(ex.getMessage());
|
||||
Log.verbose("Cannot enable custom icon using SetFile utility");
|
||||
Log.trace(ex, "Failed to set custom icon");
|
||||
}
|
||||
} else {
|
||||
Log.verbose(I18N.getString("message.setfile.dmg"));
|
||||
Log.progress(I18N.format("message.setfile.dmg"));
|
||||
}
|
||||
|
||||
} finally {
|
||||
@ -345,12 +339,7 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
|
||||
.execute();
|
||||
}
|
||||
|
||||
try {
|
||||
//Delete the temporary image
|
||||
Files.deleteIfExists(protoDMG);
|
||||
} catch (IOException ex) {
|
||||
// Don't care if fails
|
||||
}
|
||||
IOUtils.deleteIfExistsIgnoreError(protoDMG);
|
||||
}
|
||||
|
||||
private void detachVolume() throws IOException {
|
||||
@ -369,7 +358,7 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
|
||||
}
|
||||
|
||||
cmdline.addAll(List.of(
|
||||
hdiUtilVerbosityFlag(),
|
||||
"-verbose",
|
||||
normalizedAbsolutePathString(mountedVolume)
|
||||
));
|
||||
|
||||
@ -392,7 +381,7 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
|
||||
return hdiutil(
|
||||
"convert",
|
||||
normalizedAbsolutePathString(srcDmg),
|
||||
hdiUtilVerbosityFlag(),
|
||||
"-verbose",
|
||||
"-format", "UDZO",
|
||||
"-o", normalizedAbsolutePathString(finalDmg()));
|
||||
};
|
||||
@ -404,14 +393,17 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
|
||||
.setAttemptTimeout(3, TimeUnit.SECONDS)
|
||||
.execute();
|
||||
} catch (IOException ex) {
|
||||
Log.verbose(ex);
|
||||
Log.trace(ex, "Failed to convert an interim DMG into the output DMG");
|
||||
|
||||
Log.trace("Try to convert a copy of an interim DMG into the output DMG");
|
||||
|
||||
// Something holds the file, try to convert a copy.
|
||||
Path copyDmg = protoCopyDmg();
|
||||
Files.copy(protoDmg(), copyDmg);
|
||||
try {
|
||||
convert.apply(copyDmg).executeExpectSuccess();
|
||||
} finally {
|
||||
Files.deleteIfExists(copyDmg);
|
||||
IOUtils.deleteIfExistsIgnoreError(copyDmg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,11 +70,11 @@ record MacDmgSystemEnvironment(Path hdiutil, Path osascript, Optional<Path> setF
|
||||
return SETFILE_KNOWN_PATHS.stream().filter(setFilePath -> {
|
||||
// Validate SetFile, if Xcode is not installed it will run, but exit with error code
|
||||
return Result.of(
|
||||
Executor.of(setFilePath.toString(), "-h").setQuiet(true)::executeExpectSuccess,
|
||||
Executor.of(setFilePath.toString(), "-h").quiet()::executeExpectSuccess,
|
||||
IOException.class).hasValue();
|
||||
}).findFirst().or(() -> {
|
||||
// generic find attempt
|
||||
final var executor = Executor.of("/usr/bin/xcrun", "-find", "SetFile").setQuiet(true).saveFirstLineOfOutput();
|
||||
final var executor = Executor.of("/usr/bin/xcrun", "-find", "SetFile").quiet().saveFirstLineOfOutput();
|
||||
|
||||
return Result.of(executor::executeExpectSuccess, IOException.class).flatMap(execResult -> {
|
||||
return Result.of(() -> {
|
||||
|
||||
@ -186,7 +186,7 @@ final class MacFromOptions {
|
||||
|
||||
pkgSigningIdentityBuilder.ifPresent(pkgBuilder::signingBuilder);
|
||||
|
||||
return pkgBuilder.create();
|
||||
return pkgBuilder.summary(OptionUtils.summary(options)).create();
|
||||
}
|
||||
|
||||
private record ApplicationWithDetails(MacApplication app, Optional<ExternalApplication> externalApp) {
|
||||
@ -243,6 +243,8 @@ final class MacFromOptions {
|
||||
|
||||
final var appBuilder = new MacApplicationBuilder(createApplicationBuilder(options));
|
||||
|
||||
appBuilder.summary(OptionUtils.summary(options));
|
||||
|
||||
if (OptionUtils.isRuntimeInstaller(options)) {
|
||||
// Predefined runtime image, if specified, can be a macOS bundle or regular directory.
|
||||
// Notify application builder with the path to the plist file in the predefined runtime image only if the file exists.
|
||||
@ -344,6 +346,8 @@ final class MacFromOptions {
|
||||
.ifPresent(builder::predefinedAppImageSigned);
|
||||
}
|
||||
|
||||
builder.summary(OptionUtils.summary(options));
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@ -29,9 +29,12 @@ import static jdk.jpackage.internal.MacPackagingPipeline.LayoutUtils.packagerLay
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import jdk.jpackage.internal.model.MacApplication;
|
||||
import jdk.jpackage.internal.model.MacPackage;
|
||||
import jdk.jpackage.internal.model.MacPackageMixin;
|
||||
import jdk.jpackage.internal.summary.SummaryAccumulator;
|
||||
import jdk.jpackage.internal.summary.StandardWarning;
|
||||
|
||||
final class MacPackageBuilder {
|
||||
|
||||
@ -44,6 +47,11 @@ final class MacPackageBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
MacPackageBuilder summary(SummaryAccumulator v) {
|
||||
summary = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
PackageBuilder pkgBuilder() {
|
||||
return pkgBuilder;
|
||||
}
|
||||
@ -60,16 +68,22 @@ final class MacPackageBuilder {
|
||||
pkg = pkgBuilder.create();
|
||||
|
||||
var macPkg = MacPackage.create(pkg, new MacPackageMixin.Stub(pkg.predefinedAppImage().map(v -> predefinedAppImageSigned)));
|
||||
validatePredefinedAppImage(macPkg);
|
||||
summary().ifPresent(s -> {
|
||||
validatePredefinedAppImage(s, macPkg);
|
||||
});
|
||||
return macPkg;
|
||||
}
|
||||
|
||||
private static void validatePredefinedAppImage(MacPackage pkg) {
|
||||
private Optional<SummaryAccumulator> summary() {
|
||||
return Optional.ofNullable(summary);
|
||||
}
|
||||
|
||||
private static void validatePredefinedAppImage(SummaryAccumulator summary, MacPackage pkg) {
|
||||
if (pkg.predefinedAppImageSigned().orElse(false) && !pkg.isRuntimeInstaller()) {
|
||||
pkg.predefinedAppImage().ifPresent(predefinedAppImage -> {
|
||||
var thePackageFile = PackageFile.getPathInAppImage(APPLICATION_LAYOUT);
|
||||
if (!Files.exists(predefinedAppImage.resolve(thePackageFile))) {
|
||||
Log.info(I18N.format("warning.per.user.app.image.signed", thePackageFile));
|
||||
summary.put(StandardWarning.MAC_SIGNED_PREDEFINED_APP_IMAGE_WITHOUT_PACKAGE_FILE, thePackageFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -77,4 +91,5 @@ final class MacPackageBuilder {
|
||||
|
||||
private final PackageBuilder pkgBuilder;
|
||||
private boolean predefinedAppImageSigned;
|
||||
private SummaryAccumulator summary;
|
||||
}
|
||||
|
||||
@ -291,7 +291,7 @@ final class MacPackagingPipeline {
|
||||
"--raw",
|
||||
"--assess",
|
||||
"--type", "exec",
|
||||
bundle.root().toString()).setQuiet(true).saveOutput(true).binaryOutput()::execute).get();
|
||||
bundle.root().toString()).quiet().saveOutput(true).binaryOutput()::execute).get();
|
||||
|
||||
switch (result.getExitCode()) {
|
||||
case 0, 3 -> {
|
||||
@ -445,7 +445,7 @@ final class MacPackagingPipeline {
|
||||
|
||||
final var infoPlistFile = macBundleFromAppImageLayout(env.resolvedLayout()).orElseThrow().infoPlistFile();
|
||||
|
||||
Log.verbose(I18N.format("message.preparing-info-plist", PathUtils.normalizedAbsolutePathString(infoPlistFile)));
|
||||
Log.progress(I18N.format("message.preparing-info-plist", PathUtils.normalizedAbsolutePathString(infoPlistFile)));
|
||||
|
||||
final String faXml = toSupplier(() -> {
|
||||
var buf = new StringWriter();
|
||||
|
||||
@ -29,6 +29,8 @@ import java.util.Optional;
|
||||
import jdk.jpackage.internal.model.MacPkgPackage;
|
||||
import jdk.jpackage.internal.model.MacPkgPackageMixin;
|
||||
import jdk.jpackage.internal.model.PkgSigningConfig;
|
||||
import jdk.jpackage.internal.summary.StandardWarning;
|
||||
import jdk.jpackage.internal.summary.SummaryAccumulator;
|
||||
|
||||
final class MacPkgPackageBuilder {
|
||||
|
||||
@ -41,9 +43,16 @@ final class MacPkgPackageBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
MacPkgPackageBuilder summary(SummaryAccumulator v) {
|
||||
summary = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
MacPkgPackage create() {
|
||||
var pkg = MacPkgPackage.create(pkgBuilder.create(), new MacPkgPackageMixin.Stub(createSigningConfig()));
|
||||
validatePredefinedAppImage(pkg);
|
||||
summary().ifPresent(s -> {
|
||||
validatePredefinedAppImage(s, pkg);
|
||||
});
|
||||
return pkg;
|
||||
}
|
||||
|
||||
@ -53,14 +62,19 @@ final class MacPkgPackageBuilder {
|
||||
});
|
||||
}
|
||||
|
||||
private static void validatePredefinedAppImage(MacPkgPackage pkg) {
|
||||
private Optional<SummaryAccumulator> summary() {
|
||||
return Optional.ofNullable(summary);
|
||||
}
|
||||
|
||||
private static void validatePredefinedAppImage(SummaryAccumulator summary, MacPkgPackage pkg) {
|
||||
if (!pkg.predefinedAppImageSigned().orElse(false) && pkg.sign()) {
|
||||
pkg.predefinedAppImage().ifPresent(predefinedAppImage -> {
|
||||
Log.info(I18N.format("warning.unsigned.app.image", "pkg"));
|
||||
summary.put(StandardWarning.MAC_SIGNED_PKG_WITH_UNSIGNED_PREDEFINED_APP_IMAGE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private final MacPackageBuilder pkgBuilder;
|
||||
private SigningIdentityBuilder signingBuilder;
|
||||
private SummaryAccumulator summary;
|
||||
}
|
||||
|
||||
@ -34,7 +34,6 @@ import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -216,6 +215,7 @@ record MacPkgPackager(BuildEnv env, MacPkgPackage pkg, Optional<Services> servic
|
||||
pipelineBuilder
|
||||
.task(PkgPackageTaskID.PREPARE_MAIN_SCRIPTS)
|
||||
.action(this::prepareMainScripts)
|
||||
.logActionBegin("message.preparing-scripts")
|
||||
.addDependent(PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT)
|
||||
.add()
|
||||
.task(PkgPackageTaskID.LOG_NO_MAIN_SCRIPTS)
|
||||
@ -223,6 +223,7 @@ record MacPkgPackager(BuildEnv env, MacPkgPackage pkg, Optional<Services> servic
|
||||
.addDependent(PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT)
|
||||
.add()
|
||||
.task(PkgPackageTaskID.CREATE_DISTRIBUTION_XML_FILE)
|
||||
.logActionBegin("message.preparing-distribution-dist", distributionXmlFile())
|
||||
.action(this::prepareDistributionXMLFile)
|
||||
.addDependent(PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT)
|
||||
.add()
|
||||
@ -350,7 +351,6 @@ record MacPkgPackager(BuildEnv env, MacPkgPackage pkg, Optional<Services> servic
|
||||
}
|
||||
|
||||
private void prepareMainScripts() throws IOException {
|
||||
Log.verbose(I18N.getString("message.preparing-scripts"));
|
||||
|
||||
final var scriptsRoot = scriptsRoot().orElseThrow();
|
||||
|
||||
@ -371,9 +371,6 @@ record MacPkgPackager(BuildEnv env, MacPkgPackage pkg, Optional<Services> servic
|
||||
private void prepareDistributionXMLFile() throws IOException {
|
||||
final var f = distributionXmlFile();
|
||||
|
||||
Log.verbose(MessageFormat.format(I18N.getString(
|
||||
"message.preparing-distribution-dist"), f.toAbsolutePath().toString()));
|
||||
|
||||
XmlUtils.createXml(f, xml -> {
|
||||
xml.writeStartElement("installer-gui-script");
|
||||
xml.writeAttribute("minSpecVersion", "1");
|
||||
|
||||
@ -53,11 +53,14 @@ resource.pkg-background-image=pkg background image
|
||||
resource.pkg-pdf=project definition file
|
||||
resource.launchd-plist-file=launchd plist file
|
||||
|
||||
message.bundle-name-too-long-warning={0} is set to ''{1}'', which is longer than 16 characters. For a better Mac experience consider shortening it.
|
||||
summary.property.mac-bundle-identifier=CFBundleIdentifier
|
||||
summary.property.mac-bundle-name=CFBundleName
|
||||
summary.property.mac-sign-app-image.format=Sign {0} in "{1}" directory
|
||||
|
||||
warning.bundle-name-too-long-warning=Bundle name "{0}" is longer than 16 characters. For a better Mac experience consider shortening it.
|
||||
message.preparing-info-plist=Preparing Info.plist: {0}.
|
||||
message.icon-not-icns= The specified icon "{0}" is not an ICNS file and will not be used. The default icon will be used in it's place.
|
||||
message.keychain.error=Unable to get keychain list.
|
||||
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}.
|
||||
@ -69,7 +72,8 @@ message.dmg.license.button.disagree=Disagree
|
||||
message.dmg.license.button.print=Print
|
||||
message.dmg.license.button.save=Save...
|
||||
message.dmg.license.message=If you agree with the terms of this license, press "Agree" to install the software. If you do not agree, press "Disagree".
|
||||
warning.unsigned.app.image=Warning: Using unsigned app-image to build signed {0}.
|
||||
warning.per.user.app.image.signed=Warning: Support for per-user configuration of the installed application will not be supported due to missing "{0}" in predefined signed application image.
|
||||
warning.non.standard.contents.sub.dir=Warning: The file name of the directory "{0}" specified for the --app-content option is not a standard subdirectory name in the "Contents" directory of the application bundle. The result application bundle may fail code signing and/or notarization.
|
||||
warning.app.content.is.not.dir=Warning: The value "{0}" of the --app-content option is not a directory. The result application bundle may fail code signing and/or notarization.
|
||||
warning.unsigned.app.image=Unsigned predefined application image with signed output package
|
||||
warning.per.user.app.image.signed=Per-user configuration of the installed application will not be supported due to missing "{0}" file in the signed predefined application image
|
||||
warning.non-standard-app-content=The value of --app-content option may result into failure signing and/or notarizing of the result application bundle
|
||||
warning.non-standard-app-content.not-dir="{0}" is not a directory
|
||||
warning.non-standard-app-content.non-standard-dir-name=The name "{0}" of directory "{1}" is not a standard subdirectory name in the "Contents" directory of a macOS bundle
|
||||
@ -192,7 +192,7 @@ final class ApplicationBuilder {
|
||||
derivedVersion = derivedVersion.map(v -> {
|
||||
var mappedVersion = derivedVersionNormalizer.apply(v);
|
||||
if (!mappedVersion.equals(v)) {
|
||||
Log.verbose(I18N.format("message.version-normalized", mappedVersion, v));
|
||||
Log.trace("Normalize derived bundle version from [%s] to [%s]", v, mappedVersion);
|
||||
}
|
||||
return mappedVersion;
|
||||
});
|
||||
@ -205,10 +205,10 @@ final class ApplicationBuilder {
|
||||
if (appImageLayout instanceof RuntimeLayout && runtimeReleaseFile != null) {
|
||||
try {
|
||||
var releaseVersion = new RuntimeReleaseFile(runtimeReleaseFile).getJavaVersion().toString();
|
||||
Log.verbose(I18N.format("message.release-version", releaseVersion));
|
||||
Log.trace("Derive bundle version [%s] from [%s] file", releaseVersion, runtimeReleaseFile);
|
||||
return Optional.of(releaseVersion);
|
||||
} catch (Exception ex) {
|
||||
Log.verbose(ex);
|
||||
Log.trace(ex, "Failed to derive bundle version from [%s] file", runtimeReleaseFile);
|
||||
return Optional.empty();
|
||||
}
|
||||
} else if (launchers != null) {
|
||||
@ -218,7 +218,7 @@ final class ApplicationBuilder {
|
||||
.flatMap(modularStartupInfo -> {
|
||||
var moduleVersion = modularStartupInfo.moduleVersion();
|
||||
moduleVersion.ifPresent(v -> {
|
||||
Log.verbose(I18N.format("message.module-version", v, modularStartupInfo.moduleName()));
|
||||
Log.trace("Derive bundle version [%s] from [%s] module", v, modularStartupInfo.moduleName());
|
||||
});
|
||||
return moduleVersion;
|
||||
});
|
||||
|
||||
@ -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
|
||||
@ -42,13 +42,6 @@ interface BuildEnv {
|
||||
*/
|
||||
Path buildRoot();
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if the build should be verbose output.
|
||||
*
|
||||
* @return <code>true</code> if the build should be verbose output
|
||||
*/
|
||||
boolean verbose();
|
||||
|
||||
/**
|
||||
* Returns the path of the resource directory or an empty {@link Optional}
|
||||
* instance if none is configured with the build.
|
||||
@ -108,15 +101,25 @@ interface BuildEnv {
|
||||
return ((Internal.DefaultBuildEnv)env).copyWithAppImageLayout(appImageLayout);
|
||||
}
|
||||
|
||||
static BuildEnv create(Path buildRoot, Optional<Path> resourceDir, boolean verbose,
|
||||
Class<?> resourceLocator, AppImageLayout appImageLayout) {
|
||||
return new Internal.DefaultBuildEnv(buildRoot, resourceDir, verbose,
|
||||
resourceLocator, appImageLayout);
|
||||
static BuildEnv create(
|
||||
Path buildRoot,
|
||||
Optional<Path> resourceDir,
|
||||
Class<?> resourceLocator,
|
||||
AppImageLayout appImageLayout) {
|
||||
|
||||
return new Internal.DefaultBuildEnv(
|
||||
buildRoot,
|
||||
resourceDir,
|
||||
resourceLocator,
|
||||
appImageLayout);
|
||||
}
|
||||
|
||||
static final class Internal {
|
||||
private record DefaultBuildEnv(Path buildRoot, Optional<Path> resourceDir,
|
||||
boolean verbose, Class<?> resourceLocator,
|
||||
|
||||
private record DefaultBuildEnv(
|
||||
Path buildRoot,
|
||||
Optional<Path> resourceDir,
|
||||
Class<?> resourceLocator,
|
||||
AppImageLayout appImageLayout) implements BuildEnv {
|
||||
|
||||
DefaultBuildEnv {
|
||||
@ -131,7 +134,7 @@ interface BuildEnv {
|
||||
}
|
||||
|
||||
DefaultBuildEnv copyWithAppImageLayout(AppImageLayout v) {
|
||||
return new DefaultBuildEnv(buildRoot, resourceDir, verbose, resourceLocator, v);
|
||||
return new DefaultBuildEnv(buildRoot, resourceDir, resourceLocator, v);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -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
|
||||
@ -48,13 +48,11 @@ final class BuildEnvBuilder {
|
||||
String.format("Root work directory [%s] should be empty or non existent", root));
|
||||
}
|
||||
|
||||
return BuildEnv.create(root, Optional.ofNullable(resourceDir), verbose,
|
||||
ResourceLocator.class, resolvedAppImageLayout());
|
||||
}
|
||||
|
||||
BuildEnvBuilder verbose(boolean v) {
|
||||
verbose = v;
|
||||
return this;
|
||||
return BuildEnv.create(
|
||||
root,
|
||||
Optional.ofNullable(resourceDir),
|
||||
ResourceLocator.class,
|
||||
resolvedAppImageLayout());
|
||||
}
|
||||
|
||||
BuildEnvBuilder resourceDir(Path v) {
|
||||
@ -96,7 +94,6 @@ final class BuildEnvBuilder {
|
||||
private Path appImageDir;
|
||||
private AppImageLayout appImageLayout;
|
||||
private Path resourceDir;
|
||||
private boolean verbose;
|
||||
|
||||
private final Path root;
|
||||
}
|
||||
|
||||
@ -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 jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.RESOURCE_DIR;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.TEMP_ROOT;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.VERBOSE;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
@ -82,7 +81,6 @@ final class BuildEnvFromOptions {
|
||||
final var builder = new BuildEnvBuilder(TEMP_ROOT.getFrom(options));
|
||||
|
||||
RESOURCE_DIR.ifPresentIn(options, builder::resourceDir);
|
||||
VERBOSE.ifPresentIn(options, builder::verbose);
|
||||
|
||||
if (app.isRuntime()) {
|
||||
var path = PREDEFINED_RUNTIME_IMAGE.getFrom(options);
|
||||
|
||||
@ -130,13 +130,15 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
|
||||
Objects.requireNonNull(app);
|
||||
Objects.requireNonNull(pipelineBuilder);
|
||||
|
||||
OptionUtils.finalizeAndPrintSummary(options, app);
|
||||
|
||||
if (EXIT_AFTER_CONFIGURATION_PHASE.getFrom(options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var outputDir = PathUtils.normalizedAbsolutePath(OptionUtils.outputDir(options).resolve(app.appImageDirName()));
|
||||
|
||||
Log.verbose(I18N.getString("message.create-app-image"));
|
||||
Log.progress(I18N.format("message.create-app-image"));
|
||||
|
||||
IOUtils.writableOutputDir(outputDir.getParent());
|
||||
|
||||
@ -150,7 +152,7 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
|
||||
|
||||
pipelineBuilder.create().execute(BuildEnv.withAppImageDir(env, outputDir), app);
|
||||
|
||||
Log.verbose(I18N.getString("message.app-image-created"));
|
||||
Log.progress(I18N.format("message.app-image-created"));
|
||||
}
|
||||
|
||||
static <T extends Package> void createNativePackage(Options options,
|
||||
@ -175,6 +177,8 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
|
||||
Objects.requireNonNull(createPipelineBuilder);
|
||||
Objects.requireNonNull(pipelineBuilderMutatorFactory);
|
||||
|
||||
OptionUtils.finalizeAndPrintSummary(options, pkg);
|
||||
|
||||
if (EXIT_AFTER_CONFIGURATION_PHASE.getFrom(options)) {
|
||||
return;
|
||||
}
|
||||
@ -203,6 +207,9 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
|
||||
@Override
|
||||
public void createBundle(BundlingOperationDescriptor op, Options cmdline) {
|
||||
final var bundler = getBundlerSupplier(op).get().orElseThrow();
|
||||
|
||||
cmdline = OptionUtils.addSummary(cmdline);
|
||||
|
||||
Optional<Path> permanentWorkDirectory = Optional.empty();
|
||||
try (var tempDir = new TempDirectory(cmdline, Globals.instance().objectFactory())) {
|
||||
if (!tempDir.deleteOnClose()) {
|
||||
@ -213,7 +220,7 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
|
||||
throw new UncheckedIOException(ex);
|
||||
} finally {
|
||||
permanentWorkDirectory.ifPresent(workDir -> {
|
||||
Log.verbose(I18N.format("message.debug-working-directory", workDir.toAbsolutePath()));
|
||||
Log.progress(I18N.format("message.debug-working-directory", workDir.toAbsolutePath()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,11 +24,11 @@
|
||||
*/
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import static jdk.jpackage.internal.log.StandardLogger.COMMAND_LOGGER;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.spi.ToolProvider;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.log.CommandLogger;
|
||||
import jdk.jpackage.internal.model.ExecutableAttributesWithCapturedOutput;
|
||||
import jdk.jpackage.internal.util.CommandLineFormat;
|
||||
import jdk.jpackage.internal.util.CommandOutputControl;
|
||||
@ -175,11 +176,15 @@ public final class Executor {
|
||||
return args;
|
||||
}
|
||||
|
||||
public Executor setQuiet(boolean v) {
|
||||
public Executor quiet(boolean v) {
|
||||
quietCommand = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Executor quiet() {
|
||||
return quiet(true);
|
||||
}
|
||||
|
||||
public Executor mapper(UnaryOperator<Executor> v) {
|
||||
mapper = v;
|
||||
return this;
|
||||
@ -212,8 +217,10 @@ public final class Executor {
|
||||
throw new IllegalStateException("No target to execute");
|
||||
}
|
||||
|
||||
if (dumpOutput()) {
|
||||
Log.verbose(String.format("Running %s", CommandLineFormat.DEFAULT.apply(List.of(commandLine().getFirst()))));
|
||||
var logger = logger();
|
||||
|
||||
if (logger.enabled()) {
|
||||
logger.beforeCommandExecuted(quietCommand, CommandLineFormat.DEFAULT.apply(commandLine()));
|
||||
}
|
||||
|
||||
var printableOutputBuilder = new PrintableOutputBuilder(coc);
|
||||
@ -229,8 +236,21 @@ public final class Executor {
|
||||
}
|
||||
|
||||
var printableOutput = printableOutputBuilder.create();
|
||||
if (dumpOutput()) {
|
||||
log(result, printableOutput);
|
||||
|
||||
if (logger.enabled()) {
|
||||
Optional<Long> pid;
|
||||
if (result.execAttrs() instanceof ProcessAttributes attrs) {
|
||||
pid = attrs.pid();
|
||||
} else {
|
||||
pid = Optional.empty();
|
||||
}
|
||||
|
||||
logger.afterCommandExecuted(
|
||||
quietCommand,
|
||||
result.execAttrs().printableCommandLine(),
|
||||
pid,
|
||||
result.exitCode(),
|
||||
printableOutput);
|
||||
}
|
||||
|
||||
return ExecutableAttributesWithCapturedOutput.augmentResultWithOutput(result, printableOutput);
|
||||
@ -271,6 +291,10 @@ public final class Executor {
|
||||
}
|
||||
}
|
||||
|
||||
private CommandLogger logger() {
|
||||
return Globals.instance().logger(COMMAND_LOGGER);
|
||||
}
|
||||
|
||||
private ProcessBuilder copyProcessBuilder() {
|
||||
if (processBuilder == null) {
|
||||
throw new IllegalStateException();
|
||||
@ -285,47 +309,6 @@ public final class Executor {
|
||||
return copy;
|
||||
}
|
||||
|
||||
private boolean dumpOutput() {
|
||||
return Log.isVerbose() && !quietCommand;
|
||||
}
|
||||
|
||||
private static void log(Result result, String printableOutput) throws IOException {
|
||||
Objects.requireNonNull(result);
|
||||
Objects.requireNonNull(printableOutput);
|
||||
|
||||
Optional<Long> pid;
|
||||
if (result.execAttrs() instanceof ProcessAttributes attrs) {
|
||||
pid = attrs.pid();
|
||||
} else {
|
||||
pid = Optional.empty();
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.append("Command");
|
||||
pid.ifPresent(p -> {
|
||||
sb.append(" [PID: ").append(p).append("]");
|
||||
});
|
||||
sb.append(":\n ").append(result.execAttrs().printableCommandLine());
|
||||
Log.verbose(sb.toString());
|
||||
|
||||
if (!printableOutput.isEmpty()) {
|
||||
sb.delete(0, sb.length());
|
||||
sb.append("Output:");
|
||||
try (var lines = new BufferedReader(new StringReader(printableOutput)).lines()) {
|
||||
lines.forEach(line -> {
|
||||
sb.append("\n ").append(line);
|
||||
});
|
||||
}
|
||||
Log.verbose(sb.toString());
|
||||
}
|
||||
|
||||
result.exitCode().ifPresentOrElse(exitCode -> {
|
||||
Log.verbose("Returned: " + exitCode + "\n");
|
||||
}, () -> {
|
||||
Log.verbose("Aborted: timed-out" + "\n");
|
||||
});
|
||||
}
|
||||
|
||||
private static final class PrintableOutputBuilder {
|
||||
|
||||
PrintableOutputBuilder(CommandOutputControl coc) {
|
||||
|
||||
@ -24,12 +24,14 @@
|
||||
*/
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import jdk.jpackage.internal.cli.OptionValue;
|
||||
import jdk.jpackage.internal.cli.Options;
|
||||
import jdk.jpackage.internal.log.Logger;
|
||||
|
||||
public final class Globals {
|
||||
|
||||
@ -73,16 +75,14 @@ public final class Globals {
|
||||
return setProperty(EnvironmentProvider.class, v);
|
||||
}
|
||||
|
||||
Log.Logger logger() {
|
||||
return logger;
|
||||
public Globals logEnv(Options v) {
|
||||
checkMutable();
|
||||
logEnv = Objects.requireNonNull(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void loggerOutputStreams(PrintWriter out, PrintWriter err) {
|
||||
logger.setPrintWriter(out, err);
|
||||
}
|
||||
|
||||
public void loggerVerbose() {
|
||||
logger.setVerbose();
|
||||
public <T extends Logger> T logger(OptionValue<T> ov) {
|
||||
return ov.getFrom(instance().logEnv);
|
||||
}
|
||||
|
||||
public static int main(Supplier<Integer> mainBody) {
|
||||
@ -104,7 +104,7 @@ public final class Globals {
|
||||
}
|
||||
|
||||
private ObjectFactory objectFactory = ObjectFactory.DEFAULT;
|
||||
private final Log.Logger logger = new Log.Logger();
|
||||
private Options logEnv = Options.concat();
|
||||
private final Map<Object, Object> properties = new HashMap<>();
|
||||
|
||||
private static final ScopedValue<Globals> INSTANCE = ScopedValue.newInstance();
|
||||
|
||||
@ -60,4 +60,13 @@ final class IOUtils {
|
||||
throw new JPackageException(I18N.format("error.cannot-write-to-output-dir", outdir.toAbsolutePath()));
|
||||
}
|
||||
}
|
||||
|
||||
static void deleteIfExistsIgnoreError(Path path) {
|
||||
try {
|
||||
Files.deleteIfExists(path);
|
||||
} catch (IOException ex) {
|
||||
Log.trace(ex, "Failed to delete [%s]", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,103 +25,62 @@
|
||||
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import jdk.jpackage.internal.log.ProgressLogger;
|
||||
import jdk.jpackage.internal.log.ResourceLogger;
|
||||
import jdk.jpackage.internal.log.StandardLogger;
|
||||
import jdk.jpackage.internal.log.TraceLogger;
|
||||
|
||||
/**
|
||||
* Log
|
||||
*
|
||||
* General purpose logging mechanism.
|
||||
*/
|
||||
public class Log {
|
||||
public static class Logger {
|
||||
private boolean verbose = false;
|
||||
private PrintWriter out = null;
|
||||
private PrintWriter err = null;
|
||||
final class Log {
|
||||
|
||||
// verbose defaults to true unless environment variable JPACKAGE_DEBUG
|
||||
// is set to true.
|
||||
// Then it is only set to true by using --verbose jpackage option
|
||||
|
||||
public Logger() {
|
||||
verbose = ("true".equals(System.getenv("JPACKAGE_DEBUG")));
|
||||
}
|
||||
|
||||
public void setVerbose() {
|
||||
verbose = true;
|
||||
}
|
||||
|
||||
public boolean isVerbose() {
|
||||
return verbose;
|
||||
}
|
||||
|
||||
public void setPrintWriter(PrintWriter out, PrintWriter err) {
|
||||
this.out = out;
|
||||
this.err = err;
|
||||
}
|
||||
|
||||
public void info(String msg) {
|
||||
if (out != null) {
|
||||
out.println(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void fatalError(String msg) {
|
||||
if (err != null) {
|
||||
err.println(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void error(String msg) {
|
||||
msg = addTimestamp(msg);
|
||||
if (err != null) {
|
||||
err.println(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void verbose(Throwable t) {
|
||||
if (out != null && verbose) {
|
||||
out.print(addTimestamp(""));
|
||||
t.printStackTrace(out);
|
||||
}
|
||||
}
|
||||
|
||||
public void verbose(String msg) {
|
||||
msg = addTimestamp(msg);
|
||||
if (out != null && verbose) {
|
||||
out.println(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private String addTimestamp(String msg) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
|
||||
Date time = new Date(System.currentTimeMillis());
|
||||
return String.format("[%s] %s", sdf.format(time), msg);
|
||||
}
|
||||
private Log() {
|
||||
}
|
||||
|
||||
public static void info(String msg) {
|
||||
Globals.instance().logger().info(msg);
|
||||
static void trace(String format, Object... args) {
|
||||
tracer().trace(format, args);
|
||||
}
|
||||
|
||||
public static void fatalError(String msg) {
|
||||
Globals.instance().logger().fatalError(msg);
|
||||
static void trace(Throwable t, String format, Object... args) {
|
||||
tracer().trace(t, format, args);
|
||||
}
|
||||
|
||||
public static void error(String msg) {
|
||||
Globals.instance().logger().error(msg);
|
||||
static void trace(Throwable t) {
|
||||
tracer().trace(t);
|
||||
}
|
||||
|
||||
public static boolean isVerbose() {
|
||||
return Globals.instance().logger().isVerbose();
|
||||
static void useResource(String localizedMsg) {
|
||||
resourceLogger().useResource(localizedMsg);
|
||||
}
|
||||
|
||||
public static void verbose(String msg) {
|
||||
Globals.instance().logger().verbose(msg);
|
||||
static void progress(String localizedMsg) {
|
||||
progressLogger().progress(localizedMsg);
|
||||
}
|
||||
|
||||
public static void verbose(Throwable t) {
|
||||
Globals.instance().logger().verbose(t);
|
||||
static void progressWarning(Exception cause) {
|
||||
progressLogger().progressWarning(cause);
|
||||
}
|
||||
|
||||
static void progressWarning(Exception cause, String localizedMsg) {
|
||||
progressLogger().progressWarning(cause, localizedMsg);
|
||||
}
|
||||
|
||||
static void progressWarning(String localizedMsg) {
|
||||
progressLogger().progressWarning(localizedMsg);
|
||||
}
|
||||
|
||||
private static TraceLogger tracer() {
|
||||
return Globals.instance().logger(StandardLogger.TRACE_LOGGER);
|
||||
}
|
||||
|
||||
private static ProgressLogger progressLogger() {
|
||||
return Globals.instance().logger(StandardLogger.PROGRESS_LOGGER);
|
||||
}
|
||||
|
||||
private static ResourceLogger resourceLogger() {
|
||||
return Globals.instance().logger(StandardLogger.RESOURCE_LOGGER);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,13 +56,14 @@ record ModuleInfo(String name, Optional<String> version, Optional<String> mainCl
|
||||
// is linked in the runtime by simply analyzing the data
|
||||
// of `release` file.
|
||||
|
||||
var releaseFilePath = RuntimeReleaseFile.releaseFilePathInRuntime(cookedRuntime);
|
||||
try {
|
||||
var cookedRuntimeModules = RuntimeReleaseFile.loadFromRuntime(cookedRuntime).getModules();
|
||||
var cookedRuntimeModules = new RuntimeReleaseFile(releaseFilePath).getModules();
|
||||
if (!cookedRuntimeModules.contains(moduleName)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.verbose(ex);
|
||||
Log.trace(ex, "Failed to read modules from [%s]", releaseFilePath);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
@ -30,11 +30,15 @@ import static jdk.jpackage.internal.cli.StandardOption.MAIN_JAR;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.MODULE;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE;
|
||||
import static jdk.jpackage.internal.log.StandardLogger.SUMMARY_LOGGER;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import jdk.jpackage.internal.cli.OptionValue;
|
||||
import jdk.jpackage.internal.cli.Options;
|
||||
import jdk.jpackage.internal.cli.StandardBundlingOperation;
|
||||
import jdk.jpackage.internal.model.BundleSpec;
|
||||
import jdk.jpackage.internal.summary.Summary;
|
||||
|
||||
final class OptionUtils {
|
||||
|
||||
@ -56,4 +60,21 @@ final class OptionUtils {
|
||||
static boolean isBundlingOperation(Options options, StandardBundlingOperation op) {
|
||||
return bundlingOperation(options).equals(Objects.requireNonNull(op));
|
||||
}
|
||||
|
||||
static Options addSummary(Options options) {
|
||||
return options.copyWithDefaultValue(SUMMARY, Summary::new);
|
||||
}
|
||||
|
||||
static Summary summary(Options options) {
|
||||
return SUMMARY.getFrom(options);
|
||||
}
|
||||
|
||||
static void finalizeAndPrintSummary(Options options, BundleSpec bundle) {
|
||||
var summary = summary(options);
|
||||
|
||||
summary.putStandardPropertiesIfAbsent(bundlingOperation(options), outputDir(options), bundle);
|
||||
Globals.instance().logger(SUMMARY_LOGGER).summary(summary);
|
||||
}
|
||||
|
||||
private static final OptionValue<Summary> SUMMARY = OptionValue.create();
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -257,7 +257,7 @@ final class OverridableResource {
|
||||
private boolean useExternal(ResourceConsumer dest) throws IOException {
|
||||
boolean used = externalPath != null && Files.exists(externalPath);
|
||||
if (used && dest != null) {
|
||||
Log.verbose(I18N.format("message.using-custom-resource-from-file",
|
||||
Log.useResource(I18N.format("message.using-custom-resource-from-file",
|
||||
getPrintableCategory(),
|
||||
externalPath.toAbsolutePath().normalize()));
|
||||
|
||||
@ -284,7 +284,7 @@ final class OverridableResource {
|
||||
final Path logResourceName = Optional.ofNullable(logPublicName).orElse(
|
||||
resourceName).normalize();
|
||||
|
||||
Log.verbose(I18N.format("message.using-custom-resource",
|
||||
Log.useResource(I18N.format("message.using-custom-resource",
|
||||
getPrintableCategory(), logResourceName));
|
||||
|
||||
try (InputStream in = Files.newInputStream(customResource)) {
|
||||
@ -304,7 +304,7 @@ final class OverridableResource {
|
||||
.orElseGet(() -> {
|
||||
return resourceName(dest);
|
||||
});
|
||||
Log.verbose(I18N.format("message.using-default-resource",
|
||||
Log.useResource(I18N.format("message.using-default-resource",
|
||||
defaultName, getPrintableCategory(), resourceName));
|
||||
|
||||
try (InputStream in = defaultResourceGetter.apply(defaultName)) {
|
||||
@ -321,7 +321,7 @@ final class OverridableResource {
|
||||
.orElseGet(() -> {
|
||||
return resourceName(dest);
|
||||
});
|
||||
Log.verbose(I18N.format("message.no-default-resource",
|
||||
Log.useResource(I18N.format("message.no-default-resource",
|
||||
getPrintableCategory(), resourceName));
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,21 +378,21 @@ final class PackagingPipeline {
|
||||
private <T extends Application, U extends AppImageLayout> TaskBuilder logAppImageAction(ActionRole role, String keyId, Function<AppImageBuildEnv<T, U>, Object[]> formatArgsSupplier) {
|
||||
Objects.requireNonNull(keyId);
|
||||
return appImageAction(role, (AppImageBuildEnv<T, U> env) -> {
|
||||
Log.verbose(I18N.format(keyId, formatArgsSupplier.apply(env)));
|
||||
Log.progress(I18N.format(keyId, formatArgsSupplier.apply(env)));
|
||||
});
|
||||
}
|
||||
|
||||
private <T extends Package, U extends AppImageLayout> TaskBuilder logPackageAction(ActionRole role, String keyId, Function<PackageBuildEnv<T, U>, Object[]> formatArgsSupplier) {
|
||||
Objects.requireNonNull(keyId);
|
||||
return packageAction(role, (PackageBuildEnv<T, U> env) -> {
|
||||
Log.verbose(I18N.format(keyId, formatArgsSupplier.apply(env)));
|
||||
Log.progress(I18N.format(keyId, formatArgsSupplier.apply(env)));
|
||||
});
|
||||
}
|
||||
|
||||
private TaskBuilder logAction(ActionRole role, String keyId, Supplier<Object[]> formatArgsSupplier) {
|
||||
Objects.requireNonNull(keyId);
|
||||
return action(role, () -> {
|
||||
Log.verbose(I18N.format(keyId, formatArgsSupplier.get()));
|
||||
Log.progress(I18N.format(keyId, formatArgsSupplier.get()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -94,14 +94,14 @@ final class TempDirectory implements Closeable {
|
||||
path, MAX_REPORTED_UNDELETED_FILE_COUNT).paths();
|
||||
|
||||
if (remainingFiles.equals(List.of(path))) {
|
||||
Log.info(I18N.format("warning.tempdir.cleanup-failed", path));
|
||||
Log.progressWarning(I18N.format("warning.tempdir.cleanup-failed", path));
|
||||
} else {
|
||||
remainingFiles.forEach(file -> {
|
||||
Log.info(I18N.format("warning.tempdir.cleanup-file-failed", file));
|
||||
Log.progressWarning(I18N.format("warning.tempdir.cleanup-file-failed", file));
|
||||
});
|
||||
}
|
||||
|
||||
Log.verbose(ex);
|
||||
Log.progressWarning(ex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -138,7 +138,7 @@ final class TempDirectory implements Closeable {
|
||||
return addPath(dir, FileVisitResult.SKIP_SUBTREE);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Log.verbose(ex);
|
||||
Log.trace(ex);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
@ -170,7 +170,7 @@ final class TempDirectory implements Closeable {
|
||||
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
Log.verbose(ex);
|
||||
Log.trace(ex);
|
||||
}
|
||||
|
||||
return new DirectoryListing(Collections.unmodifiableList(paths), !stopped.get());
|
||||
@ -181,5 +181,5 @@ final class TempDirectory implements Closeable {
|
||||
private final boolean deleteOnClose;
|
||||
private final RetryExecutorFactory retryExecutorFactory;
|
||||
|
||||
private final static int MAX_REPORTED_UNDELETED_FILE_COUNT = 100;
|
||||
private static final int MAX_REPORTED_UNDELETED_FILE_COUNT = 100;
|
||||
}
|
||||
|
||||
@ -126,7 +126,7 @@ final class ToolValidator {
|
||||
String version = null;
|
||||
|
||||
try {
|
||||
var result = Executor.of(cmdline).setQuiet(true).saveOutput().execute();
|
||||
var result = Executor.of(cmdline).quiet().saveOutput().execute();
|
||||
var lines = result.content();
|
||||
if (versionParser != null && minimalVersion != null) {
|
||||
version = versionParser.apply(lines.stream());
|
||||
|
||||
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.cli;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.log.LogEnvironment;
|
||||
import jdk.jpackage.internal.log.LogEnvironment.Builder;
|
||||
import jdk.jpackage.internal.log.LogEnvironment.LogSink;
|
||||
import jdk.jpackage.internal.log.LoggerRole;
|
||||
import jdk.jpackage.internal.util.SetBuilder;
|
||||
|
||||
public final class LogConfigParser {
|
||||
|
||||
static Builder valueOf(String str) {
|
||||
return buildFromCategories(tokenize(str));
|
||||
}
|
||||
|
||||
public static Set<MessageCategory> tokenize(String str) {
|
||||
Objects.requireNonNull(str);
|
||||
|
||||
Supplier<IllegalArgumentException> ex = () -> {
|
||||
return new IllegalArgumentException(String.format("Invalid value: [%s]", str));
|
||||
};
|
||||
|
||||
var groupCategories = new BitSet(MessageCategory.values().length);
|
||||
var enableCategories = new BitSet(MessageCategory.values().length);
|
||||
var disableCategories = new BitSet(MessageCategory.values().length);
|
||||
|
||||
Stream.of(str.split("(?<=.),")).filter(Predicate.not(String::isEmpty)).forEach(v -> {
|
||||
if (v.charAt(0) == '-') {
|
||||
var category = CONSOLE_CATEGORIES.get(v.substring(1));
|
||||
if (category == null) {
|
||||
throw ex.get();
|
||||
} else {
|
||||
disableCategories.set(category.ordinal());
|
||||
}
|
||||
} else {
|
||||
Optional.ofNullable(GROUPS.get(v)).ifPresentOrElse(categoryGroup -> {
|
||||
for (var category : categoryGroup) {
|
||||
groupCategories.set(category.ordinal());
|
||||
}
|
||||
}, () -> {
|
||||
var category = CONSOLE_CATEGORIES.get(v);
|
||||
if (category == null) {
|
||||
throw ex.get();
|
||||
} else {
|
||||
enableCategories.set(category.ordinal());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var categories = new HashSet<MessageCategory>();
|
||||
|
||||
for (var category : MessageCategory.values()) {
|
||||
if (enableCategories.get(category.ordinal()) ||
|
||||
(groupCategories.get(category.ordinal()) && !disableCategories.get(category.ordinal()))) {
|
||||
categories.add(category);
|
||||
}
|
||||
}
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
static Builder defaultVerbose() {
|
||||
return buildFromCategories(SetBuilder.<MessageCategory>build()
|
||||
.add(MessageCategory.values())
|
||||
.remove(MessageCategory.TRACE, MessageCategory.SYSTEM_LOGGER)
|
||||
.create());
|
||||
}
|
||||
|
||||
static Builder quiet() {
|
||||
return buildFromCategories(MessageCategory.ERRORS, MessageCategory.WARNINGS);
|
||||
}
|
||||
|
||||
private LogConfigParser() {
|
||||
}
|
||||
|
||||
public enum MessageCategory {
|
||||
ERRORS {
|
||||
public void applyTo(Builder builder) {
|
||||
builder.enable(LoggerRole.ERROR_LOGGER, LogSink.CONSOLE);
|
||||
builder.printFailedCommandOutputInConsole(true);
|
||||
}
|
||||
},
|
||||
PROGRESS {
|
||||
public void applyTo(Builder builder) {
|
||||
builder.enable(LoggerRole.PROGRESS_LOGGER, LogSink.CONSOLE);
|
||||
builder.printProgressInConsole(true);
|
||||
}
|
||||
},
|
||||
RESOURCES {
|
||||
public void applyTo(Builder builder) {
|
||||
builder.enable(LoggerRole.RESOURCE_LOGGER, LogSink.CONSOLE);
|
||||
}
|
||||
},
|
||||
SUMMARY {
|
||||
public void applyTo(Builder builder) {
|
||||
builder.enable(LoggerRole.SUMMARY_LOGGER, LogSink.CONSOLE);
|
||||
builder.printSummaryInConsole(true);
|
||||
}
|
||||
},
|
||||
SYSTEM_LOGGER {
|
||||
public void applyTo(Builder builder) {
|
||||
for (var role : LoggerRole.values()) {
|
||||
builder.enable(role, LogSink.SYSTEM_LOGGER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
String asStringValue() {
|
||||
return "log";
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isConsole() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
TOOLS {
|
||||
public void applyTo(Builder builder) {
|
||||
builder.enable(LoggerRole.COMMAND_LOGGER, LogSink.CONSOLE);
|
||||
}
|
||||
},
|
||||
TRACE {
|
||||
public void applyTo(Builder builder) {
|
||||
TOOLS.applyTo(builder);
|
||||
builder.enable(LoggerRole.TRACE_LOGGER, LogSink.CONSOLE);
|
||||
builder.printErrorStackTraceInConsole(true);
|
||||
builder.printCommandOutputInConsole(true);
|
||||
builder.printQuietCommands(true);
|
||||
}
|
||||
},
|
||||
WARNINGS {
|
||||
public void applyTo(Builder builder) {
|
||||
builder.enable(LoggerRole.SUMMARY_LOGGER, LogSink.CONSOLE);
|
||||
builder.enable(LoggerRole.PROGRESS_LOGGER, LogSink.CONSOLE);
|
||||
builder.printSummaryWarningsInConsole(true);
|
||||
builder.printProgressWarningsInConsole(true);
|
||||
}
|
||||
},
|
||||
;
|
||||
|
||||
public abstract void applyTo(Builder builder);
|
||||
|
||||
String asStringValue() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
|
||||
boolean isConsole() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static Builder buildFromCategories(Set<MessageCategory> categories) {
|
||||
var builder = LogEnvironment.build();
|
||||
|
||||
categories.forEach(c -> {
|
||||
c.applyTo(builder);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static Builder buildFromCategories(MessageCategory... categories) {
|
||||
return buildFromCategories(Set.of(categories));
|
||||
}
|
||||
|
||||
private static final Map<String, MessageCategory> CONSOLE_CATEGORIES = Stream.of(MessageCategory.values())
|
||||
.collect(Collectors.toUnmodifiableMap(MessageCategory::asStringValue, x -> x));
|
||||
|
||||
private static final Map<String, Set<MessageCategory>> GROUPS = Map.ofEntries(
|
||||
Map.entry("all", Set.of(MessageCategory.values())),
|
||||
Map.entry("console", Stream.of(MessageCategory.values())
|
||||
.filter(MessageCategory::isConsole)
|
||||
.collect(Collectors.toUnmodifiableSet()))
|
||||
);
|
||||
}
|
||||
@ -28,6 +28,7 @@ package jdk.jpackage.internal.cli;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.HELP;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.VERBOSE;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.VERSION;
|
||||
import static jdk.jpackage.internal.log.StandardLogger.ERROR_LOGGER;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
@ -49,13 +50,15 @@ import java.util.spi.ToolProvider;
|
||||
import jdk.internal.opt.CommandLine;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.jpackage.internal.Globals;
|
||||
import jdk.jpackage.internal.Log;
|
||||
import jdk.jpackage.internal.cli.JOptSimpleOptionsBuilder.ConvertedOptionsBuilder;
|
||||
import jdk.jpackage.internal.log.ErrorLogger;
|
||||
import jdk.jpackage.internal.model.ConfigException;
|
||||
import jdk.jpackage.internal.model.ExecutableAttributesWithCapturedOutput;
|
||||
import jdk.jpackage.internal.model.JPackageException;
|
||||
import jdk.jpackage.internal.model.SelfContainedException;
|
||||
import jdk.jpackage.internal.util.CommandOutputControl.UnexpectedExitCodeException;
|
||||
import jdk.jpackage.internal.util.CommandOutputControl.UnexpectedResultException;
|
||||
import jdk.jpackage.internal.util.SetBuilder;
|
||||
import jdk.jpackage.internal.util.Slot;
|
||||
import jdk.jpackage.internal.util.function.ExceptionBox;
|
||||
|
||||
@ -139,12 +142,10 @@ public final class Main {
|
||||
Objects.requireNonNull(out);
|
||||
Objects.requireNonNull(err);
|
||||
|
||||
Globals.instance().loggerOutputStreams(out, err);
|
||||
Globals.instance().logEnv(LogConfigParser.quiet().out(out).err(err).create());
|
||||
|
||||
final var runner = new Runner(t -> {
|
||||
new ErrorReporter(_ -> {
|
||||
t.printStackTrace(err);
|
||||
}, Log::fatalError, Log.isVerbose()).reportError(t);
|
||||
Globals.instance().logger(ERROR_LOGGER).reportError(t);
|
||||
});
|
||||
|
||||
try {
|
||||
@ -170,7 +171,7 @@ public final class Main {
|
||||
final var parseResult = Utils.buildParser(os, bundlingEnv).create().apply(mappedArgs.get());
|
||||
|
||||
return runner.run(() -> {
|
||||
final var parsedOptionsBuilder = parseResult.orElseThrow();
|
||||
var parsedOptionsBuilder = parseResult.orElseThrow();
|
||||
|
||||
final var options = parsedOptionsBuilder.create();
|
||||
|
||||
@ -190,8 +191,30 @@ public final class Main {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
var skippedOptions = new ArrayList<OptionIdentifier>();
|
||||
|
||||
if (VERBOSE.containsIn(options)) {
|
||||
Globals.instance().loggerVerbose();
|
||||
// The "--verbose" option is on the command line.
|
||||
// Pick this option from the command line and parse its string value.
|
||||
// This will delay parsing of the rest of the command line
|
||||
// until the configuration of the logging is complete.
|
||||
parsedOptionsBuilder.copyWithExcludes(
|
||||
SetBuilder.build(StandardOption.options())
|
||||
.remove(VERBOSE.getOption())
|
||||
.create().stream().map(WithOptionIdentifier::id).toList()
|
||||
).convertedOptions().map(ConvertedOptionsBuilder::create).map(VERBOSE::getFrom).value().ifPresent(logEnvBuilder -> {
|
||||
skippedOptions.add(VERBOSE.id());
|
||||
Globals.instance().logEnv(logEnvBuilder.out(out).err(err).create());
|
||||
});
|
||||
} else if ("true".equals(System.getenv("JPACKAGE_DEBUG"))) {
|
||||
// There is no "--verbose" option on the command line,
|
||||
// but the "JPACKAGE_DEBUG" environment variable is set to "true".
|
||||
// Enable the default verbose output.
|
||||
Globals.instance().logEnv(LogConfigParser.defaultVerbose().out(out).err(err).create());
|
||||
}
|
||||
|
||||
if (!skippedOptions.isEmpty()) {
|
||||
parsedOptionsBuilder = parsedOptionsBuilder.copyWithExcludes(skippedOptions);
|
||||
}
|
||||
|
||||
final var optionsProcessor = new OptionsProcessor(parsedOptionsBuilder, os, bundlingEnv);
|
||||
@ -234,17 +257,22 @@ public final class Main {
|
||||
* Always print the messages for exceptions of any type.
|
||||
*/
|
||||
|
||||
record ErrorReporter(Consumer<Throwable> stackTracePrinter, Consumer<String> messagePrinter, boolean verbose) {
|
||||
ErrorReporter {
|
||||
public record ErrorReporter(
|
||||
Consumer<Throwable> stackTracePrinter,
|
||||
Consumer<String> messagePrinter,
|
||||
boolean alwaysPrintStackTrace,
|
||||
boolean printCommandOutput) implements ErrorLogger {
|
||||
|
||||
public ErrorReporter {
|
||||
Objects.requireNonNull(stackTracePrinter);
|
||||
Objects.requireNonNull(messagePrinter);
|
||||
}
|
||||
|
||||
ErrorReporter(Consumer<Throwable> stackTracePrinter, Consumer<String> messagePrinter) {
|
||||
this(stackTracePrinter, messagePrinter, true);
|
||||
public ErrorReporter(Consumer<Throwable> stackTracePrinter, Consumer<String> messagePrinter) {
|
||||
this(stackTracePrinter, messagePrinter, true, false);
|
||||
}
|
||||
|
||||
void reportError(Throwable t) {
|
||||
public void reportError(Throwable t) {
|
||||
|
||||
var unfoldedExceptions = new ArrayList<Exception>();
|
||||
ExceptionBox.visitUnboxedExceptionsRecursively(t, unfoldedExceptions::add);
|
||||
@ -267,7 +295,7 @@ public final class Main {
|
||||
var commandOutput = ((ExecutableAttributesWithCapturedOutput)result.execAttrs()).printableOutput();
|
||||
var printableCommandLine = result.execAttrs().printableCommandLine();
|
||||
|
||||
if (verbose) {
|
||||
if (alwaysPrintStackTrace) {
|
||||
stackTracePrinter.accept(ex);
|
||||
}
|
||||
|
||||
@ -281,16 +309,18 @@ public final class Main {
|
||||
}
|
||||
|
||||
messagePrinter.accept(I18N.format("message.error-header", msg));
|
||||
messagePrinter.accept(I18N.format("message.failed-command-output-header"));
|
||||
try (var lines = new BufferedReader(new StringReader(commandOutput)).lines()) {
|
||||
lines.forEach(messagePrinter);
|
||||
if (printCommandOutput) {
|
||||
messagePrinter.accept(I18N.format("message.failed-command-output-header"));
|
||||
try (var lines = new BufferedReader(new StringReader(commandOutput)).lines()) {
|
||||
lines.forEach(messagePrinter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void printError(Throwable t, Optional<String> advice) {
|
||||
var isSelfContained = isSelfContained(t);
|
||||
|
||||
if (!isSelfContained || verbose) {
|
||||
if (!isSelfContained || alwaysPrintStackTrace) {
|
||||
stackTracePrinter.accept(t);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
@ -81,7 +81,8 @@ public sealed interface OptionValue<T> extends WithOptionIdentifier {
|
||||
}
|
||||
|
||||
static final class Builder<T> {
|
||||
OptionValue<T> create() {
|
||||
|
||||
public OptionValue<T> create() {
|
||||
if (conv != null) {
|
||||
return conv.create(Optional.ofNullable(defaultValue));
|
||||
} else {
|
||||
@ -92,7 +93,7 @@ public sealed interface OptionValue<T> extends WithOptionIdentifier {
|
||||
}
|
||||
}
|
||||
|
||||
Builder<T> defaultValue(T v) {
|
||||
public Builder<T> defaultValue(T v) {
|
||||
defaultValue = v;
|
||||
return this;
|
||||
}
|
||||
@ -103,19 +104,19 @@ public sealed interface OptionValue<T> extends WithOptionIdentifier {
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> id(OptionIdentifier v) {
|
||||
public Builder<T> id(OptionIdentifier v) {
|
||||
id = v;
|
||||
conv = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
<U> Builder<T> from(OptionValue<U> base, Function<U, T> conv) {
|
||||
public <U> Builder<T> from(OptionValue<U> base, Function<U, T> conv) {
|
||||
id(null).spec(null);
|
||||
this.conv = new Conv<>(base, conv);
|
||||
return this;
|
||||
}
|
||||
|
||||
<U> Builder<U> to(Function<T, U> conv) {
|
||||
public <U> Builder<U> to(Function<T, U> conv) {
|
||||
return OptionValue.<U>build().from(create(), conv);
|
||||
}
|
||||
|
||||
|
||||
@ -69,6 +69,7 @@ import jdk.jpackage.internal.model.LauncherShortcutStartupDirectory;
|
||||
import jdk.jpackage.internal.util.RootedPath;
|
||||
import jdk.jpackage.internal.model.SelfContainedException;
|
||||
import jdk.jpackage.internal.util.SetBuilder;
|
||||
import jdk.jpackage.internal.log.LogEnvironment;
|
||||
|
||||
/**
|
||||
* jpackage command line options
|
||||
@ -109,7 +110,15 @@ public final class StandardOption {
|
||||
|
||||
static final OptionValue<Boolean> VERSION = auxilaryOption("version").create();
|
||||
|
||||
public static final OptionValue<Boolean> VERBOSE = auxilaryOption("verbose").create();
|
||||
static final OptionValue<LogEnvironment.Builder> VERBOSE = option("verbose", LogEnvironment.Builder.class)
|
||||
.scope(StandardBundlingOperation.values())
|
||||
.inScope(NOT_BUILDING_APP_IMAGE)
|
||||
.converterExceptionFactory(ERROR_WITH_VALUE_AND_OPTION_NAME)
|
||||
.converterExceptionFormatString("error.parameter-invalid-value")
|
||||
.converter(LogConfigParser::valueOf)
|
||||
.defaultOptionalValue(LogConfigParser.defaultVerbose())
|
||||
.valuePattern("[<[-]category(,[-]category)*>]")
|
||||
.create();
|
||||
|
||||
static final OptionValue<BundleType> TYPE = option("type", BundleType.class).addAliases("t")
|
||||
.scope(StandardBundlingOperation.values()).inScope(NOT_BUILDING_APP_IMAGE)
|
||||
@ -715,9 +724,8 @@ public final class StandardOption {
|
||||
|
||||
private static UnaryOperator<Set<OptionScope>> nativeBundling() {
|
||||
return scope -> {
|
||||
return new SetBuilder<OptionScope>()
|
||||
.set(scope)
|
||||
.remove(new SetBuilder<OptionScope>().set(StandardBundlingOperation.values()).remove(CREATE_NATIVE).create())
|
||||
return SetBuilder.build(scope)
|
||||
.remove(SetBuilder.<OptionScope>build(StandardBundlingOperation.values()).remove(CREATE_NATIVE).create())
|
||||
.create();
|
||||
};
|
||||
}
|
||||
|
||||
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.StringReader;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface CommandLogger extends Logger {
|
||||
|
||||
void beforeCommandExecuted(boolean quiet, String cmdline);
|
||||
|
||||
void afterCommandExecuted(boolean quiet,
|
||||
String cmdline, Optional<Long> pid, Optional<Integer> exitCode, String printableOutput);
|
||||
|
||||
enum CommandLoggerTrait implements LoggerTrait {
|
||||
PRINT_COMMAND_RESULT,
|
||||
PRINT_QUIET_COMMANDS,
|
||||
}
|
||||
|
||||
static CommandLogger create(ConsoleLogger sink, boolean printQuietCommands, boolean printResult) {
|
||||
var theSink = sink.addTimestampsToOut().out();
|
||||
return new Details.DefaultLogger(
|
||||
theSink,
|
||||
printQuietCommands ? theSink : Utils.DISCARDER,
|
||||
printResult);
|
||||
}
|
||||
|
||||
static CommandLogger create(System.Logger sink) {
|
||||
var consumer = Utils.toStringConsumer(sink, System.Logger.Level.DEBUG);
|
||||
return new Details.DefaultLogger(consumer, consumer, true);
|
||||
}
|
||||
|
||||
static final class Details {
|
||||
|
||||
private Details() {
|
||||
}
|
||||
|
||||
private record DefaultLogger(
|
||||
Consumer<String> sink,
|
||||
Consumer<String> quietCommandSink,
|
||||
boolean printResult) implements CommandLogger {
|
||||
|
||||
DefaultLogger {
|
||||
Objects.requireNonNull(sink);
|
||||
Objects.requireNonNull(quietCommandSink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeCommandExecuted(boolean quiet, String cmdline) {
|
||||
|
||||
Objects.requireNonNull(cmdline);
|
||||
|
||||
sink(quiet).accept(String.format("Running %s", cmdline));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCommandExecuted(boolean quiet,
|
||||
String cmdline, Optional<Long> pid, Optional<Integer> exitCode, String printableOutput) {
|
||||
|
||||
Objects.requireNonNull(cmdline);
|
||||
Objects.requireNonNull(pid);
|
||||
Objects.requireNonNull(exitCode);
|
||||
Objects.requireNonNull(printableOutput);
|
||||
|
||||
if (!printResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
var theSink = sink(quiet);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.append("Command");
|
||||
pid.ifPresent(p -> {
|
||||
sb.append(" [PID: ").append(p).append("]");
|
||||
});
|
||||
sb.append(":").append(System.lineSeparator()).append(" ").append(cmdline);
|
||||
theSink.accept(sb.toString());
|
||||
|
||||
if (!printableOutput.isEmpty()) {
|
||||
sb.delete(0, sb.length());
|
||||
sb.append("Output:");
|
||||
try (var lines = new BufferedReader(new StringReader(printableOutput)).lines()) {
|
||||
lines.forEach(line -> {
|
||||
sb.append(System.lineSeparator()).append(" ").append(line);
|
||||
});
|
||||
}
|
||||
theSink.accept(sb.toString());
|
||||
}
|
||||
|
||||
exitCode.ifPresentOrElse(v -> {
|
||||
theSink.accept("Returned: " + v);
|
||||
}, () -> {
|
||||
theSink.accept("Aborted: timed-out");
|
||||
});
|
||||
}
|
||||
|
||||
private Consumer<String> sink(boolean quietCommand) {
|
||||
return quietCommand ? quietCommandSink : sink;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static final CommandLogger DISCARDING_LOGGER = Utils.discardingLogger(CommandLogger.class);
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public record ConsoleLogger(Consumer<String> out, Consumer<String> err, Clock timestampClock) {
|
||||
|
||||
public ConsoleLogger {
|
||||
Objects.requireNonNull(out);
|
||||
Objects.requireNonNull(err);
|
||||
Objects.requireNonNull(timestampClock);
|
||||
}
|
||||
|
||||
public ConsoleLogger(Consumer<String> out, Consumer<String> err) {
|
||||
this(out, err, Clock.systemDefaultZone());
|
||||
}
|
||||
|
||||
ConsoleLogger discardOut() {
|
||||
return new ConsoleLogger(Utils.DISCARDER, err, timestampClock);
|
||||
}
|
||||
|
||||
ConsoleLogger discardErr() {
|
||||
return new ConsoleLogger(out, Utils.DISCARDER, timestampClock);
|
||||
}
|
||||
|
||||
ConsoleLogger addTimestampsToOut() {
|
||||
return new ConsoleLogger(addTimestamps(out, timestampClock), err, timestampClock);
|
||||
}
|
||||
|
||||
ConsoleLogger addTimestampsToErr() {
|
||||
return new ConsoleLogger(out, addTimestamps(err, timestampClock), timestampClock);
|
||||
}
|
||||
|
||||
record ConsoleLoggerOutSink(PrintWriter sink) implements LoggerTrait {
|
||||
|
||||
ConsoleLoggerOutSink {
|
||||
Objects.requireNonNull(sink);
|
||||
}
|
||||
}
|
||||
|
||||
record ConsoleLoggerErrSink(PrintWriter sink) implements LoggerTrait {
|
||||
|
||||
ConsoleLoggerErrSink {
|
||||
Objects.requireNonNull(sink);
|
||||
}
|
||||
}
|
||||
|
||||
record ConsoleLoggerTimestampClock(Clock clock) implements LoggerTrait {
|
||||
|
||||
ConsoleLoggerTimestampClock {
|
||||
Objects.requireNonNull(clock);
|
||||
}
|
||||
}
|
||||
|
||||
private static Consumer<String> addTimestamps(Consumer<String> sink, Clock clock) {
|
||||
Objects.requireNonNull(sink);
|
||||
Objects.requireNonNull(clock);
|
||||
if (sink == Utils.DISCARDER || sink instanceof AddingTimestampsConsumer) {
|
||||
return sink;
|
||||
} else {
|
||||
return new AddingTimestampsConsumer(sink, clock);
|
||||
}
|
||||
}
|
||||
|
||||
private record AddingTimestampsConsumer(Consumer<String> sink, Clock clock) implements Consumer<String> {
|
||||
|
||||
AddingTimestampsConsumer {
|
||||
Objects.requireNonNull(sink);
|
||||
Objects.requireNonNull(clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(String str) {
|
||||
var sb = new StringBuilder();
|
||||
var time = LocalTime.now(clock);
|
||||
sb.append('[').append(time.format(TIMESTAMP_FORMATTER)).append("] ");
|
||||
sb.append(str);
|
||||
str = sb.toString();
|
||||
sink.accept(str);
|
||||
}
|
||||
|
||||
private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Objects;
|
||||
import jdk.jpackage.internal.cli.Main.ErrorReporter;
|
||||
|
||||
public interface ErrorLogger extends Logger {
|
||||
|
||||
void reportError(Throwable t);
|
||||
|
||||
enum ErrorLoggerTrait implements LoggerTrait {
|
||||
PRINT_STACK_TRACE_ALWAYS,
|
||||
PRINT_FAILED_COMMAND_OUTPUT,
|
||||
}
|
||||
|
||||
static ErrorLogger create(ConsoleLogger sink, boolean alwaysPrintStackTrace, boolean printCommandOutput) {
|
||||
Objects.requireNonNull(sink);
|
||||
return new ErrorReporter(
|
||||
t -> {
|
||||
var buf = new StringWriter();
|
||||
t.printStackTrace(new PrintWriter(buf));
|
||||
Utils.writeWithoutTrailingLineSeparator(buf, sink.err());
|
||||
},
|
||||
sink.err(),
|
||||
alwaysPrintStackTrace,
|
||||
printCommandOutput);
|
||||
}
|
||||
|
||||
static ErrorLogger create(System.Logger sink) {
|
||||
Objects.requireNonNull(sink);
|
||||
return new ErrorLogger() {
|
||||
|
||||
@Override
|
||||
public void reportError(Throwable t) {
|
||||
var buf = new StringWriter();
|
||||
|
||||
new ErrorReporter(t2 -> {
|
||||
t2.printStackTrace(new PrintWriter(buf));
|
||||
}, str -> {
|
||||
buf.write(str);
|
||||
buf.write(System.lineSeparator());
|
||||
}, true, true).reportError(t);
|
||||
|
||||
Utils.writeWithoutTrailingLineSeparator(buf, str -> {
|
||||
sink.log(System.Logger.Level.ERROR, str);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
static final ErrorLogger DISCARDING_LOGGER = Utils.discardingLogger(ErrorLogger.class);
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.jpackage.internal.util.MultiResourceBundle;
|
||||
import jdk.jpackage.internal.util.StringBundle;
|
||||
|
||||
final class I18N {
|
||||
|
||||
private I18N() {
|
||||
}
|
||||
|
||||
static String getString(String key) {
|
||||
return BUNDLE.getString(key);
|
||||
}
|
||||
|
||||
static String format(String key, Object ... args) {
|
||||
return BUNDLE.format(key, args);
|
||||
}
|
||||
|
||||
private static final StringBundle BUNDLE;
|
||||
|
||||
static {
|
||||
var prefix = "jdk.jpackage.internal.resources.";
|
||||
BUNDLE = StringBundle.fromResourceBundle(MultiResourceBundle.create(
|
||||
prefix + "MainResources",
|
||||
Map.of(
|
||||
OperatingSystem.LINUX, List.of(prefix + "LinuxResources"),
|
||||
OperatingSystem.MACOS, List.of(prefix + "MacResources"),
|
||||
OperatingSystem.WINDOWS, List.of(prefix + "WinResources")
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.time.Clock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.cli.Options;
|
||||
import jdk.jpackage.internal.log.CommandLogger.CommandLoggerTrait;
|
||||
import jdk.jpackage.internal.log.ConsoleLogger.ConsoleLoggerErrSink;
|
||||
import jdk.jpackage.internal.log.ConsoleLogger.ConsoleLoggerOutSink;
|
||||
import jdk.jpackage.internal.log.ConsoleLogger.ConsoleLoggerTimestampClock;
|
||||
import jdk.jpackage.internal.log.ErrorLogger.ErrorLoggerTrait;
|
||||
import jdk.jpackage.internal.log.ProgressLogger.ProgressLoggerTrait;
|
||||
import jdk.jpackage.internal.log.SummaryLogger.SummaryLoggerTrait;
|
||||
|
||||
public final class LogEnvironment {
|
||||
|
||||
public enum LogSink {
|
||||
CONSOLE,
|
||||
SYSTEM_LOGGER
|
||||
}
|
||||
|
||||
public static Builder build() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
public Options create() {
|
||||
|
||||
var consoleTraits = new HashSet<LoggerTrait>();
|
||||
Optional.ofNullable(out).map(ConsoleLoggerOutSink::new).ifPresent(consoleTraits::add);
|
||||
Optional.ofNullable(err).map(ConsoleLoggerErrSink::new).ifPresent(consoleTraits::add);
|
||||
Optional.ofNullable(consoleTimestampClock).map(ConsoleLoggerTimestampClock::new).ifPresent(consoleTraits::add);
|
||||
|
||||
var loggerTraits = enabledLoggers.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> {
|
||||
var enabledSinks = e.getValue();
|
||||
|
||||
var traits = new ArrayList<LoggerTrait>(booleanTraits);
|
||||
|
||||
if (enabledSinks.contains(LogSink.SYSTEM_LOGGER)) {
|
||||
traits.add(new SystemLoggerTrait(
|
||||
Optional.ofNullable(systemLoggerFactory).orElse(System::getLogger).apply("jdk.jpackage")));
|
||||
}
|
||||
|
||||
if (enabledSinks.contains(LogSink.CONSOLE)) {
|
||||
traits.addAll(consoleTraits);
|
||||
}
|
||||
|
||||
return traits;
|
||||
}));
|
||||
|
||||
return LogEnvironment.create(loggerTraits);
|
||||
}
|
||||
|
||||
public Builder out(PrintWriter v) {
|
||||
out = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder err(PrintWriter v) {
|
||||
err = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder printErrorStackTraceInConsole(boolean v) {
|
||||
return setBooleanTrait(v, ErrorLoggerTrait.PRINT_STACK_TRACE_ALWAYS);
|
||||
}
|
||||
|
||||
public Builder printCommandOutputInConsole(boolean v) {
|
||||
return setBooleanTrait(v, CommandLoggerTrait.PRINT_COMMAND_RESULT);
|
||||
}
|
||||
|
||||
public Builder printFailedCommandOutputInConsole(boolean v) {
|
||||
return setBooleanTrait(v, ErrorLoggerTrait.PRINT_FAILED_COMMAND_OUTPUT);
|
||||
}
|
||||
|
||||
public Builder printQuietCommands(boolean v) {
|
||||
return setBooleanTrait(v, CommandLoggerTrait.PRINT_QUIET_COMMANDS);
|
||||
}
|
||||
|
||||
public Builder printProgressInConsole(boolean v) {
|
||||
return setBooleanTrait(v, ProgressLoggerTrait.PRINT_INFO);
|
||||
}
|
||||
|
||||
public Builder printProgressWarningsInConsole(boolean v) {
|
||||
return setBooleanTrait(v, ProgressLoggerTrait.PRINT_WARNINGS);
|
||||
}
|
||||
|
||||
public Builder printSummaryInConsole(boolean v) {
|
||||
return setBooleanTrait(v, SummaryLoggerTrait.PRINT_INFO);
|
||||
}
|
||||
|
||||
public Builder printSummaryWarningsInConsole(boolean v) {
|
||||
return setBooleanTrait(v, SummaryLoggerTrait.PRINT_WARNINGS);
|
||||
}
|
||||
|
||||
public Builder consoleTimestampClock(Clock v) {
|
||||
consoleTimestampClock = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder systemLoggerFactory(Function<String, System.Logger> v) {
|
||||
systemLoggerFactory = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enable(LoggerRole role, LogSink... sinks) {
|
||||
Objects.requireNonNull(role);
|
||||
if (sinks.length == 0) {
|
||||
enabledLoggers.remove(role);
|
||||
} else {
|
||||
enabledLoggers.computeIfAbsent(role, _ -> {
|
||||
return new HashSet<>();
|
||||
}).addAll(Set.of(sinks));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder mutate(Consumer<Builder> mutator) {
|
||||
mutator.accept(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder setBooleanTrait(boolean set, LoggerTrait trait) {
|
||||
Objects.requireNonNull(trait);
|
||||
if (set) {
|
||||
booleanTraits.add(trait);
|
||||
} else {
|
||||
booleanTraits.remove(trait);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private PrintWriter out;
|
||||
private PrintWriter err;
|
||||
private Clock consoleTimestampClock;
|
||||
private Function<String, System.Logger> systemLoggerFactory;
|
||||
private final Set<LoggerTrait> booleanTraits = new HashSet<>();
|
||||
private final Map<LoggerRole, Set<LogSink>> enabledLoggers = new HashMap<>();
|
||||
}
|
||||
|
||||
static Options create(Map<LoggerRole, ? extends Collection<LoggerTrait>> loggerTraits) {
|
||||
return Options.of(loggerTraits.entrySet().stream().collect(toMap(e -> {
|
||||
return e.getKey().logger();
|
||||
}, e -> {
|
||||
return e.getKey().createLogger(e.getValue());
|
||||
})));
|
||||
}
|
||||
|
||||
static CommandLogger createCommandLogger(Collection<LoggerTrait> traits) {
|
||||
return create(
|
||||
CommandLogger.class,
|
||||
traits,
|
||||
sink -> {
|
||||
var printQuietCommands = traits.contains(CommandLoggerTrait.PRINT_QUIET_COMMANDS);
|
||||
var printResult = traits.contains(CommandLoggerTrait.PRINT_COMMAND_RESULT);
|
||||
return CommandLogger.create(sink, printQuietCommands, printResult);
|
||||
},
|
||||
CommandLogger::create).orElse(CommandLogger.DISCARDING_LOGGER);
|
||||
}
|
||||
|
||||
static ProgressLogger createProgressLogger(Collection<LoggerTrait> traits) {
|
||||
return create(
|
||||
ProgressLogger.class,
|
||||
traits,
|
||||
sink -> {
|
||||
var printInfo = traits.contains(ProgressLoggerTrait.PRINT_INFO);
|
||||
var printWarnings = traits.contains(ProgressLoggerTrait.PRINT_WARNINGS);
|
||||
return ProgressLogger.create(sink, printInfo, printWarnings);
|
||||
},
|
||||
ProgressLogger::create).orElse(ProgressLogger.DISCARDING_LOGGER);
|
||||
}
|
||||
|
||||
static ErrorLogger createErrorLogger(Collection<LoggerTrait> traits) {
|
||||
return create(
|
||||
ErrorLogger.class,
|
||||
traits,
|
||||
sink -> {
|
||||
var printStackTrace = traits.contains(ErrorLoggerTrait.PRINT_STACK_TRACE_ALWAYS);
|
||||
var printCommandOutput = traits.contains(ErrorLoggerTrait.PRINT_FAILED_COMMAND_OUTPUT);
|
||||
return ErrorLogger.create(sink, printStackTrace, printCommandOutput);
|
||||
},
|
||||
ErrorLogger::create).orElse(ErrorLogger.DISCARDING_LOGGER);
|
||||
}
|
||||
|
||||
static TraceLogger createTraceLogger(Collection<LoggerTrait> traits) {
|
||||
return create(
|
||||
TraceLogger.class,
|
||||
traits,
|
||||
TraceLogger::create,
|
||||
TraceLogger::create).orElse(TraceLogger.DISCARDING_LOGGER);
|
||||
}
|
||||
|
||||
static ResourceLogger createResourceLogger(Collection<LoggerTrait> traits) {
|
||||
return create(
|
||||
ResourceLogger.class,
|
||||
traits,
|
||||
ResourceLogger::create,
|
||||
ResourceLogger::create).orElse(ResourceLogger.DISCARDING_LOGGER);
|
||||
}
|
||||
|
||||
static SummaryLogger createSummaryLogger(Collection<LoggerTrait> traits) {
|
||||
return create(SummaryLogger.class,
|
||||
traits,
|
||||
sink -> {
|
||||
var printInfo = traits.contains(SummaryLoggerTrait.PRINT_INFO);
|
||||
var printWarnings = traits.contains(SummaryLoggerTrait.PRINT_WARNINGS);
|
||||
return SummaryLogger.create(sink, printInfo, printWarnings);
|
||||
},
|
||||
SummaryLogger::create).orElse(SummaryLogger.DISCARDING_LOGGER);
|
||||
}
|
||||
|
||||
static Optional<ConsoleLogger> createConsoleLoggerSink(Collection<LoggerTrait> traits) {
|
||||
|
||||
var out = filterTraitsOfType(ConsoleLoggerOutSink.class, traits.stream())
|
||||
.map(ConsoleLoggerOutSink::sink).findFirst();
|
||||
|
||||
var err = filterTraitsOfType(ConsoleLoggerErrSink.class, traits.stream())
|
||||
.map(ConsoleLoggerErrSink::sink).findFirst();
|
||||
|
||||
var timestampClock = filterTraitsOfType(ConsoleLoggerTimestampClock.class, traits.stream())
|
||||
.map(ConsoleLoggerTimestampClock::clock)
|
||||
.findFirst().orElseGet(new ConsoleLogger(Utils.DISCARDER, Utils.DISCARDER)::timestampClock);
|
||||
|
||||
if (out.isEmpty() && err.isEmpty()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(new ConsoleLogger(toStringConsumer(out), toStringConsumer(err), timestampClock));
|
||||
}
|
||||
}
|
||||
|
||||
static Optional<System.Logger> createSystemLoggerSink(Collection<LoggerTrait> traits) {
|
||||
return filterTraitsOfType(SystemLoggerTrait.class, traits.stream()).map(SystemLoggerTrait::logger).findFirst();
|
||||
}
|
||||
|
||||
private static <T extends Logger> Optional<T> create(
|
||||
Class<T> loggerType,
|
||||
Collection<LoggerTrait> traits,
|
||||
Function<ConsoleLogger, T> fromConsoleSinkCtor,
|
||||
Function<System.Logger, T> fromSystemLoggerSinkCtor) {
|
||||
|
||||
Objects.requireNonNull(traits);
|
||||
Objects.requireNonNull(fromSystemLoggerSinkCtor);
|
||||
Objects.requireNonNull(fromConsoleSinkCtor);
|
||||
|
||||
var consoleLogger = createConsoleLoggerSink(traits).map(fromConsoleSinkCtor);
|
||||
var systemLogger = createSystemLoggerSink(traits).map(fromSystemLoggerSinkCtor);
|
||||
|
||||
if (consoleLogger.isEmpty() && systemLogger.isEmpty()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(Utils.teeLogger(loggerType, Stream.of(
|
||||
consoleLogger.stream(),
|
||||
systemLogger.stream()
|
||||
).flatMap(x -> x).toList()));
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends LoggerTrait> Stream<? extends T> filterTraitsOfType(
|
||||
Class<? extends T> type, Stream<? extends LoggerTrait> stream) {
|
||||
Objects.requireNonNull(type);
|
||||
return stream.filter(type::isInstance).map(type::cast);
|
||||
}
|
||||
|
||||
private static Consumer<String> toStringConsumer(Optional<PrintWriter> pw) {
|
||||
return pw.<Consumer<String>>map(v -> {
|
||||
return v::println;
|
||||
}).orElse(Utils.DISCARDER);
|
||||
}
|
||||
|
||||
private LogEnvironment() {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
public interface Logger {
|
||||
|
||||
default boolean enabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import jdk.jpackage.internal.cli.OptionValue;
|
||||
|
||||
public enum LoggerRole {
|
||||
|
||||
COMMAND_LOGGER(StandardLogger.COMMAND_LOGGER, LogEnvironment::createCommandLogger),
|
||||
|
||||
ERROR_LOGGER(StandardLogger.ERROR_LOGGER, LogEnvironment::createErrorLogger),
|
||||
|
||||
PROGRESS_LOGGER(StandardLogger.PROGRESS_LOGGER, LogEnvironment::createProgressLogger),
|
||||
|
||||
RESOURCE_LOGGER(StandardLogger.RESOURCE_LOGGER, LogEnvironment::createResourceLogger),
|
||||
|
||||
SUMMARY_LOGGER(StandardLogger.SUMMARY_LOGGER, LogEnvironment::createSummaryLogger),
|
||||
|
||||
TRACE_LOGGER(StandardLogger.TRACE_LOGGER, LogEnvironment::createTraceLogger),
|
||||
|
||||
;
|
||||
|
||||
LoggerRole(OptionValue<? extends Logger> logger, Function<Collection<LoggerTrait>, ? extends Logger> loggerCtor) {
|
||||
this.logger = Objects.requireNonNull(logger);
|
||||
this.loggerCtor = Objects.requireNonNull(loggerCtor);
|
||||
}
|
||||
|
||||
public OptionValue<? extends Logger> logger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
Logger createLogger(Collection<LoggerTrait> traits) {
|
||||
return loggerCtor.apply(traits);
|
||||
}
|
||||
|
||||
private final OptionValue<? extends Logger> logger;
|
||||
private final Function<Collection<LoggerTrait>, ? extends Logger> loggerCtor;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
interface LoggerTrait {
|
||||
}
|
||||
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public interface ProgressLogger extends Logger {
|
||||
|
||||
void progress(String localizedMsg);
|
||||
|
||||
void progressWarning(Exception cause);
|
||||
|
||||
void progressWarning(Exception cause, String localizedMsg);
|
||||
|
||||
void progressWarning(String localizedMsg);
|
||||
|
||||
enum ProgressLoggerTrait implements LoggerTrait {
|
||||
PRINT_INFO,
|
||||
PRINT_WARNINGS,
|
||||
}
|
||||
|
||||
static ProgressLogger create(ConsoleLogger sink, boolean printInfo, boolean printWarnings) {
|
||||
if (!printInfo) {
|
||||
sink = sink.discardOut();
|
||||
}
|
||||
if (!printWarnings) {
|
||||
sink = sink.discardErr();
|
||||
}
|
||||
return new Details.Console(sink.addTimestampsToOut());
|
||||
}
|
||||
|
||||
static ProgressLogger create(System.Logger sink) {
|
||||
return new Details.SystemLogger(sink);
|
||||
}
|
||||
|
||||
static final class Details {
|
||||
|
||||
private Details() {
|
||||
}
|
||||
|
||||
private record Console(ConsoleLogger sink) implements ProgressLogger {
|
||||
|
||||
Console {
|
||||
Objects.requireNonNull(sink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progress(String localizedMsg) {
|
||||
Objects.requireNonNull(localizedMsg);
|
||||
sink.out().accept(localizedMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progressWarning(Exception cause) {
|
||||
Objects.requireNonNull(cause);
|
||||
sink.err().accept(I18N.format("progress.warning-header", Utils.toString(cause)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progressWarning(Exception cause, String localizedMsg) {
|
||||
Objects.requireNonNull(cause);
|
||||
Objects.requireNonNull(localizedMsg);
|
||||
sink.err().accept(I18N.format("progress.warning-header2", localizedMsg, Utils.toString(cause)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progressWarning(String localizedMsg) {
|
||||
Objects.requireNonNull(localizedMsg);
|
||||
sink.err().accept(I18N.format("progress.warning-header", localizedMsg));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private record SystemLogger(System.Logger sink) implements ProgressLogger {
|
||||
|
||||
SystemLogger {
|
||||
Objects.requireNonNull(sink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progress(String localizedMsg) {
|
||||
sink.log(System.Logger.Level.INFO, localizedMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progressWarning(Exception cause) {
|
||||
sink.log(System.Logger.Level.WARNING, "Ignore error", cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progressWarning(Exception cause, String localizedMsg) {
|
||||
sink.log(System.Logger.Level.WARNING, localizedMsg, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progressWarning(String localizedMsg) {
|
||||
sink.log(System.Logger.Level.WARNING, localizedMsg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static final ProgressLogger DISCARDING_LOGGER = Utils.discardingLogger(ProgressLogger.class);
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface ResourceLogger extends Logger {
|
||||
|
||||
void useResource(String localizedMsg);
|
||||
|
||||
static ResourceLogger create(ConsoleLogger sink) {
|
||||
return new Details.DefaultLogger(sink.addTimestampsToOut().out());
|
||||
}
|
||||
|
||||
static ResourceLogger create(System.Logger sink) {
|
||||
return new Details.DefaultLogger(Utils.toStringConsumer(sink, System.Logger.Level.INFO));
|
||||
}
|
||||
|
||||
static final class Details {
|
||||
|
||||
private Details() {
|
||||
}
|
||||
|
||||
private record DefaultLogger(Consumer<String> sink) implements ResourceLogger {
|
||||
|
||||
DefaultLogger {
|
||||
Objects.requireNonNull(sink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void useResource(String localizedMsg) {
|
||||
Objects.requireNonNull(localizedMsg);
|
||||
sink.accept(localizedMsg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static final ResourceLogger DISCARDING_LOGGER = Utils.discardingLogger(ResourceLogger.class);
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import java.util.Objects;
|
||||
import jdk.jpackage.internal.cli.OptionValue;
|
||||
|
||||
public final class StandardLogger {
|
||||
|
||||
private StandardLogger() {
|
||||
}
|
||||
|
||||
public static final OptionValue<CommandLogger> COMMAND_LOGGER = create(CommandLogger.DISCARDING_LOGGER);
|
||||
|
||||
public static final OptionValue<ErrorLogger> ERROR_LOGGER = create(ErrorLogger.DISCARDING_LOGGER);
|
||||
|
||||
public static final OptionValue<ProgressLogger> PROGRESS_LOGGER = create(ProgressLogger.DISCARDING_LOGGER);
|
||||
|
||||
public static final OptionValue<ResourceLogger> RESOURCE_LOGGER = create(ResourceLogger.DISCARDING_LOGGER);
|
||||
|
||||
public static final OptionValue<SummaryLogger> SUMMARY_LOGGER = create(SummaryLogger.DISCARDING_LOGGER);
|
||||
|
||||
public static final OptionValue<TraceLogger> TRACE_LOGGER = create(TraceLogger.DISCARDING_LOGGER);
|
||||
|
||||
private static <T extends Logger> OptionValue<T> create(T defaultValue) {
|
||||
Objects.requireNonNull(defaultValue);
|
||||
return OptionValue.<T>build().defaultValue(defaultValue).create();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import jdk.jpackage.internal.summary.Summary;
|
||||
|
||||
public interface SummaryLogger extends Logger {
|
||||
|
||||
void summary(Summary summary);
|
||||
|
||||
enum SummaryLoggerTrait implements LoggerTrait {
|
||||
PRINT_INFO,
|
||||
PRINT_WARNINGS,
|
||||
}
|
||||
|
||||
static SummaryLogger create(ConsoleLogger sink, boolean printInfo, boolean printWarnings) {
|
||||
if (!printInfo) {
|
||||
sink = sink.discardOut();
|
||||
}
|
||||
if (!printWarnings) {
|
||||
sink = sink.discardErr();
|
||||
}
|
||||
return new Details.DefaultLogger(sink.out(), sink.err());
|
||||
}
|
||||
|
||||
static SummaryLogger create(System.Logger sink) {
|
||||
return new Details.DefaultLogger(
|
||||
Utils.toStringConsumer(sink, System.Logger.Level.INFO),
|
||||
Utils.toStringConsumer(sink, System.Logger.Level.WARNING));
|
||||
}
|
||||
|
||||
static final class Details {
|
||||
|
||||
private Details() {
|
||||
}
|
||||
|
||||
private record DefaultLogger(Consumer<String> sinkInfo, Consumer<String> sinkWarnings) implements SummaryLogger {
|
||||
|
||||
DefaultLogger {
|
||||
Objects.requireNonNull(sinkInfo);
|
||||
Objects.requireNonNull(sinkWarnings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void summary(Summary summary) {
|
||||
summary.print(sinkInfo, sinkWarnings);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static final SummaryLogger DISCARDING_LOGGER = Utils.discardingLogger(SummaryLogger.class);
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
record SystemLoggerTrait(System.Logger logger) implements LoggerTrait {
|
||||
|
||||
SystemLoggerTrait {
|
||||
Objects.requireNonNull(logger);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Objects;
|
||||
|
||||
public interface TraceLogger extends Logger {
|
||||
|
||||
void trace(String msg);
|
||||
|
||||
default void trace(String format, Object... args) {
|
||||
if (enabled()) {
|
||||
trace(String.format(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
void trace(Throwable t, String msg);
|
||||
|
||||
default void trace(Throwable t, String format, Object... args) {
|
||||
if (enabled()) {
|
||||
trace(t, String.format(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
void trace(Throwable t);
|
||||
|
||||
static TraceLogger create(ConsoleLogger sink) {
|
||||
return new Details.Console(sink.addTimestampsToOut().addTimestampsToErr());
|
||||
}
|
||||
|
||||
static TraceLogger create(System.Logger sink) {
|
||||
return new Details.SystemLogger(sink);
|
||||
}
|
||||
|
||||
static final class Details {
|
||||
|
||||
private Details() {
|
||||
}
|
||||
|
||||
private record Console(ConsoleLogger sink) implements TraceLogger {
|
||||
|
||||
Console {
|
||||
Objects.requireNonNull(sink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String msg) {
|
||||
Objects.requireNonNull(msg);
|
||||
sink.out().accept(MSG_PREFIX + msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(Throwable t, String msg) {
|
||||
Objects.requireNonNull(t);
|
||||
Objects.requireNonNull(msg);
|
||||
|
||||
var buf = new StringWriter();
|
||||
buf.write(MSG_PREFIX);
|
||||
buf.write(msg);
|
||||
buf.write(": ");
|
||||
t.printStackTrace(new PrintWriter(buf));
|
||||
|
||||
Utils.writeWithoutTrailingLineSeparator(buf, sink.err());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(Throwable t) {
|
||||
Objects.requireNonNull(t);
|
||||
|
||||
var buf = new StringWriter();
|
||||
buf.write(MSG_PREFIX);
|
||||
t.printStackTrace(new PrintWriter(buf));
|
||||
|
||||
Utils.writeWithoutTrailingLineSeparator(buf, sink.err());
|
||||
}
|
||||
|
||||
private static final String MSG_PREFIX = "TRACE: ";
|
||||
}
|
||||
|
||||
private record SystemLogger(System.Logger sink) implements TraceLogger {
|
||||
|
||||
SystemLogger {
|
||||
Objects.requireNonNull(sink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String msg) {
|
||||
sink.log(System.Logger.Level.TRACE, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(Throwable t, String msg) {
|
||||
sink.log(System.Logger.Level.ERROR, msg, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(Throwable t) {
|
||||
sink.log(System.Logger.Level.ERROR, "Ignored error", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final TraceLogger DISCARDING_LOGGER = Utils.discardingLogger(TraceLogger.class);
|
||||
}
|
||||
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.log;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.model.SelfContainedException;
|
||||
import jdk.jpackage.internal.util.function.ExceptionBox;
|
||||
|
||||
final class Utils {
|
||||
|
||||
private Utils() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T extends Logger> T discardingLogger(Class<T> type) {
|
||||
return (T)Proxy.newProxyInstance(Utils.class.getClassLoader(), new Class<?>[]{type}, new LoggerHandler<>(type, false) {
|
||||
|
||||
@Override
|
||||
protected void invokeLoggerMethod(Object proxy, Method method, Object[] args) {
|
||||
throw ExceptionBox.reachedUnreachable();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T extends Logger> T teeLogger(Class<T> type, List<? extends T> loggers) {
|
||||
|
||||
var enabledLoggers = loggers.stream().filter(Logger::enabled).toList();
|
||||
if (enabledLoggers.isEmpty()) {
|
||||
return discardingLogger(type);
|
||||
} else if (enabledLoggers.size() == 1) {
|
||||
return enabledLoggers.getFirst();
|
||||
}
|
||||
|
||||
return (T)Proxy.newProxyInstance(Utils.class.getClassLoader(), new Class<?>[]{type}, new LoggerHandler<>(type, true) {
|
||||
|
||||
@Override
|
||||
protected void invokeLoggerMethod(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
for (var logger : enabledLoggers) {
|
||||
method.invoke(logger, args);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
static Consumer<String> toStringConsumer(System.Logger logger, System.Logger.Level level) {
|
||||
Objects.requireNonNull(logger);
|
||||
Objects.requireNonNull(level);
|
||||
return str -> {
|
||||
logger.log(level, str);
|
||||
};
|
||||
}
|
||||
|
||||
static boolean isSelfContained(Throwable t) {
|
||||
return t.getClass().getAnnotation(SelfContainedException.class) != null;
|
||||
}
|
||||
|
||||
static String toString(Throwable t) {
|
||||
if (isSelfContained(t)) {
|
||||
return t.getMessage();
|
||||
} else {
|
||||
return t.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static void writeWithoutTrailingLineSeparator(StringWriter writer, Consumer<String> sink) {
|
||||
var buf = writer.getBuffer();
|
||||
var lineSeparator = System.lineSeparator();
|
||||
if (buf.length() >= lineSeparator.length()) {
|
||||
var tailChars = new char[lineSeparator.length()];
|
||||
buf.getChars(buf.length() - tailChars.length, buf.length(), tailChars, 0);
|
||||
if (Arrays.equals(tailChars, lineSeparator.toCharArray())) {
|
||||
buf.setLength(buf.length() - tailChars.length);
|
||||
}
|
||||
}
|
||||
sink.accept(buf.toString());
|
||||
}
|
||||
|
||||
private abstract static class LoggerHandler<T extends Logger> implements InvocationHandler {
|
||||
|
||||
protected LoggerHandler(Class<T> loggerType, boolean loggerEnabled) {
|
||||
Objects.requireNonNull(loggerType);
|
||||
if (!loggerType.isInterface() ) {
|
||||
throw new IllegalArgumentException(String.format("%s is not an interface", loggerType));
|
||||
}
|
||||
|
||||
loggerMethods = unfoldInterface(loggerType).flatMap(interfaceType -> {
|
||||
return Stream.of(interfaceType.getMethods());
|
||||
}).collect(Collectors.toSet());
|
||||
|
||||
this.loggerEnabled = loggerEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
if (loggerMethods.contains(method)) {
|
||||
var returnType = method.getReturnType();
|
||||
|
||||
if (method.getName().equals("enabled") && returnType.equals(boolean.class) && method.getParameterCount() == 0) {
|
||||
return loggerEnabled;
|
||||
} else if (returnType.equals(void.class)) {
|
||||
if (loggerEnabled) {
|
||||
invokeLoggerMethod(proxy, method, args);
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
throw new AssertionError(String.format("Don't know how to handle %s", method));
|
||||
}
|
||||
} else {
|
||||
// Presumably this is java.lang.Objects's method. Redirect it to this instance.
|
||||
return method.invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void invokeLoggerMethod(Object proxy, Method method, Object[] args) throws Throwable;
|
||||
|
||||
private final Set<Method> loggerMethods;
|
||||
private final boolean loggerEnabled;
|
||||
}
|
||||
|
||||
private static Stream<Class<?>> unfoldInterface(Class<?> interfaceType) {
|
||||
return Stream.concat(
|
||||
Stream.of(interfaceType),
|
||||
Stream.of(interfaceType.getInterfaces()
|
||||
).flatMap(Utils::unfoldInterface));
|
||||
}
|
||||
|
||||
static final Consumer<String> DISCARDER = _ -> {};
|
||||
}
|
||||
@ -43,9 +43,11 @@ public enum StandardPackageType implements PackageType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets file extension of this package type.
|
||||
* E.g.: <code>.msi</code>, <code>.dmg</code>, <code>.deb</code>.
|
||||
* @return file extension of this package type
|
||||
* Gets file extension corresponding to the package type. E.g.:
|
||||
* <code>.msi</code>, <code>.dmg</code>, <code>.deb</code>.
|
||||
*
|
||||
* @return file extension corresponding to the package type; the value starts
|
||||
* with the period (.) character.
|
||||
*/
|
||||
public String suffix() {
|
||||
return suffix;
|
||||
|
||||
@ -388,7 +388,34 @@ help.option.vendor=\
|
||||
\ Vendor of the application
|
||||
|
||||
help.option.verbose=\
|
||||
\ Enables verbose output
|
||||
\ Configures verbose output. Where "category" is one of\n\
|
||||
\ "all"\n\
|
||||
\ "console"\n\
|
||||
\ "log"\n\
|
||||
\ "errors"\n\
|
||||
\ "progress"\n\
|
||||
\ "resources"\n\
|
||||
\ "summary"\n\
|
||||
\ "tools"\n\
|
||||
\ "trace"\n\
|
||||
\ "warnings"\n\
|
||||
\n\
|
||||
\ Suppress all console output, enable logging via System.Logger API:\n\
|
||||
\ --verbose log\n\
|
||||
\ Enable all message categories in the console:\n\
|
||||
\ --verbose console\n\
|
||||
\ Enable all message categories, but "trace" and "tools" in the console:\n\
|
||||
\ --verbose console,-trace,-tools\n\
|
||||
\ Enable "trace" and "tools" message categories in the console:\n\
|
||||
\ --verbose trace,tools\n\
|
||||
\ Enable "trace" and "tools" message categories in the console and\n\
|
||||
\ enable logging via System.Logger API:\n\
|
||||
\ --verbose log,trace,tools\n\
|
||||
\n\
|
||||
\ If the option is specified without the value, it is equivalent to\n\
|
||||
\ --verbose console,-trace\n\
|
||||
\ If the option is not specified it is equivalent to\n\
|
||||
\ --verbose errors,warnings\n\
|
||||
|
||||
help.option.version=\
|
||||
\ Print the product version to the output stream and exit.
|
||||
|
||||
@ -37,6 +37,16 @@ bundle-type.linux-app=Linux Application Image
|
||||
bundle-type.linux-deb=Linux DEB Package
|
||||
bundle-type.linux-rpm=Linux RPM Package
|
||||
|
||||
summary.property.operation=Operation
|
||||
summary.property.operation.format=Create {0}
|
||||
summary.property.output-bundle=Output bundle
|
||||
summary.property.version=Version
|
||||
summary.warning=WARNING: {0}
|
||||
summary.multi-line-warning=WARNING: {0}:
|
||||
|
||||
summary.value.disabled=Disabled
|
||||
summary.value.enabled=Enabled
|
||||
|
||||
resource.post-app-image-script=script to run after application image is populated
|
||||
|
||||
message.using-default-resource=Using default package resource {0} {1} (add {2} to the resource-dir to customize).
|
||||
@ -59,6 +69,9 @@ message.error-header=Error: {0}
|
||||
message.advice-header=Advice to fix: {0}
|
||||
message.failed-command-output-header=Command output:
|
||||
|
||||
progress.warning-header=WARNING: {0}
|
||||
progress.warning-header2=WARNING: {0}: {1}
|
||||
|
||||
error.command-failed-unexpected-output=Unexpected output from executing the command {0}
|
||||
error.command-failed-unexpected-exit-code=Unexpected exit code {0} from executing the command {1}
|
||||
error.command-failed-timed-out=Timed-out command {0}
|
||||
@ -91,6 +104,7 @@ error.parameter-not-mac-bundle=The value "{0}" provided for parameter {1} is not
|
||||
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-invalid-value=Invalid value "{0}" provided for 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
|
||||
error.properties-parameter-not-path=The value "{0}" provided for property "{1}" in "{2}" file is not a valid path
|
||||
@ -113,8 +127,8 @@ error.tool-not-found.advice=Please install "{0}"
|
||||
error.tool-old-version=Can not find "{0}" {1} or newer
|
||||
error.tool-old-version.advice=Please install "{0}" {1} or newer
|
||||
|
||||
warning.tempdir.cleanup-failed=Warning: Failed to clean-up temporary directory {0}
|
||||
warning.tempdir.cleanup-file-failed=Warning: Failed to delete "{0}" file in the temporary directory
|
||||
warning.tempdir.cleanup-failed=Failed to clean-up temporary directory {0}
|
||||
warning.tempdir.cleanup-file-failed=Failed to delete "{0}" file in the temporary directory
|
||||
|
||||
error.output-bundle-cannot-be-overwritten=Output package file "{0}" exists and can not be removed.
|
||||
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.summary;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.jpackage.internal.util.MultiResourceBundle;
|
||||
import jdk.jpackage.internal.util.StringBundle;
|
||||
|
||||
final class I18N {
|
||||
|
||||
private I18N() {
|
||||
}
|
||||
|
||||
static String getString(String key) {
|
||||
return BUNDLE.getString(key);
|
||||
}
|
||||
|
||||
static String format(String key, Object ... args) {
|
||||
return BUNDLE.format(key, args);
|
||||
}
|
||||
|
||||
private static final StringBundle BUNDLE;
|
||||
|
||||
static {
|
||||
var prefix = "jdk.jpackage.internal.resources.";
|
||||
BUNDLE = StringBundle.fromResourceBundle(MultiResourceBundle.create(
|
||||
prefix + "MainResources",
|
||||
Map.of(
|
||||
OperatingSystem.LINUX, List.of(prefix + "LinuxResources"),
|
||||
OperatingSystem.MACOS, List.of(prefix + "MacResources"),
|
||||
OperatingSystem.WINDOWS, List.of(prefix + "WinResources")
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.summary;
|
||||
|
||||
/**
|
||||
* Property in summary.
|
||||
*/
|
||||
public non-sealed interface Property extends SummaryItem {
|
||||
|
||||
String formatLabel();
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.summary;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Property for summary.
|
||||
*/
|
||||
public enum StandardProperty implements Property {
|
||||
|
||||
//
|
||||
// Keep items in the order they should be printed in the summary.
|
||||
//
|
||||
|
||||
OPERATION("summary.property.operation", "summary.property.operation.format"),
|
||||
|
||||
MAC_SIGN_APP_IMAGE_OPERATION("summary.property.operation", "summary.property.mac-sign-app-image.format"),
|
||||
|
||||
OUTPUT_BUNDLE("summary.property.output-bundle"),
|
||||
|
||||
LINUX_PACKAGE_NAME("summary.property.linux-package-name"),
|
||||
|
||||
WIN_MSI_PRODUCT_CODE("summary.property.win-product-code"),
|
||||
|
||||
WIN_MSI_UPGRADE_CODE("summary.property.win-upgrade-code"),
|
||||
|
||||
MAC_BUNDLE_IDENTIFIER("summary.property.mac-bundle-identifier"),
|
||||
|
||||
MAC_BUNDLE_NAME("summary.property.mac-bundle-name"),
|
||||
|
||||
VERSION("summary.property.version"),
|
||||
|
||||
WIN_WIX_VERSION("summary.property.win-wix-version"),
|
||||
|
||||
LINUX_DISABLE_REQUIRED_PACKAGES_SEARCH("summary.property.linux-required-packages-search", "summary.value.disabled"),
|
||||
|
||||
;
|
||||
|
||||
StandardProperty(String label, Optional<String> valueFormat) {
|
||||
this.label = Objects.requireNonNull(label);
|
||||
this.valueFormat = Objects.requireNonNull(valueFormat);
|
||||
}
|
||||
|
||||
StandardProperty(String label, String valueFormatter) {
|
||||
this(label, Optional.of(valueFormatter));
|
||||
}
|
||||
|
||||
StandardProperty(String label) {
|
||||
this(label, Optional.empty());
|
||||
}
|
||||
|
||||
String label() {
|
||||
return label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> valueFormat() {
|
||||
return valueFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatLabel() {
|
||||
return I18N.getString(label);
|
||||
}
|
||||
|
||||
private final String label;
|
||||
private final Optional<String> valueFormat;
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.summary;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Warning in summary.
|
||||
*/
|
||||
public enum StandardWarning implements Warning {
|
||||
|
||||
//
|
||||
// Keep items in the order they should be printed in the summary.
|
||||
//
|
||||
|
||||
MAC_SIGNED_PREDEFINED_APP_IMAGE_WITHOUT_PACKAGE_FILE("warning.per.user.app.image.signed"),
|
||||
|
||||
MAC_SIGNED_PKG_WITH_UNSIGNED_PREDEFINED_APP_IMAGE("warning.unsigned.app.image"),
|
||||
|
||||
MAC_NON_STANDARD_APP_CONTENT("warning.non-standard-app-content"),
|
||||
|
||||
MAC_BUNDLE_NAME_TOO_LONG("warning.bundle-name-too-long-warning"),
|
||||
|
||||
LINUX_DEB_MISSING_LICENSE_FILE("message.debs-like-licenses"),
|
||||
|
||||
;
|
||||
|
||||
StandardWarning(String valueFormat) {
|
||||
this.valueFormat = Optional.of(valueFormat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> valueFormat() {
|
||||
return valueFormat;
|
||||
}
|
||||
|
||||
private final Optional<String> valueFormat;
|
||||
}
|
||||
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.summary;
|
||||
|
||||
import static jdk.jpackage.internal.util.IdentityWrapper.wrapIdentity;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
import jdk.jpackage.internal.cli.StandardBundlingOperation;
|
||||
import jdk.jpackage.internal.model.Application;
|
||||
import jdk.jpackage.internal.model.BundleSpec;
|
||||
import jdk.jpackage.internal.model.Package;
|
||||
import jdk.jpackage.internal.util.IdentityWrapper;
|
||||
import jdk.jpackage.internal.util.PathUtils;
|
||||
|
||||
/**
|
||||
* Summary of the operation jpackage will perform.
|
||||
*/
|
||||
public final class Summary implements SummaryAccumulator {
|
||||
|
||||
public void print(Consumer<String> sinkInfo, Consumer<String> sinkWarnings) {
|
||||
|
||||
// Properties
|
||||
properties.entrySet().stream().sorted(comparator()).map(e -> {
|
||||
return String.format("%s: %s", e.getKey().value().formatLabel(), e.getValue());
|
||||
}).forEach(sinkInfo);
|
||||
|
||||
// Warnings
|
||||
warnings.entrySet().stream().sorted(comparator()).map(e -> {
|
||||
switch (e.getValue()) {
|
||||
case SingleLineContent c -> {
|
||||
return I18N.format("summary.warning", c.str());
|
||||
}
|
||||
case MultiLineContent c -> {
|
||||
return Stream.concat(
|
||||
Stream.of(I18N.format("summary.multi-line-warning", c.header())),
|
||||
StreamSupport.stream(c.items().spliterator(), false).map(msg -> {
|
||||
// Add indentation.
|
||||
return " " + msg;
|
||||
})
|
||||
).collect(Collectors.joining("\n"));
|
||||
}
|
||||
}
|
||||
}).forEach(sinkWarnings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putIfAbsent(SummaryItem k, String value) {
|
||||
Objects.requireNonNull(value);
|
||||
switch (k) {
|
||||
case Property prop -> {
|
||||
properties.putIfAbsent(wrapIdentity(prop), value);
|
||||
}
|
||||
case Warning warn -> {
|
||||
warnings.putIfAbsent(wrapIdentity(warn), new SingleLineContent(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(SummaryItem k, String value) {
|
||||
Objects.requireNonNull(value);
|
||||
switch (k) {
|
||||
case Property prop -> {
|
||||
properties.put(wrapIdentity(prop), value);
|
||||
}
|
||||
case Warning warn -> {
|
||||
warnings.put(wrapIdentity(warn), new SingleLineContent(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putMultiValue(Warning k, String header, Iterable<String> items) {
|
||||
if (!items.iterator().hasNext()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
warnings.put(wrapIdentity(k), new MultiLineContent(header, items));
|
||||
}
|
||||
|
||||
public Summary putStandardPropertiesIfAbsent(StandardBundlingOperation op, Path outputDir, BundleSpec bundle) {
|
||||
Objects.requireNonNull(op);
|
||||
Objects.requireNonNull(outputDir);
|
||||
Objects.requireNonNull(bundle);
|
||||
|
||||
if (op != StandardBundlingOperation.SIGN_MAC_APP_IMAGE) {
|
||||
putIfAbsent(StandardProperty.OPERATION, (Object)op.bundleType().label());
|
||||
putIfAbsent(StandardProperty.OUTPUT_BUNDLE, PathUtils.normalizedAbsolutePath(outputDir.resolve(outputBundleName(bundle))));
|
||||
}
|
||||
|
||||
putIfAbsent(StandardProperty.VERSION, version(bundle));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static Path outputBundleName(BundleSpec bundle) {
|
||||
return getProperty(bundle, Application::appImageDirName, pkg -> {
|
||||
return Path.of(pkg.packageFileNameWithSuffix());
|
||||
});
|
||||
}
|
||||
|
||||
private static String version(BundleSpec bundle) {
|
||||
return getProperty(bundle, Application::version, Package::version);
|
||||
}
|
||||
|
||||
private static <T> T getProperty(BundleSpec bundle,
|
||||
Function<Application, T> appPropertyGetter,
|
||||
Function<Package, T> pkgPropertyGetter) {
|
||||
|
||||
Objects.requireNonNull(appPropertyGetter);
|
||||
Objects.requireNonNull(pkgPropertyGetter);
|
||||
|
||||
switch (bundle) {
|
||||
case Application app -> {
|
||||
return appPropertyGetter.apply(app);
|
||||
}
|
||||
case Package pkg -> {
|
||||
return pkgPropertyGetter.apply(pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends SummaryItem> Comparator<Map.Entry<IdentityWrapper<T>, ?>> comparator() {
|
||||
return Comparator.comparing(
|
||||
e -> {
|
||||
return e.getKey().value();
|
||||
},
|
||||
Comparator.comparingInt(SummaryItem::ordinal)
|
||||
);
|
||||
}
|
||||
|
||||
private sealed interface Content {
|
||||
}
|
||||
|
||||
record SingleLineContent(String str) implements Content {
|
||||
SingleLineContent {
|
||||
Objects.requireNonNull(str);
|
||||
}
|
||||
}
|
||||
|
||||
record MultiLineContent(String header, Iterable<String> items) implements Content {
|
||||
MultiLineContent {
|
||||
Objects.requireNonNull(header);
|
||||
Objects.requireNonNull(items);
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<IdentityWrapper<Property>, String> properties = new HashMap<>();
|
||||
private final Map<IdentityWrapper<Warning>, Content> warnings = new HashMap<>();
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.summary;
|
||||
|
||||
/**
|
||||
* Accumulator of items for summary.
|
||||
*/
|
||||
public interface SummaryAccumulator {
|
||||
|
||||
default void putIfAbsent(SummaryItem k, Object... valueFormatArgs) {
|
||||
putIfAbsent(k, k.formatValue(valueFormatArgs));
|
||||
}
|
||||
|
||||
default void put(SummaryItem k, Object... valueFormatArgs) {
|
||||
put(k, k.formatValue(valueFormatArgs));
|
||||
}
|
||||
|
||||
default void putMultiValue(Warning k, Iterable<String> items, Object... valueFormatArgs) {
|
||||
putMultiValue(k, k.formatValue(valueFormatArgs), items);
|
||||
}
|
||||
|
||||
void put(SummaryItem k, String value);
|
||||
|
||||
void putIfAbsent(SummaryItem k, String value);
|
||||
|
||||
void putMultiValue(Warning k, String header, Iterable<String> items);
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.summary;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public sealed interface SummaryItem permits Property, Warning {
|
||||
|
||||
int ordinal();
|
||||
|
||||
Optional<String> valueFormat();
|
||||
|
||||
default String formatValue(Object... valueFormatArgs) {
|
||||
return valueFormat().map(valueFormat -> {
|
||||
return I18N.format(valueFormat, valueFormatArgs);
|
||||
}).orElseGet(() -> {
|
||||
if (valueFormatArgs.length != 1) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return valueFormatArgs[0].toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal.summary;
|
||||
|
||||
/**
|
||||
* Warning in summary.
|
||||
*/
|
||||
public non-sealed interface Warning extends SummaryItem {
|
||||
}
|
||||
@ -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,11 +28,18 @@ import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class SetBuilder<T> {
|
||||
|
||||
public static <T> SetBuilder<T> build() {
|
||||
return new SetBuilder<>();
|
||||
@SafeVarargs
|
||||
@SuppressWarnings("varargs")
|
||||
public static <T> SetBuilder<T> build(T... v) {
|
||||
return new SetBuilder<T>().add(v);
|
||||
}
|
||||
|
||||
public static <T> SetBuilder<T> build(Collection<? extends T> v) {
|
||||
return new SetBuilder<T>().add(v);
|
||||
}
|
||||
|
||||
public SetBuilder<T> set(Collection<? extends T> v) {
|
||||
@ -67,6 +74,11 @@ public final class SetBuilder<T> {
|
||||
return remove(List.of(v));
|
||||
}
|
||||
|
||||
public SetBuilder<T> mutate(Consumer<SetBuilder<T>> mutator) {
|
||||
mutator.accept(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SetBuilder<T> clear() {
|
||||
values.clear();
|
||||
return this;
|
||||
|
||||
@ -121,9 +121,76 @@ The `jpackage` tool will take as input a Java application and a Java run-time im
|
||||
|
||||
: Vendor of the application
|
||||
|
||||
<a id="option-verbose">`--verbose`</a>
|
||||
<a id="option-verbose">`--verbose` <\[-\]key(,\[-\]key)*></a>
|
||||
|
||||
: Enables verbose output.
|
||||
: Configures verbose output. Enables and/or disables log message categories using zero or more of the keys described below separated by commas.
|
||||
The key `all` enables or disables all categories (respectively); other keys enable the corresponding category, or disable it if preceded by a hyphen (`-`).
|
||||
|
||||
Supported values for *key* are:
|
||||
|
||||
- `all`: Enables console output and routing of all message categories to the logging framework through the [System.Logger API](../../api/java.base/java/lang/System.Logger.html). Equivalent to `console,log`.
|
||||
|
||||
- `console`: Enables console output of all message categories. Equivalent to `all,-log`.
|
||||
|
||||
- `errors`: Enables output of fatal errors.
|
||||
If the key is specified without the `trace` key, error messages will be written to the console without the corresponding exception stacktraces.
|
||||
If the key is specified with the `trace` key, error messages will be written to the console with the corresponding exception stacktraces.
|
||||
|
||||
- `progress`: Enables output of progress messages.
|
||||
|
||||
- `resources`: Enables output of messages about the use of the configurable resources.
|
||||
|
||||
- `summary`: Enables output of the bundle properties and the versions of the tools being used.
|
||||
|
||||
- `tools`: Enables output of commands being executed.
|
||||
If the key is specified without the `trace` key, the jpackage will print the command lines without the output produced by executing these command lines.
|
||||
Only command lines that are relevant to package customization will be written to the console.
|
||||
If the key is specified with the `trace` key, the jpackage will print all command lines and their output.
|
||||
|
||||
- `trace`: Enables output of stack traces of suppressed exceptions and details of the jpackage execution.
|
||||
When combined with other keys, it enables additional information in messages from the corresponding message categories, as described above.
|
||||
|
||||
- `warning`: Enables output of warnings.
|
||||
|
||||
- `log`: Enables routing of all message categories to the logging framework through the [System.Logger API](../../api/java.base/java/lang/System.Logger.html).
|
||||
The logger name will be "jdk.jpackage".
|
||||
|
||||
If the option is specified without the value, it is equivalent to:
|
||||
```
|
||||
--verbose console,-trace
|
||||
```
|
||||
|
||||
If the option is not specified, it is equivalent to:
|
||||
```
|
||||
--verbose errors,warnings
|
||||
```
|
||||
|
||||
Sample usage:
|
||||
|
||||
Suppress all console output, enable logging via the [System.Logger API](../../api/java.base/java/lang/System.Logger.html):
|
||||
```
|
||||
--verbose log
|
||||
```
|
||||
|
||||
Enable all message categories in the console:
|
||||
```
|
||||
--verbose console
|
||||
```
|
||||
|
||||
Enable all message categories, but "trace" and "tools" in the console:
|
||||
```
|
||||
--verbose console,-trace,-tools
|
||||
```
|
||||
|
||||
Enable "trace" and "tools" message categories in the console:
|
||||
```
|
||||
--verbose trace,tools
|
||||
```
|
||||
|
||||
Enable "trace" and "tools" message categories in the console and enable logging via the [System.Logger API](../../api/java.base/java/lang/System.Logger.html):
|
||||
```
|
||||
--verbose log,trace,tools
|
||||
```
|
||||
|
||||
<a id="option-version">`--version`</a>
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@ import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_WIN_MSI
|
||||
import static jdk.jpackage.internal.util.MemoizingSupplier.runOnce;
|
||||
|
||||
import jdk.jpackage.internal.cli.Options;
|
||||
import jdk.jpackage.internal.summary.StandardProperty;
|
||||
|
||||
public class WinBundlingEnvironment extends DefaultBundlingEnvironment {
|
||||
|
||||
@ -47,28 +48,27 @@ public class WinBundlingEnvironment extends DefaultBundlingEnvironment {
|
||||
|
||||
private static void createMsiPackage(Options options, WinSystemEnvironment sysEnv) {
|
||||
|
||||
addWixSummary(options, sysEnv);
|
||||
|
||||
createNativePackage(options,
|
||||
WinFromOptions.createWinMsiPackage(options),
|
||||
buildEnv()::create,
|
||||
WinPackagingPipeline.build(),
|
||||
(env, pkg, outputDir) -> {
|
||||
|
||||
traceWixToolset(sysEnv);
|
||||
|
||||
return new WinMsiPackager(env, pkg, outputDir, sysEnv);
|
||||
});
|
||||
}
|
||||
|
||||
private static void createExePackage(Options options, WinSystemEnvironment sysEnv) {
|
||||
|
||||
addWixSummary(options, sysEnv);
|
||||
|
||||
createNativePackage(options,
|
||||
WinFromOptions.createWinExePackage(options),
|
||||
buildEnv()::create,
|
||||
WinPackagingPipeline.build(),
|
||||
(env, pkg, outputDir) -> {
|
||||
|
||||
traceWixToolset(sysEnv);
|
||||
|
||||
final var msiOutputDir = env.buildRoot().resolve("msi");
|
||||
|
||||
var msiPackager = new WinMsiPackager(env, pkg.msiPackage(),
|
||||
@ -90,14 +90,7 @@ public class WinBundlingEnvironment extends DefaultBundlingEnvironment {
|
||||
return new BuildEnvFromOptions().predefinedAppImageLayout(APPLICATION_LAYOUT);
|
||||
}
|
||||
|
||||
private static void traceWixToolset(WinSystemEnvironment sysEnv) {
|
||||
final var wixToolset = sysEnv.wixToolset();
|
||||
|
||||
for (var tool : wixToolset.getType().getTools()) {
|
||||
Log.verbose(I18N.format("message.tool-version",
|
||||
wixToolset.getToolPath(tool).getFileName(),
|
||||
wixToolset.getVersion()));
|
||||
}
|
||||
private static void addWixSummary(Options options, WinSystemEnvironment sysEnv) {
|
||||
OptionUtils.summary(options).put(StandardProperty.WIN_WIX_VERSION, sysEnv.wixToolset().getVersion());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -50,6 +50,7 @@ import jdk.jpackage.internal.model.WinExePackage;
|
||||
import jdk.jpackage.internal.model.WinLauncher;
|
||||
import jdk.jpackage.internal.model.WinLauncherMixin;
|
||||
import jdk.jpackage.internal.model.WinMsiPackage;
|
||||
import jdk.jpackage.internal.summary.StandardProperty;
|
||||
|
||||
final class WinFromOptions {
|
||||
|
||||
@ -104,7 +105,13 @@ final class WinFromOptions {
|
||||
|
||||
WIN_UPGRADE_UUID.ifPresentIn(options, pkgBuilder::upgradeCode);
|
||||
|
||||
return pkgBuilder.create();
|
||||
var pkg = pkgBuilder.create();
|
||||
|
||||
var summary = OptionUtils.summary(options);
|
||||
summary.put(StandardProperty.WIN_MSI_PRODUCT_CODE, pkg.productCode());
|
||||
summary.put(StandardProperty.WIN_MSI_UPGRADE_CODE, pkg.upgradeCode());
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
static WinExePackage createWinExePackage(Options options) {
|
||||
|
||||
@ -175,6 +175,7 @@ final class WinMsiPackager implements Consumer<PackagingPipeline.Builder> {
|
||||
IOUtils.copyFile(licenseFile, destFile);
|
||||
|
||||
RtfConverter.createSimple(licenseFile).ifPresent(toConsumer(rtfConverter -> {
|
||||
Log.trace("Convert a copy of the license file [%s] to RTF", licenseFile);
|
||||
destFile.toFile().setWritable(true);
|
||||
rtfConverter.convert(destFile);
|
||||
}));
|
||||
@ -187,7 +188,7 @@ final class WinMsiPackager implements Consumer<PackagingPipeline.Builder> {
|
||||
|
||||
final var msiOut = outputDir.resolve(pkg.packageFileNameWithSuffix());
|
||||
|
||||
Log.verbose(I18N.format("message.preparing-msi-config", msiOut.toAbsolutePath()));
|
||||
Log.progress(I18N.format("message.preparing-msi-config", msiOut.toAbsolutePath()));
|
||||
|
||||
final var wixVars = createWixVars();
|
||||
|
||||
@ -249,7 +250,7 @@ final class WinMsiPackager implements Consumer<PackagingPipeline.Builder> {
|
||||
.filter(custom -> primaryWxlFiles.stream().noneMatch(primary ->
|
||||
primary.getFileName().toString().equalsIgnoreCase(
|
||||
custom.getFileName().toString())))
|
||||
.peek(custom -> Log.verbose(I18N.format(
|
||||
.peek(custom -> Log.useResource(I18N.format(
|
||||
"message.using-custom-resource", String.format("[%s]",
|
||||
I18N.getString("resource.wxl-file")),
|
||||
custom.getFileName()))).toList();
|
||||
@ -317,9 +318,6 @@ final class WinMsiPackager implements Consumer<PackagingPipeline.Builder> {
|
||||
wixVars.put("JpProductCode", pkg.productCode().toString());
|
||||
wixVars.put("JpProductUpgradeCode", pkg.upgradeCode().toString());
|
||||
|
||||
Log.verbose(I18N.format("message.product-code", pkg.productCode()));
|
||||
Log.verbose(I18N.format("message.upgrade-code", pkg.upgradeCode()));
|
||||
|
||||
wixVars.define("JpAllowUpgrades");
|
||||
if (!pkg.isRuntimeInstaller()) {
|
||||
wixVars.define("JpAllowDowngrades");
|
||||
|
||||
@ -30,6 +30,7 @@ import static java.util.stream.Collectors.toSet;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
@ -125,6 +126,7 @@ public enum WixTool {
|
||||
.collect(toSet());
|
||||
if (sameVersionTools.equals(Set.of(Candle3)) || sameVersionTools.equals(Set.of(Light3))) {
|
||||
// There is only one tool from WiX v3 toolset of some version available. Discard it.
|
||||
Log.trace("Discard [%s]: incomplete", sameVersionLookupResults.getFirst().info());
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
@ -178,6 +180,11 @@ public enum WixTool {
|
||||
});
|
||||
});
|
||||
|
||||
Log.trace("Using %s WiX Toolkit v%s", toolset.getType(), toolset.getVersion());
|
||||
toolset.getType().getTools().stream().sorted().forEach(tool -> {
|
||||
Log.trace("%s: %s", tool, toolset.getToolPath(tool));
|
||||
});
|
||||
|
||||
return toolset;
|
||||
}
|
||||
|
||||
@ -199,6 +206,12 @@ public enum WixTool {
|
||||
Objects.requireNonNull(tool);
|
||||
Objects.requireNonNull(lookupDir);
|
||||
|
||||
lookupDir.ifPresentOrElse(theLookupDir -> {
|
||||
Log.trace("Look up for %s in [%s] directory", tool.toolFileName, theLookupDir);
|
||||
}, () -> {
|
||||
Log.trace("Look up for %s in the PATH", tool.toolFileName);
|
||||
});
|
||||
|
||||
final Path toolPath = lookupDir.map(p -> {
|
||||
return p.resolve(tool.toolFileName);
|
||||
}).orElse(tool.toolFileName);
|
||||
@ -256,7 +269,7 @@ public enum WixTool {
|
||||
// Detect FIPS mode
|
||||
var fips = false;
|
||||
try {
|
||||
final var result = Executor.of(toolPath.toString(), "-?").setQuiet(true).saveOutput(true).execute();
|
||||
final var result = Executor.of(toolPath.toString(), "-?").quiet().saveOutput(true).execute();
|
||||
final var exitCode = result.getExitCode();
|
||||
if (exitCode != 0 /* 308 */) {
|
||||
final var output = result.getOutput();
|
||||
@ -265,12 +278,18 @@ public enum WixTool {
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Log.trace(ex, "Failed to execute [%s] command with '-?' option to detect FIPS mode. Assume FIPS=false", toolPath);
|
||||
}
|
||||
info = new DefaultCandleInfo(info, fips);
|
||||
}
|
||||
|
||||
Log.trace("Found [%s]", info);
|
||||
|
||||
return Optional.of(new ToolLookupResult(tool, info));
|
||||
} else {
|
||||
if (parsedVersion.find().isPresent()) {
|
||||
Log.trace("Discard [%s]: failed validation", new DefaultToolInfo(toolPath, parsedVersion.get()));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@ -294,7 +313,14 @@ public enum WixTool {
|
||||
|
||||
private static Optional<Path> getEnvVariableAsPath(String envVar) {
|
||||
Objects.requireNonNull(envVar);
|
||||
return Optional.ofNullable(Globals.instance().system().getenv(envVar)).flatMap(PathUtils::asPath);
|
||||
return Optional.ofNullable(Globals.instance().system().getenv(envVar)).map(v -> {
|
||||
try {
|
||||
return Path.of(v);
|
||||
} catch (InvalidPathException ex) {
|
||||
Log.trace(ex, "The value of environment variable '%s' [%s] is not a path", envVar, v);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static List<Path> findWixCurrentInstallDirs() {
|
||||
@ -318,6 +344,7 @@ public enum WixTool {
|
||||
try (var paths = Files.walk(path, 1)) {
|
||||
return paths.toList();
|
||||
} catch (IOException ex) {
|
||||
Log.trace(ex, "Can not get a listing of [%s] directory", path);
|
||||
return List.<Path>of();
|
||||
}
|
||||
}).flatMap(List::stream)
|
||||
|
||||
@ -36,6 +36,10 @@ resource.launcher-as-service-wix-file=Service installer WiX project file
|
||||
resource.wix-src-conv=XSLT stylesheet converting WiX sources from WiX v3 to WiX v4 format
|
||||
resource.installer-exe=installer executable
|
||||
|
||||
summary.property.win-product-code=MSI ProductCode
|
||||
summary.property.win-upgrade-code=MSI UpgradeCode
|
||||
summary.property.win-wix-version=WiX Toolkit version
|
||||
|
||||
error.no-wix-tools=No usable WiX Toolset installation found
|
||||
error.no-wix-tools.advice=Install the latest WiX v3 from https://github.com/wixtoolset/wix3/releases or WiX v4+ from https://github.com/wixtoolset/wix/releases
|
||||
error.version-string-wrong-format.advice=Set value of --app-version parameter to a valid Windows Installer ProductVersion.
|
||||
@ -45,7 +49,6 @@ error.msi-product-version-build-out-of-range=Build part of version must be in th
|
||||
error.msi-product-version-minor-out-of-range=Minor version must be in the range [0, 255]
|
||||
error.version-swap=Failed to update version information for {0}
|
||||
error.icon-swap=Failed to update icon for {0}
|
||||
error.invalid-envvar=Invalid value of {0} environment variable
|
||||
error.lock-resource=Failed to lock: {0}
|
||||
error.unlock-resource=Failed to unlock: {0}
|
||||
error.read-wix-l10n-file=Failed to parse {0} file
|
||||
@ -56,6 +59,4 @@ error.missing-service-installer.advice=Add 'service-installer.exe' service insta
|
||||
|
||||
message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
|
||||
message.tool-version=Detected [{0}] version [{1}].
|
||||
message.product-code=MSI ProductCode: {0}.
|
||||
message.upgrade-code=MSI UpgradeCode: {0}.
|
||||
message.preparing-msi-config=Preparing MSI config: {0}.
|
||||
|
||||
@ -24,7 +24,9 @@ exclusiveAccess.dirs = \
|
||||
modules = \
|
||||
jdk.jpackage/jdk.jpackage.internal:+open \
|
||||
jdk.jpackage/jdk.jpackage.internal.cli \
|
||||
jdk.jpackage/jdk.jpackage.internal.log \
|
||||
jdk.jpackage/jdk.jpackage.internal.model \
|
||||
jdk.jpackage/jdk.jpackage.internal.summary \
|
||||
jdk.jpackage/jdk.jpackage.internal.util \
|
||||
jdk.jpackage/jdk.jpackage.internal.util.function \
|
||||
jdk.jpackage/jdk.jpackage.internal.resources:+open \
|
||||
|
||||
@ -73,15 +73,45 @@ public record CannedFormattedString(BiFunction<String, Object[], String> formatt
|
||||
String format();
|
||||
List<Object> modelArgs();
|
||||
|
||||
public enum Formatter {
|
||||
JPACKAGE_MAIN_STRING_BUNDLE,
|
||||
MESSAGE_FORMAT,
|
||||
;
|
||||
}
|
||||
|
||||
default Formatter formatter() {
|
||||
return Formatter.JPACKAGE_MAIN_STRING_BUNDLE;
|
||||
}
|
||||
|
||||
default CannedFormattedString asCannedFormattedString(Object ... args) {
|
||||
if (args.length != modelArgs().size()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return JPackageStringBundle.MAIN.cannedFormattedString(format(), args);
|
||||
|
||||
var format = Objects.requireNonNull(format());
|
||||
|
||||
return switch (Objects.requireNonNull(formatter())) {
|
||||
case JPACKAGE_MAIN_STRING_BUNDLE -> {
|
||||
yield JPackageStringBundle.MAIN.cannedFormattedString(format, args);
|
||||
}
|
||||
case MESSAGE_FORMAT -> {
|
||||
yield CannedFormattedString.createFromMessageFormat(format, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
default Pattern asPattern() {
|
||||
return JPackageStringBundle.MAIN.cannedFormattedStringAsPattern(format(), modelArgs().toArray());
|
||||
var format = Objects.requireNonNull(format());
|
||||
var args = Objects.requireNonNull(modelArgs()).toArray();
|
||||
|
||||
return switch (Objects.requireNonNull(formatter())) {
|
||||
case JPACKAGE_MAIN_STRING_BUNDLE -> {
|
||||
yield JPackageStringBundle.MAIN.cannedFormattedStringAsPattern(format, args);
|
||||
}
|
||||
case MESSAGE_FORMAT -> {
|
||||
yield CannedMessageFormat.create(format, args).toPattern();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import static java.util.stream.Collectors.groupingBy;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.Collectors.mapping;
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
@ -61,10 +62,12 @@ import java.util.spi.ToolProvider;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
import jdk.jpackage.internal.cli.LogConfigParser;
|
||||
import jdk.jpackage.internal.model.DottedVersion;
|
||||
import jdk.jpackage.internal.util.MacBundle;
|
||||
import jdk.jpackage.internal.util.Result;
|
||||
import jdk.jpackage.internal.util.RuntimeReleaseFile;
|
||||
import jdk.jpackage.internal.util.SetBuilder;
|
||||
import jdk.jpackage.internal.util.function.ThrowingConsumer;
|
||||
import jdk.jpackage.internal.util.function.ThrowingFunction;
|
||||
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
||||
@ -85,6 +88,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
verifyActions = new Actions();
|
||||
excludeStandardAsserts(StandardAssert.MAIN_LAUNCHER_DESCRIPTION);
|
||||
removeOldOutputBundle = true;
|
||||
logConfig = new LogConfig();
|
||||
}
|
||||
|
||||
private JPackageCommand(JPackageCommand cmd, boolean immutable) {
|
||||
@ -104,6 +108,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
winMsiLogFile = cmd.winMsiLogFile;
|
||||
unpackedPackageDirectory = cmd.unpackedPackageDirectory;
|
||||
explicitVersion = cmd.explicitVersion;
|
||||
logConfig = cmd.logConfig;
|
||||
}
|
||||
|
||||
JPackageCommand createImmutableCopy() {
|
||||
@ -994,6 +999,38 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public JPackageCommand setEnabledMessageCategories(Set<MessageCategory> categories) {
|
||||
verifyMutable();
|
||||
logConfig = new LogConfig(
|
||||
categories,
|
||||
SetBuilder.build(logConfig.remove()).remove(categories).emptyAllowed(true).create());
|
||||
return this;
|
||||
}
|
||||
|
||||
public JPackageCommand setEnabledMessageCategories(MessageCategory... categories) {
|
||||
return setEnabledMessageCategories(Set.of(categories));
|
||||
}
|
||||
|
||||
public JPackageCommand setDisabledMessageCategories(Set<MessageCategory> categories) {
|
||||
verifyMutable();
|
||||
logConfig = new LogConfig(
|
||||
SetBuilder.build(logConfig.add()).remove(categories).emptyAllowed(true).create(),
|
||||
categories);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JPackageCommand setDisabledMessageCategories(MessageCategory... categories) {
|
||||
return setDisabledMessageCategories(Set.of(categories));
|
||||
}
|
||||
|
||||
public static Set<MessageCategory> messageCategoriesConsoleAll() {
|
||||
return Stream.of(MessageCategory.values()).filter(MessageCategory::isConsole).collect(toSet());
|
||||
}
|
||||
|
||||
public static Set<MessageCategory> messageCategoriesConsoleNoTrace() {
|
||||
return SetBuilder.build(messageCategoriesConsoleAll()).remove(MessageCategory.TRACE).create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures this instance to optionally remove the existing output bundle
|
||||
* before running the jpackage command.
|
||||
@ -1070,6 +1107,30 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
return makeAdvice(JPackageStringBundle.MAIN.cannedFormattedString(key, args));
|
||||
}
|
||||
|
||||
public static CannedFormattedString makeProgressWarning(CannedFormattedString v) {
|
||||
return v.addPrefix("progress.warning-header");
|
||||
}
|
||||
|
||||
public static CannedFormattedString makeProgressWarning(String key, Object ... args) {
|
||||
return makeProgressWarning(JPackageStringBundle.MAIN.cannedFormattedString(key, args));
|
||||
}
|
||||
|
||||
public static CannedFormattedString makeSummaryWarning(CannedFormattedString v) {
|
||||
return v.addPrefix("summary.warning");
|
||||
}
|
||||
|
||||
public static CannedFormattedString makeSummaryWarning(String key, Object ... args) {
|
||||
return makeSummaryWarning(JPackageStringBundle.MAIN.cannedFormattedString(key, args));
|
||||
}
|
||||
|
||||
public static CannedFormattedString makeSummaryMultiLineWarning(CannedFormattedString v) {
|
||||
return v.addPrefix("summary.multi-line-warning");
|
||||
}
|
||||
|
||||
public static CannedFormattedString makeSummaryMultiLineWarning(String key, Object ... args) {
|
||||
return makeSummaryMultiLineWarning(JPackageStringBundle.MAIN.cannedFormattedString(key, args));
|
||||
}
|
||||
|
||||
public String getValue(CannedFormattedString str) {
|
||||
return new CannedFormattedString(str.formatter(), str.format(), str.args().stream().map(arg -> {
|
||||
if (arg instanceof CannedArgument cannedArg) {
|
||||
@ -1263,7 +1324,125 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public static enum Macro {
|
||||
public enum MessageCategory {
|
||||
SUMMARY,
|
||||
WARNINGS,
|
||||
ERRORS,
|
||||
PROGRESS,
|
||||
TRACE,
|
||||
RESOURCES,
|
||||
TOOLS,
|
||||
SYSTEM_LOGGER,
|
||||
;
|
||||
|
||||
MessageCategory() {
|
||||
// Ensure this item has a peer with the same name in LogConfigParser.MessageCategory enum.
|
||||
LogConfigParser.MessageCategory.valueOf(name());
|
||||
}
|
||||
|
||||
static Set<MessageCategory> parseVerboseOptionValue(String str) {
|
||||
return LogConfigParser.tokenize(str).stream()
|
||||
.map(Enum::name)
|
||||
.map(MessageCategory::valueOf)
|
||||
.collect(toSet());
|
||||
}
|
||||
|
||||
static String toVerboseOptionValue(Set<MessageCategory> categories) {
|
||||
Objects.requireNonNull(categories);
|
||||
|
||||
String negativeRoot;
|
||||
|
||||
if (categories.contains(SYSTEM_LOGGER)) {
|
||||
negativeRoot = "all";
|
||||
} else {
|
||||
negativeRoot = "console";
|
||||
}
|
||||
|
||||
var str = categories.stream()
|
||||
.map(Enum::name)
|
||||
.map(String::toLowerCase)
|
||||
.sorted()
|
||||
.collect(joining(","));
|
||||
|
||||
var negateStr = Stream.concat(
|
||||
Stream.of(negativeRoot),
|
||||
messageCategoriesConsoleAll().stream()
|
||||
.filter(Predicate.not(categories::contains))
|
||||
.map(Enum::name)
|
||||
.map(String::toLowerCase)
|
||||
.map(v -> {
|
||||
return "-" + v;
|
||||
}).sorted()
|
||||
).collect(joining(","));
|
||||
|
||||
if (str.length() < negateStr.length()) {
|
||||
return str;
|
||||
} else {
|
||||
return negateStr;
|
||||
}
|
||||
}
|
||||
|
||||
boolean isConsole() {
|
||||
switch (this) {
|
||||
case SYSTEM_LOGGER -> {
|
||||
return false;
|
||||
}
|
||||
default -> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
Function<Class<? extends Enum<?>>, String[]> names = enumType -> {
|
||||
return Stream.of(enumType.getEnumConstants()).map(Enum::name).toArray(String[]::new);
|
||||
};
|
||||
|
||||
var missingEnumItems = SetBuilder.build(names.apply(LogConfigParser.MessageCategory.class))
|
||||
.remove(names.apply(MessageCategory.class))
|
||||
.emptyAllowed(true)
|
||||
.create();
|
||||
|
||||
if (!missingEnumItems.isEmpty()) {
|
||||
throw new AssertionError(
|
||||
String.format("%s is missing items: %s", MessageCategory.class, missingEnumItems));
|
||||
}
|
||||
}
|
||||
|
||||
private record LogConfig(Set<MessageCategory> add, Set<MessageCategory> remove) {
|
||||
LogConfig {
|
||||
Objects.requireNonNull(add);
|
||||
Objects.requireNonNull(remove);
|
||||
|
||||
var common = Comm.compare(add, remove).common();
|
||||
if (!common.isEmpty()) {
|
||||
throw new IllegalArgumentException(String.format("Overlap: %s", common));
|
||||
}
|
||||
|
||||
add = Set.copyOf(add);
|
||||
remove = Set.copyOf(remove);
|
||||
}
|
||||
|
||||
LogConfig() {
|
||||
this(Set.of(), Set.of());
|
||||
}
|
||||
|
||||
Set<MessageCategory> filter(Set<MessageCategory> categories) {
|
||||
Objects.requireNonNull(categories);
|
||||
if (categories.containsAll(add) && Collections.disjoint(categories, remove)) {
|
||||
return categories;
|
||||
} else {
|
||||
return SetBuilder.build(categories).add(add).remove(remove).emptyAllowed(true).create();
|
||||
}
|
||||
}
|
||||
|
||||
static Set<MessageCategory> quiet() {
|
||||
return Set.of(MessageCategory.ERRORS, MessageCategory.WARNINGS);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Macro {
|
||||
APPDIR(cmd -> {
|
||||
return cmd.appLayout().appDirectory().toString();
|
||||
}),
|
||||
@ -1818,8 +1997,19 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
addArguments("--runtime-image", defaultRuntime);
|
||||
});
|
||||
|
||||
if (!hasArgument("--verbose") && TKit.verboseJPackage() && !ignoreDefaultVerbose) {
|
||||
addArgument("--verbose");
|
||||
if (!hasArgument("--verbose")) {
|
||||
final Set<MessageCategory> unfilteredCategories;
|
||||
if (ignoreDefaultVerbose) {
|
||||
unfilteredCategories = LogConfig.quiet();
|
||||
} else {
|
||||
unfilteredCategories = DEFAULT_VERBOSE;
|
||||
}
|
||||
|
||||
final var categories = logConfig.filter(unfilteredCategories);
|
||||
|
||||
if (!categories.equals(LogConfig.quiet())) {
|
||||
setArgumentValue("--verbose", MessageCategory.toVerboseOptionValue(categories));
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
@ -2078,6 +2268,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
private Path winMsiLogFile;
|
||||
private Path unpackedPackageDirectory;
|
||||
private String explicitVersion;
|
||||
private LogConfig logConfig;
|
||||
private Set<ReadOnlyPathAssert> readOnlyPathAsserts = Set.of(ReadOnlyPathAssert.values());
|
||||
private Set<StandardAssert> standardAsserts = Set.of(StandardAssert.values());
|
||||
private List<Consumer<Executor.Result>> validators = new ArrayList<>();
|
||||
@ -2097,6 +2288,10 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
// `--runtime-image` parameter set.
|
||||
private static final Optional<Path> DEFAULT_RUNTIME_IMAGE = Optional.ofNullable(TKit.getConfigProperty("runtime-image")).map(Path::of);
|
||||
|
||||
private static final Set<MessageCategory> DEFAULT_VERBOSE = MessageCategory.parseVerboseOptionValue(
|
||||
Optional.ofNullable(TKit.getConfigProperty("verbose")).orElse("console" /* Set max verbose level by default */)
|
||||
);
|
||||
|
||||
public static final String DEFAULT_VERSION = "1.0";
|
||||
|
||||
// [HH:mm:ss.SSS]
|
||||
|
||||
@ -1271,10 +1271,6 @@ public final class TKit {
|
||||
return state().currentTest;
|
||||
}
|
||||
|
||||
static boolean verboseJPackage() {
|
||||
return state().verboseJPackage;
|
||||
}
|
||||
|
||||
static boolean verboseTestSetup() {
|
||||
return state().verboseTestSetup;
|
||||
}
|
||||
@ -1317,7 +1313,6 @@ public final class TKit {
|
||||
Map<Object, Object> properties,
|
||||
boolean trace,
|
||||
boolean traceAsserts,
|
||||
boolean verboseJPackage,
|
||||
boolean verboseTestSetup) {
|
||||
|
||||
Objects.requireNonNull(os);
|
||||
@ -1334,7 +1329,6 @@ public final class TKit {
|
||||
this.trace = trace;
|
||||
this.traceAsserts = traceAsserts;
|
||||
|
||||
this.verboseJPackage = verboseJPackage;
|
||||
this.verboseTestSetup = verboseTestSetup;
|
||||
}
|
||||
|
||||
@ -1379,12 +1373,10 @@ public final class TKit {
|
||||
if (logOptions == null) {
|
||||
trace = true;
|
||||
traceAsserts = true;
|
||||
verboseJPackage = true;
|
||||
verboseTestSetup = true;
|
||||
} else if (logOptions.contains("all")) {
|
||||
trace = false;
|
||||
traceAsserts = false;
|
||||
verboseJPackage = false;
|
||||
verboseTestSetup = false;
|
||||
} else {
|
||||
Predicate<Set<String>> isNonOf = options -> {
|
||||
@ -1393,7 +1385,6 @@ public final class TKit {
|
||||
|
||||
trace = isNonOf.test(Set.of("trace", "t"));
|
||||
traceAsserts = isNonOf.test(Set.of("assert", "a"));
|
||||
verboseJPackage = isNonOf.test(Set.of("jpackage", "jp"));
|
||||
verboseTestSetup = isNonOf.test(Set.of("init", "i"));
|
||||
}
|
||||
|
||||
@ -1413,7 +1404,6 @@ public final class TKit {
|
||||
trace = state.trace;
|
||||
traceAsserts = state.traceAsserts;
|
||||
|
||||
verboseJPackage = state.verboseJPackage;
|
||||
verboseTestSetup = state.verboseTestSetup;
|
||||
|
||||
return this;
|
||||
@ -1462,7 +1452,6 @@ public final class TKit {
|
||||
mutable ? new HashMap<>(properties) : Map.copyOf(properties),
|
||||
trace,
|
||||
traceAsserts,
|
||||
verboseJPackage,
|
||||
verboseTestSetup);
|
||||
}
|
||||
|
||||
@ -1475,7 +1464,6 @@ public final class TKit {
|
||||
private boolean trace;
|
||||
private boolean traceAsserts;
|
||||
|
||||
private boolean verboseJPackage;
|
||||
private boolean verboseTestSetup;
|
||||
|
||||
private boolean mutable = true;
|
||||
@ -1492,7 +1480,6 @@ public final class TKit {
|
||||
private final boolean trace;
|
||||
private final boolean traceAsserts;
|
||||
|
||||
private final boolean verboseJPackage;
|
||||
private final boolean verboseTestSetup;
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +43,7 @@ import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.model.DottedVersion;
|
||||
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
||||
import jdk.jpackage.test.PackageTest.PackageHandlers;
|
||||
|
||||
@ -241,21 +242,39 @@ public class WindowsHelper {
|
||||
|
||||
public enum WixType {
|
||||
WIX3,
|
||||
WIX4
|
||||
WIX4,
|
||||
;
|
||||
|
||||
/**
|
||||
* Returns the file name of the WiX build tool outputting MSI files.
|
||||
*
|
||||
* @return the name of the WiX tool outputting MSI files
|
||||
*/
|
||||
public String buildTool() {
|
||||
return switch (this) {
|
||||
case WIX3 -> "light.exe";
|
||||
case WIX4 -> "wix.exe";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static WixType getWixTypeFromVerboseJPackageOutput(Executor.Result result) {
|
||||
return result.getOutput().stream().map(str -> {
|
||||
if (str.contains("[light.exe]")) {
|
||||
|
||||
var summaryWixVersion = JPackageStringBundle.MAIN.cannedFormattedString(
|
||||
"summary.property.win-wix-version").getValue() + ": ";
|
||||
|
||||
return result.stdout().stream().filter(str -> {
|
||||
return str.startsWith(summaryWixVersion);
|
||||
}).findFirst().map(str -> {
|
||||
var ver = str.substring(summaryWixVersion.length());
|
||||
if (DottedVersion.compareComponents(DottedVersion.lazy(ver), DottedVersion.greedy("4.0")) < 0) {
|
||||
return WixType.WIX3;
|
||||
} else if (str.contains("[wix.exe]")) {
|
||||
return WixType.WIX4;
|
||||
} else {
|
||||
return null;
|
||||
return WixType.WIX4;
|
||||
}
|
||||
}).filter(Objects::nonNull).reduce((a, b) -> {
|
||||
throw new IllegalArgumentException("Invalid input: multiple invocations of WiX tools");
|
||||
}).orElseThrow(() -> new IllegalArgumentException("Invalid input: no invocations of WiX tools"));
|
||||
}).orElseThrow(() -> {
|
||||
return new IllegalArgumentException("Failed to detect WiX version. Likely, the input is missing the summary");
|
||||
});
|
||||
}
|
||||
|
||||
static Optional<Path> toShortPath(Path path) {
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.test.stdmock;
|
||||
|
||||
public interface DebToolsMock extends EnvironmentMock {
|
||||
|
||||
String versionDpkg();
|
||||
|
||||
String versionFakeroot();
|
||||
|
||||
boolean withPackageLookup();
|
||||
|
||||
public static Builder build() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
public DebToolsMock create() {
|
||||
return new DefaultDebToolsMock(versionDpkg, versionFakeroot, packageLookup);
|
||||
}
|
||||
|
||||
public Builder env(DebToolsMock v) {
|
||||
return versionDpkg(v.versionDpkg()).versionFakeroot(v.versionFakeroot()).withPackageLookup(v.withPackageLookup());
|
||||
}
|
||||
|
||||
public Builder versionDpkg(String v) {
|
||||
versionDpkg = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder versionFakeroot(String v) {
|
||||
versionFakeroot = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPackageLookup(boolean v) {
|
||||
if (v) {
|
||||
packageLookup = LinuxPackageLookupMock.ENABLED;
|
||||
} else {
|
||||
packageLookup = LinuxPackageLookupMock.DISABLED;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private String versionDpkg;
|
||||
private String versionFakeroot;
|
||||
private LinuxPackageLookupMock packageLookup = LinuxPackageLookupMock.DISABLED;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.test.stdmock;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.test.LinuxHelper;
|
||||
import jdk.jpackage.test.PackageType;
|
||||
import jdk.jpackage.test.mock.CommandActionSpec;
|
||||
import jdk.jpackage.test.mock.CommandActionSpecs;
|
||||
import jdk.jpackage.test.mock.CommandMockSpec;
|
||||
import jdk.jpackage.test.mock.Script;
|
||||
|
||||
record DefaultDebToolsMock(
|
||||
String versionDpkg,
|
||||
String versionFakeroot,
|
||||
LinuxPackageLookupMock packageLookup) implements DebToolsMock {
|
||||
|
||||
DefaultDebToolsMock {
|
||||
Objects.requireNonNull(versionDpkg);
|
||||
Objects.requireNonNull(versionFakeroot);
|
||||
if (versionDpkg.isBlank()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (versionFakeroot.isBlank()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
Objects.requireNonNull(packageLookup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CommandMockSpec> mocks() {
|
||||
return Stream.of(dpkg(), dpkgdeb(), fakeroot()).map(action -> {
|
||||
return new CommandMockSpec(action.description(), CommandActionSpecs.build().action(action).create());
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(Script.Builder scriptBuilder) {
|
||||
mocks().forEach(scriptBuilder::map);
|
||||
packageLookup.applyTo(scriptBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean withPackageLookup() {
|
||||
return packageLookup == LinuxPackageLookupMock.ENABLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("DEB Env %s; fakeroot=%s%s",
|
||||
versionDpkg, versionFakeroot, withPackageLookup() ? "; ldd" : "");
|
||||
}
|
||||
|
||||
private CommandActionSpec dpkg() {
|
||||
var ver = versionDpkg("dpkg", versionDpkg);
|
||||
return CommandActionSpec.create("dpkg", context -> {
|
||||
if (context.args().contains("--version")) {
|
||||
context.out().println(ver);
|
||||
} else if (context.args().equals(List.of("-s", "coreutils"))) {
|
||||
var out = context.out();
|
||||
out.println("Package: coreutils");
|
||||
out.println("Essential: yes");
|
||||
out.println("Status: install ok installed");
|
||||
} else if (context.args().equals(List.of("--print-architecture"))) {
|
||||
context.out().println(LinuxHelper.getDefaultPackageArch(PackageType.LINUX_DEB));
|
||||
}
|
||||
return Optional.of(0);
|
||||
});
|
||||
}
|
||||
|
||||
private CommandActionSpec dpkgdeb() {
|
||||
var ver = versionDpkg("dpkg-deb", versionDpkg);
|
||||
return CommandActionSpec.create("dpkg-deb", context -> {
|
||||
if (context.args().contains("--version")) {
|
||||
context.out().println(versionDpkg("dpkg-deb", ver));
|
||||
}
|
||||
return Optional.of(0);
|
||||
});
|
||||
}
|
||||
|
||||
private CommandActionSpec fakeroot() {
|
||||
var ver = String.format("fakeroot version %s", versionFakeroot);
|
||||
return CommandActionSpec.create("fakeroot", context -> {
|
||||
if (context.args().contains("--version")) {
|
||||
context.out().println(ver);
|
||||
}
|
||||
return Optional.of(0);
|
||||
});
|
||||
}
|
||||
|
||||
private static String versionDpkg(String tool, String version) {
|
||||
Objects.requireNonNull(tool);
|
||||
Objects.requireNonNull(version);
|
||||
return String.format("Debian '%s' package management program version %s (%s).",
|
||||
tool, version, LinuxHelper.getDefaultPackageArch(PackageType.LINUX_DEB));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.test.stdmock;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.test.mock.CommandActionSpec;
|
||||
import jdk.jpackage.test.mock.CommandActionSpecs;
|
||||
import jdk.jpackage.test.mock.CommandMockSpec;
|
||||
|
||||
record DefaultMacToolsMock() implements MacToolsMock {
|
||||
|
||||
@Override
|
||||
public Collection<CommandMockSpec> mocks() {
|
||||
|
||||
var setfile = CommandActionSpec.create("/Developer/Tools/SetFile", context -> {
|
||||
if (context.args().contains("-h")) {
|
||||
return Optional.of(0);
|
||||
} else {
|
||||
return Optional.of(1);
|
||||
}
|
||||
});
|
||||
|
||||
return Stream.of(setfile).map(action -> {
|
||||
return new CommandMockSpec(action.description(), CommandActionSpecs.build().action(action).create());
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Mac Env";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.test.stdmock;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.test.LinuxHelper;
|
||||
import jdk.jpackage.test.PackageType;
|
||||
import jdk.jpackage.test.mock.CommandActionSpec;
|
||||
import jdk.jpackage.test.mock.CommandActionSpecs;
|
||||
import jdk.jpackage.test.mock.CommandMockSpec;
|
||||
import jdk.jpackage.test.mock.Script;
|
||||
|
||||
record DefaultRpmToolsMock(String version, LinuxPackageLookupMock packageLookup) implements RpmToolsMock {
|
||||
|
||||
DefaultRpmToolsMock {
|
||||
Objects.requireNonNull(version);
|
||||
if (version.isBlank()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
Objects.requireNonNull(packageLookup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<CommandMockSpec> mocks() {
|
||||
var versionString = "RPM version " + version;
|
||||
|
||||
var rpm = CommandActionSpec.create("rpm", context -> {
|
||||
if (context.args().contains("--version") || context.args().isEmpty()) {
|
||||
context.out().println(versionString);
|
||||
} else if (context.args().equals(List.of("-q", "rpm"))) {
|
||||
context.out().println("rpm-build");
|
||||
}
|
||||
return Optional.of(0);
|
||||
});
|
||||
|
||||
var rpmbuild = CommandActionSpec.create("rpmbuild", context -> {
|
||||
if (context.args().contains("--version")) {
|
||||
context.out().println(versionString);
|
||||
} else if (context.args().contains("--eval=%{_target_cpu}")) {
|
||||
context.out().println(LinuxHelper.getDefaultPackageArch(PackageType.LINUX_RPM));
|
||||
}
|
||||
return Optional.of(0);
|
||||
});
|
||||
|
||||
return Stream.of(rpm, rpmbuild).map(action -> {
|
||||
return new CommandMockSpec(action.description(), CommandActionSpecs.build().action(action).create());
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(Script.Builder scriptBuilder) {
|
||||
mocks().forEach(scriptBuilder::map);
|
||||
packageLookup.applyTo(scriptBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean withPackageLookup() {
|
||||
return packageLookup == LinuxPackageLookupMock.ENABLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("RPM Env %s%s", version, withPackageLookup() ? "; ldd" : "");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.test.stdmock;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import jdk.jpackage.internal.model.DottedVersion;
|
||||
import jdk.jpackage.test.mock.CommandMockSpec;
|
||||
import jdk.jpackage.test.mock.CommandActionSpecs;
|
||||
|
||||
record DefaultWixToolsMock(String version) implements WixToolsMock {
|
||||
|
||||
DefaultWixToolsMock {
|
||||
Objects.requireNonNull(version);
|
||||
if (version.isBlank()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<CommandMockSpec> mocks() {
|
||||
if (DottedVersion.compareComponents(DottedVersion.lazy(version), DottedVersion.greedy("4.0")) < 0) {
|
||||
// WiX v3
|
||||
return List.of(
|
||||
new WixToolMock().candle(version).create(),
|
||||
new WixToolMock().light(version).create(),
|
||||
new CommandMockSpec("wix.exe", CommandActionSpecs.build().exit(1).create())
|
||||
);
|
||||
} else {
|
||||
// Wix v4
|
||||
return List.of(
|
||||
new CommandMockSpec("candle.exe", CommandActionSpecs.build().exit(1).create()),
|
||||
new CommandMockSpec("light.exe", CommandActionSpecs.build().exit(1).create()),
|
||||
new WixToolMock().wix(version).create()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("WiX Env %s", version);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.test.stdmock;
|
||||
|
||||
import java.util.Collection;
|
||||
import jdk.jpackage.test.mock.CommandMockSpec;
|
||||
import jdk.jpackage.test.mock.Script;
|
||||
|
||||
public interface EnvironmentMock {
|
||||
|
||||
Collection<CommandMockSpec> mocks();
|
||||
|
||||
default void applyTo(Script.Builder scriptBuilder) {
|
||||
mocks().forEach(scriptBuilder::map);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.test.stdmock;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import jdk.jpackage.test.mock.CommandActionSpecs;
|
||||
import jdk.jpackage.test.mock.CommandMockSpec;
|
||||
import jdk.jpackage.test.mock.CommandMockExit;
|
||||
|
||||
public enum LinuxPackageLookupMock implements EnvironmentMock {
|
||||
|
||||
ENABLED(CommandMockExit.SUCCEED),
|
||||
DISABLED(CommandMockExit.EXIT_1),
|
||||
;
|
||||
|
||||
LinuxPackageLookupMock(CommandMockExit exit) {
|
||||
this.exit = Objects.requireNonNull(exit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<CommandMockSpec> mocks() {
|
||||
return List.of(new CommandMockSpec("ldd", CommandActionSpecs.build().exit(exit).create()));
|
||||
}
|
||||
|
||||
private final CommandMockExit exit;
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.test.stdmock;
|
||||
|
||||
public interface MacToolsMock extends EnvironmentMock {
|
||||
|
||||
public static Builder build() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
public MacToolsMock create() {
|
||||
return new DefaultMacToolsMock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.test.stdmock;
|
||||
|
||||
public interface RpmToolsMock extends EnvironmentMock {
|
||||
|
||||
String version();
|
||||
|
||||
boolean withPackageLookup();
|
||||
|
||||
public static Builder build() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
public RpmToolsMock create() {
|
||||
return new DefaultRpmToolsMock(version, packageLookup);
|
||||
}
|
||||
|
||||
public Builder env(RpmToolsMock v) {
|
||||
return version(v.version()).withPackageLookup(v.withPackageLookup());
|
||||
}
|
||||
|
||||
public Builder version(String v) {
|
||||
version = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPackageLookup(boolean v) {
|
||||
if (v) {
|
||||
packageLookup = LinuxPackageLookupMock.ENABLED;
|
||||
} else {
|
||||
packageLookup = LinuxPackageLookupMock.DISABLED;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private String version;
|
||||
private LinuxPackageLookupMock packageLookup = LinuxPackageLookupMock.DISABLED;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.test.stdmock;
|
||||
|
||||
public interface WixToolsMock extends EnvironmentMock {
|
||||
|
||||
String version();
|
||||
|
||||
public static Builder build() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
public WixToolsMock create() {
|
||||
return new DefaultWixToolsMock(version);
|
||||
}
|
||||
|
||||
public Builder env(WixToolsMock v) {
|
||||
return version(v.version());
|
||||
}
|
||||
|
||||
public Builder version(String v) {
|
||||
version = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
private String version;
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,9 @@ lib.dirs = \
|
||||
|
||||
modules=jdk.jpackage/jdk.jpackage.internal:+open \
|
||||
jdk.jpackage/jdk.jpackage.internal.cli:+open \
|
||||
jdk.jpackage/jdk.jpackage.internal.log:+open \
|
||||
jdk.jpackage/jdk.jpackage.internal.model:+open \
|
||||
jdk.jpackage/jdk.jpackage.internal.summary:+open \
|
||||
jdk.jpackage/jdk.jpackage.internal.pipeline:+open \
|
||||
jdk.jpackage/jdk.jpackage.internal.util:+open \
|
||||
jdk.jpackage/jdk.jpackage.internal.util.function:+open
|
||||
|
||||
@ -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
|
||||
@ -47,7 +47,7 @@ public class BuildEnvTest {
|
||||
public void testUnresolvedAppImageLayout(Path appImageDir) {
|
||||
final var rootDir = Path.of("");
|
||||
|
||||
final var env = BuildEnv.create(rootDir, Optional.empty(), true,
|
||||
final var env = BuildEnv.create(rootDir, Optional.empty(),
|
||||
BuildEnvTest.class, RuntimeLayout.DEFAULT.resolveAt(appImageDir).resetRootDirectory());
|
||||
|
||||
assertEquals(env.appImageDir(), env.appImageLayout().rootDirectory());
|
||||
@ -57,7 +57,6 @@ public class BuildEnvTest {
|
||||
assertEquals(rootDir, env.buildRoot());
|
||||
assertEquals(rootDir.resolve("config"), env.configDir());
|
||||
assertEquals(Optional.empty(), env.resourceDir());
|
||||
assertTrue(env.verbose());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -66,7 +65,7 @@ public class BuildEnvTest {
|
||||
final var appImageDir = Path.of("/foo/bar");
|
||||
|
||||
final var layout = RuntimeLayout.DEFAULT.resolveAt(appImageDir);
|
||||
final var env = BuildEnv.create(rootDir, Optional.empty(), true, BuildEnvTest.class, layout);
|
||||
final var env = BuildEnv.create(rootDir, Optional.empty(), BuildEnvTest.class, layout);
|
||||
|
||||
assertSame(layout, env.appImageLayout());
|
||||
assertEquals(env.appImageDir(), env.appImageLayout().rootDirectory());
|
||||
@ -76,7 +75,6 @@ public class BuildEnvTest {
|
||||
assertEquals(rootDir, env.buildRoot());
|
||||
assertEquals(rootDir.resolve("config"), env.configDir());
|
||||
assertEquals(Optional.empty(), env.resourceDir());
|
||||
assertTrue(env.verbose());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ -86,7 +84,7 @@ public class BuildEnvTest {
|
||||
|
||||
final var layout = RuntimeLayout.DEFAULT;
|
||||
final var env = BuildEnv.withAppImageDir(BuildEnv.create(rootDir,
|
||||
Optional.empty(), false, BuildEnvTest.class, layout), appImageDir);
|
||||
Optional.empty(), BuildEnvTest.class, layout), appImageDir);
|
||||
|
||||
assertNotSame(layout, env.appImageLayout());
|
||||
assertEquals(env.appImageDir(), env.appImageLayout().rootDirectory());
|
||||
@ -96,7 +94,6 @@ public class BuildEnvTest {
|
||||
assertEquals(rootDir, env.buildRoot());
|
||||
assertEquals(rootDir.resolve("config"), env.configDir());
|
||||
assertEquals(Optional.empty(), env.resourceDir());
|
||||
assertFalse(env.verbose());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ -114,7 +111,7 @@ public class BuildEnvTest {
|
||||
}
|
||||
|
||||
final var env = BuildEnv.withAppImageLayout(BuildEnv.create(rootDir,
|
||||
Optional.empty(), false, BuildEnvTest.class, RuntimeLayout.DEFAULT), layout);
|
||||
Optional.empty(), BuildEnvTest.class, RuntimeLayout.DEFAULT), layout);
|
||||
|
||||
assertSame(layout, env.appImageLayout());
|
||||
assertEquals(env.appImageDir(), env.appImageLayout().rootDirectory());
|
||||
@ -123,18 +120,17 @@ public class BuildEnvTest {
|
||||
assertEquals(rootDir, env.buildRoot());
|
||||
assertEquals(rootDir.resolve("config"), env.configDir());
|
||||
assertEquals(Optional.empty(), env.resourceDir());
|
||||
assertFalse(env.verbose());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_asApplicationLayout() {
|
||||
final var rootDir = Path.of("r");
|
||||
|
||||
assertTrue(BuildEnv.create(rootDir, Optional.empty(), false,
|
||||
assertTrue(BuildEnv.create(rootDir, Optional.empty(),
|
||||
BuildEnvTest.class, RuntimeLayout.DEFAULT).asApplicationLayout().isEmpty());
|
||||
|
||||
var layout = ApplicationLayout.build().setAll("foo").create();
|
||||
assertSame(layout, BuildEnv.create(rootDir, Optional.empty(), false,
|
||||
assertSame(layout, BuildEnv.create(rootDir, Optional.empty(),
|
||||
BuildEnvTest.class, layout).asApplicationLayout().orElseThrow());
|
||||
}
|
||||
}
|
||||
|
||||
@ -668,7 +668,7 @@ public class PackagingPipelineTest {
|
||||
}
|
||||
|
||||
private static BuildEnv dummyBuildEnv() {
|
||||
return BuildEnv.create(Path.of("foo"), Optional.empty(), false, PackagingPipeline.class, RuntimeLayout.DEFAULT);
|
||||
return BuildEnv.create(Path.of("foo"), Optional.empty(), PackagingPipeline.class, RuntimeLayout.DEFAULT);
|
||||
}
|
||||
|
||||
private static PackagingPipeline.Builder buildPipeline() {
|
||||
|
||||
@ -25,7 +25,6 @@ package jdk.jpackage.internal;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@ -51,10 +50,13 @@ import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.jpackage.internal.cli.LogConfigParser.MessageCategory;
|
||||
import jdk.jpackage.internal.cli.Options;
|
||||
import jdk.jpackage.internal.cli.StandardOption;
|
||||
import jdk.jpackage.internal.log.LogEnvironment;
|
||||
import jdk.jpackage.internal.util.FileUtils;
|
||||
import jdk.jpackage.internal.util.RetryExecutor;
|
||||
import jdk.jpackage.internal.util.SetBuilder;
|
||||
import jdk.jpackage.internal.util.function.ThrowingFunction;
|
||||
import jdk.jpackage.internal.util.function.ThrowingSupplier;
|
||||
import jdk.jpackage.test.PathDeletionPreventer;
|
||||
@ -155,10 +157,22 @@ public class TempDirectoryTest {
|
||||
private void test_close_impl(CloseType closeType, Path root) throws IOException {
|
||||
var logSink = new StringWriter();
|
||||
var logPrintWriter = new PrintWriter(logSink, true);
|
||||
Globals.instance().loggerOutputStreams(logPrintWriter, logPrintWriter);
|
||||
if (closeType.isVerbose()) {
|
||||
Globals.instance().loggerVerbose();
|
||||
}
|
||||
Globals.instance().logEnv(LogEnvironment.build()
|
||||
.out(logPrintWriter)
|
||||
.err(logPrintWriter)
|
||||
.mutate(logEnvBuilder -> {
|
||||
SetBuilder.build(MessageCategory.values())
|
||||
.remove(MessageCategory.SYSTEM_LOGGER)
|
||||
.mutate(b -> {
|
||||
if (!closeType.isVerbose()) {
|
||||
b.remove(MessageCategory.WARNINGS);
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.forEach(messageCategory -> {
|
||||
messageCategory.applyTo(logEnvBuilder);
|
||||
});
|
||||
}).create());
|
||||
|
||||
final var workDir = root.resolve("workdir");
|
||||
Files.createDirectories(workDir);
|
||||
@ -214,13 +228,13 @@ public class TempDirectoryTest {
|
||||
}
|
||||
|
||||
logPrintWriter.flush();
|
||||
var logMessages = new BufferedReader(new StringReader(logSink.toString())).lines().toList();
|
||||
var logLines = new BufferedReader(new StringReader(logSink.toString())).lines().toList();
|
||||
|
||||
assertTrue(Files.isDirectory(root));
|
||||
|
||||
if (closeType.isSuccess()) {
|
||||
assertFalse(Files.exists(tempDir.path()));
|
||||
assertEquals(List.of(), logMessages);
|
||||
assertEquals(List.of(), logLines);
|
||||
} else {
|
||||
assertTrue(Files.isDirectory(tempDir.path()));
|
||||
assertTrue(Files.exists(leftoverPath));
|
||||
@ -238,12 +252,17 @@ public class TempDirectoryTest {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
assertEquals(List.of(I18N.format(errMessage, leftoverPath)), logMessages.subList(0, 1));
|
||||
|
||||
if (closeType.isVerbose()) {
|
||||
// Check the log contains a stacktrace
|
||||
assertNotEquals(1, logMessages.size());
|
||||
assertEquals(2, logLines.size());
|
||||
assertEquals(List.of(I18N.format("progress.warning-header", I18N.format(errMessage, leftoverPath))), logLines.subList(0, 1));
|
||||
assertTrue(logLines.get(1).startsWith(I18N.format("progress.warning-header", "")), () -> {
|
||||
return String.format("Check [%s] starts with [%s]", logLines.get(1), I18N.format("progress.warning-header", ""));
|
||||
});
|
||||
} else {
|
||||
assertEquals(List.of(), logLines);
|
||||
}
|
||||
|
||||
FileUtils.deleteRecursive(tempDir.path());
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,693 @@
|
||||
/*
|
||||
* 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.cli;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.time.Clock;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.log.AllLoggers;
|
||||
import jdk.jpackage.internal.log.CommandLogger;
|
||||
import jdk.jpackage.internal.log.ConsoleLogger;
|
||||
import jdk.jpackage.internal.log.ErrorLogger;
|
||||
import jdk.jpackage.internal.log.LogEnvironment;
|
||||
import jdk.jpackage.internal.log.LogEnvironment.LogSink;
|
||||
import jdk.jpackage.internal.log.Logger;
|
||||
import jdk.jpackage.internal.log.LoggerRole;
|
||||
import jdk.jpackage.internal.log.ProgressLogger;
|
||||
import jdk.jpackage.internal.log.ResourceLogger;
|
||||
import jdk.jpackage.internal.log.SummaryLogger;
|
||||
import jdk.jpackage.internal.log.TraceLogger;
|
||||
import jdk.jpackage.internal.model.JPackageException;
|
||||
import jdk.jpackage.internal.summary.StandardProperty;
|
||||
import jdk.jpackage.internal.summary.Summary;
|
||||
import jdk.jpackage.internal.summary.Warning;
|
||||
import jdk.jpackage.internal.util.SetBuilder;
|
||||
import jdk.jpackage.internal.util.function.ExceptionBox;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
class LogConfigParserTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void test_valueOf(TestSpec spec) {
|
||||
spec.run();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void test_valueOf_negative(String logEnvStr) {
|
||||
assertThrowsExactly(IllegalArgumentException.class, () -> {
|
||||
LogConfigParser.valueOf(logEnvStr);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_defaultVerbose() {
|
||||
test(LogConfigParser.defaultVerbose(), MessageCategory.toLogRecords(
|
||||
SetBuilder.<MessageCategory>build()
|
||||
.add(MessageCategory.values())
|
||||
.remove(MessageCategory.SYSTEM_LOGGER, MessageCategory.TRACE)
|
||||
.create()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_quiet() {
|
||||
test(LogConfigParser.quiet(), MessageCategory.toLogRecords(
|
||||
MessageCategory.ERRORS,
|
||||
MessageCategory.WARNINGS));
|
||||
}
|
||||
|
||||
private static void test(LogEnvironment.Builder logEnvBuilder, Set<LogRecord> expectedLogRecords) {
|
||||
Objects.requireNonNull(logEnvBuilder);
|
||||
Objects.requireNonNull(expectedLogRecords);
|
||||
|
||||
var consoleSink = new StringWriter();
|
||||
var systemLoggerSink = new StringWriter();
|
||||
|
||||
var logEnv = logEnvBuilder
|
||||
.out(new PrintWriter(consoleSink, true))
|
||||
.err(new PrintWriter(consoleSink, true))
|
||||
.consoleTimestampClock(FIXED_CONSOLE_TIMESTAMP)
|
||||
.systemLoggerFactory(_ -> {
|
||||
return new SimpleSystemLogger(new PrintWriter(systemLoggerSink, true)::println);
|
||||
})
|
||||
.create();
|
||||
|
||||
var logger = AllLoggers.create(logEnv);
|
||||
|
||||
Stream.of(LogMessage.values()).sorted(Comparator.comparing(Enum::name)).forEach(logRecordSrc -> {
|
||||
|
||||
var logRecordSink = new StringWriter();
|
||||
|
||||
var logRecords = LogRecord.findLogRecords(logRecordSrc, expectedLogRecords);
|
||||
var logRecordLogger = AllLoggers.teeLogger(logRecords.stream().map(logRecord -> {
|
||||
return logRecord.createLogger(logRecordSink);
|
||||
}).toList());
|
||||
|
||||
List.of(consoleSink, systemLoggerSink).forEach(sink -> {
|
||||
var buffer = sink.getBuffer();
|
||||
buffer.delete(0, buffer.length());
|
||||
});
|
||||
|
||||
// Record log messages for the logger constructed from the string value
|
||||
// and for the logger constructed from the expected log record from
|
||||
// the same line of code to ensure recorded stack traces will be equal.
|
||||
// Don't use "Collection#forEach()" as the optimizations in the immutable list implementation in JDK27
|
||||
// produce different stack traces for the first and the last items in the two-item list.
|
||||
for (var l : List.of(logRecordLogger, logger)) {
|
||||
logRecordSrc.applyTo(l);
|
||||
}
|
||||
|
||||
switch (logRecords.size()) {
|
||||
case 0 -> {
|
||||
assertEquals("", logRecordSink.toString());
|
||||
assertEquals("", consoleSink.toString());
|
||||
assertEquals("", systemLoggerSink.toString());
|
||||
}
|
||||
case 1 -> {
|
||||
StringWriter nonEmptySink;
|
||||
StringWriter emptySink;
|
||||
if (logRecords.getFirst().isSystemLogger()) {
|
||||
nonEmptySink = systemLoggerSink;
|
||||
emptySink = consoleSink;
|
||||
} else {
|
||||
nonEmptySink = consoleSink;
|
||||
emptySink = systemLoggerSink;
|
||||
}
|
||||
assertEquals(logRecordSink.toString(), nonEmptySink.toString());
|
||||
assertEquals("", emptySink.toString());
|
||||
}
|
||||
case 2 -> {
|
||||
var sink = new StringBuilder();
|
||||
if (logRecords.getFirst().isSystemLogger()) {
|
||||
sink.append(systemLoggerSink.toString()).append(consoleSink.toString());
|
||||
} else {
|
||||
sink.append(consoleSink.toString()).append(systemLoggerSink.toString());
|
||||
}
|
||||
assertEquals(logRecordSink.toString(), sink.toString());
|
||||
}
|
||||
default -> {
|
||||
throw ExceptionBox.reachedUnreachable();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private static Collection<TestSpec> test_valueOf() {
|
||||
|
||||
var testCases = new ArrayList<TestSpec>();
|
||||
|
||||
var allCategories = MessageCategory.values();
|
||||
|
||||
IntStream.range(0, 2 << (MessageCategory.values().length - 1)).parallel().mapToObj(i -> {
|
||||
|
||||
var bitset = BitSet.valueOf(new long[] {i});
|
||||
var categories = bitset.stream().mapToObj(ordinal -> {
|
||||
return allCategories[ordinal];
|
||||
}).toList();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
categories.forEach(category -> {
|
||||
sb.append(category.asStringValue()).append(',');
|
||||
});
|
||||
|
||||
var logRecords = MessageCategory.toLogRecords(categories);
|
||||
|
||||
return new TestSpec(sb.toString(), logRecords);
|
||||
|
||||
}).toList().forEach(testCases::add);
|
||||
|
||||
testCases.addAll(test_valueOf_manual_test_cases());
|
||||
|
||||
return testCases;
|
||||
}
|
||||
|
||||
private static Collection<TestSpec> test_valueOf_manual_test_cases() {
|
||||
return List.of(
|
||||
new TestSpec("log", MessageCategory.SYSTEM_LOGGER),
|
||||
new TestSpec("log,console", MessageCategory.values()),
|
||||
new TestSpec("console", MessageCategory.toLogRecords(ALL_CONSOLE_CATEGORIES)),
|
||||
new TestSpec("all", MessageCategory.values()),
|
||||
new TestSpec("all,console", MessageCategory.values()),
|
||||
new TestSpec("-trace,trace", MessageCategory.TRACE),
|
||||
new TestSpec("-trace", Set.of()),
|
||||
new TestSpec("-log", Set.of()),
|
||||
new TestSpec("-tools,tools,-tools", MessageCategory.TOOLS),
|
||||
new TestSpec("-trace,trace,console", MessageCategory.toLogRecords(ALL_CONSOLE_CATEGORIES)),
|
||||
new TestSpec("console,-tools,tools,-tools", MessageCategory.toLogRecords(ALL_CONSOLE_CATEGORIES)),
|
||||
new TestSpec("-tools,console,", MessageCategory.toLogRecords(
|
||||
SetBuilder.build(ALL_CONSOLE_CATEGORIES).remove(MessageCategory.TOOLS).create())),
|
||||
new TestSpec(""),
|
||||
new TestSpec("errors,,errors,", MessageCategory.ERRORS)
|
||||
);
|
||||
}
|
||||
|
||||
private static Collection<String> test_valueOf_negative() {
|
||||
return List.of(
|
||||
",",
|
||||
"logerrors",
|
||||
"log,error",
|
||||
"log,-all",
|
||||
"-all",
|
||||
"-console",
|
||||
",errors,,errors,"
|
||||
);
|
||||
}
|
||||
|
||||
record TestSpec(String logEnvStr, Set<LogRecord> expectedLogRecords) {
|
||||
|
||||
TestSpec {
|
||||
Objects.requireNonNull(logEnvStr);
|
||||
Objects.requireNonNull(expectedLogRecords);
|
||||
}
|
||||
|
||||
TestSpec(String logEnvStr, MessageCategory... categories) {
|
||||
this(logEnvStr, MessageCategory.toLogRecords(categories));
|
||||
}
|
||||
|
||||
void run() {
|
||||
test(LogConfigParser.valueOf(logEnvStr), expectedLogRecords);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("<%s> => %s", logEnvStr, expectedLogRecords.stream().map(Enum::name).sorted().toList());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log message. Each enum item should wrap an invocation of one method
|
||||
* from an interface inherited from {@link Logger}.
|
||||
*/
|
||||
private enum LogMessage {
|
||||
|
||||
TRACE_STRING((TraceLogger logger) -> {
|
||||
logger.trace("Ecart foo");
|
||||
}),
|
||||
TRACE_THROWABLE((TraceLogger logger) -> {
|
||||
logger.trace(new Exception("Trace foo exception!"));
|
||||
}),
|
||||
TRACE_STRING_AND_THROWABLE((TraceLogger logger) -> {
|
||||
logger.trace(new Exception("Trace bar exception!"), "Ecart bar");
|
||||
}),
|
||||
TRACE_FORMAT((TraceLogger logger) -> {
|
||||
logger.trace("Ecart %s", "it");
|
||||
}),
|
||||
TRACE_FORMAT_AND_THROWABLE((TraceLogger logger) -> {
|
||||
logger.trace(new Exception("Trace exception again!"), "Ecart %s with", "it");
|
||||
}),
|
||||
|
||||
SUMMARY((SummaryLogger logger) -> {
|
||||
var summary = new Summary();
|
||||
summary.put(StandardProperty.OUTPUT_BUNDLE, "sample");
|
||||
logger.summary(summary);
|
||||
}),
|
||||
SUMMARY_WARNING((SummaryLogger logger) -> {
|
||||
var summary = new Summary();
|
||||
summary.put(new Warning() {
|
||||
|
||||
@Override
|
||||
public int ordinal() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> valueFormat() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}, "Foo configuration warning");
|
||||
logger.summary(summary);
|
||||
}),
|
||||
|
||||
ERROR((ErrorLogger logger) -> {
|
||||
logger.reportError(new Exception("Kaput!"));
|
||||
}),
|
||||
ERROR_SELF_CONTAINED((ErrorLogger logger) -> {
|
||||
logger.reportError(new JPackageException("Cooked!"));
|
||||
}),
|
||||
|
||||
PROGRESS_MESSAGE((ProgressLogger logger) -> {
|
||||
logger.progress("Start operation #1");
|
||||
}),
|
||||
PROGRESS_WARNING_EXCEPTION((ProgressLogger logger) -> {
|
||||
logger.progressWarning(new Exception("Minor issue"));
|
||||
}),
|
||||
PROGRESS_WARNING_MESSAGE_AND_EXCEPTION((ProgressLogger logger) -> {
|
||||
logger.progressWarning(new Exception("Minor issue"), "Ignoring the exception");
|
||||
}),
|
||||
PROGRESS_WARNING_MESSAGE((ProgressLogger logger) -> {
|
||||
logger.progressWarning("Ignoring the problem");
|
||||
}),
|
||||
|
||||
RESOURCE((ResourceLogger logger) -> {
|
||||
logger.useResource("Using the resource");
|
||||
}),
|
||||
|
||||
COMMMAND_BEFORE((CommandLogger logger) -> {
|
||||
logger.beforeCommandExecuted(false, "before -abc");
|
||||
}),
|
||||
COMMMAND_AFTER((CommandLogger logger) -> {
|
||||
logger.afterCommandExecuted(false, "after -abc", Optional.of(67L), Optional.of(0), "Hello\nGoodbye");
|
||||
}),
|
||||
COMMMAND_BEFORE_QUIET((CommandLogger logger) -> {
|
||||
logger.beforeCommandExecuted(true, "before -x -y");
|
||||
}),
|
||||
COMMMAND_AFTER_QUIET((CommandLogger logger) -> {
|
||||
logger.afterCommandExecuted(true, "after -x -y", Optional.of(321L), Optional.of(7), "Monday\nSunday");
|
||||
}),
|
||||
|
||||
;
|
||||
|
||||
LogMessage(Consumer<? super AllLoggers> useLogger) {
|
||||
this.useLogger = Objects.requireNonNull(useLogger);
|
||||
}
|
||||
|
||||
void applyTo(AllLoggers logger) {
|
||||
useLogger.accept(logger);
|
||||
}
|
||||
|
||||
private final Consumer<? super AllLoggers> useLogger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log records. Each enum item binds one or more invocations of logging methods
|
||||
* to a logger with specific configuration parameters.
|
||||
*/
|
||||
private enum LogRecord {
|
||||
|
||||
TRACE_STRING(console(TraceLogger.class, TraceLogger::create)),
|
||||
TRACE_THROWABLE(TRACE_STRING),
|
||||
TRACE_STRING_AND_THROWABLE(TRACE_STRING),
|
||||
TRACE_FORMAT(TRACE_STRING),
|
||||
TRACE_FORMAT_AND_THROWABLE(TRACE_STRING),
|
||||
TRACE_SYSTEM_LOGGER(systemLogger(TraceLogger.class, TraceLogger::create),
|
||||
LogMessage.TRACE_FORMAT,
|
||||
LogMessage.TRACE_FORMAT_AND_THROWABLE,
|
||||
LogMessage.TRACE_STRING,
|
||||
LogMessage.TRACE_STRING_AND_THROWABLE,
|
||||
LogMessage.TRACE_THROWABLE),
|
||||
|
||||
SUMMARY(console(SummaryLogger.class, sink -> {
|
||||
return SummaryLogger.create(sink, true, false);
|
||||
})),
|
||||
SUMMARY_WARNING(console(SummaryLogger.class, sink -> {
|
||||
return SummaryLogger.create(sink, false, true);
|
||||
})),
|
||||
SUMMARY_SYSTEM_LOGGER(systemLogger(SummaryLogger.class, SummaryLogger::create),
|
||||
LogMessage.SUMMARY,
|
||||
LogMessage.SUMMARY_WARNING),
|
||||
|
||||
ERROR(console(ErrorLogger.class, sink -> {
|
||||
return ErrorLogger.create(sink, false, false);
|
||||
})),
|
||||
ERROR_SELF_CONTAINED(ERROR),
|
||||
ERROR_SELF_CONTAINED_ALWAYS_PRINT_STACKTRACE(console(ErrorLogger.class, sink -> {
|
||||
return ErrorLogger.create(sink, true, false);
|
||||
}), LogMessage.ERROR_SELF_CONTAINED),
|
||||
ERROR_SYSTEM_LOGGER(systemLogger(ErrorLogger.class, ErrorLogger::create),
|
||||
LogMessage.ERROR,
|
||||
LogMessage.ERROR_SELF_CONTAINED),
|
||||
|
||||
PROGRESS_MESSAGE(console(ProgressLogger.class, sink -> {
|
||||
return ProgressLogger.create(sink, true, false);
|
||||
})),
|
||||
PROGRESS_WARNING_EXCEPTION(console(ProgressLogger.class, sink -> {
|
||||
return ProgressLogger.create(sink, false, true);
|
||||
})),
|
||||
PROGRESS_WARNING_MESSAGE_AND_EXCEPTION(PROGRESS_WARNING_EXCEPTION),
|
||||
PROGRESS_WARNING_MESSAGE(PROGRESS_WARNING_EXCEPTION),
|
||||
PROGRESS_SYSTEM_LOGGER(systemLogger(ProgressLogger.class, ProgressLogger::create),
|
||||
LogMessage.PROGRESS_MESSAGE,
|
||||
LogMessage.PROGRESS_WARNING_EXCEPTION,
|
||||
LogMessage.PROGRESS_WARNING_MESSAGE,
|
||||
LogMessage.PROGRESS_WARNING_MESSAGE_AND_EXCEPTION),
|
||||
|
||||
RESOURCE(console(ResourceLogger.class, ResourceLogger::create)),
|
||||
RESOURCE_SYSTEM_LOGGER(systemLogger(ResourceLogger.class, ResourceLogger::create),
|
||||
LogMessage.RESOURCE),
|
||||
|
||||
COMMMAND(console(CommandLogger.class, sink -> {
|
||||
return CommandLogger.create(sink, false, false);
|
||||
}), LogMessage.COMMMAND_BEFORE),
|
||||
COMMMAND_PRINT_QUIET_AND_RESULT(console(CommandLogger.class, sink -> {
|
||||
return CommandLogger.create(sink, true, true);
|
||||
}), LogMessage.COMMMAND_BEFORE,
|
||||
LogMessage.COMMMAND_AFTER,
|
||||
LogMessage.COMMMAND_BEFORE_QUIET,
|
||||
LogMessage.COMMMAND_AFTER_QUIET),
|
||||
COMMMAND_SYSTEM_LOGGER(systemLogger(CommandLogger.class, CommandLogger::create),
|
||||
LogMessage.COMMMAND_BEFORE,
|
||||
LogMessage.COMMMAND_AFTER,
|
||||
LogMessage.COMMMAND_BEFORE_QUIET,
|
||||
LogMessage.COMMMAND_AFTER_QUIET),
|
||||
;
|
||||
|
||||
LogRecord(CannedLogger<? super AllLoggers> cannedLogger, LogMessage... logRecordSources) {
|
||||
this.cannedLogger = Objects.requireNonNull(cannedLogger);
|
||||
if (logRecordSources.length > 0) {
|
||||
this.logRecordSources = Set.of(logRecordSources);
|
||||
} else {
|
||||
this.logRecordSources = Set.of(LogMessage.valueOf(name()));
|
||||
}
|
||||
}
|
||||
|
||||
LogRecord(LogRecord other) {
|
||||
this(other.cannedLogger);
|
||||
}
|
||||
|
||||
AllLoggers createLogger(StringWriter sink) {
|
||||
return cannedLogger.createLoggerWithSink(sink);
|
||||
}
|
||||
|
||||
boolean isSystemLogger() {
|
||||
return cannedLogger.sinkType() == LogSink.SYSTEM_LOGGER;
|
||||
}
|
||||
|
||||
static List<LogRecord> findLogRecords(LogMessage logRecordSrc, Collection<LogRecord> logRecords) {
|
||||
Objects.requireNonNull(logRecordSrc);
|
||||
Objects.requireNonNull(logRecords);
|
||||
|
||||
var filteredLogRecords = logRecords.stream().filter(logRecord -> {
|
||||
return logRecord.logRecordSources.contains(logRecordSrc);
|
||||
}).toList();
|
||||
|
||||
switch (filteredLogRecords.size()) {
|
||||
case 0, 1 -> {
|
||||
return filteredLogRecords;
|
||||
}
|
||||
case 2 -> {
|
||||
if (filteredLogRecords.getFirst().isSystemLogger() != filteredLogRecords.getLast().isSystemLogger()) {
|
||||
return filteredLogRecords;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException(String.format(
|
||||
"Multiple %s log records map into the %s log record source", filteredLogRecords, logRecordSrc));
|
||||
}
|
||||
|
||||
private record CannedLogger<T extends Logger>(Class<T> type, Function<PrintWriter, T> ctor, LogSink sinkType) {
|
||||
|
||||
CannedLogger {
|
||||
Objects.requireNonNull(type);
|
||||
Objects.requireNonNull(ctor);
|
||||
Objects.requireNonNull(sinkType);
|
||||
}
|
||||
|
||||
AllLoggers createLoggerWithSink(StringWriter sink) {
|
||||
Objects.requireNonNull(sink);
|
||||
|
||||
var emptyLogEnv = Options.concat();
|
||||
|
||||
return AllLoggers.create(Stream.of(LoggerRole.values()).map(LoggerRole::logger).map(ov -> {
|
||||
return ov.getFrom(emptyLogEnv);
|
||||
}).map(discardingLogger -> {
|
||||
if (type.isInstance(discardingLogger)) {
|
||||
return ctor.apply(new PrintWriter(sink));
|
||||
} else {
|
||||
return discardingLogger;
|
||||
}
|
||||
}).toList());
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends Logger> CannedLogger<T> console(
|
||||
Class<T> type, Function<ConsoleLogger, T> ctor) {
|
||||
Objects.requireNonNull(ctor);
|
||||
return new CannedLogger<>(type, sink -> {
|
||||
var timestampClock = FIXED_CONSOLE_TIMESTAMP;
|
||||
return ctor.apply(new ConsoleLogger(sink::println, sink::println, timestampClock));
|
||||
}, LogSink.CONSOLE);
|
||||
}
|
||||
|
||||
private static <T extends Logger> CannedLogger<T> systemLogger(
|
||||
Class<T> type, Function<System.Logger, T> ctor) {
|
||||
Objects.requireNonNull(ctor);
|
||||
return new CannedLogger<>(type, sink -> {
|
||||
return ctor.apply(new SimpleSystemLogger(sink::println));
|
||||
}, LogSink.SYSTEM_LOGGER);
|
||||
}
|
||||
|
||||
private final CannedLogger<? super AllLoggers> cannedLogger;
|
||||
private final Set<LogMessage> logRecordSources;
|
||||
}
|
||||
|
||||
private enum MessageCategory {
|
||||
SUMMARY(LogRecord.SUMMARY),
|
||||
WARNINGS(
|
||||
LogRecord.SUMMARY_WARNING,
|
||||
LogRecord.PROGRESS_WARNING_MESSAGE,
|
||||
LogRecord.PROGRESS_WARNING_EXCEPTION,
|
||||
LogRecord.PROGRESS_WARNING_MESSAGE_AND_EXCEPTION),
|
||||
ERRORS(LogRecord.ERROR, LogRecord.ERROR_SELF_CONTAINED),
|
||||
PROGRESS(LogRecord.PROGRESS_MESSAGE),
|
||||
TRACE(Stream.of(
|
||||
add(
|
||||
LogRecord.TRACE_STRING,
|
||||
LogRecord.TRACE_STRING_AND_THROWABLE,
|
||||
LogRecord.TRACE_FORMAT,
|
||||
LogRecord.TRACE_FORMAT_AND_THROWABLE,
|
||||
LogRecord.TRACE_THROWABLE,
|
||||
LogRecord.COMMMAND_PRINT_QUIET_AND_RESULT
|
||||
),
|
||||
replace(
|
||||
LogRecord.ERROR_SELF_CONTAINED,
|
||||
LogRecord.ERROR_SELF_CONTAINED_ALWAYS_PRINT_STACKTRACE
|
||||
),
|
||||
replace(
|
||||
LogRecord.COMMMAND,
|
||||
LogRecord.COMMMAND_PRINT_QUIET_AND_RESULT
|
||||
)
|
||||
).flatMap(x -> x)),
|
||||
RESOURCES(LogRecord.RESOURCE),
|
||||
TOOLS(LogRecord.COMMMAND),
|
||||
SYSTEM_LOGGER(Stream.of(LogRecord.values()).filter(LogRecord::isSystemLogger).map(AddLogRecordsMutator::new)) {
|
||||
@Override
|
||||
String asStringValue() {
|
||||
return "log";
|
||||
}
|
||||
},
|
||||
;
|
||||
|
||||
MessageCategory(Stream<LogRecordsMutator> mutators) {
|
||||
this.mutators = mutators.toList();
|
||||
if (this.mutators.isEmpty()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
MessageCategory(LogRecord... logRecords) {
|
||||
this(add(logRecords));
|
||||
}
|
||||
|
||||
String asStringValue() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
|
||||
private Stream<LogRecordsMutator> filterMutators(Class<? extends LogRecordsMutator> mutatorType) {
|
||||
return mutators.stream().filter(mutatorType::isInstance);
|
||||
}
|
||||
|
||||
static Set<LogRecord> toLogRecords(Collection<MessageCategory> categories) {
|
||||
|
||||
var logRecords = new HashSet<LogRecord>();
|
||||
|
||||
for (var mutatorType : List.of(AddLogRecordsMutator.class, ReplaceLogRecordsMutator.class)) {
|
||||
categories.stream().map(category -> {
|
||||
return category.filterMutators(mutatorType);
|
||||
}).flatMap(x -> x).forEach(mutator -> {
|
||||
mutator.mutate(logRecords);
|
||||
});
|
||||
}
|
||||
|
||||
return logRecords;
|
||||
}
|
||||
|
||||
static Set<LogRecord> toLogRecords(MessageCategory... categories) {
|
||||
return toLogRecords(Set.of(categories));
|
||||
}
|
||||
|
||||
private sealed interface LogRecordsMutator {
|
||||
void mutate(Set<LogRecord> logRecords);
|
||||
}
|
||||
|
||||
private record AddLogRecordsMutator(LogRecord value) implements LogRecordsMutator {
|
||||
AddLogRecordsMutator {
|
||||
Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mutate(Set<LogRecord> logRecords) {
|
||||
logRecords.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
private record ReplaceLogRecordsMutator(LogRecord from, LogRecord to) implements LogRecordsMutator {
|
||||
ReplaceLogRecordsMutator {
|
||||
Objects.requireNonNull(from);
|
||||
Objects.requireNonNull(to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mutate(Set<LogRecord> logRecords) {
|
||||
if (logRecords.contains(from)) {
|
||||
logRecords.remove(from);
|
||||
logRecords.add(to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<LogRecordsMutator> add(LogRecord... values) {
|
||||
return Stream.of(values).map(AddLogRecordsMutator::new);
|
||||
}
|
||||
|
||||
private static Stream<LogRecordsMutator> replace(LogRecord from, LogRecord to) {
|
||||
return Stream.of(new ReplaceLogRecordsMutator(from, to));
|
||||
}
|
||||
|
||||
private final List<LogRecordsMutator> mutators;
|
||||
}
|
||||
|
||||
private record SimpleSystemLogger(Consumer<String> sink) implements System.Logger {
|
||||
|
||||
SimpleSystemLogger {
|
||||
Objects.requireNonNull(sink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoggable(Level level) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
|
||||
var buf = new StringWriter();
|
||||
thrown.printStackTrace(new PrintWriter(buf));
|
||||
logImpl(level, String.format("%s: %s", msg, buf));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(Level level, ResourceBundle bundle, String format, Object... params) {
|
||||
logImpl(level, String.format(format, params));
|
||||
}
|
||||
|
||||
private void logImpl(Level level, String msg) {
|
||||
sink.accept(String.format("%s: %s: %s", SimpleSystemLogger.class.getSimpleName(), level, msg));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Clock FIXED_CONSOLE_TIMESTAMP = Clock.fixed(Clock.systemDefaultZone().instant(), ZoneId.systemDefault());
|
||||
|
||||
private static final Set<MessageCategory> ALL_CONSOLE_CATEGORIES = SetBuilder.build(MessageCategory.values())
|
||||
.remove(MessageCategory.SYSTEM_LOGGER)
|
||||
.create();
|
||||
|
||||
static {
|
||||
|
||||
// Assert log records are unique.
|
||||
Stream.of(LogRecord.values()).map(logRecord -> {
|
||||
var sink = new StringWriter();
|
||||
var logger = logRecord.createLogger(sink);
|
||||
logRecord.logRecordSources.forEach(logRecordSource -> {
|
||||
logRecordSource.applyTo(logger);
|
||||
});
|
||||
var str = sink.toString();
|
||||
if (!str.isBlank()) {
|
||||
return str;
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Source log record results into a blank %s log record", logRecord));
|
||||
}
|
||||
}).collect(Collectors.toMap(x -> x, x -> x));
|
||||
|
||||
}
|
||||
}
|
||||
@ -160,6 +160,10 @@ public class MainTest extends JUnitAdapter {
|
||||
}
|
||||
|
||||
private static Collection<TestSpec> testOutput() {
|
||||
|
||||
// Non-empty directory
|
||||
var invalidTempValue = Path.of(System.getProperty("java.home")).toString();
|
||||
|
||||
return Stream.of(
|
||||
// Print the tool version
|
||||
build().expectShortHelp(),
|
||||
@ -180,22 +184,40 @@ public class MainTest extends JUnitAdapter {
|
||||
// Invalid command line requesting to print the version of the tool.
|
||||
// Additional error messages may be printed if the default bundling operation
|
||||
// can not be identified; don't verify these errors in the output.
|
||||
build().args("foo", "--version").stderrMatchType(OutputMatchType.STARTS_WITH).expectErrors(I18N.format("error.non-option-arguments", 1))
|
||||
build().args("foo", "--version").stderrMatchType(OutputMatchType.STARTS_WITH).expectErrors(I18N.format("error.non-option-arguments", 1)),
|
||||
// Should print two errors: one for the invalid value of the "--type" option
|
||||
// and another for the invalid value of the "--verbose" option.
|
||||
build().args("--temp", invalidTempValue, "--verbose", "bar").expectErrors(
|
||||
I18N.format("error.parameter-not-empty-directory", invalidTempValue, "--temp"),
|
||||
I18N.format("error.parameter-invalid-value", "bar", "--verbose")),
|
||||
build().args("--verbose", "bar", "--temp", invalidTempValue).expectErrors(
|
||||
I18N.format("error.parameter-invalid-value", "bar", "--verbose"),
|
||||
I18N.format("error.parameter-not-empty-directory", invalidTempValue, "--temp")),
|
||||
// This is just for the coverage.
|
||||
build().args("--verbose", "errors", "--temp", invalidTempValue).expectErrors(
|
||||
I18N.format("error.parameter-not-empty-directory", invalidTempValue, "--temp")),
|
||||
// Silent failure.
|
||||
build().args("--verbose", "", "--temp", invalidTempValue).expectErrorExitCode(),
|
||||
// If the value of the "--type" option is invalid, this is the only reported error.
|
||||
build().args("--type", "foo", "--verbose", "bar").expectErrors(
|
||||
I18N.format("ERR_InvalidInstallerType", "foo"))
|
||||
).map(TestSpec.Builder::create).toList();
|
||||
}
|
||||
|
||||
private static List<ErrorReporterTestSpec> test_ErrorReporter() {
|
||||
var testCases = new ArrayList<ErrorReporterTestSpec>();
|
||||
for (var verbose : List.of(true, false)) {
|
||||
test_ErrorReporter_Exception(verbose, testCases::add);
|
||||
test_ErrorReporter_UnexpectedResultException(verbose, testCases::add);
|
||||
test_ErrorReporter_suppressedExceptions(verbose, testCases::add);
|
||||
for (var alwaysPrintStackTrace : List.of(true, false)) {
|
||||
test_ErrorReporter_Exception(alwaysPrintStackTrace, testCases::add);
|
||||
for (var printCommandOutput : List.of(true, false)) {
|
||||
test_ErrorReporter_UnexpectedResultException(alwaysPrintStackTrace, printCommandOutput, testCases::add);
|
||||
test_ErrorReporter_suppressedExceptions(alwaysPrintStackTrace, printCommandOutput, testCases::add);
|
||||
}
|
||||
}
|
||||
|
||||
return testCases;
|
||||
}
|
||||
|
||||
private static void test_ErrorReporter_Exception(boolean verbose, Consumer<ErrorReporterTestSpec> sink) {
|
||||
private static void test_ErrorReporter_Exception(boolean alwaysPrintStackTrace, Consumer<ErrorReporterTestSpec> sink) {
|
||||
|
||||
for (var makeCause : List.<UnaryOperator<Exception>>of(
|
||||
ex -> ex,
|
||||
@ -220,6 +242,8 @@ public class MainTest extends JUnitAdapter {
|
||||
for (var expect : List.of(
|
||||
new IOException("I/O error"),
|
||||
new NullPointerException(),
|
||||
// Exception without a message
|
||||
new Exception(),
|
||||
new JPackageException("Kaput!"),
|
||||
new ConfigException("It is broken", "Fix it!"),
|
||||
new ConfigException("It is broken. No advice how to fix it", (String)null),
|
||||
@ -232,13 +256,14 @@ public class MainTest extends JUnitAdapter {
|
||||
}
|
||||
|
||||
var expectedOutput = new ArrayList<ExceptionFormatter>();
|
||||
ErrorReporterTestSpec.expectExceptionFormatters(expect, verbose, expectedOutput::add);
|
||||
sink.accept(ErrorReporterTestSpec.create(cause, expect, verbose, expectedOutput));
|
||||
ErrorReporterTestSpec.expectExceptionFormatters(expect, alwaysPrintStackTrace, false, expectedOutput::add);
|
||||
sink.accept(ErrorReporterTestSpec.create(cause, expect, alwaysPrintStackTrace, false, expectedOutput));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void test_ErrorReporter_UnexpectedResultException(boolean verbose, Consumer<ErrorReporterTestSpec> sink) {
|
||||
private static void test_ErrorReporter_UnexpectedResultException(
|
||||
boolean alwaysPrintStackTrace, boolean printCommandOutput, Consumer<ErrorReporterTestSpec> sink) {
|
||||
|
||||
var execAttrs = new CommandOutputControl.ProcessAttributes(Optional.of(12345L), List.of("foo", "--bar"));
|
||||
|
||||
@ -263,8 +288,8 @@ public class MainTest extends JUnitAdapter {
|
||||
)) {
|
||||
var cause = makeCause.apply(expect);
|
||||
var expectedOutput = new ArrayList<ExceptionFormatter>();
|
||||
ErrorReporterTestSpec.expectExceptionFormatters(expect, verbose, expectedOutput::add);
|
||||
sink.accept(ErrorReporterTestSpec.create(cause, expect, verbose, expectedOutput));
|
||||
ErrorReporterTestSpec.expectExceptionFormatters(expect, alwaysPrintStackTrace, printCommandOutput, expectedOutput::add);
|
||||
sink.accept(ErrorReporterTestSpec.create(cause, expect, alwaysPrintStackTrace, printCommandOutput, expectedOutput));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -286,7 +311,8 @@ public class MainTest extends JUnitAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
private static void test_ErrorReporter_suppressedExceptions(boolean verbose, Consumer<ErrorReporterTestSpec> sink) {
|
||||
private static void test_ErrorReporter_suppressedExceptions(
|
||||
boolean alwaysPrintStackTrace, boolean printCommandOutput, Consumer<ErrorReporterTestSpec> sink) {
|
||||
|
||||
var execAttrs = new CommandOutputControl.ProcessAttributes(Optional.of(567L), List.of("foo", "--bar"));
|
||||
|
||||
@ -329,10 +355,11 @@ public class MainTest extends JUnitAdapter {
|
||||
|
||||
var expectedOutput = new ArrayList<FormattedException>();
|
||||
|
||||
ErrorReporterTestSpec.expectOutputFragments(ExceptionBox.unbox(suppressed), verbose, expectedOutput::add);
|
||||
ErrorReporterTestSpec.expectOutputFragments(main, verbose, expectedOutput::add);
|
||||
ErrorReporterTestSpec.expectOutputFragments(
|
||||
ExceptionBox.unbox(suppressed), alwaysPrintStackTrace, printCommandOutput, expectedOutput::add);
|
||||
ErrorReporterTestSpec.expectOutputFragments(main, alwaysPrintStackTrace, printCommandOutput, expectedOutput::add);
|
||||
|
||||
sink.accept(new ErrorReporterTestSpec(cause, verbose, expectedOutput));
|
||||
sink.accept(new ErrorReporterTestSpec(cause, alwaysPrintStackTrace, printCommandOutput, expectedOutput));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -614,7 +641,11 @@ public class MainTest extends JUnitAdapter {
|
||||
}
|
||||
|
||||
|
||||
record ErrorReporterTestSpec(Exception cause, boolean verbose, List<FormattedException> expectOutput) {
|
||||
record ErrorReporterTestSpec(
|
||||
Exception cause,
|
||||
boolean alwaysPrintStackTrace,
|
||||
boolean printCommandOutput,
|
||||
List<FormattedException> expectOutput) {
|
||||
|
||||
ErrorReporterTestSpec {
|
||||
Objects.requireNonNull(cause);
|
||||
@ -625,26 +656,40 @@ public class MainTest extends JUnitAdapter {
|
||||
}
|
||||
|
||||
static ErrorReporterTestSpec create(
|
||||
Exception cause, boolean verbose, List<ExceptionFormatter> expectOutput) {
|
||||
return create(cause, cause, verbose, expectOutput);
|
||||
Exception cause,
|
||||
boolean alwaysPrintStackTrace,
|
||||
boolean printCommandOutput,
|
||||
List<ExceptionFormatter> expectOutput) {
|
||||
return create(cause, cause, alwaysPrintStackTrace, printCommandOutput, expectOutput);
|
||||
}
|
||||
|
||||
static ErrorReporterTestSpec create(
|
||||
Exception cause, Exception expect, boolean verbose, List<ExceptionFormatter> expectOutput) {
|
||||
Exception cause,
|
||||
Exception expect,
|
||||
boolean alwaysPrintStackTrace,
|
||||
boolean printCommandOutput,
|
||||
List<ExceptionFormatter> expectOutput) {
|
||||
|
||||
Objects.requireNonNull(cause);
|
||||
Objects.requireNonNull(expect);
|
||||
return new ErrorReporterTestSpec(cause, verbose, expectOutput.stream().map(formatter -> {
|
||||
|
||||
return new ErrorReporterTestSpec(cause, alwaysPrintStackTrace, printCommandOutput, expectOutput.stream().map(formatter -> {
|
||||
return new FormattedException(formatter, expect);
|
||||
}).toList());
|
||||
}
|
||||
|
||||
static void expectExceptionFormatters(Exception ex, boolean verbose, Consumer<ExceptionFormatter> sink) {
|
||||
static void expectExceptionFormatters(
|
||||
Exception ex,
|
||||
boolean alwaysPrintStackTrace,
|
||||
boolean printCommandOutput,
|
||||
Consumer<ExceptionFormatter> sink) {
|
||||
|
||||
Objects.requireNonNull(ex);
|
||||
Objects.requireNonNull(sink);
|
||||
|
||||
final var isSelfContained = (ex.getClass().getAnnotation(SelfContainedException.class) != null);
|
||||
|
||||
if (verbose || !(isSelfContained || ex instanceof UnexpectedResultException)) {
|
||||
if (alwaysPrintStackTrace || !(isSelfContained || ex instanceof UnexpectedResultException)) {
|
||||
sink.accept(ExceptionFormatter.STACK_TRACE);
|
||||
}
|
||||
|
||||
@ -664,7 +709,10 @@ public class MainTest extends JUnitAdapter {
|
||||
} else {
|
||||
sink.accept(ExceptionFormatter.FAILED_COMMAND_TIMEDOUT_MESSAGE);
|
||||
}
|
||||
sink.accept(ExceptionFormatter.FAILED_COMMAND_OUTPUT);
|
||||
|
||||
if (printCommandOutput) {
|
||||
sink.accept(ExceptionFormatter.FAILED_COMMAND_OUTPUT);
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
if (isSelfContained) {
|
||||
@ -676,9 +724,14 @@ public class MainTest extends JUnitAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
static void expectOutputFragments(Exception ex, boolean verbose, Consumer<FormattedException> sink) {
|
||||
static void expectOutputFragments(
|
||||
Exception ex,
|
||||
boolean alwaysPrintStackTrace,
|
||||
boolean printCommandOutput,
|
||||
Consumer<FormattedException> sink) {
|
||||
|
||||
Objects.requireNonNull(sink);
|
||||
expectExceptionFormatters(ex, verbose, formatter -> {
|
||||
expectExceptionFormatters(ex, alwaysPrintStackTrace, printCommandOutput, formatter -> {
|
||||
sink.accept(formatter.bind(ex));
|
||||
});
|
||||
}
|
||||
@ -708,8 +761,12 @@ public class MainTest extends JUnitAdapter {
|
||||
}).collect(Collectors.joining("+")));
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
tokens.add("verbose");
|
||||
if (alwaysPrintStackTrace) {
|
||||
tokens.add("stacktrace-always");
|
||||
}
|
||||
|
||||
if (printCommandOutput) {
|
||||
tokens.add("command-output");
|
||||
}
|
||||
|
||||
return tokens.stream().collect(Collectors.joining("; "));
|
||||
@ -723,7 +780,7 @@ public class MainTest extends JUnitAdapter {
|
||||
t.printStackTrace(pw);
|
||||
}, msg -> {
|
||||
pw.println(msg);
|
||||
}, verbose).reportError(cause);
|
||||
}, alwaysPrintStackTrace, printCommandOutput).reportError(cause);
|
||||
}
|
||||
|
||||
var expected = expectOutput.stream().map(FormattedException::format).collect(Collectors.joining(""));
|
||||
|
||||
@ -99,7 +99,7 @@ public class OptionsValidationFailTest {
|
||||
|
||||
var errorReporter = new Main.ErrorReporter(ex -> {
|
||||
ex.printStackTrace(err);
|
||||
}, err::println, false);
|
||||
}, err::println, false, true);
|
||||
|
||||
return parse(args).peekErrors(errors -> {
|
||||
final var firstErr = errors.stream().findFirst().orElseThrow();
|
||||
|
||||
@ -64,8 +64,35 @@ Generic Options:
|
||||
removed upon the task completion.
|
||||
--vendor <vendor string>
|
||||
Vendor of the application
|
||||
--verbose
|
||||
Enables verbose output
|
||||
--verbose [<[-]category(,[-]category)*>]
|
||||
Configures verbose output. Where "category" is one of
|
||||
"all"
|
||||
"console"
|
||||
"log"
|
||||
"errors"
|
||||
"progress"
|
||||
"resources"
|
||||
"summary"
|
||||
"tools"
|
||||
"trace"
|
||||
"warnings"
|
||||
|
||||
Suppress all console output, enable logging via System.Logger API:
|
||||
--verbose log
|
||||
Enable all message categories in the console:
|
||||
--verbose console
|
||||
Enable all message categories, but "trace" and "tools" in the console:
|
||||
--verbose console,-trace,-tools
|
||||
Enable "trace" and "tools" message categories in the console:
|
||||
--verbose trace,tools
|
||||
Enable "trace" and "tools" message categories in the console and
|
||||
enable logging via System.Logger API:
|
||||
--verbose log,trace,tools
|
||||
|
||||
If the option is specified without the value, it is equivalent to
|
||||
--verbose console,-trace
|
||||
If the option is not specified it is equivalent to
|
||||
--verbose errors,warnings
|
||||
--version
|
||||
Print the product version to the output stream and exit.
|
||||
|
||||
|
||||
@ -70,8 +70,35 @@ Generic Options:
|
||||
removed upon the task completion.
|
||||
--vendor <vendor string>
|
||||
Vendor of the application
|
||||
--verbose
|
||||
Enables verbose output
|
||||
--verbose [<[-]category(,[-]category)*>]
|
||||
Configures verbose output. Where "category" is one of
|
||||
"all"
|
||||
"console"
|
||||
"log"
|
||||
"errors"
|
||||
"progress"
|
||||
"resources"
|
||||
"summary"
|
||||
"tools"
|
||||
"trace"
|
||||
"warnings"
|
||||
|
||||
Suppress all console output, enable logging via System.Logger API:
|
||||
--verbose log
|
||||
Enable all message categories in the console:
|
||||
--verbose console
|
||||
Enable all message categories, but "trace" and "tools" in the console:
|
||||
--verbose console,-trace,-tools
|
||||
Enable "trace" and "tools" message categories in the console:
|
||||
--verbose trace,tools
|
||||
Enable "trace" and "tools" message categories in the console and
|
||||
enable logging via System.Logger API:
|
||||
--verbose log,trace,tools
|
||||
|
||||
If the option is specified without the value, it is equivalent to
|
||||
--verbose console,-trace
|
||||
If the option is not specified it is equivalent to
|
||||
--verbose errors,warnings
|
||||
--version
|
||||
Print the product version to the output stream and exit.
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user