8319457: Update jpackage to support WiX v4 and v5 on Windows
Reviewed-by: almatvee
This commit is contained in:
parent
2c9185eb81
commit
ba67ad63ae
@ -27,6 +27,6 @@ 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 .ico .bmp .tiff .service
|
||||
.icns .scpt .wxs .wxl .wxi .ico .bmp .tiff .service .xsl
|
||||
|
||||
CLEAN += .properties
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012, 2024, 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
|
||||
@ -67,6 +67,7 @@ import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.TEMP_ROOT;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.VENDOR;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.VERSION;
|
||||
import jdk.jpackage.internal.WixToolset.WixToolsetType;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
@ -253,7 +254,7 @@ public class WinMsiBundler extends AbstractBundler {
|
||||
public boolean supported(boolean platformInstaller) {
|
||||
try {
|
||||
if (wixToolset == null) {
|
||||
wixToolset = WixTool.toolset();
|
||||
wixToolset = WixTool.createToolset();
|
||||
}
|
||||
return true;
|
||||
} catch (ConfigException ce) {
|
||||
@ -300,7 +301,7 @@ public class WinMsiBundler extends AbstractBundler {
|
||||
appImageBundler.validate(params);
|
||||
|
||||
if (wixToolset == null) {
|
||||
wixToolset = WixTool.toolset();
|
||||
wixToolset = WixTool.createToolset();
|
||||
}
|
||||
|
||||
try {
|
||||
@ -309,16 +310,17 @@ public class WinMsiBundler extends AbstractBundler {
|
||||
throw new ConfigException(ex);
|
||||
}
|
||||
|
||||
for (var toolInfo: wixToolset.values()) {
|
||||
for (var tool : wixToolset.getType().getTools()) {
|
||||
Log.verbose(MessageFormat.format(I18N.getString(
|
||||
"message.tool-version"), toolInfo.path.getFileName(),
|
||||
toolInfo.version));
|
||||
"message.tool-version"), wixToolset.getToolPath(tool).
|
||||
getFileName(), wixToolset.getVersion()));
|
||||
}
|
||||
|
||||
wixFragments.forEach(wixFragment -> wixFragment.setWixVersion(
|
||||
wixToolset.get(WixTool.Light).version));
|
||||
wixFragments.forEach(wixFragment -> wixFragment.setWixVersion(wixToolset.getVersion(),
|
||||
wixToolset.getType()));
|
||||
|
||||
wixFragments.get(0).logWixFeatures();
|
||||
wixFragments.stream().map(WixFragmentBuilder::getLoggableWixFeatures).flatMap(
|
||||
List::stream).distinct().toList().forEach(Log::verbose);
|
||||
|
||||
/********* validate bundle parameters *************/
|
||||
|
||||
@ -512,22 +514,6 @@ public class WinMsiBundler extends AbstractBundler {
|
||||
data.put("JpIsSystemWide", "yes");
|
||||
}
|
||||
|
||||
// Copy standard l10n files.
|
||||
for (String loc : Arrays.asList("de", "en", "ja", "zh_CN")) {
|
||||
String fname = "MsiInstallerStrings_" + loc + ".wxl";
|
||||
createResource(fname, params)
|
||||
.setCategory(I18N.getString("resource.wxl-file"))
|
||||
.saveToFile(configDir.resolve(fname));
|
||||
}
|
||||
|
||||
createResource("main.wxs", params)
|
||||
.setCategory(I18N.getString("resource.main-wix-file"))
|
||||
.saveToFile(configDir.resolve("main.wxs"));
|
||||
|
||||
createResource("overrides.wxi", params)
|
||||
.setCategory(I18N.getString("resource.overrides-wix-file"))
|
||||
.saveToFile(configDir.resolve("overrides.wxi"));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@ -542,13 +528,11 @@ public class WinMsiBundler extends AbstractBundler {
|
||||
.toString()));
|
||||
|
||||
WixPipeline wixPipeline = new WixPipeline()
|
||||
.setToolset(wixToolset.entrySet().stream().collect(
|
||||
Collectors.toMap(
|
||||
entry -> entry.getKey(),
|
||||
entry -> entry.getValue().path)))
|
||||
.setToolset(wixToolset)
|
||||
.setWixObjDir(TEMP_ROOT.fetchFrom(params).resolve("wixobj"))
|
||||
.setWorkDir(WIN_APP_IMAGE.fetchFrom(params))
|
||||
.addSource(CONFIG_ROOT.fetchFrom(params).resolve("main.wxs"), wixVars);
|
||||
.addSource(CONFIG_ROOT.fetchFrom(params).resolve("main.wxs"),
|
||||
wixVars);
|
||||
|
||||
for (var wixFragment : wixFragments) {
|
||||
wixFragment.configureWixPipeline(wixPipeline);
|
||||
@ -557,16 +541,46 @@ public class WinMsiBundler extends AbstractBundler {
|
||||
Log.verbose(MessageFormat.format(I18N.getString(
|
||||
"message.generating-msi"), msiOut.toAbsolutePath().toString()));
|
||||
|
||||
switch (wixToolset.getType()) {
|
||||
case Wix3 -> {
|
||||
wixPipeline.addLightOptions("-sice:ICE27");
|
||||
|
||||
if (!MSI_SYSTEM_WIDE.fetchFrom(params)) {
|
||||
wixPipeline.addLightOptions("-sice:ICE91");
|
||||
}
|
||||
}
|
||||
case Wix4 -> {
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
final Path configDir = CONFIG_ROOT.fetchFrom(params);
|
||||
|
||||
var primaryWxlFiles = Stream.of("de", "en", "ja", "zh_CN").map(loc -> {
|
||||
return configDir.resolve("MsiInstallerStrings_" + loc + ".wxl");
|
||||
}).toList();
|
||||
|
||||
var wixResources = new WixSourceConverter.ResourceGroup(wixToolset.getType());
|
||||
|
||||
// Copy standard l10n files.
|
||||
for (var path : primaryWxlFiles) {
|
||||
var name = path.getFileName().toString();
|
||||
wixResources.addResource(createResource(name, params).setPublicName(name).setCategory(
|
||||
I18N.getString("resource.wxl-file")), path);
|
||||
}
|
||||
|
||||
wixResources.addResource(createResource("main.wxs", params).setPublicName("main.wxs").
|
||||
setCategory(I18N.getString("resource.main-wix-file")), configDir.resolve("main.wxs"));
|
||||
|
||||
wixResources.addResource(createResource("overrides.wxi", params).setPublicName(
|
||||
"overrides.wxi").setCategory(I18N.getString("resource.overrides-wix-file")),
|
||||
configDir.resolve("overrides.wxi"));
|
||||
|
||||
// Filter out custom l10n files that were already used to
|
||||
// override primary l10n files. Ignore case filename comparison,
|
||||
// both lists are expected to be short.
|
||||
List<Path> primaryWxlFiles = getWxlFilesFromDir(params, CONFIG_ROOT);
|
||||
List<Path> customWxlFiles = getWxlFilesFromDir(params, RESOURCE_DIR).stream()
|
||||
.filter(custom -> primaryWxlFiles.stream().noneMatch(primary ->
|
||||
primary.getFileName().toString().equalsIgnoreCase(
|
||||
@ -577,6 +591,17 @@ public class WinMsiBundler extends AbstractBundler {
|
||||
custom.getFileName().toString())))
|
||||
.toList();
|
||||
|
||||
// Copy custom l10n files.
|
||||
for (var path : customWxlFiles) {
|
||||
var name = path.getFileName().toString();
|
||||
wixResources.addResource(createResource(name, params).setPublicName(name).
|
||||
setSourceOrder(OverridableResource.Source.ResourceDir).setCategory(I18N.
|
||||
getString("resource.wxl-file")), configDir.resolve(name));
|
||||
}
|
||||
|
||||
// Save all WiX resources into config dir.
|
||||
wixResources.saveResources();
|
||||
|
||||
// All l10n files are supplied to WiX with "-loc", but only
|
||||
// Cultures from custom files and a single primary Culture are
|
||||
// included into "-cultures" list
|
||||
@ -586,6 +611,7 @@ public class WinMsiBundler extends AbstractBundler {
|
||||
|
||||
List<String> cultures = new ArrayList<>();
|
||||
for (var wxl : customWxlFiles) {
|
||||
wxl = configDir.resolve(wxl.getFileName());
|
||||
wixPipeline.addLightOptions("-loc", wxl.toAbsolutePath().normalize().toString());
|
||||
cultures.add(getCultureFromWxlFile(wxl));
|
||||
}
|
||||
@ -598,8 +624,20 @@ public class WinMsiBundler extends AbstractBundler {
|
||||
// Build ordered list of unique cultures.
|
||||
Set<String> uniqueCultures = new LinkedHashSet<>();
|
||||
uniqueCultures.addAll(cultures);
|
||||
wixPipeline.addLightOptions(uniqueCultures.stream().collect(
|
||||
Collectors.joining(";", "-cultures:", "")));
|
||||
switch (wixToolset.getType()) {
|
||||
case Wix3 -> {
|
||||
wixPipeline.addLightOptions(uniqueCultures.stream().collect(Collectors.joining(";",
|
||||
"-cultures:", "")));
|
||||
}
|
||||
case Wix4 -> {
|
||||
uniqueCultures.forEach(culture -> {
|
||||
wixPipeline.addLightOptions("-culture", culture);
|
||||
});
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
wixPipeline.buildMsi(msiOut.toAbsolutePath());
|
||||
|
||||
@ -751,7 +789,7 @@ public class WinMsiBundler extends AbstractBundler {
|
||||
}
|
||||
|
||||
private Path installerIcon;
|
||||
private Map<WixTool, WixTool.ToolInfo> wixToolset;
|
||||
private WixToolset wixToolset;
|
||||
private AppImageBundler appImageBundler;
|
||||
private final List<WixFragmentBuilder> wixFragments;
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ import static jdk.jpackage.internal.StandardBundlerParam.VERSION;
|
||||
import static jdk.jpackage.internal.WinMsiBundler.MSI_SYSTEM_WIDE;
|
||||
import static jdk.jpackage.internal.WinMsiBundler.SERVICE_INSTALLER;
|
||||
import static jdk.jpackage.internal.WinMsiBundler.WIN_APP_IMAGE;
|
||||
import jdk.jpackage.internal.WixToolset.WixToolsetType;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
/**
|
||||
@ -152,6 +153,16 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
|
||||
super.addFilesToConfigRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
List<String> getLoggableWixFeatures() {
|
||||
if (isWithWix36Features()) {
|
||||
return List.of(MessageFormat.format(I18N.getString("message.use-wix36-features"),
|
||||
getWixVersion()));
|
||||
} else {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<XmlConsumer> getFragmentWriters() {
|
||||
return List.of(
|
||||
@ -314,13 +325,26 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
|
||||
return cfg.isFile;
|
||||
}
|
||||
|
||||
static void startElement(XMLStreamWriter xml, String componentId,
|
||||
static void startElement(WixToolsetType wixType, XMLStreamWriter xml, String componentId,
|
||||
String componentGuid) throws XMLStreamException, IOException {
|
||||
xml.writeStartElement("Component");
|
||||
switch (wixType) {
|
||||
case Wix3 -> {
|
||||
xml.writeAttribute("Win64", is64Bit() ? "yes" : "no");
|
||||
xml.writeAttribute("Id", componentId);
|
||||
xml.writeAttribute("Guid", componentGuid);
|
||||
}
|
||||
case Wix4 -> {
|
||||
xml.writeAttribute("Bitness", is64Bit() ? "always64" : "always32");
|
||||
if (!componentGuid.equals("*")) {
|
||||
xml.writeAttribute("Guid", componentGuid);
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
xml.writeAttribute("Id", componentId);
|
||||
}
|
||||
|
||||
private static final class Config {
|
||||
Config withRegistryKeyPath() {
|
||||
@ -370,23 +394,32 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
|
||||
directoryRefPath = path;
|
||||
}
|
||||
|
||||
xml.writeStartElement("DirectoryRef");
|
||||
xml.writeAttribute("Id", Id.Folder.of(directoryRefPath));
|
||||
startDirectoryElement(xml, "DirectoryRef", directoryRefPath);
|
||||
|
||||
final String componentId = "c" + role.idOf(path);
|
||||
Component.startElement(xml, componentId, String.format("{%s}",
|
||||
role.guidOf(path)));
|
||||
Component.startElement(getWixType(), xml, componentId, String.format(
|
||||
"{%s}", role.guidOf(path)));
|
||||
|
||||
if (role == Component.Shortcut) {
|
||||
xml.writeStartElement("Condition");
|
||||
String property = shortcutFolders.stream().filter(shortcutFolder -> {
|
||||
return path.startsWith(shortcutFolder.root);
|
||||
}).map(shortcutFolder -> {
|
||||
return shortcutFolder.property;
|
||||
}).findFirst().get();
|
||||
switch (getWixType()) {
|
||||
case Wix3 -> {
|
||||
xml.writeStartElement("Condition");
|
||||
xml.writeCharacters(property);
|
||||
xml.writeEndElement();
|
||||
}
|
||||
case Wix4 -> {
|
||||
xml.writeAttribute("Condition", property);
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isRegistryKeyPath = !systemWide || role.isRegistryKeyPath();
|
||||
if (isRegistryKeyPath) {
|
||||
@ -442,7 +475,7 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
|
||||
private void addShortcutComponentGroup(XMLStreamWriter xml) throws
|
||||
XMLStreamException, IOException {
|
||||
List<String> componentIds = new ArrayList<>();
|
||||
Set<ShortcutsFolder> defineShortcutFolders = new HashSet<>();
|
||||
Set<Path> defineShortcutFolders = new HashSet<>();
|
||||
for (var launcher : launchers) {
|
||||
for (var folder : shortcutFolders) {
|
||||
Path launcherPath = addExeSuffixToPath(installedAppImage
|
||||
@ -457,16 +490,27 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
|
||||
folder);
|
||||
|
||||
if (componentId != null) {
|
||||
defineShortcutFolders.add(folder);
|
||||
Path folderPath = folder.getPath(this);
|
||||
boolean defineFolder;
|
||||
switch (getWixType()) {
|
||||
case Wix3 ->
|
||||
defineFolder = true;
|
||||
case Wix4 ->
|
||||
defineFolder = !SYSTEM_DIRS.contains(folderPath);
|
||||
default ->
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (defineFolder) {
|
||||
defineShortcutFolders.add(folderPath);
|
||||
}
|
||||
componentIds.add(componentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var folder : defineShortcutFolders) {
|
||||
Path path = folder.getPath(this);
|
||||
componentIds.addAll(addRootBranch(xml, path));
|
||||
for (var folderPath : defineShortcutFolders) {
|
||||
componentIds.addAll(addRootBranch(xml, folderPath));
|
||||
}
|
||||
|
||||
addComponentGroup(xml, "Shortcuts", componentIds);
|
||||
@ -546,13 +590,18 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
|
||||
throw throwInvalidPathException(path);
|
||||
}
|
||||
|
||||
Function<Path, String> createDirectoryName = dir -> null;
|
||||
|
||||
boolean sysDir = true;
|
||||
int levels = 1;
|
||||
int levels;
|
||||
var dirIt = path.iterator();
|
||||
|
||||
if (getWixType() != WixToolsetType.Wix3 && TARGETDIR.equals(path.getName(0))) {
|
||||
levels = 0;
|
||||
dirIt.next();
|
||||
} else {
|
||||
levels = 1;
|
||||
xml.writeStartElement("DirectoryRef");
|
||||
xml.writeAttribute("Id", dirIt.next().toString());
|
||||
}
|
||||
|
||||
path = path.getName(0);
|
||||
while (dirIt.hasNext()) {
|
||||
@ -562,21 +611,11 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
|
||||
|
||||
if (sysDir && !SYSTEM_DIRS.contains(path)) {
|
||||
sysDir = false;
|
||||
createDirectoryName = dir -> dir.getFileName().toString();
|
||||
}
|
||||
|
||||
final String directoryId;
|
||||
if (!sysDir && path.equals(installDir)) {
|
||||
directoryId = INSTALLDIR.toString();
|
||||
} else {
|
||||
directoryId = Id.Folder.of(path);
|
||||
}
|
||||
xml.writeStartElement("Directory");
|
||||
xml.writeAttribute("Id", directoryId);
|
||||
|
||||
String directoryName = createDirectoryName.apply(path);
|
||||
if (directoryName != null) {
|
||||
xml.writeAttribute("Name", directoryName);
|
||||
startDirectoryElement(xml, "Directory", path);
|
||||
if (!sysDir) {
|
||||
xml.writeAttribute("Name", path.getFileName().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -584,9 +623,37 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
List<String> componentIds = new ArrayList<>();
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return componentIds;
|
||||
private void startDirectoryElement(XMLStreamWriter xml, String wix3ElementName, Path path) throws XMLStreamException {
|
||||
final String elementName;
|
||||
switch (getWixType()) {
|
||||
case Wix3 -> {
|
||||
elementName = wix3ElementName;
|
||||
}
|
||||
case Wix4 -> {
|
||||
if (SYSTEM_DIRS.contains(path)) {
|
||||
elementName = "StandardDirectory";
|
||||
} else {
|
||||
elementName = wix3ElementName;
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final String directoryId;
|
||||
if (path.equals(installDir)) {
|
||||
directoryId = INSTALLDIR.toString();
|
||||
} else {
|
||||
directoryId = Id.Folder.of(path);
|
||||
}
|
||||
|
||||
xml.writeStartElement(elementName);
|
||||
xml.writeAttribute("Id", directoryId);
|
||||
}
|
||||
|
||||
private String addRemoveDirectoryComponent(XMLStreamWriter xml, Path path)
|
||||
@ -785,7 +852,7 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
|
||||
xml.writeStartElement("RegistryKey");
|
||||
xml.writeAttribute("Root", regRoot);
|
||||
xml.writeAttribute("Key", registryKeyPath);
|
||||
if (DottedVersion.compareComponents(getWixVersion(), DottedVersion.lazy("3.6")) < 0) {
|
||||
if (!isWithWix36Features()) {
|
||||
xml.writeAttribute("Action", "createAndRemoveOnUninstall");
|
||||
}
|
||||
xml.writeStartElement("RegistryValue");
|
||||
@ -799,7 +866,7 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
|
||||
|
||||
private String addDirectoryCleaner(XMLStreamWriter xml, Path path) throws
|
||||
XMLStreamException, IOException {
|
||||
if (DottedVersion.compareComponents(getWixVersion(), DottedVersion.lazy("3.6")) < 0) {
|
||||
if (!isWithWix36Features()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -821,14 +888,13 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
|
||||
|
||||
xml.writeStartElement("DirectoryRef");
|
||||
xml.writeAttribute("Id", INSTALLDIR.toString());
|
||||
Component.startElement(xml, componentId, "*");
|
||||
Component.startElement(getWixType(), xml, componentId, "*");
|
||||
|
||||
addRegistryKeyPath(xml, INSTALLDIR, () -> propertyId, () -> {
|
||||
return toWixPath(path);
|
||||
});
|
||||
|
||||
xml.writeStartElement(
|
||||
"http://schemas.microsoft.com/wix/UtilExtension",
|
||||
xml.writeStartElement(getWixNamespaces().get(WixNamespace.Util),
|
||||
"RemoveFolderEx");
|
||||
xml.writeAttribute("On", "uninstall");
|
||||
xml.writeAttribute("Property", propertyId);
|
||||
@ -839,6 +905,10 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
|
||||
return componentId;
|
||||
}
|
||||
|
||||
private boolean isWithWix36Features() {
|
||||
return DottedVersion.compareComponents(getWixVersion(), DottedVersion.greedy("3.6")) >= 0;
|
||||
}
|
||||
|
||||
// Does the following conversions:
|
||||
// INSTALLDIR -> [INSTALLDIR]
|
||||
// TARGETDIR/ProgramFiles64Folder/foo/bar -> [ProgramFiles64Folder]foo/bar
|
||||
|
@ -29,31 +29,35 @@ import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.nio.file.Path;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
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;
|
||||
import jdk.jpackage.internal.IOUtils.XmlConsumer;
|
||||
import jdk.jpackage.internal.OverridableResource.Source;
|
||||
import static jdk.jpackage.internal.OverridableResource.createResource;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.CONFIG_ROOT;
|
||||
import jdk.internal.util.Architecture;
|
||||
import static jdk.jpackage.internal.OverridableResource.createResource;
|
||||
import jdk.jpackage.internal.WixSourceConverter.ResourceGroup;
|
||||
import jdk.jpackage.internal.WixToolset.WixToolsetType;
|
||||
|
||||
/**
|
||||
* Creates WiX fragment.
|
||||
*/
|
||||
abstract class WixFragmentBuilder {
|
||||
|
||||
void setWixVersion(DottedVersion v) {
|
||||
wixVersion = v;
|
||||
final void setWixVersion(DottedVersion version, WixToolsetType type) {
|
||||
Objects.requireNonNull(version);
|
||||
Objects.requireNonNull(type);
|
||||
wixVersion = version;
|
||||
wixType = type;
|
||||
}
|
||||
|
||||
void setOutputFileName(String v) {
|
||||
final void setOutputFileName(String v) {
|
||||
outputFileName = v;
|
||||
}
|
||||
|
||||
@ -65,11 +69,8 @@ abstract class WixFragmentBuilder {
|
||||
Source.ResourceDir);
|
||||
}
|
||||
|
||||
void logWixFeatures() {
|
||||
if (DottedVersion.compareComponents(wixVersion, DottedVersion.lazy("3.6")) >= 0) {
|
||||
Log.verbose(MessageFormat.format(I18N.getString(
|
||||
"message.use-wix36-features"), wixVersion));
|
||||
}
|
||||
List<String> getLoggableWixFeatures() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
void configureWixPipeline(WixPipeline wixPipeline) {
|
||||
@ -91,52 +92,84 @@ abstract class WixFragmentBuilder {
|
||||
}
|
||||
|
||||
if (additionalResources != null) {
|
||||
for (var resource : additionalResources) {
|
||||
resource.resource.saveToFile(configRoot.resolve(
|
||||
resource.saveAsName));
|
||||
}
|
||||
additionalResources.saveResources();
|
||||
}
|
||||
}
|
||||
|
||||
DottedVersion getWixVersion() {
|
||||
final WixToolsetType getWixType() {
|
||||
return wixType;
|
||||
}
|
||||
|
||||
final DottedVersion getWixVersion() {
|
||||
return wixVersion;
|
||||
}
|
||||
|
||||
protected static enum WixNamespace {
|
||||
Default,
|
||||
Util;
|
||||
}
|
||||
|
||||
final protected Map<WixNamespace, String> getWixNamespaces() {
|
||||
switch (wixType) {
|
||||
case Wix3 -> {
|
||||
return Map.of(WixNamespace.Default,
|
||||
"http://schemas.microsoft.com/wix/2006/wi",
|
||||
WixNamespace.Util,
|
||||
"http://schemas.microsoft.com/wix/UtilExtension");
|
||||
}
|
||||
case Wix4 -> {
|
||||
return Map.of(WixNamespace.Default,
|
||||
"http://wixtoolset.org/schemas/v4/wxs",
|
||||
WixNamespace.Util,
|
||||
"http://wixtoolset.org/schemas/v4/wxs/util");
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static boolean is64Bit() {
|
||||
return Architecture.is64bit();
|
||||
}
|
||||
|
||||
protected Path getConfigRoot() {
|
||||
final protected Path getConfigRoot() {
|
||||
return configRoot;
|
||||
}
|
||||
|
||||
protected abstract Collection<XmlConsumer> getFragmentWriters();
|
||||
|
||||
protected void defineWixVariable(String variableName) {
|
||||
final protected void defineWixVariable(String variableName) {
|
||||
setWixVariable(variableName, "yes");
|
||||
}
|
||||
|
||||
protected void setWixVariable(String variableName, String variableValue) {
|
||||
final protected void setWixVariable(String variableName, String variableValue) {
|
||||
if (wixVariables == null) {
|
||||
wixVariables = new WixVariables();
|
||||
}
|
||||
wixVariables.setWixVariable(variableName, variableValue);
|
||||
}
|
||||
|
||||
protected void addResource(OverridableResource resource, String saveAsName) {
|
||||
final protected void addResource(OverridableResource resource, String saveAsName) {
|
||||
if (additionalResources == null) {
|
||||
additionalResources = new ArrayList<>();
|
||||
additionalResources = new ResourceGroup(getWixType());
|
||||
}
|
||||
additionalResources.add(new ResourceWithName(resource, saveAsName));
|
||||
additionalResources.addResource(resource, configRoot.resolve(saveAsName));
|
||||
}
|
||||
|
||||
static void createWixSource(Path file, XmlConsumer xmlConsumer)
|
||||
throws IOException {
|
||||
private void createWixSource(Path file, XmlConsumer xmlConsumer) throws IOException {
|
||||
IOUtils.createXml(file, xml -> {
|
||||
xml.writeStartElement("Wix");
|
||||
xml.writeDefaultNamespace("http://schemas.microsoft.com/wix/2006/wi");
|
||||
xml.writeNamespace("util",
|
||||
"http://schemas.microsoft.com/wix/UtilExtension");
|
||||
for (var ns : getWixNamespaces().entrySet()) {
|
||||
switch (ns.getKey()) {
|
||||
case Default ->
|
||||
xml.writeDefaultNamespace(ns.getValue());
|
||||
default ->
|
||||
xml.writeNamespace(ns.getKey().name().toLowerCase(), ns.
|
||||
getValue());
|
||||
}
|
||||
}
|
||||
|
||||
xmlConsumer.accept((XMLStreamWriter) Proxy.newProxyInstance(
|
||||
XMLStreamWriter.class.getClassLoader(), new Class<?>[]{
|
||||
@ -146,16 +179,6 @@ abstract class WixFragmentBuilder {
|
||||
});
|
||||
}
|
||||
|
||||
private static class ResourceWithName {
|
||||
|
||||
ResourceWithName(OverridableResource resource, String saveAsName) {
|
||||
this.resource = resource;
|
||||
this.saveAsName = saveAsName;
|
||||
}
|
||||
private final OverridableResource resource;
|
||||
private final String saveAsName;
|
||||
}
|
||||
|
||||
private static class WixPreprocessorEscaper implements InvocationHandler {
|
||||
|
||||
WixPreprocessorEscaper(XMLStreamWriter target) {
|
||||
@ -208,9 +231,10 @@ abstract class WixFragmentBuilder {
|
||||
private final XMLStreamWriter target;
|
||||
}
|
||||
|
||||
private WixToolsetType wixType;
|
||||
private DottedVersion wixVersion;
|
||||
private WixVariables wixVariables;
|
||||
private List<ResourceWithName> additionalResources;
|
||||
private ResourceGroup additionalResources;
|
||||
private OverridableResource fragmentResource;
|
||||
private String outputFileName;
|
||||
private Path configRoot;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2024, 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
|
||||
@ -22,18 +22,19 @@
|
||||
* 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.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
@ -45,7 +46,7 @@ public class WixPipeline {
|
||||
lightOptions = new ArrayList<>();
|
||||
}
|
||||
|
||||
WixPipeline setToolset(Map<WixTool, Path> v) {
|
||||
WixPipeline setToolset(WixToolset v) {
|
||||
toolset = v;
|
||||
return this;
|
||||
}
|
||||
@ -79,13 +80,92 @@ public class WixPipeline {
|
||||
}
|
||||
|
||||
void buildMsi(Path msi) throws IOException {
|
||||
Objects.requireNonNull(workDir);
|
||||
|
||||
switch (toolset.getType()) {
|
||||
case Wix3 -> buildMsiWix3(msi);
|
||||
case Wix4 -> buildMsiWix4(msi);
|
||||
default -> throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
"build",
|
||||
"-nologo",
|
||||
"-pdbtype", "none",
|
||||
"-intermediatefolder", wixObjDir.toAbsolutePath().toString(),
|
||||
"-ext", "WixToolset.Util.wixext",
|
||||
"-arch", WixFragmentBuilder.is64Bit() ? "x64" : "x86"
|
||||
));
|
||||
|
||||
cmdline.addAll(lightOptions);
|
||||
|
||||
addWixVariblesToCommandLine(mergedSrcWixVars, cmdline);
|
||||
|
||||
cmdline.addAll(sources.stream().map(wixSource -> {
|
||||
return wixSource.source.toAbsolutePath().toString();
|
||||
}).toList());
|
||||
|
||||
cmdline.addAll(List.of("-out", msi.toString()));
|
||||
|
||||
execute(cmdline);
|
||||
}
|
||||
|
||||
private void buildMsiWix3(Path msi) throws IOException {
|
||||
List<Path> wixObjs = new ArrayList<>();
|
||||
for (var source : sources) {
|
||||
wixObjs.add(compile(source));
|
||||
wixObjs.add(compileWix3(source));
|
||||
}
|
||||
|
||||
List<String> lightCmdline = new ArrayList<>(List.of(
|
||||
toolset.get(WixTool.Light).toString(),
|
||||
toolset.getToolPath(WixTool.Light3).toString(),
|
||||
"-nologo",
|
||||
"-spdb",
|
||||
"-ext", "WixUtilExtension",
|
||||
@ -99,31 +179,20 @@ public class WixPipeline {
|
||||
execute(lightCmdline);
|
||||
}
|
||||
|
||||
private Path compile(WixSource wixSource) throws IOException {
|
||||
UnaryOperator<Path> adjustPath = path -> {
|
||||
return workDir != null ? path.toAbsolutePath() : path;
|
||||
};
|
||||
|
||||
Path wixObj = adjustPath.apply(wixObjDir).resolve(IOUtils.replaceSuffix(
|
||||
private Path compileWix3(WixSource wixSource) throws IOException {
|
||||
Path wixObj = wixObjDir.toAbsolutePath().resolve(IOUtils.replaceSuffix(
|
||||
IOUtils.getFileName(wixSource.source), ".wixobj"));
|
||||
|
||||
List<String> cmdline = new ArrayList<>(List.of(
|
||||
toolset.get(WixTool.Candle).toString(),
|
||||
toolset.getToolPath(WixTool.Candle3).toString(),
|
||||
"-nologo",
|
||||
adjustPath.apply(wixSource.source).toString(),
|
||||
wixSource.source.toAbsolutePath().toString(),
|
||||
"-ext", "WixUtilExtension",
|
||||
"-arch", WixFragmentBuilder.is64Bit() ? "x64" : "x86",
|
||||
"-out", wixObj.toAbsolutePath().toString()
|
||||
));
|
||||
|
||||
Map<String, String> appliedVaribales = new HashMap<>();
|
||||
Stream.of(wixVariables, wixSource.variables)
|
||||
.filter(Objects::nonNull)
|
||||
.forEachOrdered(appliedVaribales::putAll);
|
||||
|
||||
appliedVaribales.entrySet().stream().map(wixVar -> String.format("-d%s=%s",
|
||||
wixVar.getKey(), wixVar.getValue())).forEachOrdered(
|
||||
cmdline::add);
|
||||
addWixVariblesToCommandLine(wixSource.variables, cmdline);
|
||||
|
||||
execute(cmdline);
|
||||
|
||||
@ -131,8 +200,8 @@ public class WixPipeline {
|
||||
}
|
||||
|
||||
private void execute(List<String> cmdline) throws IOException {
|
||||
Executor.of(new ProcessBuilder(cmdline).directory(
|
||||
workDir != null ? workDir.toFile() : null)).executeExpectSuccess();
|
||||
Executor.of(new ProcessBuilder(cmdline).directory(workDir.toFile())).
|
||||
executeExpectSuccess();
|
||||
}
|
||||
|
||||
private static final class WixSource {
|
||||
@ -140,7 +209,7 @@ public class WixPipeline {
|
||||
Map<String, String> variables;
|
||||
}
|
||||
|
||||
private Map<WixTool, Path> toolset;
|
||||
private WixToolset toolset;
|
||||
private Map<String, String> wixVariables;
|
||||
private List<String> lightOptions;
|
||||
private Path wixObjDir;
|
||||
|
@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 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.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.stream.XMLOutputFactory;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.XMLStreamWriter;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stax.StAXResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import jdk.jpackage.internal.WixToolset.WixToolsetType;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* Converts WiX v3 source file into WiX v4 format.
|
||||
*/
|
||||
final class WixSourceConverter {
|
||||
|
||||
enum Status {
|
||||
SavedAsIs,
|
||||
SavedAsIsMalfromedXml,
|
||||
Transformed,
|
||||
}
|
||||
|
||||
WixSourceConverter(Path resourceDir) throws IOException {
|
||||
var buf = new ByteArrayOutputStream();
|
||||
|
||||
new OverridableResource("wix3-to-wix4-conv.xsl")
|
||||
.setPublicName("wix-conv.xsl")
|
||||
.setResourceDir(resourceDir)
|
||||
.setCategory(I18N.getString("resource.wix-src-conv"))
|
||||
.saveToStream(buf);
|
||||
|
||||
var xslt = new StreamSource(new ByteArrayInputStream(buf.toByteArray()));
|
||||
|
||||
var tf = TransformerFactory.newInstance();
|
||||
try {
|
||||
this.transformer = tf.newTransformer(xslt);
|
||||
} catch (TransformerException ex) {
|
||||
// Should never happen
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
this.outputFactory = XMLOutputFactory.newInstance();
|
||||
}
|
||||
|
||||
Status appyTo(OverridableResource resource, Path resourceSaveAsFile) throws IOException {
|
||||
// Save the resource into DOM tree and read xml namespaces from it.
|
||||
// If some namespaces are not recognized by this converter, save the resource as is.
|
||||
// If all detected namespaces are recognized, run transformation of the DOM tree and save
|
||||
// output into destination file.
|
||||
|
||||
var buf = saveResourceInMemory(resource);
|
||||
|
||||
Document inputXmlDom;
|
||||
try {
|
||||
inputXmlDom = IOUtils.initDocumentBuilder().parse(new ByteArrayInputStream(buf));
|
||||
} catch (SAXException ex) {
|
||||
// Malformed XML, don't run converter, save as is.
|
||||
resource.saveToFile(resourceSaveAsFile);
|
||||
return Status.SavedAsIsMalfromedXml;
|
||||
}
|
||||
|
||||
try {
|
||||
var nc = new NamespaceCollector();
|
||||
TransformerFactory.newInstance().newTransformer().
|
||||
transform(new DOMSource(inputXmlDom), new StAXResult((XMLStreamWriter) Proxy.
|
||||
newProxyInstance(XMLStreamWriter.class.getClassLoader(),
|
||||
new Class<?>[]{XMLStreamWriter.class}, nc)));
|
||||
if (!nc.isOnlyKnownNamespacesUsed()) {
|
||||
// Unsupported namespaces detected in input XML, don't run converter, save as is.
|
||||
resource.saveToFile(resourceSaveAsFile);
|
||||
return Status.SavedAsIs;
|
||||
}
|
||||
} catch (TransformerException ex) {
|
||||
// Should never happen
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
Supplier<Source> inputXml = () -> {
|
||||
// Should be "new DOMSource(inputXmlDom)", but no transfromation is applied in this case!
|
||||
return new StreamSource(new ByteArrayInputStream(buf));
|
||||
};
|
||||
|
||||
var nc = new NamespaceCollector();
|
||||
try {
|
||||
// Run transfomation to collect namespaces from the output XML.
|
||||
transformer.transform(inputXml.get(), new StAXResult((XMLStreamWriter) Proxy.
|
||||
newProxyInstance(XMLStreamWriter.class.getClassLoader(),
|
||||
new Class<?>[]{XMLStreamWriter.class}, nc)));
|
||||
} catch (TransformerException ex) {
|
||||
// Should never happen
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
try (var outXml = new ByteArrayOutputStream()) {
|
||||
transformer.transform(inputXml.get(), new StAXResult((XMLStreamWriter) Proxy.
|
||||
newProxyInstance(XMLStreamWriter.class.getClassLoader(),
|
||||
new Class<?>[]{XMLStreamWriter.class}, new NamespaceCleaner(nc.
|
||||
getPrefixToUri(), outputFactory.createXMLStreamWriter(outXml)))));
|
||||
Files.createDirectories(IOUtils.getParent(resourceSaveAsFile));
|
||||
Files.copy(new ByteArrayInputStream(outXml.toByteArray()), resourceSaveAsFile,
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (TransformerException | XMLStreamException ex) {
|
||||
// Should never happen
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
return Status.Transformed;
|
||||
}
|
||||
|
||||
private static byte[] saveResourceInMemory(OverridableResource resource) throws IOException {
|
||||
var buf = new ByteArrayOutputStream();
|
||||
resource.saveToStream(buf);
|
||||
return buf.toByteArray();
|
||||
}
|
||||
|
||||
final static class ResourceGroup {
|
||||
|
||||
ResourceGroup(WixToolsetType wixToolsetType) {
|
||||
this.wixToolsetType = wixToolsetType;
|
||||
}
|
||||
|
||||
void addResource(OverridableResource resource, Path resourceSaveAsFile) {
|
||||
resources.put(resourceSaveAsFile, resource);
|
||||
}
|
||||
|
||||
void saveResources() throws IOException {
|
||||
switch (wixToolsetType) {
|
||||
case Wix3 -> {
|
||||
for (var e : resources.entrySet()) {
|
||||
e.getValue().saveToFile(e.getKey());
|
||||
}
|
||||
}
|
||||
case Wix4 -> {
|
||||
var resourceDir = resources.values().stream().filter(res -> {
|
||||
return null != res.getResourceDir();
|
||||
}).findAny().map(OverridableResource::getResourceDir).orElse(null);
|
||||
var conv = new WixSourceConverter(resourceDir);
|
||||
for (var e : resources.entrySet()) {
|
||||
conv.appyTo(e.getValue(), e.getKey());
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<Path, OverridableResource> resources = new HashMap<>();
|
||||
private final WixToolsetType wixToolsetType;
|
||||
}
|
||||
|
||||
//
|
||||
// Default JDK XSLT v1.0 processor is not handling well default namespace mappings.
|
||||
// Running generic template:
|
||||
//
|
||||
// <xsl:template match="wix3loc:*">
|
||||
// <xsl:element name="{local-name()}" namespace="http://wixtoolset.org/schemas/v4/wxl">
|
||||
// <xsl:apply-templates select="@*|node()"/>
|
||||
// </xsl:element>
|
||||
// </xsl:template>
|
||||
//
|
||||
// produces:
|
||||
//
|
||||
// <ns0:WixLocalization xmlns:ns0="http://wixtoolset.org/schemas/v4/wxl" Culture="en-us" Codepage="1252">
|
||||
// <ns1:String xmlns:ns1="http://wixtoolset.org/schemas/v4/wxl" Value="The folder [INSTALLDIR] already exist. Would you like to install to that folder anyway?" Id="message.install.dir.exist"/>
|
||||
// <ns2:String xmlns:ns2="http://wixtoolset.org/schemas/v4/wxl" Value="Main Feature" Id="MainFeatureTitle"/>
|
||||
// ...
|
||||
// <ns12:String xmlns:ns12="http://wixtoolset.org/schemas/v4/wxl" Value="Open with [ProductName]" Id="ContextMenuCommandLabel"/>
|
||||
// </ns0:WixLocalization>
|
||||
//
|
||||
// which is conformant XML but WiX4 doesn't like it:
|
||||
//
|
||||
// wix.exe : error WIX0202: The {http://wixtoolset.org/schemas/v4/wxl}String element contains an unsupported extension attribute '{http://www.w3.org/2000/xmlns/}ns1'. The {http://wixtoolset.org/schemas/v4/wxl}String element does not currently support extension attributes. Is the {http://www.w3.org/2000/xmlns/}ns1 attribute using the correct XML namespace?
|
||||
// wix.exe : error WIX0202: The {http://wixtoolset.org/schemas/v4/wxl}String element contains an unsupported extension attribute '{http://www.w3.org/2000/xmlns/}ns2'. The {http://wixtoolset.org/schemas/v4/wxl}String element does not currently support extension attributes. Is the {http://www.w3.org/2000/xmlns/}ns2 attribute using the correct XML namespace?
|
||||
// wix.exe : error WIX0202: The {http://wixtoolset.org/schemas/v4/wxl}String element contains an unsupported extension attribute '{http://www.w3.org/2000/xmlns/}ns3'. The {http://wixtoolset.org/schemas/v4/wxl}String element does not currently support extension attributes. Is the {http://www.w3.org/2000/xmlns/}ns3 attribute using the correct XML namespace?
|
||||
//
|
||||
// Someone hit this issue long ago - https://stackoverflow.com/questions/26904623/replace-default-namespace-using-xsl and they suggested to use different XSLT processor.
|
||||
// Two online XSLT processors used in testing produce clean XML with this template indeed:
|
||||
//
|
||||
// <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Codepage="1252" Culture="en-us">
|
||||
// <String Value="The folder [INSTALLDIR] already exist. Would you like to install to that folder anyway?" Id="message.install.dir.exist"/>
|
||||
// <String Value="Main Feature" Id="MainFeatureTitle"/>
|
||||
// ...
|
||||
// <String Value="Open with [ProductName]" Id="ContextMenuCommandLabel"/>
|
||||
// </WixLocalization>
|
||||
//
|
||||
// To workaround default JDK's XSLT processor limitations we do additionl postprocessing of output XML with NamespaceCleaner class.
|
||||
//
|
||||
private static class NamespaceCleaner implements InvocationHandler {
|
||||
|
||||
NamespaceCleaner(Map<String, String> prefixToUri, XMLStreamWriter target) {
|
||||
this.uriToPrefix = prefixToUri.entrySet().stream().collect(Collectors.toMap(
|
||||
Map.Entry::getValue, e -> {
|
||||
return new Prefix(e.getKey());
|
||||
}, (x, y) -> x));
|
||||
this.prefixToUri = prefixToUri;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
switch (method.getName()) {
|
||||
case "writeNamespace" -> {
|
||||
final String uri = (String) args[1];
|
||||
var prefixObj = uriToPrefix.get(uri);
|
||||
if (!prefixObj.written) {
|
||||
prefixObj.written = true;
|
||||
target.writeNamespace(prefixObj.name, uri);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case "writeStartElement", "writeEmptyElement" -> {
|
||||
final String name;
|
||||
switch (args.length) {
|
||||
case 1 ->
|
||||
name = (String) args[0];
|
||||
case 2, 3 ->
|
||||
name = (String) args[1];
|
||||
default ->
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
final String prefix;
|
||||
final String localName;
|
||||
final String[] tokens = name.split(":", 2);
|
||||
if (tokens.length == 2) {
|
||||
prefix = tokens[0];
|
||||
localName = tokens[1];
|
||||
} else {
|
||||
localName = name;
|
||||
switch (args.length) {
|
||||
case 3 ->
|
||||
prefix = (String) args[0];
|
||||
case 2 ->
|
||||
prefix = uriToPrefix.get((String) args[0]).name;
|
||||
default ->
|
||||
prefix = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (prefix != null && !XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) {
|
||||
final String uri = prefixToUri.get(prefix);
|
||||
var prefixObj = uriToPrefix.get(uri);
|
||||
if (prefixObj.written) {
|
||||
var writeName = String.join(":", prefixObj.name, localName);
|
||||
if ("writeStartElement".equals(method.getName())) {
|
||||
target.writeStartElement(writeName);
|
||||
} else {
|
||||
target.writeEmptyElement(writeName);
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
prefixObj.written = (args.length > 1);
|
||||
args = Arrays.copyOf(args, args.length, Object[].class);
|
||||
if (localName.equals(name)) {
|
||||
// No prefix in the name
|
||||
if (args.length == 3) {
|
||||
args[0] = prefixObj.name;
|
||||
}
|
||||
} else {
|
||||
var writeName = String.join(":", prefixObj.name, localName);
|
||||
switch (args.length) {
|
||||
case 1 ->
|
||||
args[0] = writeName;
|
||||
case 2 -> {
|
||||
args[0] = uri;
|
||||
args[1] = writeName;
|
||||
}
|
||||
case 3 -> {
|
||||
args[0] = prefixObj.name;
|
||||
args[1] = writeName;
|
||||
args[2] = uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return method.invoke(target, args);
|
||||
}
|
||||
|
||||
static class Prefix {
|
||||
|
||||
Prefix(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private boolean written;
|
||||
}
|
||||
|
||||
private final Map<String, Prefix> uriToPrefix;
|
||||
private final Map<String, String> prefixToUri;
|
||||
private final XMLStreamWriter target;
|
||||
}
|
||||
|
||||
private static class NamespaceCollector implements InvocationHandler {
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
switch (method.getName()) {
|
||||
case "setPrefix", "writeNamespace" -> {
|
||||
var prefix = (String) args[0];
|
||||
var namespace = prefixToUri.computeIfAbsent(prefix, k -> createValue(args[1]));
|
||||
if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) {
|
||||
namespace.setValue(true);
|
||||
}
|
||||
}
|
||||
case "writeStartElement", "writeEmptyElement" -> {
|
||||
switch (args.length) {
|
||||
case 3 ->
|
||||
prefixToUri.computeIfAbsent((String) args[0], k -> createValue(
|
||||
(String) args[2])).setValue(true);
|
||||
case 2 ->
|
||||
initFromElementName((String) args[1], (String) args[0]);
|
||||
case 1 ->
|
||||
initFromElementName((String) args[0], null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean isOnlyKnownNamespacesUsed() {
|
||||
return prefixToUri.values().stream().filter(namespace -> {
|
||||
return namespace.getValue();
|
||||
}).allMatch(namespace -> {
|
||||
if (!namespace.getValue()) {
|
||||
return true;
|
||||
} else {
|
||||
return KNOWN_NAMESPACES.contains(namespace.getKey());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, String> getPrefixToUri() {
|
||||
return prefixToUri.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
|
||||
e -> {
|
||||
return e.getValue().getKey();
|
||||
}));
|
||||
}
|
||||
|
||||
private void initFromElementName(String name, String namespace) {
|
||||
final String[] tokens = name.split(":", 2);
|
||||
if (tokens.length == 2) {
|
||||
if (namespace != null) {
|
||||
prefixToUri.computeIfAbsent(tokens[0], k -> createValue(namespace)).setValue(
|
||||
true);
|
||||
} else {
|
||||
prefixToUri.computeIfPresent(tokens[0], (k, v) -> {
|
||||
v.setValue(true);
|
||||
return v;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map.Entry<String, Boolean> createValue(Object prefix) {
|
||||
return new AbstractMap.SimpleEntry<String, Boolean>((String) prefix, false);
|
||||
}
|
||||
|
||||
private final Map<String, Map.Entry<String, Boolean>> prefixToUri = new HashMap<>();
|
||||
}
|
||||
|
||||
private final Transformer transformer;
|
||||
private final XMLOutputFactory outputFactory;
|
||||
|
||||
// The list of WiX v3 namespaces this converter can handle
|
||||
private final static Set<String> KNOWN_NAMESPACES = Set.of(
|
||||
"http://schemas.microsoft.com/wix/2006/localization",
|
||||
"http://schemas.microsoft.com/wix/2006/wi");
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2024, 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
|
||||
@ -22,7 +22,6 @@
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -32,100 +31,193 @@ import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.text.MessageFormat;
|
||||
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.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.WixToolset.WixToolsetType;
|
||||
|
||||
/**
|
||||
* WiX tool.
|
||||
*/
|
||||
public enum WixTool {
|
||||
Candle, Light;
|
||||
Candle3("candle", DottedVersion.lazy("3.0")),
|
||||
Light3("light", DottedVersion.lazy("3.0")),
|
||||
Wix4("wix", DottedVersion.lazy("4.0.4"));
|
||||
|
||||
WixTool(String commandName, DottedVersion minimalVersion) {
|
||||
this.toolFileName = IOUtils.addSuffix(Path.of(commandName), ".exe");
|
||||
this.minimalVersion = minimalVersion;
|
||||
}
|
||||
|
||||
static final class ToolInfo {
|
||||
|
||||
ToolInfo(Path path, String version) {
|
||||
this.path = path;
|
||||
this.version = new DottedVersion(version);
|
||||
this.version = DottedVersion.lazy(version);
|
||||
}
|
||||
|
||||
final Path path;
|
||||
final DottedVersion version;
|
||||
}
|
||||
|
||||
static Map<WixTool, ToolInfo> toolset() throws ConfigException {
|
||||
Map<WixTool, ToolInfo> toolset = new HashMap<>();
|
||||
for (var tool : values()) {
|
||||
toolset.put(tool, tool.find());
|
||||
static WixToolset createToolset() throws ConfigException {
|
||||
Function<List<ToolLookupResult>, Map<WixTool, ToolInfo>> conv = lookupResults -> {
|
||||
return lookupResults.stream().filter(ToolLookupResult::isValid).collect(Collectors.
|
||||
groupingBy(lookupResult -> {
|
||||
return lookupResult.getInfo().version.toString();
|
||||
})).values().stream().filter(sameVersionLookupResults -> {
|
||||
Set<WixTool> sameVersionTools = sameVersionLookupResults.stream().map(
|
||||
ToolLookupResult::getTool).collect(Collectors.toSet());
|
||||
if (sameVersionTools.equals(Set.of(Candle3)) || sameVersionTools.equals(Set.of(
|
||||
Light3))) {
|
||||
// There is only one tool from WiX v3 toolset of some version available. Discard it.
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return toolset;
|
||||
}).flatMap(List::stream).collect(Collectors.toMap(ToolLookupResult::getTool,
|
||||
ToolLookupResult::getInfo, (ToolInfo x, ToolInfo y) -> {
|
||||
return Stream.of(x, y).sorted(Comparator.comparing((ToolInfo toolInfo) -> {
|
||||
return toolInfo.version.toComponentsString();
|
||||
}).reversed()).findFirst().get();
|
||||
}));
|
||||
};
|
||||
|
||||
Function<List<ToolLookupResult>, Optional<WixToolset>> createToolset = lookupResults -> {
|
||||
var tools = conv.apply(lookupResults);
|
||||
// Try to build a toolset found in the PATH and in known locations.
|
||||
return Stream.of(WixToolsetType.values()).map(toolsetType -> {
|
||||
return WixToolset.create(toolsetType.getTools(), tools);
|
||||
}).filter(Objects::nonNull).findFirst();
|
||||
};
|
||||
|
||||
var toolsInPath = Stream.of(values()).map(tool -> {
|
||||
return new ToolLookupResult(tool, null);
|
||||
}).toList();
|
||||
|
||||
// Try to build a toolset from tools in the PATH first.
|
||||
var toolset = createToolset.apply(toolsInPath);
|
||||
if (toolset.isPresent()) {
|
||||
return toolset.get();
|
||||
}
|
||||
|
||||
ToolInfo find() throws ConfigException {
|
||||
final Path toolFileName = IOUtils.addSuffix(
|
||||
Path.of(name().toLowerCase()), ".exe");
|
||||
// Look up for WiX tools in known locations.
|
||||
var toolsInKnownWiXDirs = findWixInstallDirs().stream().map(dir -> {
|
||||
return Stream.of(values()).map(tool -> {
|
||||
return new ToolLookupResult(tool, dir);
|
||||
});
|
||||
}).flatMap(Function.identity()).toList();
|
||||
|
||||
String[] version = new String[1];
|
||||
ConfigException reason = createToolValidator(toolFileName, version).get();
|
||||
if (version[0] != null) {
|
||||
if (reason == null) {
|
||||
// Found in PATH.
|
||||
return new ToolInfo(toolFileName, version[0]);
|
||||
// Build a toolset found in the PATH and in known locations.
|
||||
var allFoundTools = Stream.of(toolsInPath, toolsInKnownWiXDirs).flatMap(List::stream).filter(
|
||||
ToolLookupResult::isValid).toList();
|
||||
toolset = createToolset.apply(allFoundTools);
|
||||
if (toolset.isPresent()) {
|
||||
return toolset.get();
|
||||
} else if (allFoundTools.isEmpty()) {
|
||||
throw new ConfigException(I18N.getString("error.no-wix-tools"), I18N.getString(
|
||||
"error.no-wix-tools.advice"));
|
||||
} else {
|
||||
var toolOldVerErr = allFoundTools.stream().map(lookupResult -> {
|
||||
if (lookupResult.versionTooOld) {
|
||||
return new ConfigException(MessageFormat.format(I18N.getString(
|
||||
"message.wrong-tool-version"), lookupResult.getInfo().path,
|
||||
lookupResult.getInfo().version, lookupResult.getTool().minimalVersion),
|
||||
I18N.getString("error.no-wix-tools.advice"));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Found in PATH, but something went wrong.
|
||||
throw reason;
|
||||
}).filter(Objects::nonNull).findAny();
|
||||
if (toolOldVerErr.isPresent()) {
|
||||
throw toolOldVerErr.get();
|
||||
} else {
|
||||
throw new ConfigException(I18N.getString("error.no-wix-tools"), I18N.getString(
|
||||
"error.no-wix-tools.advice"));
|
||||
}
|
||||
|
||||
for (var dir : findWixInstallDirs()) {
|
||||
Path path = dir.resolve(toolFileName);
|
||||
if (Files.exists(path)) {
|
||||
reason = createToolValidator(path, version).get();
|
||||
if (reason != null) {
|
||||
throw reason;
|
||||
}
|
||||
return new ToolInfo(path, version[0]);
|
||||
}
|
||||
}
|
||||
|
||||
throw reason;
|
||||
}
|
||||
private static class ToolLookupResult {
|
||||
|
||||
private static Supplier<ConfigException> createToolValidator(Path toolPath,
|
||||
String[] versionCtnr) {
|
||||
return new ToolValidator(toolPath)
|
||||
.setCommandLine("/?")
|
||||
.setMinimalVersion(MINIMAL_VERSION)
|
||||
.setToolNotFoundErrorHandler(
|
||||
(name, ex) -> new ConfigException(
|
||||
I18N.getString("error.no-wix-tools"),
|
||||
I18N.getString("error.no-wix-tools.advice")))
|
||||
.setToolOldVersionErrorHandler(
|
||||
(name, version) -> new ConfigException(
|
||||
MessageFormat.format(I18N.getString(
|
||||
"message.wrong-tool-version"), name,
|
||||
version, MINIMAL_VERSION),
|
||||
I18N.getString("error.no-wix-tools.advice")))
|
||||
.setVersionParser(output -> {
|
||||
versionCtnr[0] = "";
|
||||
ToolLookupResult(WixTool tool, Path lookupDir) {
|
||||
|
||||
final Path toolPath = Optional.ofNullable(lookupDir).map(p -> p.resolve(
|
||||
tool.toolFileName)).orElse(tool.toolFileName);
|
||||
|
||||
final boolean[] tooOld = new boolean[1];
|
||||
final String[] parsedVersion = new String[1];
|
||||
|
||||
final var validator = new ToolValidator(toolPath).setMinimalVersion(tool.minimalVersion).
|
||||
setToolNotFoundErrorHandler((name, ex) -> {
|
||||
return new ConfigException("", "");
|
||||
}).setToolOldVersionErrorHandler((name, version) -> {
|
||||
tooOld[0] = true;
|
||||
return null;
|
||||
});
|
||||
|
||||
final Function<Stream<String>, String> versionParser;
|
||||
|
||||
if (Set.of(Candle3, Light3).contains(tool)) {
|
||||
validator.setCommandLine("/?");
|
||||
versionParser = output -> {
|
||||
String firstLineOfOutput = output.findFirst().orElse("");
|
||||
int separatorIdx = firstLineOfOutput.lastIndexOf(' ');
|
||||
if (separatorIdx == -1) {
|
||||
return null;
|
||||
}
|
||||
versionCtnr[0] = firstLineOfOutput.substring(separatorIdx + 1);
|
||||
return versionCtnr[0];
|
||||
})::validate;
|
||||
return firstLineOfOutput.substring(separatorIdx + 1);
|
||||
};
|
||||
} else {
|
||||
validator.setCommandLine("--version");
|
||||
versionParser = output -> {
|
||||
return output.findFirst().orElse("");
|
||||
};
|
||||
}
|
||||
|
||||
private static final DottedVersion MINIMAL_VERSION = DottedVersion.lazy("3.0");
|
||||
validator.setVersionParser(output -> {
|
||||
parsedVersion[0] = versionParser.apply(output);
|
||||
return parsedVersion[0];
|
||||
});
|
||||
|
||||
static Path getSystemDir(String envVar, String knownDir) {
|
||||
this.tool = tool;
|
||||
if (validator.validate() == null) {
|
||||
// Tool found
|
||||
this.versionTooOld = tooOld[0];
|
||||
this.info = new ToolInfo(toolPath, parsedVersion[0]);
|
||||
} else {
|
||||
this.versionTooOld = false;
|
||||
this.info = null;
|
||||
}
|
||||
}
|
||||
|
||||
WixTool getTool() {
|
||||
return tool;
|
||||
}
|
||||
|
||||
ToolInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
return info != null && !versionTooOld;
|
||||
}
|
||||
|
||||
boolean isVersionTooOld() {
|
||||
return versionTooOld;
|
||||
}
|
||||
|
||||
private final WixTool tool;
|
||||
private final ToolInfo info;
|
||||
private final boolean versionTooOld;
|
||||
}
|
||||
|
||||
private static Path getSystemDir(String envVar, String knownDir) {
|
||||
return Optional
|
||||
.ofNullable(getEnvVariableAsPath(envVar))
|
||||
.orElseGet(() -> Optional
|
||||
@ -147,7 +239,21 @@ public enum WixTool {
|
||||
}
|
||||
|
||||
private static List<Path> findWixInstallDirs() {
|
||||
PathMatcher wixInstallDirMatcher = FileSystems.getDefault().getPathMatcher(
|
||||
return Stream.of(findWixCurrentInstallDirs(), findWix3InstallDirs()).
|
||||
flatMap(List::stream).toList();
|
||||
}
|
||||
|
||||
private static List<Path> findWixCurrentInstallDirs() {
|
||||
return Stream.of(getEnvVariableAsPath("USERPROFILE"), Optional.ofNullable(System.
|
||||
getProperty("user.home")).map(Path::of).orElse(null)).filter(Objects::nonNull).map(
|
||||
path -> {
|
||||
return path.resolve(".dotnet/tools");
|
||||
}).filter(Files::isDirectory).distinct().toList();
|
||||
}
|
||||
|
||||
private static List<Path> findWix3InstallDirs() {
|
||||
PathMatcher wixInstallDirMatcher = FileSystems.getDefault().
|
||||
getPathMatcher(
|
||||
"glob:WiX Toolset v*");
|
||||
|
||||
Path programFiles = getSystemDir("ProgramFiles", "\\Program Files");
|
||||
@ -157,18 +263,20 @@ public enum WixTool {
|
||||
// Returns list of WiX install directories ordered by WiX version number.
|
||||
// Newer versions go first.
|
||||
return Stream.of(programFiles, programFilesX86).map(path -> {
|
||||
List<Path> result;
|
||||
try (var paths = Files.walk(path, 1)) {
|
||||
result = paths.toList();
|
||||
return paths.toList();
|
||||
} catch (IOException ex) {
|
||||
Log.verbose(ex);
|
||||
result = Collections.emptyList();
|
||||
List<Path> empty = List.of();
|
||||
return empty;
|
||||
}
|
||||
return result;
|
||||
}).flatMap(List::stream)
|
||||
.filter(path -> wixInstallDirMatcher.matches(path.getFileName()))
|
||||
.sorted(Comparator.comparing(Path::getFileName).reversed())
|
||||
.filter(path -> wixInstallDirMatcher.matches(path.getFileName())).
|
||||
sorted(Comparator.comparing(Path::getFileName).reversed())
|
||||
.map(path -> path.resolve("bin"))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private final Path toolFileName;
|
||||
private final DottedVersion minimalVersion;
|
||||
}
|
||||
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 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.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
final class WixToolset {
|
||||
|
||||
static enum WixToolsetType {
|
||||
// Wix v4+
|
||||
Wix4(WixTool.Wix4),
|
||||
// Wix v3+
|
||||
Wix3(WixTool.Candle3, WixTool.Light3);
|
||||
|
||||
WixToolsetType(WixTool... tools) {
|
||||
this.tools = Set.of(tools);
|
||||
}
|
||||
|
||||
Set<WixTool> getTools() {
|
||||
return tools;
|
||||
}
|
||||
|
||||
private final Set<WixTool> tools;
|
||||
}
|
||||
|
||||
private WixToolset(Map<WixTool, WixTool.ToolInfo> tools) {
|
||||
this.tools = tools;
|
||||
}
|
||||
|
||||
WixToolsetType getType() {
|
||||
return Stream.of(WixToolsetType.values()).filter(toolsetType -> {
|
||||
return toolsetType.getTools().equals(tools.keySet());
|
||||
}).findAny().get();
|
||||
}
|
||||
|
||||
Path getToolPath(WixTool tool) {
|
||||
return tools.get(tool).path;
|
||||
}
|
||||
|
||||
DottedVersion getVersion() {
|
||||
return tools.values().iterator().next().version;
|
||||
}
|
||||
|
||||
static WixToolset create(Set<WixTool> requiredTools, Map<WixTool, WixTool.ToolInfo> allTools) {
|
||||
var filteredTools = allTools.entrySet().stream().filter(e -> {
|
||||
return requiredTools.contains(e.getKey());
|
||||
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
if (filteredTools.keySet().equals(requiredTools)) {
|
||||
return new WixToolset(filteredTools);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<WixTool, WixTool.ToolInfo> tools;
|
||||
}
|
@ -43,6 +43,7 @@ import jdk.jpackage.internal.IOUtils.XmlConsumer;
|
||||
import static jdk.jpackage.internal.OverridableResource.createResource;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.LICENSE_FILE;
|
||||
import jdk.jpackage.internal.WixAppImageFragmentBuilder.ShortcutsFolder;
|
||||
import jdk.jpackage.internal.WixToolset.WixToolsetType;
|
||||
|
||||
/**
|
||||
* Creates UI WiX fragment.
|
||||
@ -100,7 +101,13 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
super.configureWixPipeline(wixPipeline);
|
||||
|
||||
if (withShortcutPromptDlg || withInstallDirChooserDlg || withLicenseDlg) {
|
||||
wixPipeline.addLightOptions("-ext", "WixUIExtension");
|
||||
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
|
||||
@ -148,15 +155,14 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
xml.writeEndElement(); // WixVariable
|
||||
}
|
||||
|
||||
xml.writeStartElement("UI");
|
||||
xml.writeAttribute("Id", "JpUI");
|
||||
|
||||
var ui = getUI();
|
||||
if (ui != null) {
|
||||
ui.write(this, xml);
|
||||
ui.write(getWixType(), this, xml);
|
||||
} else {
|
||||
xml.writeStartElement("UI");
|
||||
xml.writeAttribute("Id", "JpUI");
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // UI
|
||||
}
|
||||
|
||||
private UI getUI() {
|
||||
@ -187,12 +193,43 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
this.dialogPairsSupplier = dialogPairsSupplier;
|
||||
}
|
||||
|
||||
void write(WixUiFragmentBuilder outer, XMLStreamWriter xml) throws
|
||||
XMLStreamException, IOException {
|
||||
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();
|
||||
@ -210,7 +247,7 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
DialogPair pair = new DialogPair(firstId, secondId);
|
||||
for (var curPair : List.of(pair, pair.flip())) {
|
||||
for (var publish : dialogPairs.get(curPair)) {
|
||||
writePublishDialogPair(xml, publish, curPair);
|
||||
writePublishDialogPair(wixType, xml, publish, curPair);
|
||||
}
|
||||
}
|
||||
firstId = secondId;
|
||||
@ -218,6 +255,17 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -441,9 +489,8 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
return new PublishBuilder(publish);
|
||||
}
|
||||
|
||||
private static void writePublishDialogPair(XMLStreamWriter xml,
|
||||
Publish publish, DialogPair dialogPair) throws IOException,
|
||||
XMLStreamException {
|
||||
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);
|
||||
@ -452,7 +499,11 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
if (publish.order != 0) {
|
||||
xml.writeAttribute("Order", String.valueOf(publish.order));
|
||||
}
|
||||
xml.writeCharacters(publish.condition);
|
||||
switch (wixType) {
|
||||
case Wix3 -> xml.writeCharacters(publish.condition);
|
||||
case Wix4 -> xml.writeAttribute("Condition", publish.condition);
|
||||
default -> throw new IllegalArgumentException();
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
@ -463,9 +514,8 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
this.wxsFileName = wxsFileName;
|
||||
this.wixVariables = new WixVariables();
|
||||
|
||||
addResource(
|
||||
createResource(wxsFileName, params).setCategory(category),
|
||||
wxsFileName);
|
||||
addResource(createResource(wxsFileName, params).setCategory(category).setPublicName(
|
||||
wxsFileName), wxsFileName);
|
||||
}
|
||||
|
||||
void addToWixPipeline(WixPipeline wixPipeline) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2024, 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
|
||||
@ -41,9 +41,7 @@
|
||||
<Control Id="No" Type="PushButton" X="150" Y="55" Width="50" Height="15" Default="yes" Cancel="yes" Text="!(loc.WixUINo)">
|
||||
<Publish Event="NewDialog" Value="InstallDirDlg">1</Publish>
|
||||
</Control>
|
||||
<Control Id="Text" Type="Text" X="25" Y="15" Width="250" Height="30" TabSkip="no">
|
||||
<Text>!(loc.message.install.dir.exist)</Text>
|
||||
</Control>
|
||||
<Control Id="Text" Type="Text" X="25" Y="15" Width="250" Height="30" TabSkip="no" Text="!(loc.InstallDirNotEmptyDlgInstallDirExistMessage)"/>
|
||||
</Dialog>
|
||||
|
||||
<Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="JpCheckInstallDir" Order="3">1</Publish>
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?xml version = '1.0' encoding = 'utf-8'?>
|
||||
<WixLocalization Culture="de-de" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage="1252">
|
||||
<String Id="message.install.dir.exist">Der Ordner [INSTALLDIR] ist bereits vorhanden. Möchten Sie diesen Ordner trotzdem installieren?</String>
|
||||
<String Id="MainFeatureTitle">Hauptfeature</String>
|
||||
<String Id="DowngradeErrorMessage">Eine höhere Version von [ProductName] ist bereits installiert. Downgrades sind deaktiviert. Setup wird jetzt beendet.</String>
|
||||
<String Id="DisallowUpgradeErrorMessage">Eine niedrigere Version von [ProductName] ist bereits installiert. Upgrades sind deaktiviert. Setup wird jetzt beendet.</String>
|
||||
@ -11,6 +10,9 @@
|
||||
<String Id="ShortcutPromptDlgDescription">Wählen Sie die zu erstellenden Verknüpfungen aus.</String>
|
||||
<String Id="ShortcutPromptDlgDesktopShortcutControlLabel">Desktopverknüpfung(en) erstellen</String>
|
||||
<String Id="ShortcutPromptDlgStartMenuShortcutControlLabel">Startmenüverknüpfung(en) erstellen</String>
|
||||
|
||||
<String Id="InstallDirNotEmptyDlg_Title">[ProductName]-Setup</String>
|
||||
<String Id="InstallDirNotEmptyDlgInstallDirExistMessage">Der Ordner [INSTALLDIR] ist bereits vorhanden. Möchten Sie diesen Ordner trotzdem installieren?</String>
|
||||
|
||||
<String Id="ContextMenuCommandLabel">Mit [ProductName] öffnen</String>
|
||||
</WixLocalization>
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage="1252">
|
||||
<String Id="message.install.dir.exist">The folder [INSTALLDIR] already exist. Would you like to install to that folder anyway?</String>
|
||||
<String Id="MainFeatureTitle">Main Feature</String>
|
||||
<String Id="DowngradeErrorMessage">A higher version of [ProductName] is already installed. Downgrades disabled. Setup will now exit.</String>
|
||||
<String Id="DisallowUpgradeErrorMessage">A lower version of [ProductName] is already installed. Upgrades disabled. Setup will now exit.</String>
|
||||
@ -11,6 +10,9 @@
|
||||
<String Id="ShortcutPromptDlgDescription">Select shortcuts to create.</String>
|
||||
<String Id="ShortcutPromptDlgDesktopShortcutControlLabel">Create desktop shortcut(s)</String>
|
||||
<String Id="ShortcutPromptDlgStartMenuShortcutControlLabel">Create start menu shortcut(s)</String>
|
||||
|
||||
<String Id="InstallDirNotEmptyDlg_Title">[ProductName] Setup</String>
|
||||
<String Id="InstallDirNotEmptyDlgInstallDirExistMessage">The folder [INSTALLDIR] already exist. Would you like to install to that folder anyway?</String>
|
||||
|
||||
<String Id="ContextMenuCommandLabel">Open with [ProductName]</String>
|
||||
</WixLocalization>
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?xml version = '1.0' encoding = 'utf-8'?>
|
||||
<WixLocalization Culture="ja-jp" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage="932">
|
||||
<String Id="message.install.dir.exist">フォルダ[INSTALLDIR]はすでに存在します。そのフォルダにインストールしますか?</String>
|
||||
<String Id="MainFeatureTitle">主な機能</String>
|
||||
<String Id="DowngradeErrorMessage">[ProductName]のより上位のバージョンがすでにインストールされています。ダウングレードは無効です。セットアップを終了します。</String>
|
||||
<String Id="DisallowUpgradeErrorMessage">[ProductName]のより下位のバージョンがすでにインストールされています。アップグレードは無効です。セットアップを終了します。</String>
|
||||
@ -11,6 +10,9 @@
|
||||
<String Id="ShortcutPromptDlgDescription">作成するショートカットを選択します。</String>
|
||||
<String Id="ShortcutPromptDlgDesktopShortcutControlLabel">デスクトップ・ショートカットの作成</String>
|
||||
<String Id="ShortcutPromptDlgStartMenuShortcutControlLabel">スタート・メニューのショートカットの作成</String>
|
||||
|
||||
<String Id="InstallDirNotEmptyDlg_Title">[ProductName]セットアップ</String>
|
||||
<String Id="InstallDirNotEmptyDlgInstallDirExistMessage">フォルダ[INSTALLDIR]はすでに存在します。そのフォルダにインストールしますか?</String>
|
||||
|
||||
<String Id="ContextMenuCommandLabel">[ProductName]で開く</String>
|
||||
</WixLocalization>
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?xml version = '1.0' encoding = 'utf-8'?>
|
||||
<WixLocalization Culture="zh-cn" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage="936">
|
||||
<String Id="message.install.dir.exist">文件夹 [INSTALLDIR] 已存在。是否仍要安装到该文件夹?</String>
|
||||
<String Id="MainFeatureTitle">主要功能</String>
|
||||
<String Id="DowngradeErrorMessage">已安装更高版本的 [ProductName]。降级已禁用。现在将退出安装。</String>
|
||||
<String Id="DisallowUpgradeErrorMessage">已安装更低版本的 [ProductName]。升级已禁用。现在将退出安装。</String>
|
||||
@ -11,6 +10,9 @@
|
||||
<String Id="ShortcutPromptDlgDescription">选择要创建的快捷方式。</String>
|
||||
<String Id="ShortcutPromptDlgDesktopShortcutControlLabel">创建桌面快捷方式</String>
|
||||
<String Id="ShortcutPromptDlgStartMenuShortcutControlLabel">创建开始菜单快捷方式</String>
|
||||
|
||||
<String Id="InstallDirNotEmptyDlg_Title">[ProductName] 安装程序</String>
|
||||
<String Id="InstallDirNotEmptyDlgInstallDirExistMessage">文件夹 [INSTALLDIR] 已存在。是否仍要安装到该文件夹?</String>
|
||||
|
||||
<String Id="ContextMenuCommandLabel">使用 [ProductName] 打开</String>
|
||||
</WixLocalization>
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2017, 2024, 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
|
||||
@ -41,8 +41,9 @@ resource.overrides-wix-file=Overrides WiX project file
|
||||
resource.shortcutpromptdlg-wix-file=Shortcut prompt dialog WiX project file
|
||||
resource.installdirnotemptydlg-wix-file=Not empty install directory dialog WiX project file
|
||||
resource.launcher-as-service-wix-file=Service installer WiX project file
|
||||
resource.wix-src-conv=XSLT stylesheet converting WiX sources from WiX v3 to WiX v4 format
|
||||
|
||||
error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe)
|
||||
error.no-wix-tools=Can not find WiX tools. Was looking for WiX v3 light.exe and candle.exe or WiX v4/v5 wix.exe and none was found
|
||||
error.no-wix-tools.advice=Download WiX 3.0 or later from https://wixtoolset.org and add it to the PATH.
|
||||
error.version-string-wrong-format.advice=Set value of --app-version parameter to a valid Windows Installer ProductVersion.
|
||||
error.msi-product-version-components=Version string [{0}] must have between 2 and 4 components.
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2017, 2024, 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
|
||||
@ -41,8 +41,9 @@ resource.overrides-wix-file=Überschreibt WiX-Projektdatei
|
||||
resource.shortcutpromptdlg-wix-file=Dialogfeld für Verknüpfungs-Prompt der WiX-Projektdatei
|
||||
resource.installdirnotemptydlg-wix-file=Nicht leeres Installationsverzeichnis in Dialogfeld für WiX-Projektdatei
|
||||
resource.launcher-as-service-wix-file=WiX-Projektdatei für Serviceinstallationsprogramm
|
||||
resource.wix-src-conv=XSLT stylesheet converting WiX sources from WiX v3 to WiX v4 format
|
||||
|
||||
error.no-wix-tools=WiX-Tools (light.exe, candle.exe) nicht gefunden
|
||||
error.no-wix-tools=Can not find WiX tools. Was looking for WiX v3 light.exe and candle.exe or WiX v4/v5 wix.exe and none was found
|
||||
error.no-wix-tools.advice=Laden Sie WiX 3.0 oder höher von https://wixtoolset.org herunter, und fügen Sie es zu PATH hinzu.
|
||||
error.version-string-wrong-format.advice=Setzen Sie den Wert des --app-version-Parameters auf eine gültige ProductVersion des Windows-Installationsprogramms.
|
||||
error.msi-product-version-components=Versionszeichenfolge [{0}] muss zwischen 2 und 4 Komponenten aufweisen.
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2017, 2024, 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
|
||||
@ -41,8 +41,9 @@ resource.overrides-wix-file=WiXプロジェクト・ファイルのオーバー
|
||||
resource.shortcutpromptdlg-wix-file=ショートカット・プロンプト・ダイアログWiXプロジェクト・ファイル
|
||||
resource.installdirnotemptydlg-wix-file=インストール・ディレクトリ・ダイアログのWiXプロジェクト・ファイルが空ではありません
|
||||
resource.launcher-as-service-wix-file=サービス・インストーラWiXプロジェクト・ファイル
|
||||
resource.wix-src-conv=XSLT stylesheet converting WiX sources from WiX v3 to WiX v4 format
|
||||
|
||||
error.no-wix-tools=WiXツール(light.exe、candle.exe)が見つかりません
|
||||
error.no-wix-tools=Can not find WiX tools. Was looking for WiX v3 light.exe and candle.exe or WiX v4/v5 wix.exe and none was found
|
||||
error.no-wix-tools.advice=WiX 3.0以降をhttps://wixtoolset.orgからダウンロードし、PATHに追加します。
|
||||
error.version-string-wrong-format.advice=--app-versionパラメータの値を有効なWindows Installer ProductVersionに設定します。
|
||||
error.msi-product-version-components=バージョン文字列[{0}]には、2から4つのコンポーネントが含まれている必要があります。
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2017, 2024, 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
|
||||
@ -41,8 +41,9 @@ resource.overrides-wix-file=覆盖 WiX 项目文件
|
||||
resource.shortcutpromptdlg-wix-file=快捷方式提示对话框 WiX 项目文件
|
||||
resource.installdirnotemptydlg-wix-file=安装目录对话框 WiX 项目文件非空
|
||||
resource.launcher-as-service-wix-file=服务安装程序 WiX 项目文件
|
||||
resource.wix-src-conv=XSLT stylesheet converting WiX sources from WiX v3 to WiX v4 format
|
||||
|
||||
error.no-wix-tools=找不到 WiX 工具 (light.exe, candle.exe)
|
||||
error.no-wix-tools=Can not find WiX tools. Was looking for WiX v3 light.exe and candle.exe or WiX v4/v5 wix.exe and none was found
|
||||
error.no-wix-tools.advice=从 https://wixtoolset.org 下载 WiX 3.0 或更高版本,然后将其添加到 PATH。
|
||||
error.version-string-wrong-format.advice=将 --app-version 参数的值设置为有效的 Windows Installer ProductVersion。
|
||||
error.msi-product-version-components=版本字符串 [{0}] 必须包含 2 到 4 个组成部分。
|
||||
|
@ -28,4 +28,4 @@ to disable (the value doesn't matter).
|
||||
Should be defined to enable upgrades and undefined to disable upgrades.
|
||||
By default it is defined, use <?undef JpAllowUpgrades?> to disable.
|
||||
-->
|
||||
<Include/>
|
||||
<Include xmlns="http://schemas.microsoft.com/wix/2006/wi"/>
|
||||
|
@ -0,0 +1,183 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
* Copyright (c) 2024, 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.
|
||||
*/
|
||||
-->
|
||||
|
||||
<!--
|
||||
This stylesheet can be applied to Wix3 .wxl, .wxs, and .wsi source files.
|
||||
-->
|
||||
<xsl:stylesheet version="1.0"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:wix3loc="http://schemas.microsoft.com/wix/2006/localization"
|
||||
xmlns:wix3="http://schemas.microsoft.com/wix/2006/wi"
|
||||
>
|
||||
<!-- Wix4 complains about xml declaration in input files. Turn it off -->
|
||||
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
|
||||
|
||||
<!--
|
||||
Remap xmlns="http://schemas.microsoft.com/wix/2006/localization"
|
||||
to xmlns="http://wixtoolset.org/schemas/v4/wxl"
|
||||
-->
|
||||
|
||||
<xsl:template match="wix3loc:*">
|
||||
<xsl:element name="{local-name()}" namespace="http://wixtoolset.org/schemas/v4/wxl">
|
||||
<xsl:apply-templates select="@*|node()"/>
|
||||
</xsl:element>
|
||||
</xsl:template>
|
||||
|
||||
<!--
|
||||
Remap xmlns="http://schemas.microsoft.com/wix/2006/localization"
|
||||
to xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
-->
|
||||
|
||||
<xsl:template match="wix3:*">
|
||||
<xsl:element name="{local-name()}" namespace="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<xsl:apply-templates select="@*|node()"/>
|
||||
</xsl:element>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<!--
|
||||
From <String Id="foo">Bar</String> to <String Id="foo" Value="Bar"/>
|
||||
-->
|
||||
<xsl:template match="wix3loc:WixLocalization/wix3loc:String">
|
||||
<xsl:element name="{local-name()}" namespace="http://wixtoolset.org/schemas/v4/wxl">
|
||||
<xsl:attribute name="Value">
|
||||
<xsl:value-of select="text()"/>
|
||||
</xsl:attribute>
|
||||
<xsl:apply-templates select="@*"/>
|
||||
</xsl:element>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<!--
|
||||
Wix3 Product (https://wixtoolset.org/docs/v3/xsd/wix/product/):
|
||||
Id
|
||||
Codepage
|
||||
Language
|
||||
Manufacturer
|
||||
Name
|
||||
UpgradeCode
|
||||
Version
|
||||
|
||||
Wix3 Package (https://wixtoolset.org/docs/v3/xsd/wix/package/):
|
||||
AdminImage
|
||||
Comments
|
||||
Compressed
|
||||
Description
|
||||
Id
|
||||
InstallerVersion
|
||||
InstallPrivileges
|
||||
InstallScope
|
||||
Keywords
|
||||
Languages
|
||||
Manufacturer
|
||||
Platform
|
||||
Platforms
|
||||
ReadOnly
|
||||
ShortNames
|
||||
SummaryCodepage
|
||||
|
||||
Wix4 Package (https://wixtoolset.org/docs/schema/wxs/package/):
|
||||
Codepage <- Wix3:Product/@Codepage
|
||||
Compressed <- Wix3:@Compressed
|
||||
InstallerVersion <- Wix3:@InstallerVersion
|
||||
Language <- Wix3:Product/@Language
|
||||
Manufacturer <- Wix3:Product/@Manufacturer
|
||||
Name <- Wix3:Product/@Name
|
||||
ProductCode <- Wix3:Product/@Id
|
||||
Scope <- Wix3:@InstallScope
|
||||
ShortNames <- Wix3:@ShortNames
|
||||
UpgradeCode <- Wix3:Product/@UpgradeCode
|
||||
UpgradeStrategy <-
|
||||
Version <- Wix3:Product/@Version
|
||||
|
||||
Wix4 SummaryInformation (https://wixtoolset.org/docs/schema/wxs/summaryinformation/):
|
||||
Codepage <- Wix3:Product/@Codepage
|
||||
Comments <- Wix3:@Comments
|
||||
Description <- Wix3:@Description
|
||||
Keywords <- Wix3:@Keywords
|
||||
Manufacturer <- Wix3:Product/@Manufacturer
|
||||
-->
|
||||
|
||||
<xsl:template match="wix3:Product">
|
||||
<xsl:element name="Package" namespace="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<xsl:apply-templates select="@Codepage|wix3:Package/@Compressed|wix3:Package/@InstallerVersion|@Language|@Manufacturer|@Name|@Id|wix3:Package/@InstallScope|wix3:Package/@ShortNames|@UpgradeCode|@Version"/>
|
||||
<xsl:if test="@Id">
|
||||
<xsl:attribute name="ProductCode">
|
||||
<xsl:value-of select="@Id"/>
|
||||
</xsl:attribute>
|
||||
</xsl:if>
|
||||
<xsl:if test="wix3:Package/@InstallScope">
|
||||
<xsl:attribute name="Scope">
|
||||
<xsl:value-of select="wix3:Package/@InstallScope"/>
|
||||
</xsl:attribute>
|
||||
</xsl:if>
|
||||
<xsl:element name="SummaryInformation" namespace="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<xsl:apply-templates select="@Codepage|wix3:Package/@Comments|wix3:Package/@Description|wix3:Package/@Keywords|@Manufacturer"/>
|
||||
</xsl:element>
|
||||
<xsl:apply-templates select="node()"/>
|
||||
</xsl:element>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="wix3:Package|wix3:Product/@Id|wix3:Package/@InstallScope"/>
|
||||
|
||||
|
||||
<xsl:template match="wix3:CustomAction/@BinaryKey">
|
||||
<xsl:attribute name="BinaryRef">
|
||||
<xsl:value-of select="."/>
|
||||
</xsl:attribute>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:template match="wix3:Custom|wix3:Publish">
|
||||
<xsl:element name="{local-name()}" namespace="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<xsl:apply-templates select="@*"/>
|
||||
<xsl:if test="text()">
|
||||
<xsl:attribute name="Condition">
|
||||
<xsl:value-of select="text()"/>
|
||||
</xsl:attribute>
|
||||
</xsl:if>
|
||||
<xsl:apply-templates select="node()"/>
|
||||
</xsl:element>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="wix3:Custom/text()|wix3:Publish/text()"/>
|
||||
|
||||
|
||||
<xsl:template match="wix3:Directory[@Id='TARGETDIR']"/>
|
||||
|
||||
|
||||
<!--
|
||||
Identity transform
|
||||
-->
|
||||
<xsl:template match="@*|node()">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="@*|node()"/>
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2024, 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
|
||||
@ -139,6 +139,30 @@ public class WindowsHelper {
|
||||
String.format("TARGETDIR=\"%s\"",
|
||||
unpackDir.toAbsolutePath().normalize())))));
|
||||
runMsiexecWithRetries(Executor.of("cmd", "/c", unpackBat.toString()));
|
||||
|
||||
//
|
||||
// WiX3 uses "." as the value of "DefaultDir" field for "ProgramFiles64Folder" folder in msi's Directory table
|
||||
// WiX4 uses "PFiles64" as the value of "DefaultDir" field for "ProgramFiles64Folder" folder in msi's Directory table
|
||||
// msiexec creates "Program Files/./<App Installation Directory>" from WiX3 msi which translates to "Program Files/<App Installation Directory>"
|
||||
// msiexec creates "Program Files/PFiles64/<App Installation Directory>" from WiX4 msi
|
||||
// So for WiX4 msi we need to transform "Program Files/PFiles64/<App Installation Directory>" into "Program Files/<App Installation Directory>"
|
||||
//
|
||||
// WiX4 does the same thing for %LocalAppData%.
|
||||
//
|
||||
for (var extraPathComponent : List.of("PFiles64", "LocalApp")) {
|
||||
if (Files.isDirectory(unpackDir.resolve(extraPathComponent))) {
|
||||
Path installationSubDirectory = getInstallationSubDirectory(cmd);
|
||||
Path from = Path.of(extraPathComponent).resolve(installationSubDirectory);
|
||||
Path to = installationSubDirectory;
|
||||
TKit.trace(String.format("Convert [%s] into [%s] in [%s] directory", from, to,
|
||||
unpackDir));
|
||||
ThrowingRunnable.toRunnable(() -> {
|
||||
Files.createDirectories(unpackDir.resolve(to).getParent());
|
||||
Files.move(unpackDir.resolve(from), unpackDir.resolve(to));
|
||||
TKit.deleteDirectoryRecursive(unpackDir.resolve(extraPathComponent));
|
||||
}).run();
|
||||
}
|
||||
}
|
||||
return destinationDir;
|
||||
};
|
||||
return msi;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2024, 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
|
||||
@ -33,6 +33,7 @@ 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.Executor;
|
||||
|
||||
@ -55,11 +56,11 @@ import static jdk.jpackage.test.WindowsHelper.getTempDirectory;
|
||||
public class WinL10nTest {
|
||||
|
||||
public WinL10nTest(WixFileInitializer wxlFileInitializers[],
|
||||
String expectedCulture, String expectedErrorMessage,
|
||||
String[] expectedCultures, String expectedErrorMessage,
|
||||
String userLanguage, String userCountry,
|
||||
boolean enableWixUIExtension) {
|
||||
this.wxlFileInitializers = wxlFileInitializers;
|
||||
this.expectedCulture = expectedCulture;
|
||||
this.expectedCultures = expectedCultures;
|
||||
this.expectedErrorMessage = expectedErrorMessage;
|
||||
this.userLanguage = userLanguage;
|
||||
this.userCountry = userCountry;
|
||||
@ -69,56 +70,65 @@ public class WinL10nTest {
|
||||
@Parameters
|
||||
public static List<Object[]> data() {
|
||||
return List.of(new Object[][]{
|
||||
{null, "en-us", null, null, null, false},
|
||||
{null, "en-us", null, "en", "US", false},
|
||||
{null, "en-us", null, "en", "US", true},
|
||||
{null, "de-de", null, "de", "DE", false},
|
||||
{null, "de-de", null, "de", "DE", true},
|
||||
{null, "ja-jp", null, "ja", "JP", false},
|
||||
{null, "ja-jp", null, "ja", "JP", true},
|
||||
{null, "zh-cn", null, "zh", "CN", false},
|
||||
{null, "zh-cn", null, "zh", "CN", true},
|
||||
{null, new String[] {"en-us"}, null, null, null, false},
|
||||
{null, new String[] {"en-us"}, null, "en", "US", false},
|
||||
{null, new String[] {"en-us"}, null, "en", "US", true},
|
||||
{null, new String[] {"de-de"}, null, "de", "DE", false},
|
||||
{null, new String[] {"de-de"}, null, "de", "DE", true},
|
||||
{null, new String[] {"ja-jp"}, null, "ja", "JP", false},
|
||||
{null, new String[] {"ja-jp"}, null, "ja", "JP", true},
|
||||
{null, new String[] {"zh-cn"}, null, "zh", "CN", false},
|
||||
{null, new String[] {"zh-cn"}, null, "zh", "CN", true},
|
||||
{new WixFileInitializer[] {
|
||||
WixFileInitializer.create("a.wxl", "en-us")
|
||||
}, "en-us", null, null, null, false},
|
||||
}, new String[] {"en-us"}, null, null, null, false},
|
||||
{new WixFileInitializer[] {
|
||||
WixFileInitializer.create("a.wxl", "fr")
|
||||
}, "fr;en-us", null, null, null, false},
|
||||
}, new String[] {"fr", "en-us"}, null, null, null, false},
|
||||
{new WixFileInitializer[] {
|
||||
WixFileInitializer.create("a.wxl", "fr"),
|
||||
WixFileInitializer.create("b.wxl", "fr")
|
||||
}, "fr;en-us", null, null, null, false},
|
||||
}, new String[] {"fr", "en-us"}, null, null, null, false},
|
||||
{new WixFileInitializer[] {
|
||||
WixFileInitializer.create("a.wxl", "it"),
|
||||
WixFileInitializer.create("b.wxl", "fr")
|
||||
}, "it;fr;en-us", null, null, null, false},
|
||||
}, new String[] {"it", "fr", "en-us"}, null, null, null, false},
|
||||
{new WixFileInitializer[] {
|
||||
WixFileInitializer.create("c.wxl", "it"),
|
||||
WixFileInitializer.create("b.wxl", "fr")
|
||||
}, "fr;it;en-us", null, null, null, false},
|
||||
}, new String[] {"fr", "it", "en-us"}, null, null, null, false},
|
||||
{new WixFileInitializer[] {
|
||||
WixFileInitializer.create("a.wxl", "fr"),
|
||||
WixFileInitializer.create("b.wxl", "it"),
|
||||
WixFileInitializer.create("c.wxl", "fr"),
|
||||
WixFileInitializer.create("d.wxl", "it")
|
||||
}, "fr;it;en-us", null, null, null, false},
|
||||
}, new String[] {"fr", "it", "en-us"}, null, null, null, false},
|
||||
{new WixFileInitializer[] {
|
||||
WixFileInitializer.create("c.wxl", "it"),
|
||||
WixFileInitializer.createMalformed("b.wxl")
|
||||
}, null, null, null, null, false},
|
||||
{new WixFileInitializer[] {
|
||||
WixFileInitializer.create("MsiInstallerStrings_de.wxl", "de")
|
||||
}, "en-us", null, null, null, false}
|
||||
}, new String[] {"en-us"}, null, null, null, false}
|
||||
});
|
||||
}
|
||||
|
||||
private static Stream<String> getLightCommandLine(
|
||||
Executor.Result result) {
|
||||
return result.getOutput().stream().filter(s -> {
|
||||
private static Stream<String> getBuildCommandLine(Executor.Result result) {
|
||||
return result.getOutput().stream().filter(createToolCommandLinePredicate("light").or(
|
||||
createToolCommandLinePredicate("wix")));
|
||||
}
|
||||
|
||||
private static boolean isWix3(Executor.Result result) {
|
||||
return result.getOutput().stream().anyMatch(createToolCommandLinePredicate("light"));
|
||||
}
|
||||
|
||||
private final static Predicate<String> createToolCommandLinePredicate(String wixToolName) {
|
||||
var toolFileName = wixToolName + ".exe";
|
||||
return (s) -> {
|
||||
s = s.trim();
|
||||
return s.startsWith("light.exe") || ((s.contains("\\light.exe ")
|
||||
&& s.contains(" -out ")));
|
||||
});
|
||||
return s.startsWith(toolFileName) || ((s.contains(String.format("\\%s ", toolFileName)) && s.
|
||||
contains(" -out ")));
|
||||
};
|
||||
}
|
||||
|
||||
private static List<TKit.TextStreamVerifier> createDefaultL10nFilesLocVerifiers(Path tempDir) {
|
||||
@ -148,14 +158,23 @@ public class WinL10nTest {
|
||||
// 2. Instruct test to save jpackage output.
|
||||
cmd.setFakeRuntime().saveConsoleOutput(true);
|
||||
|
||||
boolean withJavaOptions = false;
|
||||
|
||||
// Set JVM default locale that is used to select primary l10n file.
|
||||
if (userLanguage != null) {
|
||||
withJavaOptions = true;
|
||||
cmd.addArguments("-J-Duser.language=" + userLanguage);
|
||||
}
|
||||
if (userCountry != null) {
|
||||
withJavaOptions = true;
|
||||
cmd.addArguments("-J-Duser.country=" + userCountry);
|
||||
}
|
||||
|
||||
if (withJavaOptions) {
|
||||
// Use jpackage as a command to allow "-J" options come through
|
||||
cmd.useToolProvider(false);
|
||||
}
|
||||
|
||||
// Cultures handling is affected by the WiX extensions used.
|
||||
// By default only WixUtilExtension is used, this flag
|
||||
// additionally enables WixUIExtension.
|
||||
@ -169,9 +188,16 @@ public class WinL10nTest {
|
||||
cmd.addArguments("--temp", tempDir.toString());
|
||||
})
|
||||
.addBundleVerifier((cmd, result) -> {
|
||||
if (expectedCulture != null) {
|
||||
TKit.assertTextStream("-cultures:" + expectedCulture).apply(
|
||||
getLightCommandLine(result));
|
||||
if (expectedCultures != null) {
|
||||
String expected;
|
||||
if (isWix3(result)) {
|
||||
expected = "-cultures:" + String.join(";", expectedCultures);
|
||||
} else {
|
||||
expected = Stream.of(expectedCultures).map(culture -> {
|
||||
return String.join(" ", "-culture", culture);
|
||||
}).collect(Collectors.joining(" "));
|
||||
}
|
||||
TKit.assertTextStream(expected).apply(getBuildCommandLine(result));
|
||||
}
|
||||
|
||||
if (expectedErrorMessage != null) {
|
||||
@ -180,24 +206,24 @@ public class WinL10nTest {
|
||||
}
|
||||
|
||||
if (wxlFileInitializers != null) {
|
||||
var wixSrcDir = Path.of(cmd.getArgumentValue("--temp")).resolve("config");
|
||||
|
||||
if (allWxlFilesValid) {
|
||||
for (var v : wxlFileInitializers) {
|
||||
if (!v.name.startsWith("MsiInstallerStrings_")) {
|
||||
v.createCmdOutputVerifier(resourceDir).apply(
|
||||
getLightCommandLine(result));
|
||||
v.createCmdOutputVerifier(wixSrcDir).apply(getBuildCommandLine(result));
|
||||
}
|
||||
}
|
||||
Path tempDir = getTempDirectory(cmd, tempRoot).toAbsolutePath();
|
||||
for (var v : createDefaultL10nFilesLocVerifiers(tempDir)) {
|
||||
v.apply(getLightCommandLine(result));
|
||||
v.apply(getBuildCommandLine(result));
|
||||
}
|
||||
} else {
|
||||
Stream.of(wxlFileInitializers)
|
||||
.filter(Predicate.not(WixFileInitializer::isValid))
|
||||
.forEach(v -> v.createCmdOutputVerifier(
|
||||
resourceDir).apply(result.getOutput().stream()));
|
||||
TKit.assertFalse(
|
||||
getLightCommandLine(result).findAny().isPresent(),
|
||||
wixSrcDir).apply(result.getOutput().stream()));
|
||||
TKit.assertFalse(getBuildCommandLine(result).findAny().isPresent(),
|
||||
"Check light.exe was not invoked");
|
||||
}
|
||||
}
|
||||
@ -223,7 +249,7 @@ public class WinL10nTest {
|
||||
}
|
||||
|
||||
final private WixFileInitializer[] wxlFileInitializers;
|
||||
final private String expectedCulture;
|
||||
final private String[] expectedCultures;
|
||||
final private String expectedErrorMessage;
|
||||
final private String userLanguage;
|
||||
final private String userCountry;
|
||||
|
Loading…
Reference in New Issue
Block a user