From 1bd04b4fba705fc553f00e90780d05a0a4b3f4aa Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 16 May 2024 02:24:51 -0400 Subject: [PATCH 01/30] Can detect Wix v4 and v5. --- .../jdk/jpackage/internal/WinMsiBundler.java | 31 +-- .../jdk/jpackage/internal/WixPipeline.java | 54 ++-- .../jdk/jpackage/internal/WixTool.java | 233 ++++++++++++++---- 3 files changed, 233 insertions(+), 85 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java index 894d41d764204..7b4b16f3c835e 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java @@ -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.TEMP_ROOT; import static jdk.jpackage.internal.StandardBundlerParam.VENDOR; import static jdk.jpackage.internal.StandardBundlerParam.VERSION; +import jdk.jpackage.internal.WixTool.WixToolsetBase; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; @@ -253,7 +254,7 @@ public String getBundleType() { 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 boolean validate(Map params) appImageBundler.validate(params); if (wixToolset == null) { - wixToolset = WixTool.toolset(); + wixToolset = WixTool.createToolset(); } try { @@ -309,14 +310,16 @@ public boolean validate(Map params) throw new ConfigException(ex); } - for (var toolInfo: wixToolset.values()) { + for (var tool : wixToolset.getTools()) { Log.verbose(MessageFormat.format(I18N.getString( - "message.tool-version"), toolInfo.path.getFileName(), - toolInfo.version)); + "message.tool-version"), wixToolset.getToolPath(tool). + getFileName(), + wixToolset.getToolVersion(tool))); } wixFragments.forEach(wixFragment -> wixFragment.setWixVersion( - wixToolset.get(WixTool.Light).version)); + wixToolset.getToolVersion(wixToolset.getTools().iterator(). + next()))); wixFragments.get(0).logWixFeatures(); @@ -542,13 +545,11 @@ private Path buildMSI(Map params, .toString())); WixPipeline wixPipeline = new WixPipeline() - .setToolset(wixToolset.entrySet().stream().collect( - Collectors.toMap( - entry -> entry.getKey(), - entry -> entry.getValue().path))) - .setWixObjDir(TEMP_ROOT.fetchFrom(params).resolve("wixobj")) - .setWorkDir(WIN_APP_IMAGE.fetchFrom(params)) - .addSource(CONFIG_ROOT.fetchFrom(params).resolve("main.wxs"), wixVars); + .setToolset(wixToolset) + .setWixObjDir(TEMP_ROOT.fetchFrom(params).resolve("wixobj")) + .setWorkDir(WIN_APP_IMAGE.fetchFrom(params)) + .addSource(CONFIG_ROOT.fetchFrom(params).resolve("main.wxs"), + wixVars); for (var wixFragment : wixFragments) { wixFragment.configureWixPipeline(wixPipeline); @@ -751,7 +752,7 @@ private static OverridableResource initServiceInstallerResource( } private Path installerIcon; - private Map wixToolset; + private WixToolsetBase wixToolset; private AppImageBundler appImageBundler; private final List wixFragments; } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java index 58a07b6cbafd8..004b9b81a0ce3 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java @@ -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,19 +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.List; import java.util.Map; import java.util.Objects; import java.util.function.UnaryOperator; import java.util.stream.Stream; +import jdk.jpackage.internal.WixTool.Wix3Toolset; +import jdk.jpackage.internal.WixTool.WixToolsetBase; /** * WiX pipeline. Compiles and links WiX sources. @@ -45,7 +45,7 @@ public class WixPipeline { lightOptions = new ArrayList<>(); } - WixPipeline setToolset(Map v) { + WixPipeline setToolset(WixToolsetBase v) { toolset = v; return this; } @@ -79,13 +79,25 @@ WixPipeline addLightOptions(String ... v) { } void buildMsi(Path msi) throws IOException { + if (toolset instanceof Wix3Toolset) { + buildMsiWix3(msi); + } else { + buildMsiWix4(msi); + } + } + + private void buildMsiWix4(Path msi) throws IOException { + + } + + private void buildMsiWix3(Path msi) throws IOException { List wixObjs = new ArrayList<>(); for (var source : sources) { - wixObjs.add(compile(source)); + wixObjs.add(compileWix3(source)); } List lightCmdline = new ArrayList<>(List.of( - toolset.get(WixTool.Light).toString(), + toolset.getToolPath(WixTool.Light3).toString(), "-nologo", "-spdb", "-ext", "WixUtilExtension", @@ -99,7 +111,7 @@ void buildMsi(Path msi) throws IOException { execute(lightCmdline); } - private Path compile(WixSource wixSource) throws IOException { + private Path compileWix3(WixSource wixSource) throws IOException { UnaryOperator adjustPath = path -> { return workDir != null ? path.toAbsolutePath() : path; }; @@ -108,7 +120,7 @@ private Path compile(WixSource wixSource) throws IOException { IOUtils.getFileName(wixSource.source), ".wixobj")); List cmdline = new ArrayList<>(List.of( - toolset.get(WixTool.Candle).toString(), + toolset.getToolPath(WixTool.Candle3).toString(), "-nologo", adjustPath.apply(wixSource.source).toString(), "-ext", "WixUtilExtension", @@ -116,14 +128,22 @@ private Path compile(WixSource wixSource) throws IOException { "-out", wixObj.toAbsolutePath().toString() )); - Map 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); + Stream.of(wixVariables, wixSource.variables).filter(Objects::nonNull). + reduce((a, b) -> { + a.putAll(b); + return a; + }).ifPresent(wixVars -> { + wixVars.entrySet().stream().map(wixVar -> { + return String.format("-d%s=%s", wixVar.getKey(), wixVar. + getValue()); + }).reduce(cmdline, (ctnr, wixVar) -> { + ctnr.add(wixVar); + return ctnr; + }, (x, y) -> { + x.addAll(y); + return x; + }); + }); execute(cmdline); @@ -140,7 +160,7 @@ private static final class WixSource { Map variables; } - private Map toolset; + private WixToolsetBase toolset; private Map wixVariables; private List lightOptions; private Path wixObjDir; diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java index 68104444b3c11..128a10f69e11f 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java @@ -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,12 +31,16 @@ import java.nio.file.Path; import java.nio.file.PathMatcher; import java.text.MessageFormat; -import java.util.Collections; +import java.util.ArrayList; 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.Set; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -46,9 +49,17 @@ * WiX tool. */ public enum WixTool { - Candle, Light; + Candle3("candle", DottedVersion.lazy("3.0")), + Light3("light", DottedVersion.lazy("3.0")), + Wix("wix", DottedVersion.lazy("4.0.4")); + + WixTool(String commandName, DottedVersion minimalVersion) { + this.toolFileName = IOUtils.addSuffix(Path.of(commandName), ".exe"); + this.minimalVersion = minimalVersion; + } + + private static final class ToolInfo { - static final class ToolInfo { ToolInfo(Path path, String version) { this.path = path; this.version = new DottedVersion(version); @@ -58,49 +69,135 @@ static final class ToolInfo { final DottedVersion version; } - static Map toolset() throws ConfigException { - Map toolset = new HashMap<>(); - for (var tool : values()) { - toolset.put(tool, tool.find()); + static abstract class WixToolsetBase { + + WixToolsetBase(Map tools) { + this.tools = tools; + } + + Set getTools() { + return tools.keySet(); + } + + Path getToolPath(WixTool tool) { + return tools.get(tool).path; + } + + DottedVersion getToolVersion(WixTool tool) { + return tools.get(tool).version; + } + + static T create(Set required, + Map allTools, + Function, T> ctor) { + var filteredTools = allTools.entrySet().stream().filter(e -> { + return required.contains(e.getKey()) && e.getValue() != null; + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + if (filteredTools.keySet().equals(required)) { + return ctor.apply(filteredTools); + } else { + return null; + } + } + + private final Map tools; + } + + static final class EmptyWixToolset extends WixToolsetBase { + + private EmptyWixToolset() { + super(Map.of()); + } + } + + static final class Wix3Toolset extends WixToolsetBase { + + static Wix3Toolset create(Map tools) { + return WixToolsetBase. + create(getWix3Tools(), tools, Wix3Toolset::new); + } + + private Wix3Toolset(Map tools) { + super(tools); } - return toolset; } - ToolInfo find() throws ConfigException { - final Path toolFileName = IOUtils.addSuffix( - Path.of(name().toLowerCase()), ".exe"); + static final class WixToolset extends WixToolsetBase { + + static WixToolset create(Map tools) { + return WixToolsetBase.create(Set.of(Wix), tools, WixToolset::new); + } + private WixToolset(Map tools) { + super(tools); + } + } + + static WixToolsetBase createToolset() throws ConfigException { + Map tools = new HashMap<>(); + + var wixInstallDirsSupplier = new Supplier>() { + @Override + public List get() { + if (wixInstallDirs == null) { + wixInstallDirs = findWixInstallDirs(); + } + return wixInstallDirs; + } + private List wixInstallDirs; + }; + + for (var tool : values()) { + ToolInfo info = tool.find(wixInstallDirsSupplier); + if (info != null) { + tools.put(tool, info); + } + } + + Stream, WixToolsetBase>> toolsetCtors = Stream. + of(WixToolset::create, Wix3Toolset::create); + return toolsetCtors.map(toolsetCtor -> { + return toolsetCtor.apply(tools); + }).filter(Objects::nonNull).findFirst().orElseGet(EmptyWixToolset::new); + } + + private ToolInfo find(Supplier> findWixInstallDirs) throws ConfigException { String[] version = new String[1]; - ConfigException reason = createToolValidator(toolFileName, version).get(); + ConfigException err = createToolValidator(toolFileName, + v -> version[0] = v).get(); if (version[0] != null) { - if (reason == null) { + if (err == null) { // Found in PATH. return new ToolInfo(toolFileName, version[0]); } // Found in PATH, but something went wrong. - throw reason; + throw err; } - for (var dir : findWixInstallDirs()) { + for (var dir : findWixInstallDirs.get()) { Path path = dir.resolve(toolFileName); if (Files.exists(path)) { - reason = createToolValidator(path, version).get(); - if (reason != null) { - throw reason; + err = createToolValidator(path, v -> version[0] = v).get(); + if (err != null) { + throw err; } return new ToolInfo(path, version[0]); } } - throw reason; + throw err; + } + + private static Set getWix3Tools() { + return Set.of(Candle3, Light3); } - private static Supplier createToolValidator(Path toolPath, - String[] versionCtnr) { - return new ToolValidator(toolPath) - .setCommandLine("/?") - .setMinimalVersion(MINIMAL_VERSION) + private Supplier createToolValidator(Path toolPath, + Consumer versionConsumer) { + final var toolValidator = new ToolValidator(toolPath) + .setMinimalVersion(minimalVersion) .setToolNotFoundErrorHandler( (name, ex) -> new ConfigException( I18N.getString("error.no-wix-tools"), @@ -109,28 +206,45 @@ private static Supplier createToolValidator(Path toolPath, (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] = ""; - String firstLineOfOutput = output.findFirst().orElse(""); - int separatorIdx = firstLineOfOutput.lastIndexOf(' '); - if (separatorIdx == -1) { - return null; - } - versionCtnr[0] = firstLineOfOutput.substring(separatorIdx + 1); - return versionCtnr[0]; - })::validate; - } + version, minimalVersion), + I18N.getString("error.no-wix-tools.advice"))); + + final Function, String> versionParser; + + if (getWix3Tools().contains(this)) { + toolValidator.setCommandLine("/?"); + versionParser = output -> { + String firstLineOfOutput = output.findFirst().orElse(""); + int separatorIdx = firstLineOfOutput.lastIndexOf(' '); + if (separatorIdx == -1) { + return null; + } + return firstLineOfOutput.substring(separatorIdx + 1); + }; + } else { + toolValidator.setCommandLine("--version"); + versionParser = output -> { + // "wix --version" command prints out "5.0.0+41e11442". + // Strip trailing "+41e11442" as it breaks version parser. + return output.findFirst().orElse("").split("\\+", 2)[0]; + }; + } - private static final DottedVersion MINIMAL_VERSION = DottedVersion.lazy("3.0"); + toolValidator.setVersionParser(output -> { + var version = versionParser.apply(output); + versionConsumer.accept(version); + return version; + }); - static Path getSystemDir(String envVar, String knownDir) { + return toolValidator::validate; + } + + private static Path getSystemDir(String envVar, String knownDir) { return Optional .ofNullable(getEnvVariableAsPath(envVar)) .orElseGet(() -> Optional - .ofNullable(getEnvVariableAsPath("SystemDrive")) - .orElseGet(() -> Path.of("C:")).resolve(knownDir)); + .ofNullable(getEnvVariableAsPath("SystemDrive")) + .orElseGet(() -> Path.of("C:")).resolve(knownDir)); } private static Path getEnvVariableAsPath(String envVar) { @@ -147,8 +261,19 @@ private static Path getEnvVariableAsPath(String envVar) { } private static List findWixInstallDirs() { - PathMatcher wixInstallDirMatcher = FileSystems.getDefault().getPathMatcher( - "glob:WiX Toolset v*"); + return Stream.of(findWixCurrentInstallDirs(), findWix3InstallDirs()). + flatMap(List::stream).toList(); + } + + private static List findWixCurrentInstallDirs() { + return Stream.of(getEnvVariableAsPath("USERPROFILE").resolve( + ".dotnet/tools")).filter(Files::isDirectory).toList(); + } + + private static List findWix3InstallDirs() { + PathMatcher wixInstallDirMatcher = FileSystems.getDefault(). + getPathMatcher( + "glob:WiX Toolset v*"); Path programFiles = getSystemDir("ProgramFiles", "\\Program Files"); Path programFilesX86 = getSystemDir("ProgramFiles(x86)", @@ -157,18 +282,20 @@ private static List findWixInstallDirs() { // Returns list of WiX install directories ordered by WiX version number. // Newer versions go first. return Stream.of(programFiles, programFilesX86).map(path -> { - List result; try (var paths = Files.walk(path, 1)) { - result = paths.toList(); + return paths.toList(); } catch (IOException ex) { Log.verbose(ex); - result = Collections.emptyList(); + List empty = List.of(); + return empty; } - return result; }).flatMap(List::stream) - .filter(path -> wixInstallDirMatcher.matches(path.getFileName())) - .sorted(Comparator.comparing(Path::getFileName).reversed()) - .map(path -> path.resolve("bin")) - .toList(); + .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; } From fb0e2ee058007d78829507c9c0d19c9b1fd387ae Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 16 May 2024 16:25:34 -0400 Subject: [PATCH 02/30] SimplePackageTest is a pass with manual "wix convert" on main.wxs, overrides.wxi, and l10n .wxl files --- .../jdk/jpackage/internal/WinMsiBundler.java | 11 +- .../internal/WixAppImageFragmentBuilder.java | 32 ++-- .../jpackage/internal/WixFragmentBuilder.java | 48 ++++-- .../jdk/jpackage/internal/WixPipeline.java | 152 ++++++++++++++---- .../jdk/jpackage/internal/WixTool.java | 83 ++-------- .../jdk/jpackage/internal/WixToolset.java | 88 ++++++++++ 6 files changed, 279 insertions(+), 135 deletions(-) create mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java index 7b4b16f3c835e..7280e838854c0 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java @@ -67,7 +67,6 @@ 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.WixTool.WixToolsetBase; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; @@ -310,16 +309,14 @@ public boolean validate(Map params) throw new ConfigException(ex); } - for (var tool : wixToolset.getTools()) { + for (var tool : wixToolset.getType().getTools()) { Log.verbose(MessageFormat.format(I18N.getString( "message.tool-version"), wixToolset.getToolPath(tool). - getFileName(), - wixToolset.getToolVersion(tool))); + getFileName(), wixToolset.getVersion())); } wixFragments.forEach(wixFragment -> wixFragment.setWixVersion( - wixToolset.getToolVersion(wixToolset.getTools().iterator(). - next()))); + wixToolset.getType())); wixFragments.get(0).logWixFeatures(); @@ -752,7 +749,7 @@ private static OverridableResource initServiceInstallerResource( } private Path installerIcon; - private WixToolsetBase wixToolset; + private WixToolset wixToolset; private AppImageBundler appImageBundler; private final List wixFragments; } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java index 7578712ab4d5b..9db3395db4c36 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java @@ -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 @@ -64,6 +64,7 @@ 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; /** @@ -314,12 +315,22 @@ boolean isFile() { return cfg.isFile; } - static void startElement(XMLStreamWriter xml, String componentId, + static void startElement(WixToolsetType wixVersion, XMLStreamWriter xml, String componentId, String componentGuid) throws XMLStreamException, IOException { xml.writeStartElement("Component"); - xml.writeAttribute("Win64", is64Bit() ? "yes" : "no"); + switch (wixVersion) { + case Wix3, Wix36 -> { + xml.writeAttribute("Win64", is64Bit() ? "yes" : "no"); + xml.writeAttribute("Guid", componentGuid); + } + default -> { + xml.writeAttribute("Bitness", is64Bit() ? "always64" : "always32"); + if (!componentGuid.equals("*")) { + xml.writeAttribute("Guid", componentGuid); + } + } + } xml.writeAttribute("Id", componentId); - xml.writeAttribute("Guid", componentGuid); } private static final class Config { @@ -374,8 +385,8 @@ private String addComponent(XMLStreamWriter xml, Path path, xml.writeAttribute("Id", Id.Folder.of(directoryRefPath)); final String componentId = "c" + role.idOf(path); - Component.startElement(xml, componentId, String.format("{%s}", - role.guidOf(path))); + Component.startElement(getWixVersion(), xml, componentId, String.format( + "{%s}", role.guidOf(path))); if (role == Component.Shortcut) { xml.writeStartElement("Condition"); @@ -785,7 +796,7 @@ private void addRegistryKeyPath(XMLStreamWriter xml, Path path, xml.writeStartElement("RegistryKey"); xml.writeAttribute("Root", regRoot); xml.writeAttribute("Key", registryKeyPath); - if (getWixVersion().compareTo("3.6") < 0) { + if (getWixVersion() == WixToolsetType.Wix3) { xml.writeAttribute("Action", "createAndRemoveOnUninstall"); } xml.writeStartElement("RegistryValue"); @@ -799,7 +810,7 @@ private void addRegistryKeyPath(XMLStreamWriter xml, Path path, private String addDirectoryCleaner(XMLStreamWriter xml, Path path) throws XMLStreamException, IOException { - if (getWixVersion().compareTo("3.6") < 0) { + if (getWixVersion() == WixToolsetType.Wix3) { return null; } @@ -821,14 +832,13 @@ private String addDirectoryCleaner(XMLStreamWriter xml, Path path) throws xml.writeStartElement("DirectoryRef"); xml.writeAttribute("Id", INSTALLDIR.toString()); - Component.startElement(xml, componentId, "*"); + Component.startElement(getWixVersion(), 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); diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java index 5671701e7a9fd..ff83e0bc809a1 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, 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 @@ -43,13 +43,14 @@ import static jdk.jpackage.internal.OverridableResource.createResource; import static jdk.jpackage.internal.StandardBundlerParam.CONFIG_ROOT; import jdk.internal.util.Architecture; +import jdk.jpackage.internal.WixToolset.WixToolsetType; /** * Creates WiX fragment. */ abstract class WixFragmentBuilder { - void setWixVersion(DottedVersion v) { + void setWixVersion(WixToolsetType v) { wixVersion = v; } @@ -66,7 +67,7 @@ void initFromParams(Map params) { } void logWixFeatures() { - if (wixVersion.compareTo("3.6") >= 0) { + if (wixVersion != WixToolsetType.Wix3) { Log.verbose(MessageFormat.format(I18N.getString( "message.use-wix36-features"), wixVersion)); } @@ -98,10 +99,32 @@ void addFilesToConfigRoot() throws IOException { } } - DottedVersion getWixVersion() { + WixToolsetType getWixVersion() { return wixVersion; } + static enum WixNamespace { + Default, + Util; + } + + Map getWixNamespaces() { + switch (wixVersion) { + case Wix4 -> { + return Map.of(WixNamespace.Default, + "http://wixtoolset.org/schemas/v4/wxs", + WixNamespace.Util, + "http://wixtoolset.org/schemas/v4/wxs/util"); + } + default -> { + return Map.of(WixNamespace.Default, + "http://schemas.microsoft.com/wix/2006/wi", + WixNamespace.Util, + "http://schemas.microsoft.com/wix/UtilExtension"); + } + } + } + static boolean is64Bit() { return Architecture.is64bit(); } @@ -130,13 +153,18 @@ protected void addResource(OverridableResource resource, String saveAsName) { additionalResources.add(new ResourceWithName(resource, 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[]{ @@ -208,7 +236,7 @@ private String escape(CharSequence str) { private final XMLStreamWriter target; } - private DottedVersion wixVersion; + private WixToolsetType wixVersion; private WixVariables wixVariables; private List additionalResources; private OverridableResource fragmentResource; diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java index 004b9b81a0ce3..94365096b2f71 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java @@ -28,13 +28,15 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +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; -import jdk.jpackage.internal.WixTool.Wix3Toolset; -import jdk.jpackage.internal.WixTool.WixToolsetBase; +import static jdk.jpackage.internal.WixToolset.WixToolsetType.Wix4; /** * WiX pipeline. Compiles and links WiX sources. @@ -45,7 +47,7 @@ public class WixPipeline { lightOptions = new ArrayList<>(); } - WixPipeline setToolset(WixToolsetBase v) { + WixPipeline setToolset(WixToolset v) { toolset = v; return this; } @@ -79,15 +81,118 @@ WixPipeline addLightOptions(String ... v) { } void buildMsi(Path msi) throws IOException { - if (toolset instanceof Wix3Toolset) { - buildMsiWix3(msi); - } else { - buildMsiWix4(msi); + Objects.requireNonNull(workDir); + + switch (toolset.getType()) { + case Wix4 -> + buildMsiWix4(msi); + case Wix36, Wix3 -> + buildMsiWix3(msi); } } + private void addWixVariblesToCommandLine( + Map otherWixVariables, List 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 stream; + switch (toolset.getType()) { + case Wix4 -> { + stream = entryStream.map(wixVar -> { + return Stream.of("-d", String.format("%s=%s", wixVar. + getKey(), wixVar.getValue())); + }).flatMap(Function.identity()); + } + case Wix3, Wix36 -> { + stream = entryStream.map(wixVar -> { + return String.format("-d%s=%s", wixVar.getKey(), wixVar. + getValue()); + }); + } + default -> + throw new IllegalStateException(); + } + + stream.reduce(cmdline, (ctnr, wixVar) -> { + ctnr.add(wixVar); + return ctnr; + }, (x, y) -> { + x.addAll(y); + return x; + }); + }); + } + + private static List convLightOptionWix4(List options) { + final var culturesPrefix = "-cultures:"; + final var sicePrefix = "-sice:"; + + return options.stream().map(op -> { + if (op.startsWith(culturesPrefix)) { + op = op.substring(culturesPrefix.length()); + return Stream.of(op.split("[:,]")).map(culture -> { + return Stream.of("-culture", culture); + }).flatMap(Function.identity()); + } else if (op.startsWith(sicePrefix)) { + // Don't know how to translate "-sice:" light option + return null; + } + return Stream.of(op); + }).filter(Objects::nonNull).flatMap(Function.identity()).toList(); + } + + private void convertWix3SourcesToWix4() throws IOException { + List cmdline = new ArrayList<>(List.of( + toolset.getToolPath(WixTool.Wix4).toString(), + "convert", + "-nologo" + )); + + cmdline.addAll(lightOptions.stream().filter(op -> { + return op.endsWith(".wxl"); + }).toList()); + + cmdline.addAll(sources.stream().map(wixSource -> { + return wixSource.source.toAbsolutePath().toString(); + }).toList()); + + Executor.of(new ProcessBuilder(cmdline)).execute(); + } + 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 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(convLightOptionWix4(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 { @@ -112,38 +217,19 @@ private void buildMsiWix3(Path msi) throws IOException { } private Path compileWix3(WixSource wixSource) throws IOException { - UnaryOperator adjustPath = path -> { - return workDir != null ? path.toAbsolutePath() : path; - }; - - Path wixObj = adjustPath.apply(wixObjDir).resolve(IOUtils.replaceSuffix( + Path wixObj = wixObjDir.toAbsolutePath().resolve(IOUtils.replaceSuffix( IOUtils.getFileName(wixSource.source), ".wixobj")); List cmdline = new ArrayList<>(List.of( 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() )); - Stream.of(wixVariables, wixSource.variables).filter(Objects::nonNull). - reduce((a, b) -> { - a.putAll(b); - return a; - }).ifPresent(wixVars -> { - wixVars.entrySet().stream().map(wixVar -> { - return String.format("-d%s=%s", wixVar.getKey(), wixVar. - getValue()); - }).reduce(cmdline, (ctnr, wixVar) -> { - ctnr.add(wixVar); - return ctnr; - }, (x, y) -> { - x.addAll(y); - return x; - }); - }); + addWixVariblesToCommandLine(wixSource.variables, cmdline); execute(cmdline); @@ -151,8 +237,8 @@ private Path compileWix3(WixSource wixSource) throws IOException { } private void execute(List 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 { @@ -160,7 +246,7 @@ private static final class WixSource { Map variables; } - private WixToolsetBase toolset; + private WixToolset toolset; private Map wixVariables; private List lightOptions; private Path wixObjDir; diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java index 128a10f69e11f..e374973417c0c 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java @@ -31,7 +31,6 @@ import java.nio.file.Path; import java.nio.file.PathMatcher; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -42,8 +41,8 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.Stream; +import jdk.jpackage.internal.WixToolset.WixToolsetType; /** * WiX tool. @@ -51,14 +50,14 @@ public enum WixTool { Candle3("candle", DottedVersion.lazy("3.0")), Light3("light", DottedVersion.lazy("3.0")), - Wix("wix", DottedVersion.lazy("4.0.4")); + Wix4("wix", DottedVersion.lazy("4.0.4")); WixTool(String commandName, DottedVersion minimalVersion) { this.toolFileName = IOUtils.addSuffix(Path.of(commandName), ".exe"); this.minimalVersion = minimalVersion; } - private static final class ToolInfo { + static final class ToolInfo { ToolInfo(Path path, String version) { this.path = path; @@ -69,72 +68,8 @@ private static final class ToolInfo { final DottedVersion version; } - static abstract class WixToolsetBase { - WixToolsetBase(Map tools) { - this.tools = tools; - } - - Set getTools() { - return tools.keySet(); - } - - Path getToolPath(WixTool tool) { - return tools.get(tool).path; - } - - DottedVersion getToolVersion(WixTool tool) { - return tools.get(tool).version; - } - - static T create(Set required, - Map allTools, - Function, T> ctor) { - var filteredTools = allTools.entrySet().stream().filter(e -> { - return required.contains(e.getKey()) && e.getValue() != null; - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - if (filteredTools.keySet().equals(required)) { - return ctor.apply(filteredTools); - } else { - return null; - } - } - - private final Map tools; - } - - static final class EmptyWixToolset extends WixToolsetBase { - - private EmptyWixToolset() { - super(Map.of()); - } - } - - static final class Wix3Toolset extends WixToolsetBase { - - static Wix3Toolset create(Map tools) { - return WixToolsetBase. - create(getWix3Tools(), tools, Wix3Toolset::new); - } - - private Wix3Toolset(Map tools) { - super(tools); - } - } - - static final class WixToolset extends WixToolsetBase { - - static WixToolset create(Map tools) { - return WixToolsetBase.create(Set.of(Wix), tools, WixToolset::new); - } - - private WixToolset(Map tools) { - super(tools); - } - } - - static WixToolsetBase createToolset() throws ConfigException { + static WixToolset createToolset() throws ConfigException { Map tools = new HashMap<>(); var wixInstallDirsSupplier = new Supplier>() { @@ -155,11 +90,11 @@ public List get() { } } - Stream, WixToolsetBase>> toolsetCtors = Stream. - of(WixToolset::create, Wix3Toolset::create); - return toolsetCtors.map(toolsetCtor -> { - return toolsetCtor.apply(tools); - }).filter(Objects::nonNull).findFirst().orElseGet(EmptyWixToolset::new); + return Stream.of(WixToolsetType.values()).map(toolsetType -> { + return WixToolset.create(toolsetType.getTools(), tools); + }).takeWhile(Objects::nonNull).findFirst().orElseGet(() -> { + return WixToolset.create(Set.of(), Map.of()); + }); } private ToolInfo find(Supplier> findWixInstallDirs) throws ConfigException { diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java new file mode 100644 index 0000000000000..86e2ba71b9bd9 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java @@ -0,0 +1,88 @@ +/* + * 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.0-v3.6 + Wix3(WixTool.Candle3, WixTool.Light3), + + // Wix v3.6+ + Wix36(WixTool.Candle3, WixTool.Light3); + + WixToolsetType(WixTool... tools) { + this.tools = Set.of(tools); + } + + Set getTools() { + return tools; + } + + private final Set tools; + } + + private WixToolset(Map 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 requiredTools, + Map allTools) { + var filteredTools = allTools.entrySet().stream().filter(e -> { + return requiredTools.contains(e.getKey()) && e.getValue() != null; + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + if (filteredTools.keySet().equals(requiredTools)) { + return new WixToolset(filteredTools); + } else { + return null; + } + } + + private final Map tools; +} From f78ebefee1d945d5d046b650de68297d2551a48a Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Fri, 17 May 2024 17:03:08 -0400 Subject: [PATCH 03/30] SimplePackageTest is a pass without manual tweaks --- make/modules/jdk.jpackage/Java.gmk | 2 +- .../jdk/jpackage/internal/WinMsiBundler.java | 47 ++-- .../internal/WixAppImageFragmentBuilder.java | 22 +- .../jdk/jpackage/internal/WixPipeline.java | 45 +--- .../jpackage/internal/WixSourceConverter.java | 217 ++++++++++++++++++ .../jpackage/internal/resources/overrides.wxi | 2 +- .../internal/resources/wix3-to-wix4-conv.xsl | 190 +++++++++++++++ .../jdk/jpackage/test/WindowsHelper.java | 15 +- 8 files changed, 477 insertions(+), 63 deletions(-) create mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java create mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/wix3-to-wix4-conv.xsl diff --git a/make/modules/jdk.jpackage/Java.gmk b/make/modules/jdk.jpackage/Java.gmk index acb8529f9ef0d..e1cf79ac27f59 100644 --- a/make/modules/jdk.jpackage/Java.gmk +++ b/make/modules/jdk.jpackage/Java.gmk @@ -25,6 +25,6 @@ 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 diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java index 7280e838854c0..3cc221f1dcdaa 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java @@ -67,6 +67,7 @@ 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; @@ -512,21 +513,28 @@ private Map prepareMainProjectFile( data.put("JpIsSystemWide", "yes"); } + var wxlResources = new WixSourceConverter.ResourceGroup(wixToolset.getType(), + "wix3-to-wix4-conv.xsl"); + // 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)); + wxlResources.addResource(createResource(fname, params).setPublicName(fname).setCategory( + I18N.getString("resource.wxl-file")), configDir.resolve(fname)); } - createResource("main.wxs", params) - .setCategory(I18N.getString("resource.main-wix-file")) - .saveToFile(configDir.resolve("main.wxs")); + var wxsResources = new WixSourceConverter.ResourceGroup(wixToolset.getType(), + "wix3-to-wix4-conv.xsl"); + + wxsResources.addResource(createResource("main.wxs", params).setPublicName("main.wxs"). + setCategory(I18N.getString("resource.main-wix-file")), configDir.resolve("main.wxs")); - createResource("overrides.wxi", params) - .setCategory(I18N.getString("resource.overrides-wix-file")) - .saveToFile(configDir.resolve("overrides.wxi")); + wxsResources.addResource(createResource("overrides.wxi", params).setPublicName( + "overrides.wxi").setCategory(I18N.getString("resource.overrides-wix-file")), + configDir.resolve("overrides.wxi")); + + wxlResources.saveResources(); + wxsResources.saveResources(); return data; } @@ -555,10 +563,12 @@ private Path buildMSI(Map params, Log.verbose(MessageFormat.format(I18N.getString( "message.generating-msi"), msiOut.toAbsolutePath().toString())); - wixPipeline.addLightOptions("-sice:ICE27"); + if (Set.of(WixToolsetType.Wix3, WixToolsetType.Wix36).contains(wixToolset.getType())) { + wixPipeline.addLightOptions("-sice:ICE27"); - if (!MSI_SYSTEM_WIDE.fetchFrom(params)) { - wixPipeline.addLightOptions("-sice:ICE91"); + if (!MSI_SYSTEM_WIDE.fetchFrom(params)) { + wixPipeline.addLightOptions("-sice:ICE91"); + } } // Filter out custom l10n files that were already used to @@ -596,8 +606,17 @@ private Path buildMSI(Map params, // Build ordered list of unique cultures. Set uniqueCultures = new LinkedHashSet<>(); uniqueCultures.addAll(cultures); - wixPipeline.addLightOptions(uniqueCultures.stream().collect( - Collectors.joining(";", "-cultures:", ""))); + switch (wixToolset.getType()) { + case Wix3, Wix36 -> { + wixPipeline.addLightOptions(uniqueCultures.stream().collect(Collectors.joining(";", + "-cultures:", ""))); + } + case Wix4 -> { + uniqueCultures.forEach(culture -> { + wixPipeline.addLightOptions("-culture", culture); + }); + } + } wixPipeline.buildMsi(msiOut.toAbsolutePath()); diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java index 9db3395db4c36..27c813f218f16 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java @@ -560,10 +560,17 @@ private List addRootBranch(XMLStreamWriter xml, Path path) Function createDirectoryName = dir -> null; boolean sysDir = true; - int levels = 1; + int levels; var dirIt = path.iterator(); - xml.writeStartElement("DirectoryRef"); - xml.writeAttribute("Id", dirIt.next().toString()); + + if (getWixVersion() == WixToolsetType.Wix4 && SYSTEM_DIRS.contains(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()) { @@ -582,7 +589,14 @@ private List addRootBranch(XMLStreamWriter xml, Path path) } else { directoryId = Id.Folder.of(path); } - xml.writeStartElement("Directory"); + + final String elementName; + if (getWixVersion() == WixToolsetType.Wix4 && sysDir) { + elementName = "StandardDirectory"; + } else { + elementName = "Directory"; + } + xml.writeStartElement(elementName); xml.writeAttribute("Id", directoryId); String directoryName = createDirectoryName.apply(path); diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java index 94365096b2f71..f459be8d0f4c3 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java @@ -36,7 +36,6 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import static jdk.jpackage.internal.WixToolset.WixToolsetType.Wix4; /** * WiX pipeline. Compiles and links WiX sources. @@ -84,10 +83,8 @@ void buildMsi(Path msi) throws IOException { Objects.requireNonNull(workDir); switch (toolset.getType()) { - case Wix4 -> - buildMsiWix4(msi); - case Wix36, Wix3 -> - buildMsiWix3(msi); + case Wix4 -> buildMsiWix4(msi); + case Wix36, Wix3 -> buildMsiWix3(msi); } } @@ -129,42 +126,6 @@ private void addWixVariblesToCommandLine( }); } - private static List convLightOptionWix4(List options) { - final var culturesPrefix = "-cultures:"; - final var sicePrefix = "-sice:"; - - return options.stream().map(op -> { - if (op.startsWith(culturesPrefix)) { - op = op.substring(culturesPrefix.length()); - return Stream.of(op.split("[:,]")).map(culture -> { - return Stream.of("-culture", culture); - }).flatMap(Function.identity()); - } else if (op.startsWith(sicePrefix)) { - // Don't know how to translate "-sice:" light option - return null; - } - return Stream.of(op); - }).filter(Objects::nonNull).flatMap(Function.identity()).toList(); - } - - private void convertWix3SourcesToWix4() throws IOException { - List cmdline = new ArrayList<>(List.of( - toolset.getToolPath(WixTool.Wix4).toString(), - "convert", - "-nologo" - )); - - cmdline.addAll(lightOptions.stream().filter(op -> { - return op.endsWith(".wxl"); - }).toList()); - - cmdline.addAll(sources.stream().map(wixSource -> { - return wixSource.source.toAbsolutePath().toString(); - }).toList()); - - Executor.of(new ProcessBuilder(cmdline)).execute(); - } - private void buildMsiWix4(Path msi) throws IOException { var mergedSrcWixVars = sources.stream().map(wixSource -> { return Optional.ofNullable(wixSource.variables).orElseGet( @@ -182,7 +143,7 @@ private void buildMsiWix4(Path msi) throws IOException { "-arch", WixFragmentBuilder.is64Bit() ? "x64" : "x86" )); - cmdline.addAll(convLightOptionWix4(lightOptions)); + cmdline.addAll(lightOptions); addWixVariblesToCommandLine(mergedSrcWixVars, cmdline); diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java new file mode 100644 index 0000000000000..5f10879c062b3 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java @@ -0,0 +1,217 @@ +/* + * 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.util.HashMap; +import java.util.Map; +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.stax.StAXResult; +import javax.xml.transform.stream.StreamSource; +import jdk.jpackage.internal.WixToolset.WixToolsetType; +import jdk.jpackage.internal.resources.ResourceLocator; + +/** + * Converts Wix v3 source file into Wix v4 format. + */ +final class WixSourceConverter { + + WixSourceConverter(String xsltResourceName) throws IOException { + var xslt = new StreamSource(ResourceLocator.class.getResourceAsStream(xsltResourceName)); + + TransformerFactory factory = TransformerFactory.newInstance(); + try { + this.transformer = factory.newTransformer(xslt); + } catch (TransformerException ex) { + // Should never happen + throw new RuntimeException(ex); + } + + this.outputFactory = XMLOutputFactory.newInstance(); + } + + void appyTo(OverridableResource resource, Path resourceSaveAsFile) throws IOException { + if (resource.saveToStream(null) != OverridableResource.Source.DefaultResource) { + // Don't convert external resources + resource.saveToFile(resourceSaveAsFile); + return; + } + + var buf = new ByteArrayOutputStream(); + resource.saveToStream(buf); + + Source input = new StreamSource(new ByteArrayInputStream(buf.toByteArray())); + + try (var outXml = Files.newOutputStream(resourceSaveAsFile)) { + XMLStreamWriter xmlWriter = (XMLStreamWriter) Proxy.newProxyInstance( + XMLStreamWriter.class.getClassLoader(), new Class[]{XMLStreamWriter.class}, + new NamespaceCleaner(outputFactory.createXMLStreamWriter(outXml))); + + transformer.transform(input, new StAXResult(xmlWriter)); + + } catch (TransformerException | XMLStreamException ex) { + // Should never happen + throw new RuntimeException(ex); + } + } + + final static class ResourceGroup { + + ResourceGroup(WixToolsetType wixToolsetType, String xsltResourceName) throws IOException { + if (wixToolsetType == WixToolsetType.Wix4) { + // Need to convert internal WiX sources + conv = new WixSourceConverter(xsltResourceName); + } else { + conv = null; + } + } + + void addResource(OverridableResource resource, Path resourceSaveAsFile) { + resources.put(resourceSaveAsFile, resource); + } + + void saveResources() throws IOException { + if (conv != null) { + for (var e : resources.entrySet()) { + conv.appyTo(e.getValue(), e.getKey()); + } + } else { + for (var e : resources.entrySet()) { + e.getValue().saveToFile(e.getKey()); + } + } + } + + private final Map resources = new HashMap<>(); + private final WixSourceConverter conv; + } + + // + // Default JDK XSLT v1.0 processor is not handling well default namespace mappings. + // Running generic template: + // + // + // + // + // + // + // + // produces: + // + // + // + // + // ... + // + // + // + // 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: + // + // + // + // + // ... + // + // + // + // 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(XMLStreamWriter target) { + this.target = target; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch (method.getName()) { + case "setPrefix" -> { + if (defaultNamespace == null) { + defaultNamespace = (String)args[1]; + target.setDefaultNamespace(defaultNamespace); + } + } + case "writeNamespace" -> { + if (!rootElementProcessed) { + rootElementProcessed = true; + target.writeDefaultNamespace(defaultNamespace); + } + } + 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 localName; + final String[] tokens = name.split(":", 2); + if (tokens.length == 2) { + // The name has a prefix + localName = tokens[1]; + } else { + localName = name; + } + + if (method.getName().equals("writeStartElement")) { + target.writeStartElement(localName); + } else { + target.writeEmptyElement(localName); + } + } + default -> method.invoke(target, args); + } + return null; + } + + private boolean rootElementProcessed; + private String defaultNamespace; + private final XMLStreamWriter target; + } + + private final Transformer transformer; + private final XMLOutputFactory outputFactory; +} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/overrides.wxi b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/overrides.wxi index d8c8f16438f14..c2faa850563c9 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/overrides.wxi +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/overrides.wxi @@ -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 to disable. --> - + diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/wix3-to-wix4-conv.xsl b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/wix3-to-wix4-conv.xsl new file mode 100644 index 0000000000000..792e64cbabe3a --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/wix3-to-wix4-conv.xsl @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java index 3faabcd6f8d2c..b830f903708c5 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java @@ -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,19 @@ static PackageHandlers createMsiPackageHandlers() { String.format("TARGETDIR=\"%s\"", unpackDir.toAbsolutePath().normalize()))))); runMsiexecWithRetries(Executor.of("cmd", "/c", unpackBat.toString())); + if (Files.isDirectory(unpackDir.resolve("PFiles64"))) { + // 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/./" from WiX3 msi which translates to "Program Files/" + // msiexec creates "Program Files/PFiles64/" from WiX4 msi + // So for WiX4 msi we need to transform "Program Files/PFiles64/" into "Program Files/" + var installationSubDirectory = getInstallationSubDirectory(cmd); + ThrowingRunnable.toRunnable(() -> { + Files.move(unpackDir.resolve("PFiles64").resolve(installationSubDirectory), + unpackDir.resolve(installationSubDirectory)); + TKit.deleteDirectoryRecursive(unpackDir.resolve("PFiles64")); + }).run(); + } return destinationDir; }; return msi; From 1bdfd64feb97081744ddca4374e160dd748d1dc2 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Sat, 18 May 2024 03:58:37 -0400 Subject: [PATCH 04/30] WiX4 doesn't accept localization IDs with dots (.). Rename them --- .../resources/InstallDirNotEmptyDlg.wxs | 6 ++-- .../resources/MsiInstallerStrings_de.wxl | 34 ++++++++++--------- .../resources/MsiInstallerStrings_en.wxl | 4 ++- .../resources/MsiInstallerStrings_ja.wxl | 34 ++++++++++--------- .../resources/MsiInstallerStrings_zh_CN.wxl | 34 ++++++++++--------- 5 files changed, 59 insertions(+), 53 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/InstallDirNotEmptyDlg.wxs b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/InstallDirNotEmptyDlg.wxs index 936984b1fff92..755b2a0aab9e2 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/InstallDirNotEmptyDlg.wxs +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/InstallDirNotEmptyDlg.wxs @@ -1,7 +1,7 @@ @@ -160,9 +153,7 @@ - - - + @@ -174,10 +165,12 @@ + + - + From d90e042e476fb5521ae2bb78ca8e25edb801525e Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Sat, 18 May 2024 11:14:04 -0400 Subject: [PATCH 10/30] Log WiX4 post-processing of unpacked msi --- .../jpackage/helpers/jdk/jpackage/test/WindowsHelper.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java index 7f537a2eac061..5bf2fbe6bcc32 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java @@ -151,7 +151,11 @@ static PackageHandlers createMsiPackageHandlers() { // for (var extraPathComponent : List.of("PFiles64", "LocalApp")) { if (Files.isDirectory(unpackDir.resolve(extraPathComponent))) { - var installationSubDirectory = getInstallationSubDirectory(cmd); + 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.move(unpackDir.resolve(extraPathComponent).resolve( installationSubDirectory), unpackDir.resolve( From e8f43ee10fe779148e7f668778320924ba0ee45a Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Sat, 18 May 2024 20:26:45 -0400 Subject: [PATCH 11/30] DottedVersion refactored. Old parsing code used "==" to test equality of two BiInteger objects and it didn't work right. When the bug was fixed app version check on Windows platform stopped working. It required a bit of work to get it working right. --- .../jdk/jpackage/internal/DottedVersion.java | 226 +++++++++++------- .../jdk/jpackage/internal/ToolValidator.java | 6 +- .../internal/CompareDottedVersionTest.java | 6 +- .../jpackage/internal/DottedVersionTest.java | 139 ++++++++--- 4 files changed, 257 insertions(+), 120 deletions(-) diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DottedVersion.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DottedVersion.java index 61b285f4756da..91c0a4915fd43 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DottedVersion.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DottedVersion.java @@ -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,33 +22,122 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package jdk.jpackage.internal; import java.math.BigInteger; import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; import java.util.Objects; -import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.Stream; /** - * Dotted numeric version string. - * E.g.: 1.0.37, 10, 0.5 + * Dotted numeric version string. E.g.: 1.0.37, 10, 0.5 */ -final class DottedVersion implements Comparable { +final class DottedVersion { DottedVersion(String version) { - greedy = true; - components = parseVersionString(version, greedy); - value = version; + this(version, true); } private DottedVersion(String version, boolean greedy) { - this.greedy = greedy; - components = parseVersionString(version, greedy); - value = version; + this.value = version; + if (version.isEmpty()) { + if (greedy) { + throw new IllegalArgumentException(I18N.getString("error.version-string-empty")); + } else { + this.components = new BigInteger[0]; + this.suffix = ""; + } + } else { + var ds = new DigitsSupplier(version); + components = Stream.generate(ds::getNextDigits).takeWhile(Objects::nonNull).map( + digits -> { + if (digits.isEmpty()) { + if (!greedy) { + return null; + } else { + throw new IllegalArgumentException(MessageFormat.format(I18N. + getString("error.version-string-zero-length-component"), + version)); + } + } + + try { + return new BigInteger(digits); + } catch (NumberFormatException ex) { + if (!greedy) { + return null; + } else { + throw new IllegalArgumentException(MessageFormat.format(I18N. + getString("error.version-string-invalid-component"), version, + digits)); + } + } + }).takeWhile(Objects::nonNull).toArray(BigInteger[]::new); + suffix = ds.getUnprocessedString(); + if (!suffix.isEmpty() && greedy) { + throw new IllegalArgumentException(MessageFormat.format(I18N.getString( + "error.version-string-invalid-component"), version, suffix)); + } + } + } + + private static class DigitsSupplier { + + DigitsSupplier(String input) { + this.input = input; + } + + public String getNextDigits() { + if (stoped) { + return null; + } + + var sb = new StringBuilder(); + while (cursor != input.length()) { + var chr = input.charAt(cursor++); + if (Character.isDigit(chr)) { + sb.append(chr); + } else { + var curStopAtDot = (chr == '.'); + if (!curStopAtDot) { + if (lastDotPos >= 0) { + cursor = lastDotPos; + } else { + cursor--; + } + stoped = true; + } else if (!sb.isEmpty()) { + lastDotPos = cursor - 1; + } else { + cursor = Math.max(lastDotPos, 0); + stoped = true; + } + return sb.toString(); + } + } + + if (sb.isEmpty()) { + if (lastDotPos >= 0) { + cursor = lastDotPos; + } else { + cursor--; + } + } + + stoped = true; + return sb.toString(); + } + + public String getUnprocessedString() { + return input.substring(cursor); + } + + private int cursor; + private int lastDotPos = -1; + private boolean stoped; + private final String input; } static DottedVersion greedy(String version) { @@ -59,22 +148,22 @@ static DottedVersion lazy(String version) { return new DottedVersion(version, false); } - @Override - public int compareTo(String o) { + static int compareComponents(DottedVersion a, DottedVersion b) { int result = 0; - BigInteger[] otherComponents = parseVersionString(o, greedy); - for (int i = 0; i < Math.max(components.length, otherComponents.length) + BigInteger[] aComponents = a.getComponents(); + BigInteger[] bComponents = b.getComponents(); + for (int i = 0; i < Math.max(aComponents.length, bComponents.length) && result == 0; ++i) { final BigInteger x; - if (i < components.length) { - x = components[i]; + if (i < aComponents.length) { + x = aComponents[i]; } else { x = BigInteger.ZERO; } final BigInteger y; - if (i < otherComponents.length) { - y = otherComponents[i]; + if (i < bComponents.length) { + y = bComponents[i]; } else { y = BigInteger.ZERO; } @@ -84,71 +173,30 @@ public int compareTo(String o) { return result; } - private static BigInteger[] parseVersionString(String version, boolean greedy) { - Objects.requireNonNull(version); - if (version.isEmpty()) { - if (!greedy) { - return new BigInteger[] {BigInteger.ZERO}; - } - throw new IllegalArgumentException(I18N.getString( - "error.version-string-empty")); - } - - int lastNotZeroIdx = -1; - List components = new ArrayList<>(); - for (var component : version.split("\\.", -1)) { - if (component.isEmpty()) { - if (!greedy) { - break; - } - - throw new IllegalArgumentException(MessageFormat.format( - I18N.getString( - "error.version-string-zero-length-component"), - version)); - } - - if (!DIGITS.matcher(component).matches()) { - // Catch "+N" and "-N" cases. - if (!greedy) { - break; - } - - throw new IllegalArgumentException(MessageFormat.format( - I18N.getString( - "error.version-string-invalid-component"), - version, component)); - } - - final BigInteger num; - try { - num = new BigInteger(component); - } catch (NumberFormatException ex) { - if (!greedy) { - break; - } - - throw new IllegalArgumentException(MessageFormat.format( - I18N.getString( - "error.version-string-invalid-component"), - version, component)); - } + @Override + public int hashCode() { + int hash = 3; + hash = 29 * hash + Arrays.deepHashCode(this.components); + hash = 29 * hash + Objects.hashCode(this.suffix); + return hash; + } - if (num != BigInteger.ZERO) { - lastNotZeroIdx = components.size(); - } - components.add(num); + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; } - - if (lastNotZeroIdx + 1 != components.size()) { - // Strip trailing zeros. - components = components.subList(0, lastNotZeroIdx + 1); + if (obj == null) { + return false; } - - if (components.isEmpty()) { - components.add(BigInteger.ZERO); + if (getClass() != obj.getClass()) { + return false; } - return components.toArray(BigInteger[]::new); + final DottedVersion other = (DottedVersion) obj; + if (!Objects.equals(this.suffix, other.suffix)) { + return false; + } + return Arrays.deepEquals(this.components, other.components); } @Override @@ -156,13 +204,19 @@ public String toString() { return value; } + public String getUnprocessedSuffix() { + return suffix; + } + + String toComponentsString() { + return Stream.of(components).map(BigInteger::toString).collect(Collectors.joining(".")); + } + BigInteger[] getComponents() { return components; } private final BigInteger[] components; private final String value; - private final boolean greedy; - - private static final Pattern DIGITS = Pattern.compile("\\d+"); + private final String suffix; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java index 793ff623c529f..64876468afbab 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, 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 @@ -65,6 +65,10 @@ ToolValidator setMinimalVersion(Comparable v) { return this; } + ToolValidator setMinimalVersion(DottedVersion v) { + return setMinimalVersion(t -> DottedVersion.compareComponents(v, DottedVersion.lazy(t))); + } + ToolValidator setVersionParser(Function, String> v) { versionParser = v; return this; diff --git a/test/jdk/tools/jpackage/junit/jdk.jpackage/jdk/jpackage/internal/CompareDottedVersionTest.java b/test/jdk/tools/jpackage/junit/jdk.jpackage/jdk/jpackage/internal/CompareDottedVersionTest.java index aef0092748a58..d77b4e85eb718 100644 --- a/test/jdk/tools/jpackage/junit/jdk.jpackage/jdk/jpackage/internal/CompareDottedVersionTest.java +++ b/test/jdk/tools/jpackage/junit/jdk.jpackage/jdk/jpackage/internal/CompareDottedVersionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, 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 @@ -68,6 +68,8 @@ public static List data() { data.addAll(List.of(new Object[][] { { false, "", "1", -1 }, + { false, "", "0", 0 }, + { false, "0", "", 0 }, { false, "1.2.4-R4", "1.2.4-R5", 0 }, { false, "1.2.4.-R4", "1.2.4.R5", 0 }, { false, "7+1", "7+4", 0 }, @@ -89,7 +91,7 @@ public void testIt() { } private int compare(String x, String y) { - int result = createTestee.apply(x).compareTo(y); + int result = DottedVersion.compareComponents(createTestee.apply(x), createTestee.apply(y)); if (result < 0) { return -1; diff --git a/test/jdk/tools/jpackage/junit/jdk.jpackage/jdk/jpackage/internal/DottedVersionTest.java b/test/jdk/tools/jpackage/junit/jdk.jpackage/jdk/jpackage/internal/DottedVersionTest.java index d924611b4ae4e..0443fdf64e7ea 100644 --- a/test/jdk/tools/jpackage/junit/jdk.jpackage/jdk/jpackage/internal/DottedVersionTest.java +++ b/test/jdk/tools/jpackage/junit/jdk.jpackage/jdk/jpackage/internal/DottedVersionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, 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 @@ -30,6 +30,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -54,45 +55,103 @@ public static List data() { @Rule public ExpectedException exceptionRule = ExpectedException.none(); + private static class CtorTester { + + CtorTester(String input, boolean greedy, String expectedSuffix, + int expectedComponentCount, String expectedToComponent) { + this.input = input; + this.greedy = greedy; + this.expectedSuffix = expectedSuffix; + this.expectedComponentCount = expectedComponentCount; + this.expectedToComponent = expectedToComponent; + } + + CtorTester(String input, boolean greedy, int expectedComponentCount, + String expectedToComponent) { + this(input, greedy, "", expectedComponentCount, expectedToComponent); + } + + CtorTester(String input, boolean greedy, int expectedComponentCount) { + this(input, greedy, "", expectedComponentCount, input); + } + + static CtorTester greedy(String input, int expectedComponentCount, + String expectedToComponent) { + return new CtorTester(input, true, "", expectedComponentCount, expectedToComponent); + } + + static CtorTester greedy(String input, int expectedComponentCount) { + return new CtorTester(input, true, "", expectedComponentCount, input); + } + + static CtorTester lazy(String input, String expectedSuffix, int expectedComponentCount, + String expectedToComponent) { + return new CtorTester(input, false, expectedSuffix, expectedComponentCount, + expectedToComponent); + } + + void run() { + DottedVersion dv; + if (greedy) { + dv = DottedVersion.greedy(input); + } else { + dv = DottedVersion.lazy(input); + } + + assertEquals(expectedSuffix, dv.getUnprocessedSuffix()); + assertEquals(expectedComponentCount, dv.getComponents().length); + assertEquals(expectedToComponent, dv.toComponentsString()); + } + + private final String input; + private final boolean greedy; + private final String expectedSuffix; + private final int expectedComponentCount; + private final String expectedToComponent; + } + @Test public void testValid() { - final List validStrings = List.of( - "1.0", - "1", - "2.234.045", - "2.234.0", - "0", - "0.1", - "9".repeat(1000) + final List validStrings = List.of( + new CtorTester("1.0", greedy, 2), + new CtorTester("1", greedy, 1), + new CtorTester("2.20034.045", greedy, 3, "2.20034.45"), + new CtorTester("2.234.0", greedy, 3), + new CtorTester("0", greedy, 1), + new CtorTester("0.1", greedy, 2), + new CtorTester("9".repeat(1000), greedy, 1), + new CtorTester("00.0.0", greedy, 3, "0.0.0") ); - final List validLazyStrings; + final List validLazyStrings; if (greedy) { validLazyStrings = Collections.emptyList(); } else { validLazyStrings = List.of( - "1.-1", - "5.", - "4.2.", - "3..2", - "2.a", - "0a", - ".", - " ", - " 1", - "1. 2", - "+1", - "-1", - "-0", - "+0" + CtorTester.lazy("1.-1", ".-1", 1, "1"), + CtorTester.lazy("5.", ".", 1, "5"), + CtorTester.lazy("4.2.", ".", 2, "4.2"), + CtorTester.lazy("3..2", "..2", 1, "3"), + CtorTester.lazy("3......2", "......2", 1, "3"), + CtorTester.lazy("2.a", ".a", 1, "2"), + CtorTester.lazy("a", "a", 0, ""), + CtorTester.lazy("2..a", "..a", 1, "2"), + CtorTester.lazy("0a", "a", 1, "0"), + CtorTester.lazy("120a", "a", 1, "120"), + CtorTester.lazy("120abc", "abc", 1, "120"), + CtorTester.lazy(".", ".", 0, ""), + CtorTester.lazy("....", "....", 0, ""), + CtorTester.lazy(" ", " ", 0, ""), + CtorTester.lazy(" 1", " 1", 0, ""), + CtorTester.lazy("678. 2", ". 2", 1, "678"), + CtorTester.lazy("+1", "+1", 0, ""), + CtorTester.lazy("-1", "-1", 0, ""), + CtorTester.lazy("-0", "-0", 0, ""), + CtorTester.lazy("+0", "+0", 0, "") ); } - Stream.concat(validStrings.stream(), validLazyStrings.stream()) - .forEach(value -> { - DottedVersion version = createTestee.apply(value); - assertEquals(version.toString(), value); - }); + Stream.concat(validStrings.stream(), validLazyStrings.stream()).forEach(CtorTester::run); } @Test @@ -108,8 +167,26 @@ public void testEmpty() { exceptionRule.expectMessage("Version may not be empty string"); createTestee.apply(""); } else { - assertTrue(0 == createTestee.apply("").compareTo("")); - assertTrue(0 == createTestee.apply("").compareTo("0")); + assertEquals(0, createTestee.apply("").getComponents().length); + } + } + + @Test + public void testEquals() { + DottedVersion dv = createTestee.apply("1.0"); + assertFalse(dv.equals(null)); + assertFalse(dv.equals(Integer.valueOf(1))); + + for (var ver : List.of("3", "3.4", "3.0.0")) { + DottedVersion a = createTestee.apply(ver); + DottedVersion b = createTestee.apply(ver); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + } + + if (!greedy) { + assertTrue(createTestee.apply("3.6+67").equals(createTestee.apply("3.6+67"))); + assertFalse(createTestee.apply("3.6+67").equals(createTestee.apply("3.6+067"))); } } From aa0200c60aae8403705a0c6a3c83c52f6ef446ec Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Sat, 18 May 2024 21:07:04 -0400 Subject: [PATCH 12/30] Detect WiX3 full set of tools (candle.exe and light.exe) or WiX4 wix.exe. --- .../jdk/jpackage/internal/WixTool.java | 221 +++++++++++------- .../jdk/jpackage/internal/WixToolset.java | 14 +- .../resources/WinResources.properties | 2 +- .../resources/WinResources_de.properties | 2 +- .../resources/WinResources_ja.properties | 2 +- .../resources/WinResources_zh_CN.properties | 2 +- 6 files changed, 141 insertions(+), 102 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java index e374973417c0c..51d5c7594991d 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java @@ -32,15 +32,13 @@ import java.nio.file.PathMatcher; import java.text.MessageFormat; 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.Set; -import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Supplier; +import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.internal.WixToolset.WixToolsetType; @@ -61,117 +59,162 @@ 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 WixToolset createToolset() throws ConfigException { - Map tools = new HashMap<>(); - - var wixInstallDirsSupplier = new Supplier>() { - @Override - public List get() { - if (wixInstallDirs == null) { - wixInstallDirs = findWixInstallDirs(); + Function, Map> conv = lookupResults -> { + return lookupResults.stream().filter(ToolLookupResult::isValid).collect(Collectors. + groupingBy(lookupResult -> { + return lookupResult.getInfo().version.toString(); + })).values().stream().filter(sameVersionLookupResults -> { + Set 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 wixInstallDirs; - } - private List wixInstallDirs; + }).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(); + })); }; - for (var tool : values()) { - ToolInfo info = tool.find(wixInstallDirsSupplier); - if (info != null) { - tools.put(tool, info); - } + Function, Optional> 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(); } - return Stream.of(WixToolsetType.values()).map(toolsetType -> { - return WixToolset.create(toolsetType.getTools(), tools); - }).takeWhile(Objects::nonNull).findFirst().orElseGet(() -> { - return WixToolset.create(Set.of(), Map.of()); - }); + // 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(); + + // 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; + } + }).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")); + } + } } - private ToolInfo find(Supplier> findWixInstallDirs) throws ConfigException { - String[] version = new String[1]; - ConfigException err = createToolValidator(toolFileName, - v -> version[0] = v).get(); - if (version[0] != null) { - if (err == null) { - // Found in PATH. - return new ToolInfo(toolFileName, version[0]); + private static class ToolLookupResult { + + 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, 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; + } + return firstLineOfOutput.substring(separatorIdx + 1); + }; + } else { + validator.setCommandLine("--version"); + versionParser = output -> { + return output.findFirst().orElse(""); + }; } - // Found in PATH, but something went wrong. - throw err; - } + validator.setVersionParser(output -> { + parsedVersion[0] = versionParser.apply(output); + return parsedVersion[0]; + }); - for (var dir : findWixInstallDirs.get()) { - Path path = dir.resolve(toolFileName); - if (Files.exists(path)) { - err = createToolValidator(path, v -> version[0] = v).get(); - if (err != null) { - throw err; - } - return new ToolInfo(path, version[0]); + 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; } } - throw err; - } + WixTool getTool() { + return tool; + } - private static Set getWix3Tools() { - return Set.of(Candle3, Light3); - } + ToolInfo getInfo() { + return info; + } - private Supplier createToolValidator(Path toolPath, - Consumer versionConsumer) { - final var toolValidator = new ToolValidator(toolPath) - .setMinimalVersion(minimalVersion) - .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, minimalVersion), - I18N.getString("error.no-wix-tools.advice"))); - - final Function, String> versionParser; - - if (getWix3Tools().contains(this)) { - toolValidator.setCommandLine("/?"); - versionParser = output -> { - String firstLineOfOutput = output.findFirst().orElse(""); - int separatorIdx = firstLineOfOutput.lastIndexOf(' '); - if (separatorIdx == -1) { - return null; - } - return firstLineOfOutput.substring(separatorIdx + 1); - }; - } else { - toolValidator.setCommandLine("--version"); - versionParser = output -> { - // "wix --version" command prints out "5.0.0+41e11442". - // Strip trailing "+41e11442" as it breaks version parser. - return output.findFirst().orElse("").split("\\+", 2)[0]; - }; + boolean isValid() { + return info != null && !versionTooOld; } - toolValidator.setVersionParser(output -> { - var version = versionParser.apply(output); - versionConsumer.accept(version); - return version; - }); + boolean isVersionTooOld() { + return versionTooOld; + } - return toolValidator::validate; + private final WixTool tool; + private final ToolInfo info; + private final boolean versionTooOld; } private static Path getSystemDir(String envVar, String knownDir) { diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java index 86e2ba71b9bd9..ba09af56042fa 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java @@ -35,10 +35,8 @@ final class WixToolset { static enum WixToolsetType { // Wix v4+ Wix4(WixTool.Wix4), - // Wix v3.0-v3.6 Wix3(WixTool.Candle3, WixTool.Light3), - // Wix v3.6+ Wix36(WixTool.Candle3, WixTool.Light3); @@ -58,10 +56,9 @@ private WixToolset(Map tools) { } WixToolsetType getType() { - return Stream.of(WixToolsetType.values()). - filter(toolsetType -> { - return toolsetType.getTools().equals(tools.keySet()); - }).findAny().get(); + return Stream.of(WixToolsetType.values()).filter(toolsetType -> { + return toolsetType.getTools().equals(tools.keySet()); + }).findAny().get(); } Path getToolPath(WixTool tool) { @@ -72,10 +69,9 @@ DottedVersion getVersion() { return tools.values().iterator().next().version; } - static WixToolset create(Set requiredTools, - Map allTools) { + static WixToolset create(Set requiredTools, Map allTools) { var filteredTools = allTools.entrySet().stream().filter(e -> { - return requiredTools.contains(e.getKey()) && e.getValue() != null; + return requiredTools.contains(e.getKey()); }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); if (filteredTools.keySet().equals(requiredTools)) { return new WixToolset(filteredTools); diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties index cdbf04e746460..8147a01359df2 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties @@ -42,7 +42,7 @@ 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 -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 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. diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties index 6bdb9b06e09be..f4a82428f8d64 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties @@ -42,7 +42,7 @@ resource.shortcutpromptdlg-wix-file=Dialogfeld für Verknüpfungs-Prompt der WiX resource.installdirnotemptydlg-wix-file=Nicht leeres Installationsverzeichnis in Dialogfeld für WiX-Projektdatei resource.launcher-as-service-wix-file=WiX-Projektdatei für Serviceinstallationsprogramm -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 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. diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties index 6a89e8cbf5ae5..2b75fc67ae5a8 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties @@ -42,7 +42,7 @@ resource.shortcutpromptdlg-wix-file=ショートカット・プロンプト・ resource.installdirnotemptydlg-wix-file=インストール・ディレクトリ・ダイアログのWiXプロジェクト・ファイルが空ではありません resource.launcher-as-service-wix-file=サービス・インストーラWiXプロジェクト・ファイル -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 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つのコンポーネントが含まれている必要があります。 diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties index f786b00155dbf..8358cfa223085 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties @@ -42,7 +42,7 @@ resource.shortcutpromptdlg-wix-file=快捷方式提示对话框 WiX 项目文件 resource.installdirnotemptydlg-wix-file=安装目录对话框 WiX 项目文件非空 resource.launcher-as-service-wix-file=服务安装程序 WiX 项目文件 -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 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 个组成部分。 From 9fcc668245130c5f5131800e0985a572b1fc77ea Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Sat, 18 May 2024 23:39:15 -0400 Subject: [PATCH 13/30] Add missing "--add-opens jdk.jpackage/jdk.jpackage.internal=ALL-UNNAMED" to test desc. It fixes the following error: [22:09:20.347] TRACE: assertFalse(): Check [C:\Program Files\UpdateServiceTest\foo.ico] path doesn't exist [22:09:20.375] [ FAILED ] ServiceTest.testUpdate; checks=60 java.lang.ExceptionInInitializerError at jdk.jpackage.test.LauncherIconVerifier.applyTo(LauncherIconVerifier.java:70) at jdk.jpackage.test.AdditionalLauncher.verifyIcon(AdditionalLauncher.java:298) at jdk.jpackage.test.AdditionalLauncher.verify(AdditionalLauncher.java:363) at jdk.jpackage.test.LauncherAsServiceVerifier$1.verify(LauncherAsServiceVerifier.java:261) at jdk.jpackage.test.Functional$ThrowingConsumer.lambda$toConsumer$0(Functional.java:41) at jdk.jpackage.test.PackageTest$Handler.lambda$verifyPackageInstalled$6(PackageTest.java:660) at java.base/java.util.ArrayList.forEach(ArrayList.java:1597) at jdk.jpackage.test.PackageTest$Handler.verifyPackageInstalled(PackageTest.java:660) at jdk.jpackage.test.PackageTest$Handler.accept(PackageTest.java:594) at jdk.jpackage.test.PackageTest$2.accept(PackageTest.java:504) at jdk.jpackage.test.PackageTest$2.accept(PackageTest.java:411) at jdk.jpackage.test.Functional$ThrowingConsumer.lambda$toConsumer$0(Functional.java:41) at java.base/java.lang.Iterable.forEach(Iterable.java:75) at jdk.jpackage.test.PackageTest$Group.lambda$runAction$0(PackageTest.java:364) at java.base/java.lang.Iterable.forEach(Iterable.java:75) at jdk.jpackage.test.PackageTest$Group.runAction(PackageTest.java:364) at java.base/java.util.ArrayList.forEach(ArrayList.java:1597) at jdk.jpackage.test.RunnablePackageTest.runActions(RunnablePackageTest.java:66) at jdk.jpackage.test.RunnablePackageTest.run(RunnablePackageTest.java:58) at ServiceTest.testUpdate(ServiceTest.java:132) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at jdk.jpackage.test.MethodCall.accept(MethodCall.java:145) at jdk.jpackage.test.TestInstance.run(TestInstance.java:230) at jdk.jpackage.test.TKit.lambda$ignoreExceptions$5(TKit.java:141) at jdk.jpackage.test.TKit.lambda$runTests$3(TKit.java:126) at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1709) at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:807) at jdk.jpackage.test.TKit.lambda$runTests$4(TKit.java:123) at jdk.jpackage.test.Functional$ThrowingRunnable.lambda$toRunnable$0(Functional.java:105) at jdk.jpackage.test.TKit.withExtraLogStream(TKit.java:105) at jdk.jpackage.test.TKit.runTests(TKit.java:122) at jdk.jpackage.test.Main.runTests(Main.java:79) at jdk.jpackage.test.Main.lambda$main$2(Main.java:75) at jdk.jpackage.test.Functional$ThrowingRunnable.lambda$toRunnable$0(Functional.java:105) at jdk.jpackage.test.TKit.withExtraLogStream(TKit.java:109) at jdk.jpackage.test.Main.main(Main.java:75) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at com.sun.javatest.regtest.agent.MainWrapper$MainTask.run(MainWrapper.java:138) at java.base/java.lang.Thread.run(Thread.java:1575) Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make private static native long jdk.jpackage.internal.ExecutableRebrander.lockResource(java.lang.String) accessible: module jdk.jpackage does not "opens jdk.jpackage.internal" to unnamed module @3761e16e at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:388) at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:364) : --- test/jdk/tools/jpackage/share/ServiceTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/jdk/tools/jpackage/share/ServiceTest.java b/test/jdk/tools/jpackage/share/ServiceTest.java index 5142ada274245..4cf96b62cd2c1 100644 --- a/test/jdk/tools/jpackage/share/ServiceTest.java +++ b/test/jdk/tools/jpackage/share/ServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -49,7 +49,9 @@ * @build jdk.jpackage.test.* * @modules jdk.jpackage/jdk.jpackage.internal * @compile ServiceTest.java - * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main + * @run main/othervm/timeout=360 -Xmx512m + * --add-opens jdk.jpackage/jdk.jpackage.internal=ALL-UNNAMED + * jdk.jpackage.test.Main * --jpt-run=ServiceTest */ public class ServiceTest { From 1b35d20cea643964eaafa100855c317ede43a245 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Sun, 19 May 2024 00:06:25 -0400 Subject: [PATCH 14/30] Fix InstallDirTest.testCommon test failure --- .../jpackage/helpers/jdk/jpackage/test/WindowsHelper.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java index 5bf2fbe6bcc32..1a4dbd22897bf 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java @@ -157,9 +157,8 @@ static PackageHandlers createMsiPackageHandlers() { TKit.trace(String.format("Convert [%s] into [%s] in [%s] directory", from, to, unpackDir)); ThrowingRunnable.toRunnable(() -> { - Files.move(unpackDir.resolve(extraPathComponent).resolve( - installationSubDirectory), unpackDir.resolve( - installationSubDirectory)); + Files.createDirectories(unpackDir.resolve(to).getParent()); + Files.move(unpackDir.resolve(from), unpackDir.resolve(to)); TKit.deleteDirectoryRecursive(unpackDir.resolve(extraPathComponent)); }).run(); } From 6b510e174e90266c3058bcbcd83604d762cc4d23 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Mon, 20 May 2024 16:54:16 -0400 Subject: [PATCH 15/30] WinL10nTest is a pass --- .../internal/OverridableResource.java | 36 ++++- .../jdk/jpackage/internal/WinMsiBundler.java | 52 +++--- .../jpackage/internal/WixSourceConverter.java | 148 ++++++++++++++---- .../tools/jpackage/windows/WinL10nTest.java | 92 +++++++---- 4 files changed, 237 insertions(+), 91 deletions(-) diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java index ecd88bb7fb975..47bebe6687579 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java @@ -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 @@ -26,6 +26,7 @@ import java.io.BufferedReader; import java.io.ByteArrayInputStream; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -42,6 +43,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; @@ -73,7 +75,7 @@ final class OverridableResource { OverridableResource(String defaultName) { this.defaultName = defaultName; - setSourceOrder(Source.values()); + setSourceOrder(Source.values()).setLogger(Log::verbose); } Path getResourceDir() { @@ -132,6 +134,11 @@ OverridableResource setSourceOrder(Source... v) { return this; } + OverridableResource setLogger(Consumer v) { + logger = v; + return this; + } + /** * Set name of file to look for in resource dir. * @@ -222,6 +229,20 @@ static OverridableResource createResource(String defaultName, RESOURCE_DIR.fetchFrom(params)); } + final class NoLogging implements Closeable { + NoLogging() { + logger = OverridableResource.this.logger; + OverridableResource.this.setLogger(v -> {}); + } + + @Override + public void close() throws IOException { + OverridableResource.this.setLogger(logger); + } + + private final Consumer logger; + } + private Source sendToConsumer(ResourceConsumer consumer) throws IOException { for (var source: sources) { if (source.getValue().apply(consumer)) { @@ -241,7 +262,7 @@ private String getPrintableCategory() { private boolean useExternal(ResourceConsumer dest) throws IOException { boolean used = externalPath != null && Files.exists(externalPath); if (used && dest != null) { - Log.verbose(MessageFormat.format(I18N.getString( + logger.accept(MessageFormat.format(I18N.getString( "message.using-custom-resource-from-file"), getPrintableCategory(), externalPath.toAbsolutePath().normalize())); @@ -270,9 +291,9 @@ private boolean useResourceDir(ResourceConsumer dest) throws IOException { final Path logResourceName = Optional.ofNullable(logPublicName).orElse( resourceName).normalize(); - Log.verbose(MessageFormat.format(I18N.getString( + logger.accept((MessageFormat.format(I18N.getString( "message.using-custom-resource"), getPrintableCategory(), - logResourceName)); + logResourceName))); try (InputStream in = Files.newInputStream(customResource)) { processResourceStream(in, dest); @@ -291,9 +312,9 @@ private boolean useDefault(ResourceConsumer dest) throws IOException { .orElse(Optional .ofNullable(publicName) .orElseGet(() -> dest.publicName())); - Log.verbose(MessageFormat.format( + logger.accept((MessageFormat.format( I18N.getString("message.using-default-resource"), - defaultName, getPrintableCategory(), resourceName)); + defaultName, getPrintableCategory(), resourceName))); try (InputStream in = readDefault(defaultName)) { processResourceStream(in, dest); @@ -380,6 +401,7 @@ private SourceHandler getHandler(Source sourceType) { } private Map substitutionData; + private Consumer logger; private String category; private Path resourceDir; private Path publicName; diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java index 2ee1e0f46c65b..f9efd6eae787d 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java @@ -513,24 +513,6 @@ private Map prepareMainProjectFile( data.put("JpIsSystemWide", "yes"); } - var resources = new WixSourceConverter.ResourceGroup(wixToolset.getType()); - - // Copy standard l10n files. - for (String loc : Arrays.asList("de", "en", "ja", "zh_CN")) { - String fname = "MsiInstallerStrings_" + loc + ".wxl"; - resources.addResource(createResource(fname, params).setPublicName(fname).setCategory( - I18N.getString("resource.wxl-file")), configDir.resolve(fname)); - } - - resources.addResource(createResource("main.wxs", params).setPublicName("main.wxs"). - setCategory(I18N.getString("resource.main-wix-file")), configDir.resolve("main.wxs")); - - resources.addResource(createResource("overrides.wxi", params).setPublicName( - "overrides.wxi").setCategory(I18N.getString("resource.overrides-wix-file")), - configDir.resolve("overrides.wxi")); - - resources.saveResources(); - return data; } @@ -566,10 +548,31 @@ private Path buildMSI(Map params, } } + 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 primaryWxlFiles = getWxlFilesFromDir(params, CONFIG_ROOT); List customWxlFiles = getWxlFilesFromDir(params, RESOURCE_DIR).stream() .filter(custom -> primaryWxlFiles.stream().noneMatch(primary -> primary.getFileName().toString().equalsIgnoreCase( @@ -580,6 +583,17 @@ private Path buildMSI(Map params, 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")), path); + } + + // 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 diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java index fed75965384b5..1d2ea3d1c74a4 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java @@ -32,10 +32,14 @@ import java.lang.reflect.Proxy; import java.nio.file.Files; import java.nio.file.Path; +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; @@ -43,18 +47,25 @@ import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMResult; 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 jdk.jpackage.internal.resources.ResourceLocator; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; /** - * Converts Wix v3 source file into Wix v4 format. + * Converts WiX v3 source file into WiX v4 format. */ final class WixSourceConverter { + enum Status { + SavedAsIs, + SavedAsIsMalfromedXml, + Transformed, + } + WixSourceConverter() { var xslt = new StreamSource(ResourceLocator.class.getResourceAsStream("wix3-to-wix4-conv.xsl")); @@ -69,40 +80,75 @@ final class WixSourceConverter { this.outputFactory = XMLOutputFactory.newInstance(); } - void appyTo(OverridableResource resource, Path resourceSaveAsFile) throws IOException { - if (resource.saveToStream(null) != OverridableResource.Source.DefaultResource) { - // Don't convert external resources - resource.saveToFile(resourceSaveAsFile); - return; - } + 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 = new ByteArrayOutputStream(); - resource.saveToStream(buf); + var buf = saveResourceInMemory(resource); - var dom = new DOMResult(); - var nc = new NamespaceCollector(); + 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 { - Source input = new StreamSource(new ByteArrayInputStream(buf.toByteArray())); - transformer.transform(input, dom); + var nc = new NamespaceCollector(); TransformerFactory.newInstance().newTransformer(). - transform(new DOMSource(dom.getNode()), new StAXResult((XMLStreamWriter) Proxy. + 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 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 = Files.newOutputStream(resourceSaveAsFile)) { - transformer.transform(new DOMSource(dom.getNode()), new StAXResult( - (XMLStreamWriter) Proxy.newProxyInstance(XMLStreamWriter.class.getClassLoader(), + transformer.transform(inputXml.get(), new StAXResult((XMLStreamWriter) Proxy. + newProxyInstance(XMLStreamWriter.class.getClassLoader(), new Class[]{XMLStreamWriter.class}, new NamespaceCleaner(nc. getPrefixToUri(), outputFactory.createXMLStreamWriter(outXml))))); } catch (TransformerException | XMLStreamException ex) { // Should never happen throw new RuntimeException(ex); } + + return Status.Transformed; + } + + @SuppressWarnings("try") + private static byte[] saveResourceInMemory(OverridableResource resource) throws IOException { + var buf = new ByteArrayOutputStream(); + try (var nolog = resource.new NoLogging()) { + resource.saveToStream(buf); + } + return buf.toByteArray(); } final static class ResourceGroup { @@ -265,8 +311,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl } } - method.invoke(target, args); - return null; + return method.invoke(target, args); } static class Prefix { @@ -290,32 +335,73 @@ private static class NamespaceCollector implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { switch (method.getName()) { case "setPrefix", "writeNamespace" -> { - prefixToUri.computeIfAbsent((String) args[0], k -> (String) args[1]); + 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 -> (String) args[2]); - case 2 -> { - final String name = (String) args[1]; - final String[] tokens = name.split(":", 2); - if (tokens.length == 2) { - prefixToUri.computeIfAbsent(tokens[0], k -> (String) args[1]); - } - } + 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; } - public Map getPrefixToUri() { - return prefixToUri; + 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()); + } + }); } - private final Map prefixToUri = new HashMap<>(); + Map 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 createValue(Object prefix) { + return new AbstractMap.SimpleEntry((String) prefix, false); + } + + private final Map> 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 KNOWN_NAMESPACES = Set.of( + "http://schemas.microsoft.com/wix/2006/localization", + "http://schemas.microsoft.com/wix/2006/wi"); } diff --git a/test/jdk/tools/jpackage/windows/WinL10nTest.java b/test/jdk/tools/jpackage/windows/WinL10nTest.java index deb6b1a8705d1..6f51b1158e552 100644 --- a/test/jdk/tools/jpackage/windows/WinL10nTest.java +++ b/test/jdk/tools/jpackage/windows/WinL10nTest.java @@ -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 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 @@ 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 WinL10nTest(WixFileInitializer wxlFileInitializers[], @Parameters public static List 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 getLightCommandLine( - Executor.Result result) { - return result.getOutput().stream().filter(s -> { + private static Stream 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 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 createDefaultL10nFilesLocVerifiers(Path tempDir) { @@ -148,14 +158,23 @@ public void test() throws IOException { // 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 void test() throws IOException { 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) { @@ -183,21 +209,19 @@ public void test() throws IOException { if (allWxlFilesValid) { for (var v : wxlFileInitializers) { if (!v.name.startsWith("MsiInstallerStrings_")) { - v.createCmdOutputVerifier(resourceDir).apply( - getLightCommandLine(result)); + v.createCmdOutputVerifier(resourceDir).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(), + TKit.assertFalse(getBuildCommandLine(result).findAny().isPresent(), "Check light.exe was not invoked"); } } @@ -223,7 +247,7 @@ public void test() throws IOException { } final private WixFileInitializer[] wxlFileInitializers; - final private String expectedCulture; + final private String[] expectedCultures; final private String expectedErrorMessage; final private String userLanguage; final private String userCountry; From 8cb8fbbb4bc69c62fffd604943ee4a0473e32135 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 21 May 2024 10:02:32 -0400 Subject: [PATCH 16/30] Fix "warning WIX5436: Using DirectoryRef to reference the standard directory 'DesktopFolder' is deprecated. Use the StandardDirectory element instead." WiX4 warning --- .../internal/WixAppImageFragmentBuilder.java | 77 +++++++++++-------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java index c08d57896b946..ab8011fceb6e0 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java @@ -381,8 +381,7 @@ private String addComponent(XMLStreamWriter xml, Path path, 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(getWixVersion(), xml, componentId, String.format( @@ -460,7 +459,7 @@ private void addFaComponentGroup(XMLStreamWriter xml) private void addShortcutComponentGroup(XMLStreamWriter xml) throws XMLStreamException, IOException { List componentIds = new ArrayList<>(); - Set defineShortcutFolders = new HashSet<>(); + Set defineShortcutFolders = new HashSet<>(); for (var launcher : launchers) { for (var folder : shortcutFolders) { Path launcherPath = addExeSuffixToPath(installedAppImage @@ -475,16 +474,25 @@ private void addShortcutComponentGroup(XMLStreamWriter xml) throws folder); if (componentId != null) { - defineShortcutFolders.add(folder); + Path folderPath = folder.getPath(this); + boolean defineFolder; + switch (getWixVersion()) { + case Wix3, Wix36 -> + defineFolder = true; + default -> + defineFolder = !SYSTEM_DIRS.contains(folderPath); + } + 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); @@ -564,13 +572,11 @@ private List addRootBranch(XMLStreamWriter xml, Path path) throw throwInvalidPathException(path); } - Function createDirectoryName = dir -> null; - boolean sysDir = true; int levels; var dirIt = path.iterator(); - if (getWixVersion() == WixToolsetType.Wix4 && SYSTEM_DIRS.contains(path.getName(0))) { + if (getWixVersion() == WixToolsetType.Wix4 && TARGETDIR.equals(path.getName(0))) { levels = 0; dirIt.next(); } else { @@ -587,28 +593,11 @@ private List addRootBranch(XMLStreamWriter xml, Path path) 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); - } - - final String elementName; - if (getWixVersion() == WixToolsetType.Wix4 && sysDir) { - elementName = "StandardDirectory"; - } else { - elementName = "Directory"; - } - xml.writeStartElement(elementName); - 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()); } } @@ -616,9 +605,33 @@ private List addRootBranch(XMLStreamWriter xml, Path path) xml.writeEndElement(); } - List componentIds = new ArrayList<>(); + return List.of(); + } - return componentIds; + private void startDirectoryElement(XMLStreamWriter xml, String wix3ElementName, Path path) throws XMLStreamException { + final String elementName; + switch (getWixVersion()) { + case Wix3, Wix36 -> { + elementName = wix3ElementName; + } + default -> { + if (SYSTEM_DIRS.contains(path)) { + elementName = "StandardDirectory"; + } else { + elementName = wix3ElementName; + } + } + } + + 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) From 51b4c0c9c7d5346b3c9bf01cefb2c2060f9b9b25 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 21 May 2024 11:44:51 -0400 Subject: [PATCH 17/30] Got rid of Wix36 WiX toolset type --- .../jdk/jpackage/internal/WinMsiBundler.java | 23 +++++---- .../internal/WixAppImageFragmentBuilder.java | 50 ++++++++++++------- .../jpackage/internal/WixFragmentBuilder.java | 48 ++++++++++-------- .../jdk/jpackage/internal/WixPipeline.java | 17 ++++--- .../jpackage/internal/WixSourceConverter.java | 10 ++-- .../jdk/jpackage/internal/WixToolset.java | 6 +-- .../internal/WixUiFragmentBuilder.java | 26 +++++----- 7 files changed, 102 insertions(+), 78 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java index f9efd6eae787d..b428590b82127 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java @@ -316,10 +316,11 @@ public boolean validate(Map params) getFileName(), wixToolset.getVersion())); } - wixFragments.forEach(wixFragment -> wixFragment.setWixVersion( + 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 *************/ @@ -540,11 +541,13 @@ private Path buildMSI(Map params, Log.verbose(MessageFormat.format(I18N.getString( "message.generating-msi"), msiOut.toAbsolutePath().toString())); - if (Set.of(WixToolsetType.Wix3, WixToolsetType.Wix36).contains(wixToolset.getType())) { - wixPipeline.addLightOptions("-sice:ICE27"); + switch (wixToolset.getType()) { + case Wix3 -> { + wixPipeline.addLightOptions("-sice:ICE27"); - if (!MSI_SYSTEM_WIDE.fetchFrom(params)) { - wixPipeline.addLightOptions("-sice:ICE91"); + if (!MSI_SYSTEM_WIDE.fetchFrom(params)) { + wixPipeline.addLightOptions("-sice:ICE91"); + } } } @@ -616,15 +619,15 @@ private Path buildMSI(Map params, Set uniqueCultures = new LinkedHashSet<>(); uniqueCultures.addAll(cultures); switch (wixToolset.getType()) { - case Wix3, Wix36 -> { - wixPipeline.addLightOptions(uniqueCultures.stream().collect(Collectors.joining(";", - "-cultures:", ""))); - } case Wix4 -> { uniqueCultures.forEach(culture -> { wixPipeline.addLightOptions("-culture", culture); }); } + case Wix3 -> { + wixPipeline.addLightOptions(uniqueCultures.stream().collect(Collectors.joining(";", + "-cultures:", ""))); + } } wixPipeline.buildMsi(msiOut.toAbsolutePath()); diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java index ab8011fceb6e0..45fcda0cd1bda 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java @@ -153,6 +153,16 @@ void addFilesToConfigRoot() throws IOException { super.addFilesToConfigRoot(); } + @Override + List getLoggableWixFeatures() { + if (isWithWix36Features()) { + return List.of(MessageFormat.format(I18N.getString("message.use-wix36-features"), + getWixVersion())); + } else { + return List.of(); + } + } + @Override protected Collection getFragmentWriters() { return List.of( @@ -315,15 +325,15 @@ boolean isFile() { return cfg.isFile; } - static void startElement(WixToolsetType wixVersion, XMLStreamWriter xml, String componentId, + static void startElement(WixToolsetType wixType, XMLStreamWriter xml, String componentId, String componentGuid) throws XMLStreamException, IOException { xml.writeStartElement("Component"); - switch (wixVersion) { - case Wix3, Wix36 -> { + switch (wixType) { + case Wix3 -> { xml.writeAttribute("Win64", is64Bit() ? "yes" : "no"); xml.writeAttribute("Guid", componentGuid); } - default -> { + case Wix4 -> { xml.writeAttribute("Bitness", is64Bit() ? "always64" : "always32"); if (!componentGuid.equals("*")) { xml.writeAttribute("Guid", componentGuid); @@ -384,7 +394,7 @@ private String addComponent(XMLStreamWriter xml, Path path, startDirectoryElement(xml, "DirectoryRef", directoryRefPath); final String componentId = "c" + role.idOf(path); - Component.startElement(getWixVersion(), xml, componentId, String.format( + Component.startElement(getWixType(), xml, componentId, String.format( "{%s}", role.guidOf(path))); if (role == Component.Shortcut) { @@ -393,15 +403,15 @@ private String addComponent(XMLStreamWriter xml, Path path, }).map(shortcutFolder -> { return shortcutFolder.property; }).findFirst().get(); - switch (getWixVersion()) { - case Wix4 -> { - xml.writeAttribute("Condition", property); - } - case Wix3, Wix36 -> { + switch (getWixType()) { + case Wix3 -> { xml.writeStartElement("Condition"); xml.writeCharacters(property); xml.writeEndElement(); } + case Wix4 -> { + xml.writeAttribute("Condition", property); + } } } @@ -476,8 +486,8 @@ private void addShortcutComponentGroup(XMLStreamWriter xml) throws if (componentId != null) { Path folderPath = folder.getPath(this); boolean defineFolder; - switch (getWixVersion()) { - case Wix3, Wix36 -> + switch (getWixType()) { + case Wix3 -> defineFolder = true; default -> defineFolder = !SYSTEM_DIRS.contains(folderPath); @@ -576,7 +586,7 @@ private List addRootBranch(XMLStreamWriter xml, Path path) int levels; var dirIt = path.iterator(); - if (getWixVersion() == WixToolsetType.Wix4 && TARGETDIR.equals(path.getName(0))) { + if (getWixType() != WixToolsetType.Wix3 && TARGETDIR.equals(path.getName(0))) { levels = 0; dirIt.next(); } else { @@ -610,8 +620,8 @@ private List addRootBranch(XMLStreamWriter xml, Path path) private void startDirectoryElement(XMLStreamWriter xml, String wix3ElementName, Path path) throws XMLStreamException { final String elementName; - switch (getWixVersion()) { - case Wix3, Wix36 -> { + switch (getWixType()) { + case Wix3 -> { elementName = wix3ElementName; } default -> { @@ -830,7 +840,7 @@ private void addRegistryKeyPath(XMLStreamWriter xml, Path path, xml.writeStartElement("RegistryKey"); xml.writeAttribute("Root", regRoot); xml.writeAttribute("Key", registryKeyPath); - if (getWixVersion() == WixToolsetType.Wix3) { + if (!isWithWix36Features()) { xml.writeAttribute("Action", "createAndRemoveOnUninstall"); } xml.writeStartElement("RegistryValue"); @@ -844,7 +854,7 @@ private void addRegistryKeyPath(XMLStreamWriter xml, Path path, private String addDirectoryCleaner(XMLStreamWriter xml, Path path) throws XMLStreamException, IOException { - if (getWixVersion() == WixToolsetType.Wix3) { + if (!isWithWix36Features()) { return null; } @@ -866,7 +876,7 @@ private String addDirectoryCleaner(XMLStreamWriter xml, Path path) throws xml.writeStartElement("DirectoryRef"); xml.writeAttribute("Id", INSTALLDIR.toString()); - Component.startElement(getWixVersion(), xml, componentId, "*"); + Component.startElement(getWixType(), xml, componentId, "*"); addRegistryKeyPath(xml, INSTALLDIR, () -> propertyId, () -> { return toWixPath(path); @@ -883,6 +893,10 @@ private String addDirectoryCleaner(XMLStreamWriter xml, Path path) throws 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 diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java index 4e9003ebbffd8..68d80d18ad1e6 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java @@ -29,9 +29,10 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.nio.file.Path; -import java.text.MessageFormat; 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; @@ -49,11 +50,14 @@ */ abstract class WixFragmentBuilder { - void setWixVersion(WixToolsetType 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 @@ void initFromParams(Map params) { Source.ResourceDir); } - void logWixFeatures() { - if (wixVersion != WixToolsetType.Wix3) { - Log.verbose(MessageFormat.format(I18N.getString( - "message.use-wix36-features"), wixVersion)); - } + List getLoggableWixFeatures() { + return List.of(); } void configureWixPipeline(WixPipeline wixPipeline) { @@ -95,29 +96,35 @@ void addFilesToConfigRoot() throws IOException { } } - WixToolsetType getWixVersion() { + final WixToolsetType getWixType() { + return wixType; + } + + final DottedVersion getWixVersion() { return wixVersion; } - static enum WixNamespace { + protected static enum WixNamespace { Default, Util; } - Map getWixNamespaces() { - switch (wixVersion) { + final protected Map getWixNamespaces() { + switch (wixType) { case Wix4 -> { return Map.of(WixNamespace.Default, "http://wixtoolset.org/schemas/v4/wxs", WixNamespace.Util, "http://wixtoolset.org/schemas/v4/wxs/util"); } - default -> { + case Wix3 -> { return Map.of(WixNamespace.Default, "http://schemas.microsoft.com/wix/2006/wi", WixNamespace.Util, "http://schemas.microsoft.com/wix/UtilExtension"); } + default -> + throw new IllegalArgumentException(); } } @@ -125,26 +132,26 @@ static boolean is64Bit() { return Architecture.is64bit(); } - protected Path getConfigRoot() { + final protected Path getConfigRoot() { return configRoot; } protected abstract Collection 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 ResourceGroup(getWixVersion()); + additionalResources = new ResourceGroup(getWixType()); } additionalResources.addResource(resource, configRoot.resolve(saveAsName)); } @@ -222,7 +229,8 @@ private String escape(CharSequence str) { private final XMLStreamWriter target; } - private WixToolsetType wixVersion; + private WixToolsetType wixType; + private DottedVersion wixVersion; private WixVariables wixVariables; private ResourceGroup additionalResources; private OverridableResource fragmentResource; diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java index f459be8d0f4c3..1e553a3680de2 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java @@ -83,8 +83,8 @@ void buildMsi(Path msi) throws IOException { Objects.requireNonNull(workDir); switch (toolset.getType()) { + case Wix3 -> buildMsiWix3(msi); case Wix4 -> buildMsiWix4(msi); - case Wix36, Wix3 -> buildMsiWix3(msi); } } @@ -100,20 +100,21 @@ private void addWixVariblesToCommandLine( Stream 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()); } - case Wix3, Wix36 -> { - stream = entryStream.map(wixVar -> { - return String.format("-d%s=%s", wixVar.getKey(), wixVar. - getValue()); - }); + default -> { + throw new IllegalArgumentException(); } - default -> - throw new IllegalStateException(); } stream.reduce(cmdline, (ctnr, wixVar) -> { diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java index 1d2ea3d1c74a4..5cc8c70a0569c 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java @@ -154,11 +154,11 @@ private static byte[] saveResourceInMemory(OverridableResource resource) throws final static class ResourceGroup { ResourceGroup(WixToolsetType wixToolsetType) { - if (wixToolsetType == WixToolsetType.Wix4) { - // Need to convert internal WiX sources - conv = new WixSourceConverter(); - } else { - conv = null; + switch (wixToolsetType) { + case Wix3 -> + conv = null; + default -> + conv = new WixSourceConverter(); } } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java index ba09af56042fa..ab433616f44a6 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java @@ -35,10 +35,8 @@ final class WixToolset { static enum WixToolsetType { // Wix v4+ Wix4(WixTool.Wix4), - // Wix v3.0-v3.6 - Wix3(WixTool.Candle3, WixTool.Light3), - // Wix v3.6+ - Wix36(WixTool.Candle3, WixTool.Light3); + // Wix v3+ + Wix3(WixTool.Candle3, WixTool.Light3); WixToolsetType(WixTool... tools) { this.tools = Set.of(tools); diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java index 1da33f412c77c..6bb96a76961f4 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java @@ -102,9 +102,9 @@ void configureWixPipeline(WixPipeline wixPipeline) { if (withShortcutPromptDlg || withInstallDirChooserDlg || withLicenseDlg) { final String extName; - switch (getWixVersion()) { + switch (getWixType()) { case Wix4 -> extName = "WixToolset.UI.wixext"; - case Wix3, Wix36 -> extName = "WixUIExtension"; + case Wix3 -> extName = "WixUIExtension"; default -> throw new IllegalArgumentException(); } wixPipeline.addLightOptions("-ext", extName); @@ -157,7 +157,7 @@ private void addUI(XMLStreamWriter xml) throws XMLStreamException, var ui = getUI(); if (ui != null) { - ui.write(getWixVersion(), this, xml); + ui.write(getWixType(), this, xml); } else { xml.writeStartElement("UI"); xml.writeAttribute("Id", "JpUI"); @@ -193,8 +193,8 @@ private enum UI { this.dialogPairsSupplier = dialogPairsSupplier; } - void write(WixToolsetType wixVersion, WixUiFragmentBuilder outer, XMLStreamWriter xml) throws XMLStreamException, IOException { - switch (wixVersion) { + void write(WixToolsetType wixType, WixUiFragmentBuilder outer, XMLStreamWriter xml) throws XMLStreamException, IOException { + switch (wixType) { case Wix4 -> { // https://wixtoolset.org/docs/fourthree/faqs/#converting-custom-wixui-dialog-sets xml.writeProcessingInstruction("foreach WIXUIARCH in X86;X64;A64"); @@ -206,22 +206,22 @@ void write(WixToolsetType wixVersion, WixUiFragmentBuilder outer, XMLStreamWrite } xml.writeStartElement("UI"); - switch (wixVersion) { + switch (wixType) { case Wix4 -> { xml.writeAttribute("Id", "JpUIInternal"); } - case Wix3, Wix36 -> { + case Wix3 -> { xml.writeAttribute("Id", "JpUI"); xml.writeStartElement("UIRef"); xml.writeAttribute("Id", wixUIRef); xml.writeEndElement(); // UIRef } } - writeContents(wixVersion, outer, xml); + writeContents(wixType, outer, xml); xml.writeEndElement(); // UI } - private void writeContents(WixToolsetType wixVersion, WixUiFragmentBuilder outer, + private void writeContents(WixToolsetType wixType, WixUiFragmentBuilder outer, XMLStreamWriter xml) throws XMLStreamException, IOException { if (dialogIdsSupplier != null) { List dialogIds = dialogIdsSupplier.apply(outer); @@ -240,7 +240,7 @@ private void writeContents(WixToolsetType wixVersion, WixUiFragmentBuilder outer DialogPair pair = new DialogPair(firstId, secondId); for (var curPair : List.of(pair, pair.flip())) { for (var publish : dialogPairs.get(curPair)) { - writePublishDialogPair(wixVersion, xml, publish, curPair); + writePublishDialogPair(wixType, xml, publish, curPair); } } firstId = secondId; @@ -482,7 +482,7 @@ private static PublishBuilder buildPublish(Publish publish) { return new PublishBuilder(publish); } - private static void writePublishDialogPair(WixToolsetType wixVersion, XMLStreamWriter xml, + private static void writePublishDialogPair(WixToolsetType wixType, XMLStreamWriter xml, Publish publish, DialogPair dialogPair) throws IOException, XMLStreamException { xml.writeStartElement("Publish"); xml.writeAttribute("Dialog", dialogPair.firstId); @@ -492,9 +492,9 @@ private static void writePublishDialogPair(WixToolsetType wixVersion, XMLStreamW if (publish.order != 0) { xml.writeAttribute("Order", String.valueOf(publish.order)); } - switch (wixVersion) { + switch (wixType) { case Wix4 -> xml.writeAttribute("Condition", publish.condition); - case Wix3, Wix36 -> xml.writeCharacters(publish.condition); + case Wix3 -> xml.writeCharacters(publish.condition); } xml.writeEndElement(); } From f846a4318164dd0d6103e2d7b7d4b7988227e450 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 30 May 2024 14:17:36 -0400 Subject: [PATCH 18/30] Fix issue with running jtreg tests when wix.exe is not available in the PATH. When wix.exe is not in the PATH, jpackage uses %USERPROFILE% env variable to get user home directory. But jtreg removes it from the environment when running tests, so without wix.exe in the PATH all jpackage tests will fail on Windows. This makes it impossible to test jpackage with wix3. This patch addresses this issue by reading user home additionally from "java.home" system property. --- .../windows/classes/jdk/jpackage/internal/WixTool.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java index 51d5c7594991d..f16b28edf2448 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java @@ -244,8 +244,11 @@ private static List findWixInstallDirs() { } private static List findWixCurrentInstallDirs() { - return Stream.of(getEnvVariableAsPath("USERPROFILE").resolve( - ".dotnet/tools")).filter(Files::isDirectory).toList(); + 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 findWix3InstallDirs() { From 74407c8fa55c6207fbfffb8f31ea7d263ad840e9 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 30 May 2024 14:27:48 -0400 Subject: [PATCH 19/30] Don't suppress jpackage output when it is detecting what packaging tools available and what bundlers are supported. --- .../jdk/jpackage/test/PackageType.java | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageType.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageType.java index 9b8b577185ea8..71637ef7134a6 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageType.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageType.java @@ -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,13 +22,16 @@ */ package jdk.jpackage.test; +import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collections; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import java.util.stream.Stream; +import jdk.jpackage.internal.Log; /** * jpackage type traits. @@ -91,7 +94,7 @@ static PackageType fromSuffix(String packageFilename) { return null; } - private static boolean isBundlerSupported(String bundlerClass) { + private static boolean isBundlerSupportedImpl(String bundlerClass) { try { Class clazz = Class.forName(bundlerClass); Method supported = clazz.getMethod("supported", boolean.class); @@ -105,6 +108,30 @@ private static boolean isBundlerSupported(String bundlerClass) { return false; } + private static boolean isBundlerSupported(String bundlerClass) { + AtomicBoolean reply = new AtomicBoolean(); + try { + // Capture jpackage's activity on configuring bundlers. + // Log configuration is thread-local. + // Call Log.setPrintWriter and Log.setVerbose in a separate + // thread to keep the main log configuration intact. + var thread = new Thread(() -> { + Log.setPrintWriter(new PrintWriter(System.out), new PrintWriter(System.err)); + Log.setVerbose(); + try { + reply.set(isBundlerSupportedImpl(bundlerClass)); + } finally { + Log.flush(); + } + }); + thread.run(); + thread.join(); + } catch (InterruptedException ex) { + Functional.rethrowUnchecked(ex); + } + return reply.get(); + } + private final String name; private final String suffix; private final boolean supported; From 00d7cc6c102f3b2c8db84d47cc6a04aad53f74fc Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Mon, 3 Jun 2024 10:52:15 -0400 Subject: [PATCH 20/30] Rollback unneeded change --- .../internal/OverridableResource.java | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java index 47bebe6687579..ecd88bb7fb975 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,6 @@ import java.io.BufferedReader; import java.io.ByteArrayInputStream; -import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -43,7 +42,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; @@ -75,7 +73,7 @@ final class OverridableResource { OverridableResource(String defaultName) { this.defaultName = defaultName; - setSourceOrder(Source.values()).setLogger(Log::verbose); + setSourceOrder(Source.values()); } Path getResourceDir() { @@ -134,11 +132,6 @@ OverridableResource setSourceOrder(Source... v) { return this; } - OverridableResource setLogger(Consumer v) { - logger = v; - return this; - } - /** * Set name of file to look for in resource dir. * @@ -229,20 +222,6 @@ static OverridableResource createResource(String defaultName, RESOURCE_DIR.fetchFrom(params)); } - final class NoLogging implements Closeable { - NoLogging() { - logger = OverridableResource.this.logger; - OverridableResource.this.setLogger(v -> {}); - } - - @Override - public void close() throws IOException { - OverridableResource.this.setLogger(logger); - } - - private final Consumer logger; - } - private Source sendToConsumer(ResourceConsumer consumer) throws IOException { for (var source: sources) { if (source.getValue().apply(consumer)) { @@ -262,7 +241,7 @@ private String getPrintableCategory() { private boolean useExternal(ResourceConsumer dest) throws IOException { boolean used = externalPath != null && Files.exists(externalPath); if (used && dest != null) { - logger.accept(MessageFormat.format(I18N.getString( + Log.verbose(MessageFormat.format(I18N.getString( "message.using-custom-resource-from-file"), getPrintableCategory(), externalPath.toAbsolutePath().normalize())); @@ -291,9 +270,9 @@ private boolean useResourceDir(ResourceConsumer dest) throws IOException { final Path logResourceName = Optional.ofNullable(logPublicName).orElse( resourceName).normalize(); - logger.accept((MessageFormat.format(I18N.getString( + Log.verbose(MessageFormat.format(I18N.getString( "message.using-custom-resource"), getPrintableCategory(), - logResourceName))); + logResourceName)); try (InputStream in = Files.newInputStream(customResource)) { processResourceStream(in, dest); @@ -312,9 +291,9 @@ private boolean useDefault(ResourceConsumer dest) throws IOException { .orElse(Optional .ofNullable(publicName) .orElseGet(() -> dest.publicName())); - logger.accept((MessageFormat.format( + Log.verbose(MessageFormat.format( I18N.getString("message.using-default-resource"), - defaultName, getPrintableCategory(), resourceName))); + defaultName, getPrintableCategory(), resourceName)); try (InputStream in = readDefault(defaultName)) { processResourceStream(in, dest); @@ -401,7 +380,6 @@ private SourceHandler getHandler(Source sourceType) { } private Map substitutionData; - private Consumer logger; private String category; private Path resourceDir; private Path publicName; From 5fded0b107643b20a5c517a58f611b89406aec8e Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Mon, 3 Jun 2024 11:02:18 -0400 Subject: [PATCH 21/30] Revert "Rollback unneeded change" This reverts commit 00d7cc6c102f3b2c8db84d47cc6a04aad53f74fc. --- .../internal/OverridableResource.java | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java index ecd88bb7fb975..47bebe6687579 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java @@ -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 @@ -26,6 +26,7 @@ import java.io.BufferedReader; import java.io.ByteArrayInputStream; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -42,6 +43,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; @@ -73,7 +75,7 @@ final class OverridableResource { OverridableResource(String defaultName) { this.defaultName = defaultName; - setSourceOrder(Source.values()); + setSourceOrder(Source.values()).setLogger(Log::verbose); } Path getResourceDir() { @@ -132,6 +134,11 @@ OverridableResource setSourceOrder(Source... v) { return this; } + OverridableResource setLogger(Consumer v) { + logger = v; + return this; + } + /** * Set name of file to look for in resource dir. * @@ -222,6 +229,20 @@ static OverridableResource createResource(String defaultName, RESOURCE_DIR.fetchFrom(params)); } + final class NoLogging implements Closeable { + NoLogging() { + logger = OverridableResource.this.logger; + OverridableResource.this.setLogger(v -> {}); + } + + @Override + public void close() throws IOException { + OverridableResource.this.setLogger(logger); + } + + private final Consumer logger; + } + private Source sendToConsumer(ResourceConsumer consumer) throws IOException { for (var source: sources) { if (source.getValue().apply(consumer)) { @@ -241,7 +262,7 @@ private String getPrintableCategory() { private boolean useExternal(ResourceConsumer dest) throws IOException { boolean used = externalPath != null && Files.exists(externalPath); if (used && dest != null) { - Log.verbose(MessageFormat.format(I18N.getString( + logger.accept(MessageFormat.format(I18N.getString( "message.using-custom-resource-from-file"), getPrintableCategory(), externalPath.toAbsolutePath().normalize())); @@ -270,9 +291,9 @@ private boolean useResourceDir(ResourceConsumer dest) throws IOException { final Path logResourceName = Optional.ofNullable(logPublicName).orElse( resourceName).normalize(); - Log.verbose(MessageFormat.format(I18N.getString( + logger.accept((MessageFormat.format(I18N.getString( "message.using-custom-resource"), getPrintableCategory(), - logResourceName)); + logResourceName))); try (InputStream in = Files.newInputStream(customResource)) { processResourceStream(in, dest); @@ -291,9 +312,9 @@ private boolean useDefault(ResourceConsumer dest) throws IOException { .orElse(Optional .ofNullable(publicName) .orElseGet(() -> dest.publicName())); - Log.verbose(MessageFormat.format( + logger.accept((MessageFormat.format( I18N.getString("message.using-default-resource"), - defaultName, getPrintableCategory(), resourceName)); + defaultName, getPrintableCategory(), resourceName))); try (InputStream in = readDefault(defaultName)) { processResourceStream(in, dest); @@ -380,6 +401,7 @@ private SourceHandler getHandler(Source sourceType) { } private Map substitutionData; + private Consumer logger; private String category; private Path resourceDir; private Path publicName; From 3302aa0803937d82efbbee0ed9a697ba4293eb59 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Mon, 3 Jun 2024 12:29:59 -0400 Subject: [PATCH 22/30] Ensure WiX source converter works with the default JDK XSLT processor and Saxon HE (v12.4) --- .../classes/jdk/jpackage/internal/WixSourceConverter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java index 5cc8c70a0569c..aabdbe2438852 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java @@ -271,7 +271,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl } } - if (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) { @@ -316,7 +316,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl static class Prefix { - public Prefix(String name) { + Prefix(String name) { this.name = name; } From 5e97c4d84f6cb8b4928b3762542f54dc2926f9ec Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Mon, 3 Jun 2024 18:19:28 -0400 Subject: [PATCH 23/30] Support the use of custom wix source converter --- .../jpackage/internal/WixSourceConverter.java | 46 +++++++++++-------- .../resources/WinResources.properties | 3 +- .../resources/WinResources_de.properties | 3 +- .../resources/WinResources_ja.properties | 3 +- .../resources/WinResources_zh_CN.properties | 3 +- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java index aabdbe2438852..93f2b81b2e845 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java @@ -51,7 +51,6 @@ import javax.xml.transform.stax.StAXResult; import javax.xml.transform.stream.StreamSource; import jdk.jpackage.internal.WixToolset.WixToolsetType; -import jdk.jpackage.internal.resources.ResourceLocator; import org.w3c.dom.Document; import org.xml.sax.SAXException; @@ -66,8 +65,16 @@ enum Status { Transformed, } - WixSourceConverter() { - var xslt = new StreamSource(ResourceLocator.class.getResourceAsStream("wix3-to-wix4-conv.xsl")); + 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 { @@ -142,24 +149,16 @@ Status appyTo(OverridableResource resource, Path resourceSaveAsFile) throws IOEx return Status.Transformed; } - @SuppressWarnings("try") private static byte[] saveResourceInMemory(OverridableResource resource) throws IOException { var buf = new ByteArrayOutputStream(); - try (var nolog = resource.new NoLogging()) { - resource.saveToStream(buf); - } + resource.saveToStream(buf); return buf.toByteArray(); } final static class ResourceGroup { ResourceGroup(WixToolsetType wixToolsetType) { - switch (wixToolsetType) { - case Wix3 -> - conv = null; - default -> - conv = new WixSourceConverter(); - } + this.wixToolsetType = wixToolsetType; } void addResource(OverridableResource resource, Path resourceSaveAsFile) { @@ -167,19 +166,26 @@ void addResource(OverridableResource resource, Path resourceSaveAsFile) { } void saveResources() throws IOException { - if (conv != null) { - for (var e : resources.entrySet()) { - conv.appyTo(e.getValue(), e.getKey()); + switch (wixToolsetType) { + case Wix3 -> { + for (var e : resources.entrySet()) { + e.getValue().saveToFile(e.getKey()); + } } - } else { - for (var e : resources.entrySet()) { - e.getValue().saveToFile(e.getKey()); + default -> { + 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()); + } } } } private final Map resources = new HashMap<>(); - private final WixSourceConverter conv; + private final WixToolsetType wixToolsetType; } // diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties index 8147a01359df2..4c264857b7495 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties @@ -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,6 +41,7 @@ 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. Was looking for WiX v3 light.exe and candle.exe or WiX v4 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. diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties index f4a82428f8d64..960043b590493 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties @@ -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,6 +41,7 @@ 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=Can not find WiX tools. Was looking for WiX v3 light.exe and candle.exe or WiX v4 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. diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties index 2b75fc67ae5a8..a4db63e46a416 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties @@ -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,6 +41,7 @@ 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=Can not find WiX tools. Was looking for WiX v3 light.exe and candle.exe or WiX v4 wix.exe and none was found error.no-wix-tools.advice=WiX 3.0以降をhttps://wixtoolset.orgからダウンロードし、PATHに追加します。 diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties index 8358cfa223085..c3c6dc1cfe141 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties @@ -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,6 +41,7 @@ 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=Can not find WiX tools. Was looking for WiX v3 light.exe and candle.exe or WiX v4 wix.exe and none was found error.no-wix-tools.advice=从 https://wixtoolset.org 下载 WiX 3.0 或更高版本,然后将其添加到 PATH。 From fbb9f4182f2cafd7a4daf71ec7b9eca7e159a311 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Mon, 3 Jun 2024 18:21:35 -0400 Subject: [PATCH 24/30] Revert "Revert "Rollback unneeded change"" This reverts commit 5fded0b107643b20a5c517a58f611b89406aec8e. --- .../internal/OverridableResource.java | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java index 47bebe6687579..ecd88bb7fb975 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,6 @@ import java.io.BufferedReader; import java.io.ByteArrayInputStream; -import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -43,7 +42,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; @@ -75,7 +73,7 @@ final class OverridableResource { OverridableResource(String defaultName) { this.defaultName = defaultName; - setSourceOrder(Source.values()).setLogger(Log::verbose); + setSourceOrder(Source.values()); } Path getResourceDir() { @@ -134,11 +132,6 @@ OverridableResource setSourceOrder(Source... v) { return this; } - OverridableResource setLogger(Consumer v) { - logger = v; - return this; - } - /** * Set name of file to look for in resource dir. * @@ -229,20 +222,6 @@ static OverridableResource createResource(String defaultName, RESOURCE_DIR.fetchFrom(params)); } - final class NoLogging implements Closeable { - NoLogging() { - logger = OverridableResource.this.logger; - OverridableResource.this.setLogger(v -> {}); - } - - @Override - public void close() throws IOException { - OverridableResource.this.setLogger(logger); - } - - private final Consumer logger; - } - private Source sendToConsumer(ResourceConsumer consumer) throws IOException { for (var source: sources) { if (source.getValue().apply(consumer)) { @@ -262,7 +241,7 @@ private String getPrintableCategory() { private boolean useExternal(ResourceConsumer dest) throws IOException { boolean used = externalPath != null && Files.exists(externalPath); if (used && dest != null) { - logger.accept(MessageFormat.format(I18N.getString( + Log.verbose(MessageFormat.format(I18N.getString( "message.using-custom-resource-from-file"), getPrintableCategory(), externalPath.toAbsolutePath().normalize())); @@ -291,9 +270,9 @@ private boolean useResourceDir(ResourceConsumer dest) throws IOException { final Path logResourceName = Optional.ofNullable(logPublicName).orElse( resourceName).normalize(); - logger.accept((MessageFormat.format(I18N.getString( + Log.verbose(MessageFormat.format(I18N.getString( "message.using-custom-resource"), getPrintableCategory(), - logResourceName))); + logResourceName)); try (InputStream in = Files.newInputStream(customResource)) { processResourceStream(in, dest); @@ -312,9 +291,9 @@ private boolean useDefault(ResourceConsumer dest) throws IOException { .orElse(Optional .ofNullable(publicName) .orElseGet(() -> dest.publicName())); - logger.accept((MessageFormat.format( + Log.verbose(MessageFormat.format( I18N.getString("message.using-default-resource"), - defaultName, getPrintableCategory(), resourceName))); + defaultName, getPrintableCategory(), resourceName)); try (InputStream in = readDefault(defaultName)) { processResourceStream(in, dest); @@ -401,7 +380,6 @@ private SourceHandler getHandler(Source sourceType) { } private Map substitutionData; - private Consumer logger; private String category; private Path resourceDir; private Path publicName; From 99aac32367ce4bbd4bdc346ce0ecc92208a2beca Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 4 Jun 2024 09:48:36 -0400 Subject: [PATCH 25/30] EOLs restored --- .../resources/MsiInstallerStrings_de.wxl | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_de.wxl b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_de.wxl index 635b330a34e34..31be69aa5d0ae 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_de.wxl +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_de.wxl @@ -1,18 +1,18 @@ - - - Hauptfeature - Eine höhere Version von [ProductName] ist bereits installiert. Downgrades sind deaktiviert. Setup wird jetzt beendet. - Eine niedrigere Version von [ProductName] ist bereits installiert. Upgrades sind deaktiviert. Setup wird jetzt beendet. - - [ProductName]-Setup - {\WixUI_Font_Title}-Verknüpfungen - WixUI_Bmp_Banner - Wählen Sie die zu erstellenden Verknüpfungen aus. - Desktopverknüpfung(en) erstellen - Startmenüverknüpfung(en) erstellen - - [ProductName]-Setup - Der Ordner [INSTALLDIR] ist bereits vorhanden. Möchten Sie diesen Ordner trotzdem installieren? - - Mit [ProductName] öffnen - + + + Hauptfeature + Eine höhere Version von [ProductName] ist bereits installiert. Downgrades sind deaktiviert. Setup wird jetzt beendet. + Eine niedrigere Version von [ProductName] ist bereits installiert. Upgrades sind deaktiviert. Setup wird jetzt beendet. + + [ProductName]-Setup + {\WixUI_Font_Title}-Verknüpfungen + WixUI_Bmp_Banner + Wählen Sie die zu erstellenden Verknüpfungen aus. + Desktopverknüpfung(en) erstellen + Startmenüverknüpfung(en) erstellen + + [ProductName]-Setup + Der Ordner [INSTALLDIR] ist bereits vorhanden. Möchten Sie diesen Ordner trotzdem installieren? + + Mit [ProductName] öffnen + From 782d121b324759c618a1967cceea84a1dd74ffa5 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 4 Jun 2024 09:57:06 -0400 Subject: [PATCH 26/30] EOLs restored --- .../resources/MsiInstallerStrings_ja.wxl | 36 +++++++++---------- .../resources/MsiInstallerStrings_zh_CN.wxl | 36 +++++++++---------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_ja.wxl b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_ja.wxl index a9440321bd50b..32a57433829be 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_ja.wxl +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_ja.wxl @@ -1,18 +1,18 @@ - - - 主な機能 - [ProductName]のより上位のバージョンがすでにインストールされています。ダウングレードは無効です。セットアップを終了します。 - [ProductName]のより下位のバージョンがすでにインストールされています。アップグレードは無効です。セットアップを終了します。 - - [ProductName]セットアップ - {\WixUI_Font_Title}ショートカット - WixUI_Bmp_Banner - 作成するショートカットを選択します。 - デスクトップ・ショートカットの作成 - スタート・メニューのショートカットの作成 - - [ProductName]セットアップ - フォルダ[INSTALLDIR]はすでに存在します。そのフォルダにインストールしますか? - - [ProductName]で開く - + + + 主な機能 + [ProductName]のより上位のバージョンがすでにインストールされています。ダウングレードは無効です。セットアップを終了します。 + [ProductName]のより下位のバージョンがすでにインストールされています。アップグレードは無効です。セットアップを終了します。 + + [ProductName]セットアップ + {\WixUI_Font_Title}ショートカット + WixUI_Bmp_Banner + 作成するショートカットを選択します。 + デスクトップ・ショートカットの作成 + スタート・メニューのショートカットの作成 + + [ProductName]セットアップ + フォルダ[INSTALLDIR]はすでに存在します。そのフォルダにインストールしますか? + + [ProductName]で開く + diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_zh_CN.wxl b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_zh_CN.wxl index 5b55c1e270ec6..978f74a1546b1 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_zh_CN.wxl +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_zh_CN.wxl @@ -1,18 +1,18 @@ - - - 主要功能 - 已安装更高版本的 [ProductName]。降级已禁用。现在将退出安装。 - 已安装更低版本的 [ProductName]。升级已禁用。现在将退出安装。 - - [ProductName] 安装程序 - {\WixUI_Font_Title}快捷方式 - WixUI_Bmp_Banner - 选择要创建的快捷方式。 - 创建桌面快捷方式 - 创建开始菜单快捷方式 - - [ProductName] 安装程序 - 文件夹 [INSTALLDIR] 已存在。是否仍要安装到该文件夹? - - 使用 [ProductName] 打开 - + + + 主要功能 + 已安装更高版本的 [ProductName]。降级已禁用。现在将退出安装。 + 已安装更低版本的 [ProductName]。升级已禁用。现在将退出安装。 + + [ProductName] 安装程序 + {\WixUI_Font_Title}快捷方式 + WixUI_Bmp_Banner + 选择要创建的快捷方式。 + 创建桌面快捷方式 + 创建开始菜单快捷方式 + + [ProductName] 安装程序 + 文件夹 [INSTALLDIR] 已存在。是否仍要安装到该文件夹? + + 使用 [ProductName] 打开 + From cbf7d31e4aeec296abd59ab668805b48d7bf56f8 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 4 Jun 2024 11:59:37 -0400 Subject: [PATCH 27/30] Fix issue with overwriting custom l10n file in the resource directory. All WiX source files from the resource directory should be copied to jpackage work directory before running wxi tools. jpackage should not change files in the resource directory. --- .../classes/jdk/jpackage/internal/WinMsiBundler.java | 3 ++- test/jdk/tools/jpackage/windows/WinL10nTest.java | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java index b428590b82127..912a205106dce 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java @@ -591,7 +591,7 @@ private Path buildMSI(Map params, var name = path.getFileName().toString(); wixResources.addResource(createResource(name, params).setPublicName(name). setSourceOrder(OverridableResource.Source.ResourceDir).setCategory(I18N. - getString("resource.wxl-file")), path); + getString("resource.wxl-file")), configDir.resolve(name)); } // Save all WiX resources into config dir. @@ -606,6 +606,7 @@ private Path buildMSI(Map params, List cultures = new ArrayList<>(); for (var wxl : customWxlFiles) { + wxl = configDir.resolve(wxl.getFileName()); wixPipeline.addLightOptions("-loc", wxl.toAbsolutePath().normalize().toString()); cultures.add(getCultureFromWxlFile(wxl)); } diff --git a/test/jdk/tools/jpackage/windows/WinL10nTest.java b/test/jdk/tools/jpackage/windows/WinL10nTest.java index 6f51b1158e552..a868fd5f051c4 100644 --- a/test/jdk/tools/jpackage/windows/WinL10nTest.java +++ b/test/jdk/tools/jpackage/windows/WinL10nTest.java @@ -206,10 +206,12 @@ public void test() throws IOException { } 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(getBuildCommandLine(result)); + v.createCmdOutputVerifier(wixSrcDir).apply(getBuildCommandLine(result)); } } Path tempDir = getTempDirectory(cmd, tempRoot).toAbsolutePath(); @@ -220,7 +222,7 @@ public void test() throws IOException { Stream.of(wxlFileInitializers) .filter(Predicate.not(WixFileInitializer::isValid)) .forEach(v -> v.createCmdOutputVerifier( - resourceDir).apply(result.getOutput().stream())); + wixSrcDir).apply(result.getOutput().stream())); TKit.assertFalse(getBuildCommandLine(result).findAny().isPresent(), "Check light.exe was not invoked"); } From af8fb680ca34fe30b0b06069d3cdb37ff50693fc Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 4 Jun 2024 12:02:20 -0400 Subject: [PATCH 28/30] WixSourceConverter#applyTo should do what OverridableResource#saveToFile() does: create parent directory and replace output file if it exists. --- .../classes/jdk/jpackage/internal/WixSourceConverter.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java index 93f2b81b2e845..2cccf40bca1c3 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java @@ -32,6 +32,7 @@ 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; @@ -136,11 +137,14 @@ Status appyTo(OverridableResource resource, Path resourceSaveAsFile) throws IOEx throw new RuntimeException(ex); } - try (var outXml = Files.newOutputStream(resourceSaveAsFile)) { + 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); From 899470e4d51d90f101a7c2ad937c0433a0f2acb1 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 6 Jun 2024 08:48:55 -0400 Subject: [PATCH 29/30] Unify "switch(wixVersion) ..." expressions. Always throw IllegalArgumentException if "wixVersion" is not WixToolkit#Wix3 or WixToolkit#Wix4. When/If we add another value to WixToolkit enum this will help not to miss adjusting all WiX version-specific code. --- .../jdk/jpackage/internal/WinMsiBundler.java | 14 +++++++++++--- .../internal/WixAppImageFragmentBuilder.java | 16 ++++++++++++++-- .../jpackage/internal/WixFragmentBuilder.java | 16 +++++++++------- .../jdk/jpackage/internal/WixPipeline.java | 1 + .../jpackage/internal/WixSourceConverter.java | 5 ++++- .../internal/WixUiFragmentBuilder.java | 18 +++++++++++++----- 6 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java index 912a205106dce..c0ae65b3b0bcc 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java @@ -549,6 +549,11 @@ private Path buildMSI(Map params, wixPipeline.addLightOptions("-sice:ICE91"); } } + case Wix4 -> { + } + default -> { + throw new IllegalArgumentException(); + } } final Path configDir = CONFIG_ROOT.fetchFrom(params); @@ -620,14 +625,17 @@ private Path buildMSI(Map params, Set uniqueCultures = new LinkedHashSet<>(); uniqueCultures.addAll(cultures); switch (wixToolset.getType()) { + case Wix3 -> { + wixPipeline.addLightOptions(uniqueCultures.stream().collect(Collectors.joining(";", + "-cultures:", ""))); + } case Wix4 -> { uniqueCultures.forEach(culture -> { wixPipeline.addLightOptions("-culture", culture); }); } - case Wix3 -> { - wixPipeline.addLightOptions(uniqueCultures.stream().collect(Collectors.joining(";", - "-cultures:", ""))); + default -> { + throw new IllegalArgumentException(); } } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java index 45fcda0cd1bda..5bc20c1413c86 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java @@ -339,6 +339,9 @@ static void startElement(WixToolsetType wixType, XMLStreamWriter xml, String com xml.writeAttribute("Guid", componentGuid); } } + default -> { + throw new IllegalArgumentException(); + } } xml.writeAttribute("Id", componentId); } @@ -412,6 +415,9 @@ private String addComponent(XMLStreamWriter xml, Path path, case Wix4 -> { xml.writeAttribute("Condition", property); } + default -> { + throw new IllegalArgumentException(); + } } } @@ -489,8 +495,10 @@ private void addShortcutComponentGroup(XMLStreamWriter xml) throws switch (getWixType()) { case Wix3 -> defineFolder = true; - default -> + case Wix4 -> defineFolder = !SYSTEM_DIRS.contains(folderPath); + default -> + throw new IllegalArgumentException(); } if (defineFolder) { defineShortcutFolders.add(folderPath); @@ -624,13 +632,17 @@ private void startDirectoryElement(XMLStreamWriter xml, String wix3ElementName, case Wix3 -> { elementName = wix3ElementName; } - default -> { + case Wix4 -> { if (SYSTEM_DIRS.contains(path)) { elementName = "StandardDirectory"; } else { elementName = wix3ElementName; } } + default -> { + throw new IllegalArgumentException(); + } + } final String directoryId; diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java index 68d80d18ad1e6..0276cc96e6521 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java @@ -111,20 +111,22 @@ protected static enum WixNamespace { final protected Map getWixNamespaces() { switch (wixType) { - case Wix4 -> { - return Map.of(WixNamespace.Default, - "http://wixtoolset.org/schemas/v4/wxs", - WixNamespace.Util, - "http://wixtoolset.org/schemas/v4/wxs/util"); - } case Wix3 -> { return Map.of(WixNamespace.Default, "http://schemas.microsoft.com/wix/2006/wi", WixNamespace.Util, "http://schemas.microsoft.com/wix/UtilExtension"); } - default -> + 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(); + } + } } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java index 1e553a3680de2..835247ed1debb 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java @@ -85,6 +85,7 @@ void buildMsi(Path msi) throws IOException { switch (toolset.getType()) { case Wix3 -> buildMsiWix3(msi); case Wix4 -> buildMsiWix4(msi); + default -> throw new IllegalArgumentException(); } } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java index 2cccf40bca1c3..7786d64a78693 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java @@ -176,7 +176,7 @@ void saveResources() throws IOException { e.getValue().saveToFile(e.getKey()); } } - default -> { + case Wix4 -> { var resourceDir = resources.values().stream().filter(res -> { return null != res.getResourceDir(); }).findAny().map(OverridableResource::getResourceDir).orElse(null); @@ -185,6 +185,9 @@ void saveResources() throws IOException { conv.appyTo(e.getValue(), e.getKey()); } } + default -> { + throw new IllegalArgumentException(); + } } } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java index 6bb96a76961f4..4f39a65e3b6ad 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java @@ -103,8 +103,8 @@ void configureWixPipeline(WixPipeline wixPipeline) { if (withShortcutPromptDlg || withInstallDirChooserDlg || withLicenseDlg) { final String extName; switch (getWixType()) { - case Wix4 -> extName = "WixToolset.UI.wixext"; case Wix3 -> extName = "WixUIExtension"; + case Wix4 -> extName = "WixToolset.UI.wixext"; default -> throw new IllegalArgumentException(); } wixPipeline.addLightOptions("-ext", extName); @@ -195,6 +195,7 @@ private enum UI { 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"); @@ -203,19 +204,25 @@ void write(WixToolsetType wixType, WixUiFragmentBuilder outer, XMLStreamWriter x writeWix4UIRef(xml, "JpUIInternal", "JpUI"); } + default -> { + throw new IllegalArgumentException(); + } } xml.writeStartElement("UI"); switch (wixType) { - case Wix4 -> { - xml.writeAttribute("Id", "JpUIInternal"); - } 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 @@ -493,8 +500,9 @@ private static void writePublishDialogPair(WixToolsetType wixType, XMLStreamWrit xml.writeAttribute("Order", String.valueOf(publish.order)); } switch (wixType) { - case Wix4 -> xml.writeAttribute("Condition", publish.condition); case Wix3 -> xml.writeCharacters(publish.condition); + case Wix4 -> xml.writeAttribute("Condition", publish.condition); + default -> throw new IllegalArgumentException(); } xml.writeEndElement(); } From d4469735edec56445a71d315b208706f54f9f86f Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 6 Jun 2024 08:55:51 -0400 Subject: [PATCH 30/30] Add WiX v5 to "error.no-wix-tools" --- .../jdk/jpackage/internal/resources/WinResources.properties | 2 +- .../jdk/jpackage/internal/resources/WinResources_de.properties | 2 +- .../jdk/jpackage/internal/resources/WinResources_ja.properties | 2 +- .../jpackage/internal/resources/WinResources_zh_CN.properties | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties index 4c264857b7495..5843a750adea9 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties @@ -43,7 +43,7 @@ resource.installdirnotemptydlg-wix-file=Not empty install directory dialog WiX p 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. Was looking for WiX v3 light.exe and candle.exe or WiX v4 wix.exe and none was found +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. diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties index 960043b590493..dce4a911fe144 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties @@ -43,7 +43,7 @@ resource.installdirnotemptydlg-wix-file=Nicht leeres Installationsverzeichnis in 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=Can not find WiX tools. Was looking for WiX v3 light.exe and candle.exe or WiX v4 wix.exe and none was found +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. diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties index a4db63e46a416..e0bcd18c81103 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties @@ -43,7 +43,7 @@ resource.installdirnotemptydlg-wix-file=インストール・ディレクトリ 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=Can not find WiX tools. Was looking for WiX v3 light.exe and candle.exe or WiX v4 wix.exe and none was found +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つのコンポーネントが含まれている必要があります。 diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties index c3c6dc1cfe141..e2b8bd37135f4 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties @@ -43,7 +43,7 @@ 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=Can not find WiX tools. Was looking for WiX v3 light.exe and candle.exe or WiX v4 wix.exe and none was found +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 个组成部分。