mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-28 08:39:56 +00:00
8278591: Jpackage post installation information message
Reviewed-by: almatvee, erikj
This commit is contained in:
parent
615aba8257
commit
96f6ffbff4
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
#
|
||||
# This code is free software; you can redistribute it and/or modify it
|
||||
@ -29,7 +29,7 @@ DISABLED_WARNINGS_java += dangling-doc-comments
|
||||
|
||||
COPY += .gif .png .txt .spec .script .prerm .preinst \
|
||||
.postrm .postinst .list .sh .desktop .copyright .control .plist .template \
|
||||
.icns .scpt .wxs .wxl .wxi .wxf .ico .bmp .tiff .service .xsl
|
||||
.icns .scpt .wxs .wxl .wxi .wxf .ico .bmp .tiff .service .xsl .js
|
||||
|
||||
CLEAN += .properties
|
||||
|
||||
|
||||
@ -400,6 +400,8 @@ public final class StandardOption {
|
||||
|
||||
public static final OptionValue<Boolean> WIN_INSTALLDIR_CHOOSER = booleanOption("win-dir-chooser").scope(nativeBundling()).create();
|
||||
|
||||
public static final OptionValue<Boolean> WIN_WITH_UI = booleanOption("win-with-ui").scope(nativeBundling()).create();
|
||||
|
||||
public static final OptionValue<UUID> WIN_UPGRADE_UUID = uuidOption("win-upgrade-uuid").scope(nativeBundling()).create();
|
||||
|
||||
public static final OptionValue<Boolean> WIN_CONSOLE_HINT = booleanOption("win-console")
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2017, 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
|
||||
@ -426,3 +426,5 @@ help.option.win-update-url=\
|
||||
help.option.win-upgrade-uuid=\
|
||||
\ UUID associated with upgrades for this package
|
||||
|
||||
help.option.win-with-ui=\
|
||||
\ Enforces the installer to have UI
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
# Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
#
|
||||
# This code is free software; you can redistribute it and/or modify it
|
||||
@ -436,6 +436,10 @@ The `jpackage` tool will take as input a Java application and a Java run-time im
|
||||
|
||||
: UUID associated with upgrades for this package
|
||||
|
||||
<a id="option-win-with-ui">`--win-with-ui`</a>
|
||||
|
||||
: Enforces the installer to have UI
|
||||
|
||||
#### Linux platform options (available only when running on Linux):
|
||||
|
||||
<a id="option-linux-package-name">`--linux-package-name` *name*</a>
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import jdk.jpackage.internal.resources.ResourceLocator;
|
||||
|
||||
/**
|
||||
* WSH script altering cooked .msi file.
|
||||
*/
|
||||
record MsiMutator(String scriptResourceName) {
|
||||
|
||||
MsiMutator {
|
||||
Objects.requireNonNull(scriptResourceName);
|
||||
if (Path.of(scriptResourceName).getNameCount() != 1) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
void addToConfigRoot(Path configRoot) throws IOException {
|
||||
var scriptFile = configRoot.resolve(pathInConfigRoot());
|
||||
try (var in = ResourceLocator.class.getResourceAsStream(scriptResourceName)) {
|
||||
Files.createDirectories(scriptFile.getParent());
|
||||
Files.copy(in, scriptFile);
|
||||
}
|
||||
}
|
||||
|
||||
Path pathInConfigRoot() {
|
||||
return Path.of(scriptResourceName);
|
||||
}
|
||||
}
|
||||
@ -39,6 +39,7 @@ import static jdk.jpackage.internal.cli.StandardOption.WIN_SHORTCUT_HINT;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.WIN_SHORTCUT_PROMPT;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.WIN_UPDATE_URL;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.WIN_UPGRADE_UUID;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.WIN_WITH_UI;
|
||||
import static jdk.jpackage.internal.model.StandardPackageType.WIN_MSI;
|
||||
|
||||
import jdk.jpackage.internal.cli.Options;
|
||||
@ -93,6 +94,7 @@ final class WinFromOptions {
|
||||
WIN_UPDATE_URL.ifPresentIn(options, pkgBuilder::updateURL);
|
||||
WIN_INSTALLDIR_CHOOSER.ifPresentIn(options, pkgBuilder::withInstallDirChooser);
|
||||
WIN_SHORTCUT_PROMPT.ifPresentIn(options, pkgBuilder::withShortcutPrompt);
|
||||
WIN_WITH_UI.ifPresentIn(options, pkgBuilder::withUi);
|
||||
|
||||
if (app.isService()) {
|
||||
RESOURCE_DIR.ifPresentIn(options, resourceDir -> {
|
||||
|
||||
@ -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
|
||||
@ -65,6 +65,7 @@ final class WinMsiPackageBuilder {
|
||||
MsiVersion.of(pkg.version()),
|
||||
withInstallDirChooser,
|
||||
withShortcutPrompt,
|
||||
withUi,
|
||||
Optional.ofNullable(helpURL),
|
||||
Optional.ofNullable(updateURL),
|
||||
Optional.ofNullable(startMenuGroupName).orElseGet(DEFAULTS::startMenuGroupName),
|
||||
@ -92,6 +93,11 @@ final class WinMsiPackageBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
WinMsiPackageBuilder withUi(boolean v) {
|
||||
withUi = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
WinMsiPackageBuilder helpURL(String v) {
|
||||
helpURL = v;
|
||||
return this;
|
||||
@ -131,6 +137,7 @@ final class WinMsiPackageBuilder {
|
||||
|
||||
private boolean withInstallDirChooser;
|
||||
private boolean withShortcutPrompt;
|
||||
private boolean withUi;
|
||||
private String helpURL;
|
||||
private String updateURL;
|
||||
private String startMenuGroupName;
|
||||
|
||||
@ -36,7 +36,6 @@ import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -315,50 +314,50 @@ final class WinMsiPackager implements Consumer<PackagingPipeline.Builder> {
|
||||
wixPipeline.buildMsi(msiOut.toAbsolutePath());
|
||||
}
|
||||
|
||||
private Map<String, String> createWixVars() throws IOException {
|
||||
Map<String, String> data = new HashMap<>();
|
||||
private WixVariables createWixVars() throws IOException {
|
||||
var wixVars = new WixVariables();
|
||||
|
||||
data.put("JpProductCode", pkg.productCode().toString());
|
||||
data.put("JpProductUpgradeCode", pkg.upgradeCode().toString());
|
||||
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()));
|
||||
|
||||
data.put("JpAllowUpgrades", "yes");
|
||||
wixVars.define("JpAllowUpgrades");
|
||||
if (!pkg.isRuntimeInstaller()) {
|
||||
data.put("JpAllowDowngrades", "yes");
|
||||
wixVars.define("JpAllowDowngrades");
|
||||
}
|
||||
|
||||
data.put("JpAppName", pkg.packageName());
|
||||
data.put("JpAppDescription", pkg.description());
|
||||
data.put("JpAppVendor", pkg.app().vendor());
|
||||
data.put("JpAppVersion", pkg.version());
|
||||
wixVars.put("JpAppName", pkg.packageName());
|
||||
wixVars.put("JpAppDescription", pkg.description());
|
||||
wixVars.put("JpAppVendor", pkg.app().vendor());
|
||||
wixVars.put("JpAppVersion", pkg.version());
|
||||
if (Files.exists(installerIcon)) {
|
||||
data.put("JpIcon", installerIcon.toString());
|
||||
wixVars.put("JpIcon", installerIcon.toString());
|
||||
}
|
||||
|
||||
pkg.helpURL().ifPresent(value -> {
|
||||
data.put("JpHelpURL", value);
|
||||
wixVars.put("JpHelpURL", value);
|
||||
});
|
||||
|
||||
pkg.updateURL().ifPresent(value -> {
|
||||
data.put("JpUpdateURL", value);
|
||||
wixVars.put("JpUpdateURL", value);
|
||||
});
|
||||
|
||||
pkg.aboutURL().ifPresent(value -> {
|
||||
data.put("JpAboutURL", value);
|
||||
wixVars.put("JpAboutURL", value);
|
||||
});
|
||||
|
||||
data.put("JpAppSizeKb", Long.toString(AppImageLayout.toPathGroup(
|
||||
wixVars.put("JpAppSizeKb", Long.toString(AppImageLayout.toPathGroup(
|
||||
env.appImageLayout()).sizeInBytes() >> 10));
|
||||
|
||||
data.put("JpConfigDir", env.configDir().toAbsolutePath().toString());
|
||||
wixVars.put("JpConfigDir", env.configDir().toAbsolutePath().toString());
|
||||
|
||||
if (pkg.isSystemWideInstall()) {
|
||||
data.put("JpIsSystemWide", "yes");
|
||||
wixVars.define("JpIsSystemWide");
|
||||
}
|
||||
|
||||
return data;
|
||||
return wixVars;
|
||||
}
|
||||
|
||||
private static List<Path> getWxlFilesFromDir(Path dir) {
|
||||
|
||||
@ -32,7 +32,6 @@ import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.xml.stream.XMLStreamWriter;
|
||||
@ -65,16 +64,14 @@ abstract class WixFragmentBuilder {
|
||||
}
|
||||
|
||||
void initFromParams(BuildEnv env, WinMsiPackage pkg) {
|
||||
wixVariables = null;
|
||||
wixVariables = new WixVariables();
|
||||
additionalResources = null;
|
||||
configRoot = env.configDir();
|
||||
fragmentResource = env.createResource(defaultResourceName).setPublicName(outputFileName);
|
||||
}
|
||||
|
||||
void configureWixPipeline(WixPipeline.Builder wixPipeline) {
|
||||
wixPipeline.addSource(configRoot.resolve(outputFileName),
|
||||
Optional.ofNullable(wixVariables).map(WixVariables::getValues).orElse(
|
||||
null));
|
||||
wixPipeline.addSource(configRoot.resolve(outputFileName), wixVariables);
|
||||
}
|
||||
|
||||
void addFilesToConfigRoot() throws IOException {
|
||||
@ -147,14 +144,11 @@ abstract class WixFragmentBuilder {
|
||||
protected abstract Collection<XmlConsumer> getFragmentWriters();
|
||||
|
||||
protected final void defineWixVariable(String variableName) {
|
||||
setWixVariable(variableName, "yes");
|
||||
wixVariables.define(variableName);
|
||||
}
|
||||
|
||||
protected final void setWixVariable(String variableName, String variableValue) {
|
||||
if (wixVariables == null) {
|
||||
wixVariables = new WixVariables();
|
||||
}
|
||||
wixVariables.setWixVariable(variableName, variableValue);
|
||||
wixVariables.put(variableName, variableValue);
|
||||
}
|
||||
|
||||
protected final void addResource(OverridableResource resource, String saveAsName) {
|
||||
|
||||
@ -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
|
||||
@ -24,22 +24,18 @@
|
||||
*/
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import static jdk.jpackage.internal.ShortPathUtils.adjustPath;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import static jdk.jpackage.internal.ShortPathUtils.adjustPath;
|
||||
import jdk.jpackage.internal.util.PathUtils;
|
||||
|
||||
/**
|
||||
@ -61,18 +57,20 @@ final class WixPipeline {
|
||||
|
||||
final var absWorkDir = workDir.normalize().toAbsolutePath();
|
||||
|
||||
final UnaryOperator<Path> normalizePath = path -> {
|
||||
return path.normalize().toAbsolutePath();
|
||||
};
|
||||
final var absObjWorkDir = PathUtils.normalizedAbsolutePath(wixObjDir);
|
||||
|
||||
final var absObjWorkDir = normalizePath.apply(wixObjDir);
|
||||
|
||||
var relSources = sources.stream().map(source -> {
|
||||
return source.overridePath(normalizePath.apply(source.path));
|
||||
final var absSources = sources.stream().map(source -> {
|
||||
return source.copyWithPath(PathUtils.normalizedAbsolutePath(source.path));
|
||||
}).toList();
|
||||
|
||||
return new WixPipeline(toolset, adjustPath(absWorkDir), absObjWorkDir,
|
||||
wixVariables, mapLightOptions(normalizePath), relSources);
|
||||
return new WixPipeline(
|
||||
toolset,
|
||||
adjustPath(absWorkDir),
|
||||
absObjWorkDir,
|
||||
wixVariables.createdImmutableCopy(),
|
||||
mapLightOptions(PathUtils::normalizedAbsolutePath),
|
||||
absSources,
|
||||
msiMutators);
|
||||
}
|
||||
|
||||
Builder setWixObjDir(Path v) {
|
||||
@ -85,17 +83,30 @@ final class WixPipeline {
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setWixVariables(Map<String, String> v) {
|
||||
wixVariables.clear();
|
||||
Builder putWixVariables(WixVariables v) {
|
||||
wixVariables.putAll(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder addSource(Path source, Map<String, String> wixVariables) {
|
||||
sources.add(new WixSource(source, wixVariables));
|
||||
Builder putWixVariables(Map<String, String> v) {
|
||||
wixVariables.putAll(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder addSource(Path source, WixVariables wixVariables) {
|
||||
sources.add(new WixSource(source, wixVariables.createdImmutableCopy()));
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder addMsiMutator(MsiMutator msiMutator, List<String> args) {
|
||||
msiMutators.add(new MsiMutatorWithArgs(msiMutator, args));
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder addSource(Path source) {
|
||||
return addSource(source, WixVariables.EMPTY);
|
||||
}
|
||||
|
||||
Builder addLightOptions(String ... v) {
|
||||
lightOptions.addAll(List.of(v));
|
||||
return this;
|
||||
@ -119,87 +130,59 @@ final class WixPipeline {
|
||||
|
||||
private Path workDir;
|
||||
private Path wixObjDir;
|
||||
private final Map<String, String> wixVariables = new HashMap<>();
|
||||
private final WixVariables wixVariables = new WixVariables();
|
||||
private final List<String> lightOptions = new ArrayList<>();
|
||||
private final List<WixSource> sources = new ArrayList<>();
|
||||
private final List<MsiMutatorWithArgs> msiMutators = new ArrayList<>();
|
||||
}
|
||||
|
||||
static Builder build() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
private WixPipeline(WixToolset toolset, Path workDir, Path wixObjDir,
|
||||
Map<String, String> wixVariables, List<String> lightOptions,
|
||||
List<WixSource> sources) {
|
||||
this.toolset = toolset;
|
||||
this.workDir = workDir;
|
||||
this.wixObjDir = wixObjDir;
|
||||
this.wixVariables = wixVariables;
|
||||
this.lightOptions = lightOptions;
|
||||
this.sources = sources;
|
||||
private WixPipeline(
|
||||
WixToolset toolset,
|
||||
Path workDir,
|
||||
Path wixObjDir,
|
||||
WixVariables wixVariables,
|
||||
List<String> lightOptions,
|
||||
List<WixSource> sources,
|
||||
List<MsiMutatorWithArgs> msiMutators) {
|
||||
|
||||
this.toolset = Objects.requireNonNull(toolset);
|
||||
this.workDir = Objects.requireNonNull(workDir);
|
||||
this.wixObjDir = Objects.requireNonNull(wixObjDir);
|
||||
this.wixVariables = Objects.requireNonNull(wixVariables);
|
||||
this.lightOptions = Objects.requireNonNull(lightOptions);
|
||||
this.sources = Objects.requireNonNull(sources);
|
||||
this.msiMutators = Objects.requireNonNull(msiMutators);
|
||||
}
|
||||
|
||||
void buildMsi(Path msi) throws IOException {
|
||||
Objects.requireNonNull(workDir);
|
||||
|
||||
// Use short path to the output msi to workaround
|
||||
// WiX limitations of handling long paths.
|
||||
var transientMsi = wixObjDir.resolve("a.msi");
|
||||
|
||||
var configRoot = workDir.resolve(transientMsi).getParent();
|
||||
|
||||
for (var msiMutator : msiMutators) {
|
||||
msiMutator.addToConfigRoot(configRoot);
|
||||
}
|
||||
|
||||
switch (toolset.getType()) {
|
||||
case Wix3 -> buildMsiWix3(transientMsi);
|
||||
case Wix4 -> buildMsiWix4(transientMsi);
|
||||
default -> throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
for (var msiMutator : msiMutators) {
|
||||
msiMutator.execute(configRoot, workDir.resolve(transientMsi));
|
||||
}
|
||||
|
||||
IOUtils.copyFile(workDir.resolve(transientMsi), msi);
|
||||
}
|
||||
|
||||
private void addWixVariblesToCommandLine(
|
||||
Map<String, String> otherWixVariables, List<String> cmdline) {
|
||||
Stream.of(wixVariables, Optional.ofNullable(otherWixVariables).
|
||||
orElseGet(Collections::emptyMap)).filter(Objects::nonNull).
|
||||
reduce((a, b) -> {
|
||||
a.putAll(b);
|
||||
return a;
|
||||
}).ifPresent(wixVars -> {
|
||||
var entryStream = wixVars.entrySet().stream();
|
||||
|
||||
Stream<String> stream;
|
||||
switch (toolset.getType()) {
|
||||
case Wix3 -> {
|
||||
stream = entryStream.map(wixVar -> {
|
||||
return String.format("-d%s=%s", wixVar.getKey(), wixVar.
|
||||
getValue());
|
||||
});
|
||||
}
|
||||
case Wix4 -> {
|
||||
stream = entryStream.map(wixVar -> {
|
||||
return Stream.of("-d", String.format("%s=%s", wixVar.
|
||||
getKey(), wixVar.getValue()));
|
||||
}).flatMap(Function.identity());
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
stream.reduce(cmdline, (ctnr, wixVar) -> {
|
||||
ctnr.add(wixVar);
|
||||
return ctnr;
|
||||
}, (x, y) -> {
|
||||
x.addAll(y);
|
||||
return x;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void buildMsiWix4(Path msi) throws IOException {
|
||||
var mergedSrcWixVars = sources.stream().map(wixSource -> {
|
||||
return Optional.ofNullable(wixSource.variables).orElseGet(
|
||||
Collections::emptyMap).entrySet().stream();
|
||||
}).flatMap(Function.identity()).collect(Collectors.toMap(
|
||||
Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
List<String> cmdline = new ArrayList<>(List.of(
|
||||
toolset.getToolPath(WixTool.Wix4).toString(),
|
||||
@ -213,7 +196,7 @@ final class WixPipeline {
|
||||
|
||||
cmdline.addAll(lightOptions);
|
||||
|
||||
addWixVariblesToCommandLine(mergedSrcWixVars, cmdline);
|
||||
addWixVariablesToCommandLine(sources.stream(), cmdline::addAll);
|
||||
|
||||
cmdline.addAll(sources.stream().map(wixSource -> {
|
||||
return wixSource.path.toString();
|
||||
@ -241,7 +224,6 @@ final class WixPipeline {
|
||||
lightCmdline.addAll(lightOptions);
|
||||
wixObjs.stream().map(Path::toString).forEach(lightCmdline::add);
|
||||
|
||||
Files.createDirectories(msi.getParent());
|
||||
execute(lightCmdline);
|
||||
}
|
||||
|
||||
@ -262,7 +244,7 @@ final class WixPipeline {
|
||||
cmdline.add("-fips");
|
||||
}
|
||||
|
||||
addWixVariblesToCommandLine(wixSource.variables, cmdline);
|
||||
addWixVariablesToCommandLine(Stream.of(wixSource), cmdline::addAll);
|
||||
|
||||
execute(cmdline);
|
||||
|
||||
@ -273,16 +255,47 @@ final class WixPipeline {
|
||||
Executor.of(new ProcessBuilder(cmdline).directory(workDir.toFile())).executeExpectSuccess();
|
||||
}
|
||||
|
||||
private record WixSource(Path path, Map<String, String> variables) {
|
||||
WixSource overridePath(Path path) {
|
||||
private void addWixVariablesToCommandLine(Stream<WixSource> wixSources, Consumer<List<String>> sink) {
|
||||
sink.accept(wixSources.map(WixSource::variables).reduce(wixVariables, (a, b) -> {
|
||||
return new WixVariables().putAll(a).putAll(b);
|
||||
}).toWixCommandLine(toolset.getType()));
|
||||
}
|
||||
|
||||
private record WixSource(Path path, WixVariables variables) {
|
||||
WixSource {
|
||||
Objects.requireNonNull(path);
|
||||
Objects.requireNonNull(variables);
|
||||
}
|
||||
|
||||
WixSource copyWithPath(Path path) {
|
||||
return new WixSource(path, variables);
|
||||
}
|
||||
}
|
||||
|
||||
private record MsiMutatorWithArgs(MsiMutator mutator, List<String> args) {
|
||||
MsiMutatorWithArgs {
|
||||
Objects.requireNonNull(mutator);
|
||||
Objects.requireNonNull(args);
|
||||
}
|
||||
|
||||
void addToConfigRoot(Path configRoot) throws IOException {
|
||||
mutator.addToConfigRoot(configRoot);
|
||||
}
|
||||
|
||||
void execute(Path configRoot, Path transientMsi) throws IOException {
|
||||
Executor.of("cscript", "//Nologo")
|
||||
.args(PathUtils.normalizedAbsolutePathString(configRoot.resolve(mutator.pathInConfigRoot())))
|
||||
.args(PathUtils.normalizedAbsolutePathString(transientMsi))
|
||||
.args(args)
|
||||
.executeExpectSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
private final WixToolset toolset;
|
||||
private final Map<String, String> wixVariables;
|
||||
private final WixVariables wixVariables;
|
||||
private final List<String> lightOptions;
|
||||
private final Path wixObjDir;
|
||||
private final Path workDir;
|
||||
private final List<WixSource> sources;
|
||||
private final List<MsiMutatorWithArgs> msiMutators;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -24,25 +24,32 @@
|
||||
*/
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import jdk.jpackage.internal.model.WinMsiPackage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.XMLStreamWriter;
|
||||
import jdk.jpackage.internal.WixAppImageFragmentBuilder.ShortcutsFolder;
|
||||
import jdk.jpackage.internal.WixToolset.WixToolsetType;
|
||||
import jdk.jpackage.internal.model.WinMsiPackage;
|
||||
import jdk.jpackage.internal.resources.ResourceLocator;
|
||||
import jdk.jpackage.internal.util.XmlConsumer;
|
||||
import jdk.jpackage.internal.wixui.Dialog;
|
||||
import jdk.jpackage.internal.wixui.DialogPair;
|
||||
import jdk.jpackage.internal.wixui.Publish;
|
||||
import jdk.jpackage.internal.wixui.ShowActionSuppresser;
|
||||
import jdk.jpackage.internal.wixui.UIConfig;
|
||||
import jdk.jpackage.internal.wixui.UISpec;
|
||||
|
||||
/**
|
||||
* Creates UI WiX fragment.
|
||||
@ -53,63 +60,83 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
void initFromParams(BuildEnv env, WinMsiPackage pkg) {
|
||||
super.initFromParams(env, pkg);
|
||||
|
||||
withLicenseDlg = pkg.licenseFile().isPresent();
|
||||
if (withLicenseDlg) {
|
||||
final var shortcutFolders = ShortcutsFolder.getForPackage(pkg);
|
||||
|
||||
uiConfig = UIConfig.build()
|
||||
.withLicenseDlg(pkg.licenseFile().isPresent())
|
||||
.withInstallDirChooserDlg(pkg.withInstallDirChooser())
|
||||
.withShortcutPromptDlg(!shortcutFolders.isEmpty() && pkg.withShortcutPrompt())
|
||||
.create();
|
||||
|
||||
if (!uiConfig.equals(UIConfig.build().create()) || pkg.withUI()) {
|
||||
uiSpec = Optional.of(UISpec.create(uiConfig));
|
||||
} else {
|
||||
uiSpec = Optional.empty();
|
||||
}
|
||||
|
||||
if (uiConfig.isWithLicenseDlg()) {
|
||||
Path licenseFileName = pkg.licenseFile().orElseThrow().getFileName();
|
||||
Path destFile = getConfigRoot().resolve(licenseFileName);
|
||||
setWixVariable("JpLicenseRtf", destFile.toAbsolutePath().toString());
|
||||
}
|
||||
|
||||
withInstallDirChooserDlg = pkg.withInstallDirChooser();
|
||||
|
||||
final var shortcutFolders = ShortcutsFolder.getForPackage(pkg);
|
||||
|
||||
withShortcutPromptDlg = !shortcutFolders.isEmpty() && pkg.withShortcutPrompt();
|
||||
|
||||
customDialogs = new ArrayList<>();
|
||||
|
||||
if (withShortcutPromptDlg) {
|
||||
CustomDialog dialog = new CustomDialog(env::createResource, I18N.getString(
|
||||
"resource.shortcutpromptdlg-wix-file"),
|
||||
if (uiConfig.isWithShortcutPromptDlg()) {
|
||||
CustomDialog dialog = new CustomDialog(
|
||||
env::createResource,
|
||||
I18N.getString("resource.shortcutpromptdlg-wix-file"),
|
||||
"ShortcutPromptDlg.wxs");
|
||||
for (var shortcutFolder : shortcutFolders) {
|
||||
dialog.wixVariables.defineWixVariable(
|
||||
dialog.wixVariables.define(
|
||||
shortcutFolder.getWixVariableName());
|
||||
}
|
||||
customDialogs.add(dialog);
|
||||
}
|
||||
|
||||
if (withInstallDirChooserDlg) {
|
||||
CustomDialog dialog = new CustomDialog(env::createResource, I18N.getString(
|
||||
"resource.installdirnotemptydlg-wix-file"),
|
||||
if (uiConfig.isWithInstallDirChooserDlg()) {
|
||||
CustomDialog dialog = new CustomDialog(
|
||||
env::createResource,
|
||||
I18N.getString("resource.installdirnotemptydlg-wix-file"),
|
||||
"InstallDirNotEmptyDlg.wxs");
|
||||
List<Dialog> dialogIds = getUI().dialogIdsSupplier.apply(this);
|
||||
dialog.wixVariables.setWixVariable("JpAfterInstallDirDlg",
|
||||
dialogIds.get(dialogIds.indexOf(Dialog.InstallDirDlg) + 1).id);
|
||||
customDialogs.add(dialog);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
void configureWixPipeline(WixPipeline.Builder wixPipeline) {
|
||||
super.configureWixPipeline(wixPipeline);
|
||||
|
||||
if (withShortcutPromptDlg || withInstallDirChooserDlg || withLicenseDlg) {
|
||||
final String extName;
|
||||
switch (getWixType()) {
|
||||
case Wix3 -> extName = "WixUIExtension";
|
||||
case Wix4 -> extName = "WixToolset.UI.wixext";
|
||||
default -> throw new IllegalArgumentException();
|
||||
}
|
||||
wixPipeline.addLightOptions("-ext", extName);
|
||||
}
|
||||
|
||||
// Only needed if we using CA dll, so Wix can find it
|
||||
if (withCustomActionsDll) {
|
||||
wixPipeline.addLightOptions("-b",
|
||||
getConfigRoot().toAbsolutePath().toString());
|
||||
}
|
||||
|
||||
if (uiSpec.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var extName = switch (getWixType()) {
|
||||
case Wix3 -> "WixUIExtension";
|
||||
case Wix4 -> "WixToolset.UI.wixext";
|
||||
};
|
||||
wixPipeline.addLightOptions("-ext", extName);
|
||||
wixPipeline.putWixVariables(uiSpec.get().wixVariables());
|
||||
|
||||
if (!uiSpec.get().hideDialogs().isEmpty() && getWixType() == WixToolsetType.Wix3) {
|
||||
// Older WiX doesn't support multiple overrides of a "ShowAction" element.
|
||||
// Have to run a script to alter the msi.
|
||||
var removeActions = uiSpec.get().hideDialogs().stream()
|
||||
.map(ShowActionSuppresser::dialog)
|
||||
.sorted(Dialog.DEFAULT_COMPARATOR)
|
||||
.map(Dialog::id);
|
||||
wixPipeline.addMsiMutator(
|
||||
new MsiMutator("msi-disable-actions.js"),
|
||||
Stream.concat(Stream.of("InstallUISequence"), removeActions).toList());
|
||||
}
|
||||
|
||||
for (var customDialog : customDialogs) {
|
||||
customDialog.addToWixPipeline(wixPipeline);
|
||||
}
|
||||
@ -132,26 +159,24 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
return List.of(this::addUI);
|
||||
}
|
||||
|
||||
private void addUI(XMLStreamWriter xml) throws XMLStreamException,
|
||||
IOException {
|
||||
private void addUI(XMLStreamWriter xml) throws XMLStreamException, IOException {
|
||||
|
||||
if (withInstallDirChooserDlg) {
|
||||
if (uiConfig.isWithInstallDirChooserDlg()) {
|
||||
xml.writeStartElement("Property");
|
||||
xml.writeAttribute("Id", "WIXUI_INSTALLDIR");
|
||||
xml.writeAttribute("Value", "INSTALLDIR");
|
||||
xml.writeEndElement(); // Property
|
||||
}
|
||||
|
||||
if (withLicenseDlg) {
|
||||
if (uiConfig.isWithLicenseDlg()) {
|
||||
xml.writeStartElement("WixVariable");
|
||||
xml.writeAttribute("Id", "WixUILicenseRtf");
|
||||
xml.writeAttribute("Value", "$(var.JpLicenseRtf)");
|
||||
xml.writeEndElement(); // WixVariable
|
||||
}
|
||||
|
||||
var ui = getUI();
|
||||
if (ui != null) {
|
||||
ui.write(getWixType(), this, xml);
|
||||
if (uiSpec.isPresent()) {
|
||||
writeNonEmptyUIElement(xml);
|
||||
} else {
|
||||
xml.writeStartElement("UI");
|
||||
xml.writeAttribute("Id", "JpUI");
|
||||
@ -159,371 +184,140 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
private UI getUI() {
|
||||
if (withInstallDirChooserDlg || withShortcutPromptDlg) {
|
||||
// WixUI_InstallDir for shortcut prompt dialog too because in
|
||||
// WixUI_Minimal UI sequence WelcomeEulaDlg dialog doesn't have "Next"
|
||||
// button, but has "Install" button. So inserting shortcut prompt dialog
|
||||
// after welcome dialog in WixUI_Minimal UI sequence would be confusing
|
||||
return UI.InstallDir;
|
||||
} else if (withLicenseDlg) {
|
||||
return UI.Minimal;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
void writeNonEmptyUIElement(XMLStreamWriter xml) throws XMLStreamException, IOException {
|
||||
|
||||
private enum UI {
|
||||
InstallDir("WixUI_InstallDir",
|
||||
WixUiFragmentBuilder::dialogSequenceForWixUI_InstallDir,
|
||||
Dialog::createPairsForWixUI_InstallDir),
|
||||
Minimal("WixUI_Minimal", null, null);
|
||||
switch (getWixType()) {
|
||||
case Wix3 -> {}
|
||||
case Wix4 -> {
|
||||
// https://wixtoolset.org/docs/fourthree/faqs/#converting-custom-wixui-dialog-sets
|
||||
xml.writeProcessingInstruction("foreach WIXUIARCH in X86;X64;A64");
|
||||
writeWix4UIRef(xml, uiSpec.get().wixUI().id(), "JpUIInternal_$(WIXUIARCH)");
|
||||
xml.writeProcessingInstruction("endforeach");
|
||||
|
||||
UI(String wixUIRef,
|
||||
Function<WixUiFragmentBuilder, List<Dialog>> dialogIdsSupplier,
|
||||
Supplier<Map<DialogPair, List<Publish>>> dialogPairsSupplier) {
|
||||
this.wixUIRef = wixUIRef;
|
||||
this.dialogIdsSupplier = dialogIdsSupplier;
|
||||
this.dialogPairsSupplier = dialogPairsSupplier;
|
||||
}
|
||||
|
||||
void write(WixToolsetType wixType, WixUiFragmentBuilder outer, XMLStreamWriter xml) throws XMLStreamException, IOException {
|
||||
switch (wixType) {
|
||||
case Wix3 -> {}
|
||||
case Wix4 -> {
|
||||
// https://wixtoolset.org/docs/fourthree/faqs/#converting-custom-wixui-dialog-sets
|
||||
xml.writeProcessingInstruction("foreach WIXUIARCH in X86;X64;A64");
|
||||
writeWix4UIRef(xml, wixUIRef, "JpUIInternal_$(WIXUIARCH)");
|
||||
xml.writeProcessingInstruction("endforeach");
|
||||
|
||||
writeWix4UIRef(xml, "JpUIInternal", "JpUI");
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
xml.writeStartElement("UI");
|
||||
switch (wixType) {
|
||||
case Wix3 -> {
|
||||
xml.writeAttribute("Id", "JpUI");
|
||||
xml.writeStartElement("UIRef");
|
||||
xml.writeAttribute("Id", wixUIRef);
|
||||
xml.writeEndElement(); // UIRef
|
||||
}
|
||||
case Wix4 -> {
|
||||
xml.writeAttribute("Id", "JpUIInternal");
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
writeContents(wixType, outer, xml);
|
||||
xml.writeEndElement(); // UI
|
||||
}
|
||||
|
||||
private void writeContents(WixToolsetType wixType, WixUiFragmentBuilder outer,
|
||||
XMLStreamWriter xml) throws XMLStreamException, IOException {
|
||||
if (dialogIdsSupplier != null) {
|
||||
List<Dialog> dialogIds = dialogIdsSupplier.apply(outer);
|
||||
Map<DialogPair, List<Publish>> dialogPairs = dialogPairsSupplier.get();
|
||||
|
||||
if (dialogIds.contains(Dialog.InstallDirDlg)) {
|
||||
xml.writeStartElement("DialogRef");
|
||||
xml.writeAttribute("Id", "InstallDirNotEmptyDlg");
|
||||
xml.writeEndElement(); // DialogRef
|
||||
}
|
||||
|
||||
var it = dialogIds.iterator();
|
||||
Dialog firstId = it.next();
|
||||
while (it.hasNext()) {
|
||||
Dialog secondId = it.next();
|
||||
DialogPair pair = new DialogPair(firstId, secondId);
|
||||
for (var curPair : List.of(pair, pair.flip())) {
|
||||
for (var publish : dialogPairs.get(curPair)) {
|
||||
writePublishDialogPair(wixType, xml, publish, curPair);
|
||||
}
|
||||
}
|
||||
firstId = secondId;
|
||||
}
|
||||
writeWix4UIRef(xml, "JpUIInternal", "JpUI");
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeWix4UIRef(XMLStreamWriter xml, String uiRef, String id) throws XMLStreamException, IOException {
|
||||
// https://wixtoolset.org/docs/fourthree/faqs/#referencing-the-standard-wixui-dialog-sets
|
||||
xml.writeStartElement("UI");
|
||||
xml.writeAttribute("Id", id);
|
||||
xml.writeStartElement("ui:WixUI");
|
||||
xml.writeAttribute("Id", uiRef);
|
||||
xml.writeNamespace("ui", "http://wixtoolset.org/schemas/v4/wxs/ui");
|
||||
xml.writeEndElement(); // UIRef
|
||||
xml.writeEndElement(); // UI
|
||||
}
|
||||
|
||||
private final String wixUIRef;
|
||||
private final Function<WixUiFragmentBuilder, List<Dialog>> dialogIdsSupplier;
|
||||
private final Supplier<Map<DialogPair, List<Publish>>> dialogPairsSupplier;
|
||||
}
|
||||
|
||||
private List<Dialog> dialogSequenceForWixUI_InstallDir() {
|
||||
List<Dialog> dialogIds = new ArrayList<>(
|
||||
List.of(Dialog.WixUI_WelcomeDlg));
|
||||
if (withLicenseDlg) {
|
||||
dialogIds.add(Dialog.WixUI_LicenseAgreementDlg);
|
||||
}
|
||||
|
||||
if (withInstallDirChooserDlg) {
|
||||
dialogIds.add(Dialog.InstallDirDlg);
|
||||
}
|
||||
|
||||
if (withShortcutPromptDlg) {
|
||||
dialogIds.add(Dialog.ShortcutPromptDlg);
|
||||
}
|
||||
|
||||
dialogIds.add(Dialog.WixUI_VerifyReadyDlg);
|
||||
|
||||
return dialogIds;
|
||||
}
|
||||
|
||||
private enum Dialog {
|
||||
WixUI_WelcomeDlg,
|
||||
WixUI_LicenseAgreementDlg,
|
||||
InstallDirDlg,
|
||||
ShortcutPromptDlg,
|
||||
WixUI_VerifyReadyDlg;
|
||||
|
||||
Dialog() {
|
||||
if (name().startsWith("WixUI_")) {
|
||||
id = name().substring("WixUI_".length());
|
||||
} else {
|
||||
id = name();
|
||||
xml.writeStartElement("UI");
|
||||
switch (getWixType()) {
|
||||
case Wix3 -> {
|
||||
xml.writeAttribute("Id", "JpUI");
|
||||
xml.writeStartElement("UIRef");
|
||||
xml.writeAttribute("Id", uiSpec.get().wixUI().id());
|
||||
xml.writeEndElement(); // UIRef
|
||||
}
|
||||
case Wix4 -> {
|
||||
xml.writeAttribute("Id", "JpUIInternal");
|
||||
}
|
||||
}
|
||||
|
||||
static Map<DialogPair, List<Publish>> createPair(Dialog firstId,
|
||||
Dialog secondId, List<PublishBuilder> nextBuilders,
|
||||
List<PublishBuilder> prevBuilders) {
|
||||
var pair = new DialogPair(firstId, secondId);
|
||||
return Map.of(pair, nextBuilders.stream().map(b -> {
|
||||
return buildPublish(b.create()).next().create();
|
||||
}).toList(), pair.flip(),
|
||||
prevBuilders.stream().map(b -> {
|
||||
return buildPublish(b.create()).back().create();
|
||||
}).toList());
|
||||
}
|
||||
|
||||
static Map<DialogPair, List<Publish>> createPair(Dialog firstId,
|
||||
Dialog secondId, List<PublishBuilder> builders) {
|
||||
return createPair(firstId, secondId, builders, builders);
|
||||
}
|
||||
|
||||
static Map<DialogPair, List<Publish>> createPairsForWixUI_InstallDir() {
|
||||
Map<DialogPair, List<Publish>> map = new HashMap<>();
|
||||
|
||||
// Order is a "weight" of action. If there are multiple
|
||||
// "NewDialog" action for the same dialog Id, MSI would pick the one
|
||||
// with higher order value. In WixUI_InstallDir dialog sequence the
|
||||
// highest order value is 4. InstallDirNotEmptyDlg adds NewDialog
|
||||
// action with order 5. Setting order to 6 for all
|
||||
// actions configured in this function would make them executed
|
||||
// instead of corresponding default actions defined in
|
||||
// WixUI_InstallDir dialog sequence.
|
||||
var order = 6;
|
||||
|
||||
// Based on WixUI_InstallDir.wxs
|
||||
var backFromVerifyReadyDlg = List.of(buildPublish().condition(
|
||||
"NOT Installed").order(order));
|
||||
var uncondinal = List.of(buildPublish().condition("1"));
|
||||
var ifNotIstalled = List.of(
|
||||
buildPublish().condition("NOT Installed").order(order));
|
||||
var ifLicenseAccepted = List.of(buildPublish().condition(
|
||||
"LicenseAccepted = \"1\"").order(order));
|
||||
|
||||
// Empty condition list for the default dialog sequence
|
||||
map.putAll(createPair(WixUI_WelcomeDlg, WixUI_LicenseAgreementDlg,
|
||||
List.of()));
|
||||
map.putAll(
|
||||
createPair(WixUI_WelcomeDlg, InstallDirDlg, ifNotIstalled));
|
||||
map.putAll(createPair(WixUI_WelcomeDlg, ShortcutPromptDlg,
|
||||
ifNotIstalled));
|
||||
|
||||
map.putAll(createPair(WixUI_LicenseAgreementDlg, InstallDirDlg,
|
||||
List.of()));
|
||||
map.putAll(createPair(WixUI_LicenseAgreementDlg, ShortcutPromptDlg,
|
||||
ifLicenseAccepted, uncondinal));
|
||||
map.putAll(createPair(WixUI_LicenseAgreementDlg,
|
||||
WixUI_VerifyReadyDlg, ifLicenseAccepted,
|
||||
backFromVerifyReadyDlg));
|
||||
|
||||
map.putAll(createPair(InstallDirDlg, ShortcutPromptDlg, List.of(),
|
||||
uncondinal));
|
||||
map.putAll(createPair(InstallDirDlg, WixUI_VerifyReadyDlg, List.of()));
|
||||
|
||||
map.putAll(createPair(ShortcutPromptDlg, WixUI_VerifyReadyDlg,
|
||||
uncondinal, backFromVerifyReadyDlg));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private final String id;
|
||||
writeUIElementContents(xml);
|
||||
xml.writeEndElement(); // UI
|
||||
}
|
||||
|
||||
private static final class DialogPair {
|
||||
private void writeUIElementContents(XMLStreamWriter xml) throws XMLStreamException, IOException {
|
||||
|
||||
DialogPair(Dialog first, Dialog second) {
|
||||
this(first.id, second.id);
|
||||
if (uiConfig.isWithInstallDirChooserDlg()) {
|
||||
xml.writeStartElement("DialogRef");
|
||||
xml.writeAttribute("Id", "InstallDirNotEmptyDlg");
|
||||
xml.writeEndElement(); // DialogRef
|
||||
}
|
||||
|
||||
DialogPair(String firstId, String secondId) {
|
||||
this.firstId = firstId;
|
||||
this.secondId = secondId;
|
||||
for (var e : uiSpec.get().customDialogSequence().entrySet().stream()
|
||||
.sorted(Comparator.comparing(Map.Entry::getKey, DialogPair.DEFAULT_COMPARATOR))
|
||||
.toList()) {
|
||||
writePublishDialogPair(getWixType(), xml, e.getValue(), e.getKey());
|
||||
}
|
||||
|
||||
DialogPair flip() {
|
||||
return new DialogPair(secondId, firstId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 97 * hash + Objects.hashCode(this.firstId);
|
||||
hash = 97 * hash + Objects.hashCode(this.secondId);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final DialogPair other = (DialogPair) obj;
|
||||
if (!Objects.equals(this.firstId, other.firstId)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.secondId, other.secondId)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private final String firstId;
|
||||
private final String secondId;
|
||||
hideDialogs(getWixType(), xml, uiSpec.get().hideDialogs());
|
||||
}
|
||||
|
||||
private static final class Publish {
|
||||
|
||||
Publish(String control, String condition, int order) {
|
||||
this.control = control;
|
||||
this.condition = condition;
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
private final String control;
|
||||
private final String condition;
|
||||
private final int order;
|
||||
private static void writeWix4UIRef(XMLStreamWriter xml, String uiRef, String id) throws XMLStreamException, IOException {
|
||||
// https://wixtoolset.org/docs/fourthree/faqs/#referencing-the-standard-wixui-dialog-sets
|
||||
xml.writeStartElement("UI");
|
||||
xml.writeAttribute("Id", Objects.requireNonNull(id));
|
||||
xml.writeStartElement("ui:WixUI");
|
||||
xml.writeAttribute("Id", Objects.requireNonNull(uiRef));
|
||||
xml.writeNamespace("ui", "http://wixtoolset.org/schemas/v4/wxs/ui");
|
||||
xml.writeEndElement(); // UIRef
|
||||
xml.writeEndElement(); // UI
|
||||
}
|
||||
|
||||
private static final class PublishBuilder {
|
||||
private static void writePublishDialogPair(
|
||||
WixToolsetType wixType,
|
||||
XMLStreamWriter xml,
|
||||
Publish publish,
|
||||
DialogPair dialogPair) throws IOException, XMLStreamException {
|
||||
|
||||
PublishBuilder() {
|
||||
order(0);
|
||||
next();
|
||||
condition("1");
|
||||
}
|
||||
|
||||
PublishBuilder(Publish publish) {
|
||||
order(publish.order);
|
||||
control(publish.control);
|
||||
condition(publish.condition);
|
||||
}
|
||||
|
||||
public PublishBuilder control(String v) {
|
||||
control = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PublishBuilder next() {
|
||||
return control("Next");
|
||||
}
|
||||
|
||||
public PublishBuilder back() {
|
||||
return control("Back");
|
||||
}
|
||||
|
||||
public PublishBuilder condition(String v) {
|
||||
condition = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PublishBuilder order(int v) {
|
||||
order = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Publish create() {
|
||||
return new Publish(control, condition, order);
|
||||
}
|
||||
|
||||
private String control;
|
||||
private String condition;
|
||||
private int order;
|
||||
}
|
||||
|
||||
private static PublishBuilder buildPublish() {
|
||||
return new PublishBuilder();
|
||||
}
|
||||
|
||||
private static PublishBuilder buildPublish(Publish publish) {
|
||||
return new PublishBuilder(publish);
|
||||
}
|
||||
|
||||
private static void writePublishDialogPair(WixToolsetType wixType, XMLStreamWriter xml,
|
||||
Publish publish, DialogPair dialogPair) throws IOException, XMLStreamException {
|
||||
xml.writeStartElement("Publish");
|
||||
xml.writeAttribute("Dialog", dialogPair.firstId);
|
||||
xml.writeAttribute("Control", publish.control);
|
||||
xml.writeAttribute("Dialog", dialogPair.first().id());
|
||||
xml.writeAttribute("Control", publish.control().id());
|
||||
xml.writeAttribute("Event", "NewDialog");
|
||||
xml.writeAttribute("Value", dialogPair.secondId);
|
||||
if (publish.order != 0) {
|
||||
xml.writeAttribute("Order", String.valueOf(publish.order));
|
||||
xml.writeAttribute("Value", dialogPair.second().id());
|
||||
if (publish.order() != 0) {
|
||||
xml.writeAttribute("Order", String.valueOf(publish.order()));
|
||||
}
|
||||
|
||||
switch (wixType) {
|
||||
case Wix3 -> xml.writeCharacters(publish.condition);
|
||||
case Wix4 -> xml.writeAttribute("Condition", publish.condition);
|
||||
default -> throw new IllegalArgumentException();
|
||||
case Wix3 -> {
|
||||
xml.writeCharacters(publish.condition());
|
||||
}
|
||||
case Wix4 -> {
|
||||
xml.writeAttribute("Condition", publish.condition());
|
||||
}
|
||||
}
|
||||
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
private static void hideDialogs(
|
||||
WixToolsetType wixType,
|
||||
XMLStreamWriter xml,
|
||||
Collection<? extends ShowActionSuppresser> hideDialogs) throws IOException, XMLStreamException {
|
||||
|
||||
if (!hideDialogs.isEmpty()) {
|
||||
if (wixType == WixToolsetType.Wix4) {
|
||||
xml.writeStartElement("InstallUISequence");
|
||||
for (var showAction : hideDialogs.stream().sorted(ShowActionSuppresser.DEFAULT_COMPARATOR).toList()) {
|
||||
writeWix4ShowAction(wixType, xml, showAction);
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeWix4ShowAction(
|
||||
WixToolsetType wixType,
|
||||
XMLStreamWriter xml,
|
||||
ShowActionSuppresser hideDialog) throws IOException, XMLStreamException {
|
||||
|
||||
xml.writeStartElement("Show");
|
||||
xml.writeAttribute("Dialog", String.format("override %s", hideDialog.dialog().id()));
|
||||
xml.writeAttribute(switch (hideDialog.order()) {
|
||||
case AFTER -> "After";
|
||||
}, hideDialog.anchor().id());
|
||||
xml.writeAttribute("Condition", "0");
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
private final class CustomDialog {
|
||||
|
||||
CustomDialog(Function<String, OverridableResource> createResource, String category,
|
||||
String wxsFileName) {
|
||||
CustomDialog(Function<String, OverridableResource> createResource, String category, String wxsFileName) {
|
||||
this.wxsFileName = wxsFileName;
|
||||
this.wixVariables = new WixVariables();
|
||||
|
||||
addResource(createResource.apply(wxsFileName).setCategory(category).setPublicName(
|
||||
wxsFileName), wxsFileName);
|
||||
addResource(createResource.apply(wxsFileName).setCategory(category).setPublicName(wxsFileName), wxsFileName);
|
||||
}
|
||||
|
||||
void addToWixPipeline(WixPipeline.Builder wixPipeline) {
|
||||
wixPipeline.addSource(getConfigRoot().toAbsolutePath().resolve(
|
||||
wxsFileName), wixVariables.getValues());
|
||||
wixPipeline.addSource(getConfigRoot().toAbsolutePath().resolve(wxsFileName), wixVariables);
|
||||
}
|
||||
|
||||
private final WixVariables wixVariables;
|
||||
private final String wxsFileName;
|
||||
}
|
||||
|
||||
private boolean withInstallDirChooserDlg;
|
||||
private boolean withShortcutPromptDlg;
|
||||
private boolean withLicenseDlg;
|
||||
private UIConfig uiConfig;
|
||||
private Optional<UISpec> uiSpec;
|
||||
private boolean withCustomActionsDll = true;
|
||||
private List<CustomDialog> customDialogs;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -24,22 +24,103 @@
|
||||
*/
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.WixToolset.WixToolsetType;
|
||||
|
||||
final class WixVariables {
|
||||
|
||||
void defineWixVariable(String variableName) {
|
||||
setWixVariable(variableName, "yes");
|
||||
WixVariables() {
|
||||
this.values = new HashMap<>();
|
||||
}
|
||||
|
||||
void setWixVariable(String variableName, String variableValue) {
|
||||
values.put(variableName, variableValue);
|
||||
private WixVariables(Map<String, String> values) {
|
||||
this.values = values;
|
||||
this.isImmutable = true;
|
||||
}
|
||||
|
||||
Map<String, String> getValues() {
|
||||
return values;
|
||||
WixVariables define(String variableName) {
|
||||
return put(variableName, "yes");
|
||||
}
|
||||
|
||||
private final Map<String, String> values = new HashMap<>();
|
||||
WixVariables put(String variableName, String variableValue) {
|
||||
Objects.requireNonNull(variableName);
|
||||
Objects.requireNonNull(variableValue);
|
||||
validateMutable();
|
||||
values.compute(variableName, (k, v) -> {
|
||||
if (!allowOverrides && v != null) {
|
||||
throw overridingDisabled();
|
||||
}
|
||||
return variableValue;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
WixVariables putAll(Map<String, String> values) {
|
||||
Objects.requireNonNull(values);
|
||||
validateMutable();
|
||||
if (!allowOverrides && !Collections.disjoint(this.values.keySet(), values.keySet())) {
|
||||
throw overridingDisabled();
|
||||
} else {
|
||||
values.entrySet().forEach(e -> {
|
||||
put(e.getKey(), e.getValue());
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
WixVariables putAll(WixVariables other) {
|
||||
return putAll(other.values);
|
||||
}
|
||||
|
||||
WixVariables allowOverrides(boolean v) {
|
||||
validateMutable();
|
||||
allowOverrides = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
WixVariables createdImmutableCopy() {
|
||||
if (isImmutable) {
|
||||
return this;
|
||||
} else {
|
||||
return new WixVariables(Map.copyOf(values));
|
||||
}
|
||||
}
|
||||
|
||||
List<String> toWixCommandLine(WixToolsetType wixType) {
|
||||
var orderedWixVars = values.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey));
|
||||
return (switch (Objects.requireNonNull(wixType)) {
|
||||
case Wix3 -> {
|
||||
yield orderedWixVars.map(wixVar -> {
|
||||
return String.format("-d%s=%s", wixVar.getKey(), wixVar.getValue());
|
||||
});
|
||||
}
|
||||
case Wix4 -> {
|
||||
yield orderedWixVars.flatMap(wixVar -> {
|
||||
return Stream.of("-d", String.format("%s=%s", wixVar.getKey(), wixVar.getValue()));
|
||||
});
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private void validateMutable() {
|
||||
if (isImmutable) {
|
||||
throw new IllegalStateException("WiX variables container is immutable");
|
||||
}
|
||||
}
|
||||
|
||||
private static IllegalStateException overridingDisabled() {
|
||||
return new IllegalStateException("Overriding variables is unsupported");
|
||||
}
|
||||
|
||||
private final Map<String, String> values;
|
||||
private boolean allowOverrides;
|
||||
private boolean isImmutable;
|
||||
|
||||
static final WixVariables EMPTY = new WixVariables().createdImmutableCopy();
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -36,6 +36,8 @@ public interface WinMsiPackageMixin {
|
||||
|
||||
boolean withShortcutPrompt();
|
||||
|
||||
boolean withUI();
|
||||
|
||||
Optional<String> helpURL();
|
||||
|
||||
Optional<String> updateURL();
|
||||
@ -50,8 +52,16 @@ public interface WinMsiPackageMixin {
|
||||
|
||||
Optional<Path> serviceInstaller();
|
||||
|
||||
record Stub(DottedVersion msiVersion, boolean withInstallDirChooser, boolean withShortcutPrompt,
|
||||
Optional<String> helpURL, Optional<String> updateURL, String startMenuGroupName,
|
||||
boolean isSystemWideInstall, UUID upgradeCode, UUID productCode,
|
||||
record Stub(
|
||||
DottedVersion msiVersion,
|
||||
boolean withInstallDirChooser,
|
||||
boolean withShortcutPrompt,
|
||||
boolean withUI,
|
||||
Optional<String> helpURL,
|
||||
Optional<String> updateURL,
|
||||
String startMenuGroupName,
|
||||
boolean isSystemWideInstall,
|
||||
UUID upgradeCode,
|
||||
UUID productCode,
|
||||
Optional<Path> serviceInstaller) implements WinMsiPackageMixin {}
|
||||
}
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
function modifyMsi(msiPath, callback) {
|
||||
var installer = new ActiveXObject('WindowsInstaller.Installer')
|
||||
var database = installer.OpenDatabase(msiPath, 1 /* msiOpenDatabaseModeTransact */)
|
||||
|
||||
callback(installer, database)
|
||||
|
||||
database.Commit()
|
||||
}
|
||||
|
||||
|
||||
function disableActions(installer, db, sequence, actionIDs) {
|
||||
var tables = {}
|
||||
|
||||
var view = db.OpenView("SELECT `Action`, `Condition`, `Sequence` FROM " + sequence)
|
||||
view.Execute()
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var record = view.Fetch()
|
||||
if (!record) {
|
||||
break
|
||||
}
|
||||
|
||||
var action = record.StringData(1)
|
||||
|
||||
if (actionIDs.hasOwnProperty(action)) {
|
||||
WScript.Echo("Set condition of [" + action + "] action in [" + sequence + "] sequence to [0]")
|
||||
var newRecord = installer.CreateRecord(3)
|
||||
for (var i = 1; i !== newRecord.FieldCount + 1; i++) {
|
||||
newRecord.StringData(i) = record.StringData(i)
|
||||
}
|
||||
newRecord.StringData(2) = "0" // Set condition value to `0`
|
||||
view.Modify(3 /* msiViewModifyAssign */, newRecord) // Replace existing record
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
view.Close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
(function () {
|
||||
var msi = WScript.arguments(0)
|
||||
var sequence = WScript.arguments(1)
|
||||
var actionIDs = {}
|
||||
for (var i = 0; i !== WScript.arguments.Count(); i++) {
|
||||
actionIDs[WScript.arguments(i)] = true
|
||||
}
|
||||
|
||||
modifyMsi(msi, function (installer, db) {
|
||||
disableActions(installer, db, sequence, actionIDs)
|
||||
})
|
||||
})()
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.wixui;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* WiX Dialog's control.
|
||||
*/
|
||||
public sealed interface Control permits StandardControl {
|
||||
String id();
|
||||
|
||||
public static final Comparator<Control> DEFAULT_COMPARATOR = Comparator.comparing(Control::id);
|
||||
}
|
||||
@ -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. 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.wixui;
|
||||
|
||||
/**
|
||||
* Custom jpackage dialogs.
|
||||
*/
|
||||
public enum CustomDialog implements Dialog {
|
||||
ShortcutPromptDlg,
|
||||
;
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.wixui;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* WiX Dialog.
|
||||
*/
|
||||
public sealed interface Dialog permits WixDialog, CustomDialog {
|
||||
String id();
|
||||
|
||||
public static final Comparator<Dialog> DEFAULT_COMPARATOR = Comparator.comparing(Dialog::id);
|
||||
}
|
||||
@ -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.wixui;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
public record DialogPair(Dialog first, Dialog second) {
|
||||
|
||||
public DialogPair {
|
||||
Objects.requireNonNull(first);
|
||||
Objects.requireNonNull(second);
|
||||
if (first.equals(second) || first.id().equals(second.id())) {
|
||||
throw new IllegalArgumentException("Dialogs must be different");
|
||||
}
|
||||
}
|
||||
|
||||
DialogPair flip() {
|
||||
return new DialogPair(second, first);
|
||||
}
|
||||
|
||||
public static final Comparator<DialogPair> DEFAULT_COMPARATOR =
|
||||
comparing(DialogPair::first, Dialog.DEFAULT_COMPARATOR)
|
||||
.thenComparing(comparing(DialogPair::second, Dialog.DEFAULT_COMPARATOR));
|
||||
}
|
||||
@ -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.wixui;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record Publish(Control control, String condition, int order) {
|
||||
|
||||
public Publish {
|
||||
Objects.requireNonNull(control);
|
||||
Objects.requireNonNull(condition);
|
||||
if (order < 0) {
|
||||
throw new IllegalArgumentException("Negative order");
|
||||
}
|
||||
}
|
||||
|
||||
Builder toBuilder() {
|
||||
return new Builder(this);
|
||||
}
|
||||
|
||||
static Builder build() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
static final class Builder {
|
||||
|
||||
private Builder() {
|
||||
order(0);
|
||||
next();
|
||||
condition("1");
|
||||
}
|
||||
|
||||
private Builder(Publish publish) {
|
||||
order(publish.order);
|
||||
control(publish.control);
|
||||
condition(publish.condition);
|
||||
}
|
||||
|
||||
Publish create() {
|
||||
return new Publish(control, condition, order);
|
||||
}
|
||||
|
||||
Builder control(Control v) {
|
||||
control = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder next() {
|
||||
return control(StandardControl.NEXT);
|
||||
}
|
||||
|
||||
Builder back() {
|
||||
return control(StandardControl.BACK);
|
||||
}
|
||||
|
||||
Builder condition(String v) {
|
||||
condition = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder order(int v) {
|
||||
order = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
private int order;
|
||||
private Control control;
|
||||
private String condition;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.wixui;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
public record ShowActionSuppresser(Dialog dialog, Dialog anchor, Order order) {
|
||||
|
||||
public enum Order {
|
||||
AFTER,
|
||||
;
|
||||
}
|
||||
|
||||
public ShowActionSuppresser {
|
||||
Objects.requireNonNull(order);
|
||||
validate(dialog);
|
||||
validate(anchor);
|
||||
}
|
||||
|
||||
static Builder suppressShowAction(WixDialog dialog) {
|
||||
return new Builder(dialog);
|
||||
}
|
||||
|
||||
static final class Builder {
|
||||
|
||||
private Builder(WixDialog dialog) {
|
||||
this.dialog = Objects.requireNonNull(dialog);
|
||||
}
|
||||
|
||||
ShowActionSuppresser after(WixDialog anchor) {
|
||||
return new ShowActionSuppresser(dialog, anchor, Order.AFTER);
|
||||
}
|
||||
|
||||
private final WixDialog dialog;
|
||||
}
|
||||
|
||||
private static void validate(Dialog v) {
|
||||
if (!(Objects.requireNonNull(v) instanceof WixDialog)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static final Comparator<ShowActionSuppresser> DEFAULT_COMPARATOR =
|
||||
comparing(ShowActionSuppresser::dialog, Dialog.DEFAULT_COMPARATOR)
|
||||
.thenComparing(comparing(ShowActionSuppresser::anchor, Dialog.DEFAULT_COMPARATOR))
|
||||
.thenComparing(comparing(ShowActionSuppresser::order));
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.wixui;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Dialog controls referenced in adjustments of the standard WiX UI.
|
||||
*/
|
||||
enum StandardControl implements Control {
|
||||
NEXT("Next"),
|
||||
BACK("Back"),
|
||||
;
|
||||
|
||||
StandardControl(String id) {
|
||||
this.id = Objects.requireNonNull(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private final String id;
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.wixui;
|
||||
|
||||
/**
|
||||
* UI config.
|
||||
*/
|
||||
public record UIConfig(
|
||||
boolean isWithInstallDirChooserDlg,
|
||||
boolean isWithShortcutPromptDlg,
|
||||
boolean isWithLicenseDlg) {
|
||||
|
||||
public static Builder build() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public UIConfig create() {
|
||||
return new UIConfig(isWithInstallDirChooserDlg, isWithShortcutPromptDlg, isWithLicenseDlg);
|
||||
}
|
||||
|
||||
public Builder withInstallDirChooserDlg(boolean v) {
|
||||
isWithInstallDirChooserDlg = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withInstallDirChooserDlg() {
|
||||
return withInstallDirChooserDlg(true);
|
||||
}
|
||||
|
||||
public Builder withShortcutPromptDlg(boolean v) {
|
||||
isWithShortcutPromptDlg = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withShortcutPromptDlg() {
|
||||
return withShortcutPromptDlg(true);
|
||||
}
|
||||
|
||||
public Builder withLicenseDlg(boolean v) {
|
||||
isWithLicenseDlg = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLicenseDlg() {
|
||||
return withLicenseDlg(true);
|
||||
}
|
||||
|
||||
private boolean isWithInstallDirChooserDlg;
|
||||
private boolean isWithShortcutPromptDlg;
|
||||
private boolean isWithLicenseDlg;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,293 @@
|
||||
/*
|
||||
* 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.wixui;
|
||||
|
||||
import static jdk.jpackage.internal.wixui.CustomDialog.ShortcutPromptDlg;
|
||||
import static jdk.jpackage.internal.wixui.ShowActionSuppresser.suppressShowAction;
|
||||
import static jdk.jpackage.internal.wixui.WixDialog.InstallDirDlg;
|
||||
import static jdk.jpackage.internal.wixui.WixDialog.LicenseAgreementDlg;
|
||||
import static jdk.jpackage.internal.wixui.WixDialog.ProgressDlg;
|
||||
import static jdk.jpackage.internal.wixui.WixDialog.VerifyReadyDlg;
|
||||
import static jdk.jpackage.internal.wixui.WixDialog.WelcomeDlg;
|
||||
import static jdk.jpackage.internal.wixui.WixDialog.WelcomeEulaDlg;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* UI spec.
|
||||
* <p>
|
||||
* UI is based on one of the standard WiX UIs with optional alterations.
|
||||
*/
|
||||
public record UISpec(
|
||||
WixUI wixUI,
|
||||
Map<String, String> wixVariables,
|
||||
Map<DialogPair, Publish> customDialogSequence,
|
||||
Collection<ShowActionSuppresser> hideDialogs) {
|
||||
|
||||
public UISpec {
|
||||
Objects.requireNonNull(wixUI);
|
||||
Objects.requireNonNull(wixVariables);
|
||||
Objects.requireNonNull(customDialogSequence);
|
||||
Objects.requireNonNull(hideDialogs);
|
||||
}
|
||||
|
||||
static Builder build(WixUI wixUI) {
|
||||
return new Builder().wixUI(wixUI);
|
||||
}
|
||||
|
||||
static final class Builder {
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
UISpec create() {
|
||||
return new UISpec(
|
||||
wixUI,
|
||||
Optional.ofNullable(wixVariables).map(Collections::unmodifiableMap).orElseGet(Map::of),
|
||||
Optional.ofNullable(customDialogSequence).map(Collections::unmodifiableMap).orElseGet(Map::of),
|
||||
Optional.ofNullable(hideDialogs).map(List::copyOf).orElseGet(List::of));
|
||||
}
|
||||
|
||||
Builder wixUI(WixUI v) {
|
||||
wixUI = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setWixVariable(String name, String value) {
|
||||
wixVariables.put(Objects.requireNonNull(name), Objects.requireNonNull(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder customDialogSequence(Map<DialogPair, Publish> v) {
|
||||
customDialogSequence = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder hideDialogs(Collection<ShowActionSuppresser> v) {
|
||||
hideDialogs = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder hideDialogs(ShowActionSuppresser... v) {
|
||||
return hideDialogs(List.of(v));
|
||||
}
|
||||
|
||||
private WixUI wixUI;
|
||||
private final Map<String, String> wixVariables = new HashMap<>();
|
||||
private Map<DialogPair, Publish> customDialogSequence;
|
||||
private Collection<ShowActionSuppresser> hideDialogs;
|
||||
}
|
||||
|
||||
public static UISpec create(UIConfig cfg) {
|
||||
Objects.requireNonNull(cfg);
|
||||
return Optional.ofNullable(DEFAULT_SPECS.get(cfg)).map(Supplier::get).orElseGet(() -> {
|
||||
return createCustom(cfg);
|
||||
});
|
||||
}
|
||||
|
||||
private static UISpec createCustom(UIConfig cfg) {
|
||||
Objects.requireNonNull(cfg);
|
||||
|
||||
var dialogs = installDirUiDialogs(cfg);
|
||||
var dialogPairs = toDialogPairs(dialogs);
|
||||
|
||||
var customDialogSequence = overrideInstallDirDialogSequence().stream().filter(e -> {
|
||||
return dialogPairs.contains(e.getKey());
|
||||
}).collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
var uiSpec = build(WixUI.INSTALL_DIR).customDialogSequence(customDialogSequence);
|
||||
|
||||
var it = dialogs.iterator();
|
||||
do {
|
||||
if (it.next().equals(InstallDirDlg)) {
|
||||
uiSpec.setWixVariable("JpAfterInstallDirDlg", it.next().id());
|
||||
}
|
||||
} while (it.hasNext());
|
||||
|
||||
return uiSpec.create();
|
||||
}
|
||||
|
||||
private static void createPairs(
|
||||
BiConsumer<DialogPair, Publish> sink,
|
||||
Dialog first,
|
||||
Dialog second,
|
||||
Publish publishNext,
|
||||
Publish publishPrev) {
|
||||
|
||||
createPairNext(sink, first, second, publishNext);
|
||||
createPairBack(sink, second, first, publishPrev);
|
||||
}
|
||||
|
||||
private static void createPairs(
|
||||
BiConsumer<DialogPair, Publish> sink,
|
||||
Dialog first,
|
||||
Dialog second,
|
||||
Publish publish) {
|
||||
createPairs(sink, first, second, publish, publish);
|
||||
}
|
||||
|
||||
private static void createPairNext(
|
||||
BiConsumer<DialogPair, Publish> sink,
|
||||
Dialog first,
|
||||
Dialog second,
|
||||
Publish publish) {
|
||||
|
||||
var pair = new DialogPair(first, second);
|
||||
|
||||
sink.accept(pair, publish.toBuilder().next().create());
|
||||
}
|
||||
|
||||
private static void createPairBack(
|
||||
BiConsumer<DialogPair, Publish> sink,
|
||||
Dialog first,
|
||||
Dialog second,
|
||||
Publish publish) {
|
||||
|
||||
var pair = new DialogPair(first, second);
|
||||
|
||||
sink.accept(pair, publish.toBuilder().back().create());
|
||||
}
|
||||
|
||||
private static Collection<DialogPair> toDialogPairs(List<Dialog> dialogs) {
|
||||
if (dialogs.size() < 2) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
var pairs = new ArrayList<DialogPair>();
|
||||
var it = dialogs.listIterator();
|
||||
var prev = it.next();
|
||||
do {
|
||||
var next = it.next();
|
||||
var pair = new DialogPair(prev, next);
|
||||
pairs.add(pair);
|
||||
pairs.add(pair.flip());
|
||||
prev = next;
|
||||
} while (it.hasNext());
|
||||
|
||||
return pairs;
|
||||
}
|
||||
|
||||
private static List<Dialog> installDirUiDialogs(UIConfig cfg) {
|
||||
var dialogs = new ArrayList<Dialog>();
|
||||
|
||||
dialogs.add(WelcomeDlg);
|
||||
|
||||
if (cfg.isWithLicenseDlg()) {
|
||||
dialogs.add(LicenseAgreementDlg);
|
||||
}
|
||||
|
||||
if (cfg.isWithInstallDirChooserDlg()) {
|
||||
dialogs.add(InstallDirDlg);
|
||||
}
|
||||
|
||||
if (cfg.isWithShortcutPromptDlg()) {
|
||||
dialogs.add(ShortcutPromptDlg);
|
||||
}
|
||||
|
||||
dialogs.add(VerifyReadyDlg);
|
||||
|
||||
return dialogs;
|
||||
}
|
||||
|
||||
private static Collection<Map.Entry<DialogPair, Publish>> overrideInstallDirDialogSequence() {
|
||||
|
||||
List<Map.Entry<DialogPair, Publish>> entries = new ArrayList<>();
|
||||
|
||||
BiConsumer<DialogPair, Publish> acc = (pair, publish) -> {
|
||||
entries.add(Map.entry(pair, publish));
|
||||
};
|
||||
|
||||
// Order is a "weight" of action. If there are multiple
|
||||
// "NewDialog" action for the same dialog Id, MSI would pick the one
|
||||
// with higher order value. In WixUI_InstallDir dialog sequence the
|
||||
// highest order value is 4. InstallDirNotEmptyDlg adds NewDialog
|
||||
// action with order 5. Setting order to 6 for all
|
||||
// actions configured in this function would make them executed
|
||||
// instead of corresponding default actions defined in
|
||||
// WixUI_InstallDir dialog sequence.
|
||||
var order = 6;
|
||||
|
||||
// Based on WixUI_InstallDir.wxs
|
||||
var backFromVerifyReadyDlg = Publish.build().condition(CONDITION_NOT_INSTALLED).order(order).create();
|
||||
var uncondinal = Publish.build().condition(CONDITION_ALWAYS).create();
|
||||
var ifNotIstalled = Publish.build().condition(CONDITION_NOT_INSTALLED).order(order).create();
|
||||
var ifLicenseAccepted = Publish.build().condition("LicenseAccepted = \"1\"").order(order).create();
|
||||
|
||||
// Define all alternative transitions:
|
||||
// - Skip standard license dialog
|
||||
// - Insert shortcut prompt dialog after the standard install dir dialog
|
||||
// - Replace the standard install dir dialog with the shortcut prompt dialog
|
||||
|
||||
createPairs(acc, WelcomeDlg, InstallDirDlg, ifNotIstalled);
|
||||
createPairs(acc, WelcomeDlg, VerifyReadyDlg, ifNotIstalled);
|
||||
createPairs(acc, WelcomeDlg, ShortcutPromptDlg, ifNotIstalled);
|
||||
|
||||
createPairs(acc, LicenseAgreementDlg, ShortcutPromptDlg, ifLicenseAccepted, uncondinal);
|
||||
createPairs(acc, LicenseAgreementDlg, VerifyReadyDlg, ifLicenseAccepted, backFromVerifyReadyDlg);
|
||||
|
||||
createPairs(acc, InstallDirDlg, ShortcutPromptDlg, uncondinal);
|
||||
|
||||
createPairs(acc, ShortcutPromptDlg, VerifyReadyDlg, uncondinal, backFromVerifyReadyDlg);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static final Map<UIConfig, Supplier<UISpec>> DEFAULT_SPECS;
|
||||
|
||||
private static final String CONDITION_ALWAYS = "1";
|
||||
private static final String CONDITION_NOT_INSTALLED = "NOT Installed";
|
||||
|
||||
static {
|
||||
|
||||
var specs = new HashMap<UIConfig, Supplier<UISpec>>();
|
||||
|
||||
// Verbatim WiX "Minimal" dialog set.
|
||||
specs.put(UIConfig.build()
|
||||
.withLicenseDlg()
|
||||
.create(), () -> {
|
||||
return build(WixUI.MINIMAL).create();
|
||||
});
|
||||
|
||||
// Standard WiX "Minimal" dialog set without the license dialog.
|
||||
// The license dialog is removed by overriding the default "Show"
|
||||
// action with the condition that always evaluates to "FALSE".
|
||||
specs.put(UIConfig.build()
|
||||
.create(), () -> {
|
||||
return build(WixUI.MINIMAL).hideDialogs(suppressShowAction(WelcomeEulaDlg).after(ProgressDlg)).create();
|
||||
});
|
||||
|
||||
DEFAULT_SPECS = Collections.unmodifiableMap(specs);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.wixui;
|
||||
|
||||
/**
|
||||
* Standard WiX dialogs.
|
||||
*/
|
||||
enum WixDialog implements Dialog {
|
||||
InstallDirDlg,
|
||||
LicenseAgreementDlg,
|
||||
ProgressDlg,
|
||||
VerifyReadyDlg,
|
||||
WelcomeDlg,
|
||||
WelcomeEulaDlg,
|
||||
;
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.wixui;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Standard WiX UI.
|
||||
*/
|
||||
public enum WixUI {
|
||||
MINIMAL("WixUI_Minimal"),
|
||||
INSTALL_DIR("WixUI_InstallDir"),
|
||||
;
|
||||
|
||||
WixUI(String id) {
|
||||
this.id = Objects.requireNonNull(id);
|
||||
}
|
||||
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private final String id;
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -30,7 +30,9 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -42,9 +44,10 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
final class MsiDatabase {
|
||||
public final class MsiDatabase {
|
||||
|
||||
static MsiDatabase load(Path msiFile, Path idtFileOutputDir, Set<Table> tableNames) {
|
||||
try {
|
||||
@ -61,7 +64,7 @@ final class MsiDatabase {
|
||||
.execute(0);
|
||||
|
||||
var tables = orderedTableNames.stream().map(tableName -> {
|
||||
return Map.entry(tableName, idtFileOutputDir.resolve(tableName + ".idt"));
|
||||
return Map.entry(tableName, idtFileOutputDir.resolve(tableName.tableName() + ".idt"));
|
||||
}).filter(e -> {
|
||||
return Files.exists(e.getValue());
|
||||
}).collect(Collectors.toMap(Map.Entry::getKey, e -> {
|
||||
@ -81,6 +84,8 @@ final class MsiDatabase {
|
||||
FILE("File"),
|
||||
PROPERTY("Property"),
|
||||
SHORTCUT("Shortcut"),
|
||||
CONTROL_EVENT("ControlEvent"),
|
||||
INSTALL_UI_SEQUENCE("InstallUISequence"),
|
||||
;
|
||||
|
||||
Table(String name) {
|
||||
@ -95,6 +100,7 @@ final class MsiDatabase {
|
||||
|
||||
static final Set<Table> FIND_PROPERTY_REQUIRED_TABLES = Set.of(PROPERTY);
|
||||
static final Set<Table> LIST_SHORTCUTS_REQUIRED_TABLES = Set.of(COMPONENT, DIRECTORY, FILE, SHORTCUT);
|
||||
static final Set<Table> UI_ALTERATIONS_REQUIRED_TABLES = Set.of(CONTROL_EVENT, INSTALL_UI_SEQUENCE);
|
||||
}
|
||||
|
||||
|
||||
@ -120,12 +126,7 @@ final class MsiDatabase {
|
||||
}
|
||||
|
||||
Collection<Shortcut> listShortcuts() {
|
||||
var shortcuts = tables.get(Table.SHORTCUT);
|
||||
if (shortcuts == null) {
|
||||
return List.of();
|
||||
}
|
||||
return IntStream.range(0, shortcuts.rowCount()).mapToObj(i -> {
|
||||
var row = shortcuts.row(i);
|
||||
return rows(Table.SHORTCUT).map(row -> {
|
||||
var shortcutPath = directoryPath(row.apply("Directory_")).resolve(fileNameFromFieldValue(row.apply("Name")));
|
||||
var workDir = directoryPath(row.apply("WkDir"));
|
||||
var shortcutTarget = Path.of(expandFormattedString(row.apply("Target")));
|
||||
@ -133,6 +134,53 @@ final class MsiDatabase {
|
||||
}).toList();
|
||||
}
|
||||
|
||||
UIAlterations uiAlterations() {
|
||||
|
||||
var includeActions = Set.of("WelcomeEulaDlg", "WelcomeDlg");
|
||||
var actions = actionSequence(Table.INSTALL_UI_SEQUENCE).filter(action -> {
|
||||
return includeActions.contains(action.action());
|
||||
}).sorted(Comparator.comparing(Action::sequence)).toList();
|
||||
|
||||
// Custom jpackage dialogs.
|
||||
var jpackageDialogs = Set.of("InstallDirNotEmptyDlg", "ShortcutPromptDlg");
|
||||
|
||||
var includeControls = Set.of("Next", "Back");
|
||||
var controlEvents = rows(Table.CONTROL_EVENT).map(row -> {
|
||||
return new ControlEvent(
|
||||
row.apply("Dialog_"),
|
||||
row.apply("Control_"),
|
||||
row.apply("Event"),
|
||||
row.apply("Argument"),
|
||||
row.apply("Condition"),
|
||||
Integer.parseInt(row.apply("Ordering")));
|
||||
}).filter(controlEvent -> {
|
||||
if (jpackageDialogs.contains(controlEvent.dialog())) {
|
||||
// Include controls of all custom jpackage dialogs.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (controlEvent.ordering() >= 6) {
|
||||
// jpackage assumes the standard WiX UI doesn't define control events
|
||||
// for dialog sequences it alters with the order higher than 6.
|
||||
// Include all such items.
|
||||
|
||||
if (includeControls.contains(controlEvent.control())) {
|
||||
// Include only specific controls that jpackage alters.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}).sorted(Comparator.comparing(ControlEvent::dialog)
|
||||
.thenComparing(ControlEvent::control)
|
||||
.thenComparing(ControlEvent::event)
|
||||
.thenComparing(ControlEvent::argument)
|
||||
.thenComparing(ControlEvent::condition)
|
||||
.thenComparing(Comparator.comparingInt(ControlEvent::ordering))).toList();
|
||||
|
||||
return new UIAlterations(actions, controlEvents);
|
||||
}
|
||||
|
||||
record Shortcut(Path path, Path target, Path workDir) {
|
||||
|
||||
Shortcut {
|
||||
@ -148,6 +196,49 @@ final class MsiDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public record Action(String action, String condition, int sequence) {
|
||||
public Action {
|
||||
Objects.requireNonNull(action);
|
||||
Objects.requireNonNull(condition);
|
||||
}
|
||||
}
|
||||
|
||||
public record ControlEvent(
|
||||
String dialog,
|
||||
String control,
|
||||
String event,
|
||||
String argument,
|
||||
String condition,
|
||||
int ordering) {
|
||||
|
||||
public ControlEvent {
|
||||
Objects.requireNonNull(dialog);
|
||||
Objects.requireNonNull(control);
|
||||
Objects.requireNonNull(event);
|
||||
Objects.requireNonNull(argument);
|
||||
Objects.requireNonNull(condition);
|
||||
}
|
||||
}
|
||||
|
||||
public record UIAlterations(Collection<Action> installUISequence, Collection<ControlEvent> controlEvents) {
|
||||
|
||||
public UIAlterations {
|
||||
Objects.requireNonNull(installUISequence);
|
||||
}
|
||||
}
|
||||
|
||||
private Stream<Action> actionSequence(Table tableName) {
|
||||
return rows(tableName).map(row -> {
|
||||
return new Action(row.apply("Action"), row.apply("Condition"), Integer.parseInt(row.apply("Sequence")));
|
||||
});
|
||||
}
|
||||
|
||||
private Stream<Function<String, String>> rows(Table tableName) {
|
||||
return Optional.ofNullable(tables.get(tableName)).stream().flatMap(table -> {
|
||||
return IntStream.range(0, table.rowCount()).mapToObj(table::row);
|
||||
});
|
||||
}
|
||||
|
||||
private Path directoryPath(String directoryId) {
|
||||
var table = tables.get(Table.DIRECTORY);
|
||||
Path result = null;
|
||||
@ -339,20 +430,28 @@ final class MsiDatabase {
|
||||
|
||||
var columns = headerLines.get(0).split("\t");
|
||||
|
||||
var header = headerLines.get(2).split("\t", 4);
|
||||
if (header.length == 3) {
|
||||
if (Pattern.matches("^[1-9]\\d+$", header[0])) {
|
||||
charset = Charset.forName(header[0]);
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Unexpected charset name [%s] in [%s] file", header[0], idtFile));
|
||||
}
|
||||
} else if (header.length != 2) {
|
||||
int tableNameIdx;
|
||||
|
||||
var header = headerLines.get(2).split("\t");
|
||||
if (Pattern.matches("^[1-9]\\d+$", header[0])) {
|
||||
charset = Charset.forName(header[0]);
|
||||
tableNameIdx = 1;
|
||||
} else {
|
||||
tableNameIdx = 0;
|
||||
}
|
||||
|
||||
if (header.length < (tableNameIdx + 2)) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Unexpected number of fields (%d) in the 3rd line of [%s] file",
|
||||
header.length, idtFile));
|
||||
}
|
||||
|
||||
var tableName = header[tableNameIdx];
|
||||
List<String> primaryKeys = Arrays.asList(header).subList(tableNameIdx + 1, header.length);
|
||||
|
||||
TKit.trace(String.format("Table in [%s] file: charset=%s; name=%s; primary keys=%s",
|
||||
idtFile, charset, tableName, primaryKeys));
|
||||
|
||||
return new IdtFileHeader(charset, List.of(columns));
|
||||
|
||||
} catch (IOException ex) {
|
||||
|
||||
@ -276,6 +276,11 @@ public class WindowsHelper {
|
||||
return MsiDatabaseCache.INSTANCE.findProperty(cmd.outputBundle(), propertyName).orElseThrow();
|
||||
}
|
||||
|
||||
public static MsiDatabase.UIAlterations getUIAlterations(JPackageCommand cmd) {
|
||||
cmd.verifyIsOfType(PackageType.WIN_MSI);
|
||||
return MsiDatabaseCache.INSTANCE.uiAlterations(cmd.outputBundle());
|
||||
}
|
||||
|
||||
static Collection<MsiDatabase.Shortcut> getMsiShortcuts(JPackageCommand cmd) {
|
||||
cmd.verifyIsOfType(PackageType.WIN_MSI);
|
||||
return MsiDatabaseCache.INSTANCE.listShortcuts(cmd.outputBundle());
|
||||
@ -572,6 +577,10 @@ public class WindowsHelper {
|
||||
return ensureTables(msiPath, MsiDatabase.Table.LIST_SHORTCUTS_REQUIRED_TABLES).listShortcuts();
|
||||
}
|
||||
|
||||
MsiDatabase.UIAlterations uiAlterations(Path msiPath) {
|
||||
return ensureTables(msiPath, MsiDatabase.Table.UI_ALTERATIONS_REQUIRED_TABLES).uiAlterations();
|
||||
}
|
||||
|
||||
MsiDatabase ensureTables(Path msiPath, Set<MsiDatabase.Table> tableNames) {
|
||||
Objects.requireNonNull(msiPath);
|
||||
try {
|
||||
|
||||
@ -203,3 +203,5 @@ Platform dependent options for creating the application package:
|
||||
URL of available application update information
|
||||
--win-upgrade-uuid <uuid>
|
||||
UUID associated with upgrades for this package
|
||||
--win-with-ui
|
||||
Enforces the installer to have UI
|
||||
|
||||
@ -61,3 +61,4 @@
|
||||
| --win-shortcut-prompt | win-exe, win-msi | x | x | | USE_LAST |
|
||||
| --win-update-url | win-exe, win-msi | x | x | | USE_LAST |
|
||||
| --win-upgrade-uuid | win-exe, win-msi | x | x | | USE_LAST |
|
||||
| --win-with-ui | win-exe, win-msi | x | x | | USE_LAST |
|
||||
|
||||
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import static jdk.jpackage.internal.WixToolset.WixToolsetType.Wix4;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import jdk.jpackage.internal.WixToolset.WixToolsetType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
class WixVariablesTest {
|
||||
|
||||
@Test
|
||||
void test_define() {
|
||||
assertEquals(List.of("-d", "foo=yes"), new WixVariables().define("foo").toWixCommandLine(Wix4));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void test_define_null(boolean immutable) {
|
||||
assertThrows(NullPointerException.class, vars -> {
|
||||
vars.define(null);
|
||||
}, create(immutable));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_put() {
|
||||
assertEquals(List.of("-d", "foo=bar"), new WixVariables().put("foo", "bar").toWixCommandLine(Wix4));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void test_put_null(boolean immutable) {
|
||||
assertThrows(NullPointerException.class, vars -> {
|
||||
vars.put("foo", null);
|
||||
}, create(immutable));
|
||||
|
||||
assertThrows(NullPointerException.class, vars -> {
|
||||
vars.put(null, "foo");
|
||||
}, create(immutable));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_putAll() {
|
||||
assertEquals(List.of("-d", "foo=bar"), new WixVariables().putAll(Map.of("foo", "bar")).toWixCommandLine(Wix4));
|
||||
assertEquals(List.of("-d", "foo=yes"), new WixVariables().putAll(new WixVariables().define("foo")).toWixCommandLine(Wix4));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void test_putAll_null(boolean immutable) {
|
||||
|
||||
assertThrows(NullPointerException.class, vars -> {
|
||||
vars.putAll((Map<String, String>)null);
|
||||
}, create(immutable));
|
||||
|
||||
assertThrows(NullPointerException.class, vars -> {
|
||||
vars.putAll((WixVariables)null);
|
||||
}, create(immutable));
|
||||
|
||||
final var expectedExceptionType = immutable ? IllegalStateException.class : NullPointerException.class;
|
||||
|
||||
var other = new HashMap<String, String>();
|
||||
|
||||
other.clear();
|
||||
other.put("foo", null);
|
||||
assertThrows(expectedExceptionType, vars -> {
|
||||
vars.putAll(other);
|
||||
}, create(immutable));
|
||||
|
||||
other.clear();
|
||||
other.put(null, "foo");
|
||||
assertThrows(expectedExceptionType, vars -> {
|
||||
vars.putAll(other);
|
||||
}, create(immutable));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testImmutable() {
|
||||
var vars = new WixVariables().define("foo").createdImmutableCopy();
|
||||
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.putAll(Map.of());
|
||||
}, vars);
|
||||
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.putAll(new WixVariables());
|
||||
}, vars);
|
||||
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.define("foo");
|
||||
}, vars);
|
||||
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.put("foo", "bar");
|
||||
}, vars);
|
||||
|
||||
for (var allowOverrides : List.of(true, false)) {
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.allowOverrides(allowOverrides);
|
||||
}, vars);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultOverridable() {
|
||||
var vars = new WixVariables().define("foo");
|
||||
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.define("foo");
|
||||
}, vars);
|
||||
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.put("foo", "no");
|
||||
}, vars);
|
||||
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.put("foo", "yes");
|
||||
}, vars);
|
||||
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.putAll(Map.of("foo", "A", "bar", "B"));
|
||||
}, vars);
|
||||
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.putAll(new WixVariables().putAll(Map.of("foo", "A", "bar", "B")));
|
||||
}, vars);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testOverridable_define(boolean overridable) {
|
||||
var vars = new WixVariables().allowOverrides(overridable).define("foo");
|
||||
|
||||
if (overridable) {
|
||||
vars.define("foo");
|
||||
} else {
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.define("foo");
|
||||
}, vars);
|
||||
vars.allowOverrides(true);
|
||||
vars.define("foo");
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testOverridable_put(boolean overridable) {
|
||||
var vars = new WixVariables().allowOverrides(overridable).define("foo");
|
||||
|
||||
if (overridable) {
|
||||
vars.put("foo", "bar");
|
||||
assertEquals(List.of("-d", "foo=bar"), vars.toWixCommandLine(Wix4));
|
||||
} else {
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.put("foo", "bar");
|
||||
}, vars);
|
||||
vars.allowOverrides(true);
|
||||
vars.put("foo", "bar");
|
||||
assertEquals(List.of("-d", "foo=bar"), vars.toWixCommandLine(Wix4));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testOverridable_putAll(boolean overridable) {
|
||||
var vars = new WixVariables().allowOverrides(overridable).define("foo");
|
||||
|
||||
var other = Map.of("foo", "A", "bar", "B");
|
||||
|
||||
if (overridable) {
|
||||
vars.putAll(other);
|
||||
assertEquals(List.of("-d", "bar=B", "-d", "foo=A"), vars.toWixCommandLine(Wix4));
|
||||
} else {
|
||||
assertThrows(IllegalStateException.class, _ -> {
|
||||
vars.putAll(other);
|
||||
}, vars);
|
||||
vars.allowOverrides(true);
|
||||
vars.putAll(other);
|
||||
assertEquals(List.of("-d", "bar=B", "-d", "foo=A"), vars.toWixCommandLine(Wix4));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_createdImmutableCopy() {
|
||||
var vars = new WixVariables().define("foo");
|
||||
|
||||
var copy = vars.createdImmutableCopy();
|
||||
|
||||
assertNotSame(vars, copy);
|
||||
|
||||
assertSame(copy, copy.createdImmutableCopy());
|
||||
|
||||
assertEquals(List.of("-d", "foo=yes"), copy.toWixCommandLine(Wix4));
|
||||
|
||||
vars.allowOverrides(true).put("foo", "bar");
|
||||
assertEquals(List.of("-d", "foo=bar"), vars.toWixCommandLine(Wix4));
|
||||
assertEquals(List.of("-d", "foo=yes"), copy.toWixCommandLine(Wix4));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(WixToolsetType.class)
|
||||
void test_toWixCommandLine(WixToolsetType wixType) {
|
||||
var args = new WixVariables().define("foo").put("bar", "a").toWixCommandLine(wixType);
|
||||
|
||||
var expectedArgs = switch (wixType) {
|
||||
case Wix3 -> {
|
||||
yield List.of("-dbar=a", "-dfoo=yes");
|
||||
}
|
||||
case Wix4 -> {
|
||||
yield List.of("-d", "bar=a", "-d", "foo=yes");
|
||||
}
|
||||
};
|
||||
|
||||
assertEquals(expectedArgs, args);
|
||||
}
|
||||
|
||||
private static WixVariables create(boolean immutable) {
|
||||
var vars = new WixVariables();
|
||||
if (immutable) {
|
||||
return vars.createdImmutableCopy();
|
||||
} else {
|
||||
return vars;
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertThrows(
|
||||
Class<? extends RuntimeException> expectedExceptionType, Consumer<WixVariables> mutator, WixVariables vars) {
|
||||
|
||||
var content = vars.toWixCommandLine(Wix4);
|
||||
|
||||
assertThrowsExactly(expectedExceptionType, () -> {
|
||||
mutator.accept(vars);
|
||||
});
|
||||
|
||||
assertEquals(content, vars.toWixCommandLine(Wix4));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.wixui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
class UISpecTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void test(UIConfig cfg) {
|
||||
var uiSpec = UISpec.create(cfg);
|
||||
|
||||
validateCustomDialogSequence(uiSpec.customDialogSequence());
|
||||
}
|
||||
|
||||
private static Collection<UIConfig> test() {
|
||||
|
||||
var testCases = new ArrayList<UIConfig>();
|
||||
|
||||
for (boolean withInstallDirChooserDlg : List.of(true, false)) {
|
||||
for (boolean withShortcutPromptDlg : List.of(true, false)) {
|
||||
for (boolean withLicenseDlg : List.of(true, false)) {
|
||||
testCases.add(UIConfig.build()
|
||||
.withInstallDirChooserDlg(withInstallDirChooserDlg)
|
||||
.withShortcutPromptDlg(withShortcutPromptDlg)
|
||||
.withLicenseDlg(withLicenseDlg)
|
||||
.create());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return testCases;
|
||||
}
|
||||
|
||||
static void validateCustomDialogSequence(Map<DialogPair, Publish> seq) {
|
||||
seq.entrySet().stream().map(DialogControl::new).collect(Collectors.toMap(x -> x, x -> x, (a, b) -> {
|
||||
throw new AssertionError(String.format(
|
||||
"Dialog [%s] has multiple Publish elements associated with [%s] control", a.host(), a.hostedControl()));
|
||||
}));
|
||||
}
|
||||
|
||||
record DialogControl(Dialog host, Control hostedControl) {
|
||||
DialogControl {
|
||||
Objects.requireNonNull(host);
|
||||
Objects.requireNonNull(hostedControl);
|
||||
}
|
||||
|
||||
DialogControl(DialogPair pair, Publish publish) {
|
||||
this(pair.first(), publish.control());
|
||||
}
|
||||
|
||||
DialogControl(Map.Entry<DialogPair, Publish> e) {
|
||||
this(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,3 +28,20 @@
|
||||
* jdk/jpackage/internal/ExecutableOSVersionTest.java
|
||||
* @run junit jdk.jpackage/jdk.jpackage.internal.ExecutableOSVersionTest
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary WixVariables unit tests
|
||||
* @requires (os.family == "windows")
|
||||
* @compile/module=jdk.jpackage -Xlint:all -Werror
|
||||
* jdk/jpackage/internal/WixVariablesTest.java
|
||||
* @run junit jdk.jpackage/jdk.jpackage.internal.WixVariablesTest
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary UiSpec unit tests
|
||||
* @requires (os.family == "windows")
|
||||
* @modules jdk.jpackage/jdk.jpackage.internal.wixui:open
|
||||
* @compile/module=jdk.jpackage -Xlint:all -Werror
|
||||
* jdk/jpackage/internal/wixui/UISpecTest.java
|
||||
* @run junit jdk.jpackage/jdk.jpackage.internal.wixui.UISpecTest
|
||||
*/
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
| Dialog | Control | Event | Argument | Condition | Ordering |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| InstallDirNotEmptyDlg | No | NewDialog | InstallDirDlg | 1 | 1 |
|
||||
| InstallDirNotEmptyDlg | Yes | NewDialog | ShortcutPromptDlg | 1 | 1 |
|
||||
| ShortcutPromptDlg | Back | NewDialog | InstallDirDlg | 1 | 1 |
|
||||
| ShortcutPromptDlg | Cancel | SpawnDialog | CancelDlg | 1 | 1 |
|
||||
| ShortcutPromptDlg | Next | NewDialog | VerifyReadyDlg | 1 | 1 |
|
||||
| VerifyReadyDlg | Back | NewDialog | ShortcutPromptDlg | NOT Installed | 6 |
|
||||
@ -0,0 +1,3 @@
|
||||
| Action | Condition |
|
||||
| --- | --- |
|
||||
| WelcomeDlg | NOT Installed OR PATCH |
|
||||
@ -0,0 +1,4 @@
|
||||
| Dialog | Control | Event | Argument | Condition | Ordering |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| InstallDirNotEmptyDlg | No | NewDialog | InstallDirDlg | 1 | 1 |
|
||||
| InstallDirNotEmptyDlg | Yes | NewDialog | VerifyReadyDlg | 1 | 1 |
|
||||
@ -0,0 +1,3 @@
|
||||
| Action | Condition |
|
||||
| --- | --- |
|
||||
| WelcomeDlg | NOT Installed OR PATCH |
|
||||
@ -0,0 +1,10 @@
|
||||
| Dialog | Control | Event | Argument | Condition | Ordering |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| InstallDirDlg | Back | NewDialog | WelcomeDlg | NOT Installed | 6 |
|
||||
| InstallDirNotEmptyDlg | No | NewDialog | InstallDirDlg | 1 | 1 |
|
||||
| InstallDirNotEmptyDlg | Yes | NewDialog | ShortcutPromptDlg | 1 | 1 |
|
||||
| ShortcutPromptDlg | Back | NewDialog | InstallDirDlg | 1 | 1 |
|
||||
| ShortcutPromptDlg | Cancel | SpawnDialog | CancelDlg | 1 | 1 |
|
||||
| ShortcutPromptDlg | Next | NewDialog | VerifyReadyDlg | 1 | 1 |
|
||||
| VerifyReadyDlg | Back | NewDialog | ShortcutPromptDlg | NOT Installed | 6 |
|
||||
| WelcomeDlg | Next | NewDialog | InstallDirDlg | NOT Installed | 6 |
|
||||
@ -0,0 +1,3 @@
|
||||
| Action | Condition |
|
||||
| --- | --- |
|
||||
| WelcomeDlg | NOT Installed OR PATCH |
|
||||
@ -0,0 +1,6 @@
|
||||
| Dialog | Control | Event | Argument | Condition | Ordering |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| InstallDirDlg | Back | NewDialog | WelcomeDlg | NOT Installed | 6 |
|
||||
| InstallDirNotEmptyDlg | No | NewDialog | InstallDirDlg | 1 | 1 |
|
||||
| InstallDirNotEmptyDlg | Yes | NewDialog | VerifyReadyDlg | 1 | 1 |
|
||||
| WelcomeDlg | Next | NewDialog | InstallDirDlg | NOT Installed | 6 |
|
||||
@ -0,0 +1,3 @@
|
||||
| Action | Condition |
|
||||
| --- | --- |
|
||||
| WelcomeDlg | NOT Installed OR PATCH |
|
||||
@ -0,0 +1,7 @@
|
||||
| Dialog | Control | Event | Argument | Condition | Ordering |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| LicenseAgreementDlg | Next | NewDialog | ShortcutPromptDlg | LicenseAccepted = "1" | 6 |
|
||||
| ShortcutPromptDlg | Back | NewDialog | LicenseAgreementDlg | 1 | 1 |
|
||||
| ShortcutPromptDlg | Cancel | SpawnDialog | CancelDlg | 1 | 1 |
|
||||
| ShortcutPromptDlg | Next | NewDialog | VerifyReadyDlg | 1 | 1 |
|
||||
| VerifyReadyDlg | Back | NewDialog | ShortcutPromptDlg | NOT Installed | 6 |
|
||||
@ -0,0 +1,3 @@
|
||||
| Action | Condition |
|
||||
| --- | --- |
|
||||
| WelcomeDlg | NOT Installed OR PATCH |
|
||||
@ -0,0 +1,2 @@
|
||||
| Dialog | Control | Event | Argument | Condition | Ordering |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
@ -0,0 +1,4 @@
|
||||
| Action | Condition |
|
||||
| --- | --- |
|
||||
| WelcomeDlg | Installed AND PATCH |
|
||||
| WelcomeEulaDlg | NOT Installed |
|
||||
@ -0,0 +1,7 @@
|
||||
| Dialog | Control | Event | Argument | Condition | Ordering |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| ShortcutPromptDlg | Back | NewDialog | WelcomeDlg | NOT Installed | 6 |
|
||||
| ShortcutPromptDlg | Cancel | SpawnDialog | CancelDlg | 1 | 1 |
|
||||
| ShortcutPromptDlg | Next | NewDialog | VerifyReadyDlg | 1 | 1 |
|
||||
| VerifyReadyDlg | Back | NewDialog | ShortcutPromptDlg | NOT Installed | 6 |
|
||||
| WelcomeDlg | Next | NewDialog | ShortcutPromptDlg | NOT Installed | 6 |
|
||||
@ -0,0 +1,3 @@
|
||||
| Action | Condition |
|
||||
| --- | --- |
|
||||
| WelcomeDlg | NOT Installed OR PATCH |
|
||||
@ -0,0 +1,2 @@
|
||||
| Dialog | Control | Event | Argument | Condition | Ordering |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
@ -0,0 +1,4 @@
|
||||
| Action | Condition |
|
||||
| --- | --- |
|
||||
| WelcomeDlg | Installed AND PATCH |
|
||||
| WelcomeEulaDlg | 0 |
|
||||
@ -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
|
||||
@ -71,7 +71,7 @@ function exportTables(db, outputDir, requestedTableNames) {
|
||||
var msi = WScript.arguments(0)
|
||||
var outputDir = WScript.arguments(1)
|
||||
var tables = {}
|
||||
for (var i = 0; i !== WScript.arguments.Count(); i++) {
|
||||
for (var i = 2; i !== WScript.arguments.Count(); i++) {
|
||||
tables[WScript.arguments(i)] = true
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -21,114 +21,299 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.util.Slot;
|
||||
import jdk.jpackage.test.Annotations.Parameters;
|
||||
import jdk.jpackage.test.Annotations.ParameterSupplier;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
import jdk.jpackage.test.MsiDatabase;
|
||||
import jdk.jpackage.test.MsiDatabase.UIAlterations;
|
||||
import jdk.jpackage.test.MsiDatabase.ControlEvent;
|
||||
import jdk.jpackage.test.PackageTest;
|
||||
import jdk.jpackage.test.PackageType;
|
||||
import jdk.jpackage.test.RunnablePackageTest.Action;
|
||||
import jdk.jpackage.test.TKit;
|
||||
import jdk.jpackage.test.WindowsHelper;
|
||||
|
||||
/**
|
||||
* Test all possible combinations of --win-dir-chooser, --win-shortcut-prompt
|
||||
* Test combinations of --win-dir-chooser, --win-shortcut-prompt, --win-with-ui,
|
||||
* and --license parameters.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary jpackage with --win-dir-chooser, --win-shortcut-prompt and --license parameters
|
||||
* @summary jpackage with --win-dir-chooser, --win-shortcut-prompt, --with-with-ui and --license parameters
|
||||
* @library /test/jdk/tools/jpackage/helpers
|
||||
* @key jpackagePlatformPackage
|
||||
* @build jdk.jpackage.test.*
|
||||
* @build WinInstallerUiTest
|
||||
* @requires (os.family == "windows")
|
||||
* @requires (jpackage.test.SQETest != null)
|
||||
* @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
|
||||
* --jpt-run=WinInstallerUiTest
|
||||
* --jpt-exclude=(dir_chooser)
|
||||
* --jpt-exclude=(license)
|
||||
* --jpt-exclude=+ui
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary jpackage with --win-dir-chooser, --win-shortcut-prompt, --with-with-ui and --license parameters
|
||||
* @library /test/jdk/tools/jpackage/helpers
|
||||
* @key jpackagePlatformPackage
|
||||
* @build jdk.jpackage.test.*
|
||||
* @build WinInstallerUiTest
|
||||
* @requires (os.family == "windows")
|
||||
* @requires (jpackage.test.SQETest == null)
|
||||
* @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
|
||||
* --jpt-run=WinInstallerUiTest
|
||||
*/
|
||||
|
||||
public class WinInstallerUiTest {
|
||||
|
||||
public WinInstallerUiTest(Boolean withDirChooser, Boolean withLicense,
|
||||
Boolean withShortcutPrompt) {
|
||||
this.withShortcutPrompt = withShortcutPrompt;
|
||||
this.withDirChooser = withDirChooser;
|
||||
this.withLicense = withLicense;
|
||||
@Test
|
||||
@ParameterSupplier
|
||||
public void test(TestSpec spec) {
|
||||
spec.run();
|
||||
}
|
||||
|
||||
@Parameters
|
||||
public static List<Object[]> data() {
|
||||
List<Object[]> data = new ArrayList<>();
|
||||
for (var withDirChooser : List.of(Boolean.TRUE, Boolean.FALSE)) {
|
||||
for (var withLicense : List.of(Boolean.TRUE, Boolean.FALSE)) {
|
||||
for (var withShortcutPrompt : List.of(Boolean.TRUE, Boolean.FALSE)) {
|
||||
public static void updateExpectedMsiTables() {
|
||||
for (var spec : testCases()) {
|
||||
spec.createTest(true).addBundleVerifier(cmd -> {
|
||||
spec.save(WindowsHelper.getUIAlterations(cmd));
|
||||
}).run(Action.CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
record TestSpec(
|
||||
boolean withDirChooser,
|
||||
boolean withLicense,
|
||||
boolean withShortcutPrompt,
|
||||
boolean withUi) {
|
||||
|
||||
TestSpec {
|
||||
if (!withDirChooser && !withLicense && !withShortcutPrompt && !withUi) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var tokens = new ArrayList<String>();
|
||||
if (withDirChooser) {
|
||||
tokens.add("dir_chooser");
|
||||
}
|
||||
|
||||
if (withShortcutPrompt) {
|
||||
tokens.add("shortcut_prompt");
|
||||
}
|
||||
|
||||
if (withLicense) {
|
||||
tokens.add("license");
|
||||
}
|
||||
|
||||
if (withUi) {
|
||||
tokens.add("ui");
|
||||
}
|
||||
|
||||
return tokens.stream().sorted().collect(Collectors.joining("+"));
|
||||
}
|
||||
|
||||
TestSpec copyWithUi(boolean withUi) {
|
||||
return new TestSpec(withDirChooser, withLicense, withShortcutPrompt, withUi);
|
||||
}
|
||||
|
||||
TestSpec copyWithUi() {
|
||||
return copyWithUi(true);
|
||||
}
|
||||
|
||||
TestSpec copyWithoutUi() {
|
||||
return copyWithUi(false);
|
||||
}
|
||||
|
||||
void run() {
|
||||
createTest(false).forTypes(PackageType.WIN_MSI).addBundleVerifier(cmd -> {
|
||||
var expectedFilesDir = expectedFilesDir();
|
||||
|
||||
var expectedInstallUISequence = Files.readAllLines(expectedFilesDir.resolve(INSTALL_UI_SEQUENCE_FILE));
|
||||
var expectedControlEvents = Files.readAllLines(expectedFilesDir.resolve(CONTROL_EVENTS_FILE));
|
||||
|
||||
var uiAlterations = WindowsHelper.getUIAlterations(cmd);
|
||||
|
||||
var actualInstallUISequence = actionSequenceToMarkdownTable(uiAlterations.installUISequence());
|
||||
var actualControlEvents = controlEventsToMarkdownTable(uiAlterations.controlEvents());
|
||||
|
||||
TKit.assertStringListEquals(expectedInstallUISequence, actualInstallUISequence,
|
||||
String.format("Check alterations to the `InstallUISequence` MSI table match the contents of [%s] file",
|
||||
expectedFilesDir.resolve(INSTALL_UI_SEQUENCE_FILE)));
|
||||
|
||||
TKit.assertStringListEquals(expectedControlEvents, actualControlEvents,
|
||||
String.format("Check alterations to the `ControlEvents` MSI table match the contents of [%s] file",
|
||||
expectedFilesDir.resolve(CONTROL_EVENTS_FILE)));
|
||||
}).run();
|
||||
}
|
||||
|
||||
PackageTest createTest(boolean onlyMsi) {
|
||||
return new PackageTest()
|
||||
.forTypes(onlyMsi ? Set.of(PackageType.WIN_MSI) : PackageType.WINDOWS)
|
||||
.configureHelloApp()
|
||||
.addInitializer(JPackageCommand::setFakeRuntime)
|
||||
.addInitializer(this::setPackageName)
|
||||
.mutate(test -> {
|
||||
if (withDirChooser) {
|
||||
test.addInitializer(cmd -> cmd.addArgument("--win-dir-chooser"));
|
||||
}
|
||||
|
||||
if (withShortcutPrompt) {
|
||||
test.addInitializer(cmd -> {
|
||||
cmd.addArgument("--win-shortcut-prompt");
|
||||
cmd.addArgument("--win-menu");
|
||||
cmd.addArgument("--win-shortcut");
|
||||
});
|
||||
}
|
||||
|
||||
if (withLicense) {
|
||||
setLicenseFile(test);
|
||||
}
|
||||
|
||||
if (withUi) {
|
||||
test.addInitializer(cmd -> cmd.addArgument("--win-with-ui"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setPackageName(JPackageCommand cmd) {
|
||||
StringBuilder sb = new StringBuilder(cmd.name());
|
||||
sb.append("With");
|
||||
if (withDirChooser) {
|
||||
sb.append("Dc"); // DirChooser
|
||||
}
|
||||
if (withShortcutPrompt) {
|
||||
sb.append("Sp"); // ShortcutPrompt
|
||||
}
|
||||
if (withLicense) {
|
||||
sb.append("L"); // License
|
||||
}
|
||||
if (withUi) {
|
||||
sb.append("Ui"); // UI
|
||||
}
|
||||
cmd.setArgumentValue("--name", sb.toString());
|
||||
}
|
||||
|
||||
void save(UIAlterations uiAlterations) {
|
||||
var expectedFilesDir = expectedFilesDir();
|
||||
|
||||
write(expectedFilesDir.resolve(INSTALL_UI_SEQUENCE_FILE),
|
||||
actionSequenceToMarkdownTable(uiAlterations.installUISequence()));
|
||||
|
||||
write(expectedFilesDir.resolve(CONTROL_EVENTS_FILE),
|
||||
controlEventsToMarkdownTable(uiAlterations.controlEvents()));
|
||||
}
|
||||
|
||||
private Path expectedFilesDir() {
|
||||
if ((withDirChooser || withShortcutPrompt || withLicense) && withUi) {
|
||||
return copyWithoutUi().expectedFilesDir();
|
||||
} else {
|
||||
return EXPECTED_MSI_TABLES_ROOT.resolve(toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void write(Path file, List<String> lines) {
|
||||
try {
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.write(file, lines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> toMarkdownTable(List<String> header, Stream<String[]> content) {
|
||||
return Stream.of(
|
||||
Stream.<String[]>of(header.toArray(String[]::new)),
|
||||
Stream.<String[]>of(Collections.nCopies(header.size(), "---").toArray(String[]::new)),
|
||||
content
|
||||
).flatMap(x -> x).map(row -> {
|
||||
return Stream.of(row).map(v -> {
|
||||
// Escape the pipe (|) character.
|
||||
return v.replaceAll(Pattern.quote("|"), "|");
|
||||
}).collect(Collectors.joining(" | ", "| ", " |"));
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private static List<String> actionSequenceToMarkdownTable(Collection<MsiDatabase.Action> actions) {
|
||||
return toMarkdownTable(
|
||||
List.of("Action", "Condition"),
|
||||
actions.stream().map(action -> {
|
||||
return toStringArray(action.action(), action.condition());
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private static List<String> controlEventsToMarkdownTable(Collection<ControlEvent> controlEvents) {
|
||||
return toMarkdownTable(
|
||||
List.of("Dialog", "Control", "Event", "Argument", "Condition", "Ordering"),
|
||||
controlEvents.stream().map(controlEvent -> {
|
||||
return toStringArray(
|
||||
controlEvent.dialog(),
|
||||
controlEvent.control(),
|
||||
controlEvent.event(),
|
||||
controlEvent.argument(),
|
||||
controlEvent.condition(),
|
||||
Integer.toString(controlEvent.ordering()));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private static String[] toStringArray(String... items) {
|
||||
return items;
|
||||
}
|
||||
|
||||
private static final String CONTROL_EVENTS_FILE = "ControlEvents.md";
|
||||
private static final String INSTALL_UI_SEQUENCE_FILE = "InstallUISequence.md";
|
||||
}
|
||||
|
||||
public static Collection<?> test() {
|
||||
return Stream.concat(
|
||||
testCases().stream().filter(Predicate.not(TestSpec::withUi)).map(TestSpec::copyWithUi),
|
||||
testCases().stream()
|
||||
).map(v -> {
|
||||
return new Object[] {v};
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private static Collection<TestSpec> testCases() {
|
||||
var testCases = new ArrayList<TestSpec>();
|
||||
|
||||
for (var withDirChooser : List.of(true, false)) {
|
||||
for (var withLicense : List.of(true, false)) {
|
||||
for (var withShortcutPrompt : List.of(true, false)) {
|
||||
if (!withDirChooser && !withLicense && !withShortcutPrompt) {
|
||||
// Duplicates SimplePackageTest
|
||||
continue;
|
||||
}
|
||||
|
||||
if (withDirChooser && !withLicense && !withShortcutPrompt) {
|
||||
// Duplicates WinDirChooserTest
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!withDirChooser && withLicense && !withShortcutPrompt) {
|
||||
// Duplicates LicenseTest
|
||||
continue;
|
||||
}
|
||||
|
||||
data.add(new Object[]{withDirChooser, withLicense,
|
||||
withShortcutPrompt});
|
||||
testCases.add(new TestSpec(withDirChooser, withLicense, withShortcutPrompt, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
// Enforce UI
|
||||
testCases.add(new TestSpec(false, false, false, true));
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
PackageTest test = new PackageTest()
|
||||
.forTypes(PackageType.WINDOWS)
|
||||
.configureHelloApp();
|
||||
|
||||
test.addInitializer(JPackageCommand::setFakeRuntime);
|
||||
test.addInitializer(this::setPackageName);
|
||||
|
||||
if (withDirChooser) {
|
||||
test.addInitializer(cmd -> cmd.addArgument("--win-dir-chooser"));
|
||||
}
|
||||
|
||||
if (withShortcutPrompt) {
|
||||
test.addInitializer(cmd -> {
|
||||
cmd.addArgument("--win-shortcut-prompt");
|
||||
cmd.addArgument("--win-menu");
|
||||
cmd.addArgument("--win-shortcut");
|
||||
});
|
||||
}
|
||||
|
||||
if (withLicense) {
|
||||
setLicenseFile(test);
|
||||
}
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
private void setPackageName(JPackageCommand cmd) {
|
||||
StringBuilder sb = new StringBuilder(cmd.name());
|
||||
sb.append("With");
|
||||
if (withDirChooser) {
|
||||
sb.append("Dc"); // DirChooser
|
||||
}
|
||||
if (withShortcutPrompt) {
|
||||
sb.append("Sp"); // ShortcutPrompt
|
||||
}
|
||||
if (withLicense) {
|
||||
sb.append("L"); // License
|
||||
}
|
||||
cmd.setArgumentValue("--name", sb.toString());
|
||||
return testCases;
|
||||
}
|
||||
|
||||
private static void setLicenseFile(PackageTest test) {
|
||||
@ -143,9 +328,8 @@ public class WinInstallerUiTest {
|
||||
});
|
||||
}
|
||||
|
||||
private final boolean withDirChooser;
|
||||
private final boolean withLicense;
|
||||
private final boolean withShortcutPrompt;
|
||||
|
||||
private static final Path LICENSE_FILE = TKit.TEST_SRC_ROOT.resolve(Path.of("resources", "license.txt"));
|
||||
|
||||
private static final Path EXPECTED_MSI_TABLES_ROOT = TKit.TEST_SRC_ROOT.resolve(
|
||||
Path.of("resources", WinInstallerUiTest.class.getSimpleName()));
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -21,22 +21,22 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import static jdk.jpackage.test.WindowsHelper.getWixTypeFromVerboseJPackageOutput;
|
||||
import static jdk.jpackage.test.WindowsHelper.WixType.WIX3;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import jdk.jpackage.test.TKit;
|
||||
import jdk.jpackage.test.PackageTest;
|
||||
import jdk.jpackage.test.PackageType;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.Annotations.Parameters;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.test.Annotations.Parameters;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.Executor;
|
||||
import static jdk.jpackage.test.WindowsHelper.WixType.WIX3;
|
||||
import static jdk.jpackage.test.WindowsHelper.getWixTypeFromVerboseJPackageOutput;
|
||||
import jdk.jpackage.test.PackageTest;
|
||||
import jdk.jpackage.test.PackageType;
|
||||
import jdk.jpackage.test.TKit;
|
||||
|
||||
/*
|
||||
* @test
|
||||
@ -124,8 +124,17 @@ public class WinL10nTest {
|
||||
var toolFileName = wixToolName + ".exe";
|
||||
return (s) -> {
|
||||
s = s.trim();
|
||||
return s.startsWith(toolFileName) || ((s.contains(String.format("\\%s ", toolFileName)) && s.
|
||||
contains(" -out ")));
|
||||
|
||||
if (s.startsWith(toolFileName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Accommodate for:
|
||||
// 'C:\Program Files (x86)\WiX Toolset v3.14\bin\light.exe' ...
|
||||
// light.exe ...
|
||||
return Stream.of("\\%s ", "\\%s' ").map(format -> {
|
||||
return String.format(format, toolFileName);
|
||||
}).anyMatch(s::contains) && s.contains(" -out ");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user