mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-22 21:00:31 +00:00
8381384: jpackage: add test coverage to WiX discovery
Reviewed-by: almatvee
This commit is contained in:
parent
3f6271b2b9
commit
c76381996a
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
public interface EnvironmentProvider {
|
||||
|
||||
String getProperty(String propertyName);
|
||||
|
||||
String getenv(String envVarName);
|
||||
|
||||
public static EnvironmentProvider DEFAULT = new EnvironmentProvider() {
|
||||
|
||||
@Override
|
||||
public String getenv(String envVarName) {
|
||||
return System.getenv(envVarName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String propertyName) {
|
||||
return System.getProperty(propertyName);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -65,6 +65,14 @@ public final class Globals {
|
||||
return this;
|
||||
}
|
||||
|
||||
public EnvironmentProvider system() {
|
||||
return this.<EnvironmentProvider>findProperty(EnvironmentProvider.class).orElse(EnvironmentProvider.DEFAULT);
|
||||
}
|
||||
|
||||
public Globals system(EnvironmentProvider v) {
|
||||
return setProperty(EnvironmentProvider.class, v);
|
||||
}
|
||||
|
||||
Log.Logger logger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -24,6 +24,7 @@
|
||||
*/
|
||||
package jdk.jpackage.internal.util;
|
||||
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@ -31,18 +32,26 @@ import java.util.function.UnaryOperator;
|
||||
|
||||
public final class PathUtils {
|
||||
|
||||
private PathUtils() {
|
||||
}
|
||||
|
||||
public static String getSuffix(Path path) {
|
||||
String filename = replaceSuffix(path.getFileName(), null).toString();
|
||||
return path.getFileName().toString().substring(filename.length());
|
||||
}
|
||||
|
||||
public static Path addSuffix(Path path, String suffix) {
|
||||
Objects.requireNonNull(path);
|
||||
Objects.requireNonNull(suffix);
|
||||
|
||||
Path parent = path.getParent();
|
||||
String filename = path.getFileName().toString() + suffix;
|
||||
return parent != null ? parent.resolve(filename) : Path.of(filename);
|
||||
}
|
||||
|
||||
public static Path replaceSuffix(Path path, String suffix) {
|
||||
Objects.requireNonNull(path);
|
||||
|
||||
Path parent = path.getParent();
|
||||
String filename = path.getFileName().toString().replaceAll("\\.[^.]*$",
|
||||
"") + Optional.ofNullable(suffix).orElse("");
|
||||
@ -59,18 +68,22 @@ public final class PathUtils {
|
||||
}
|
||||
|
||||
public static Path normalizedAbsolutePath(Path path) {
|
||||
if (path != null) {
|
||||
return mapNullablePath(_ -> {
|
||||
return path.normalize().toAbsolutePath();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, path);
|
||||
}
|
||||
|
||||
public static String normalizedAbsolutePathString(Path path) {
|
||||
if (path != null) {
|
||||
return normalizedAbsolutePath(path).toString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return Optional.ofNullable(normalizedAbsolutePath(path)).map(Path::toString).orElse(null);
|
||||
}
|
||||
|
||||
public static Optional<Path> asPath(String value) {
|
||||
return Optional.ofNullable(value).map(v -> {
|
||||
try {
|
||||
return Path.of(v);
|
||||
} catch (InvalidPathException ex) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,14 +23,14 @@
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.internal;
|
||||
import static java.util.stream.Collectors.groupingBy;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -38,12 +38,13 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.WixToolset.WixToolsetType;
|
||||
import jdk.jpackage.internal.model.ConfigException;
|
||||
import jdk.jpackage.internal.model.DottedVersion;
|
||||
import jdk.jpackage.internal.util.PathUtils;
|
||||
import jdk.jpackage.internal.util.Slot;
|
||||
|
||||
/**
|
||||
* WiX tool.
|
||||
@ -58,16 +59,20 @@ public enum WixTool {
|
||||
this.minimalVersion = minimalVersion;
|
||||
}
|
||||
|
||||
interface ToolInfo {
|
||||
Path fileName() {
|
||||
return toolFileName;
|
||||
}
|
||||
|
||||
sealed interface ToolInfo {
|
||||
Path path();
|
||||
DottedVersion version();
|
||||
}
|
||||
|
||||
interface CandleInfo extends ToolInfo {
|
||||
sealed interface CandleInfo extends ToolInfo {
|
||||
boolean fips();
|
||||
}
|
||||
|
||||
private record DefaultToolInfo(Path path, DottedVersion version) implements ToolInfo {
|
||||
record DefaultToolInfo(Path path, DottedVersion version) implements ToolInfo {
|
||||
DefaultToolInfo {
|
||||
Objects.requireNonNull(path);
|
||||
Objects.requireNonNull(version);
|
||||
@ -76,9 +81,14 @@ public enum WixTool {
|
||||
DefaultToolInfo(Path path, String version) {
|
||||
this(path, DottedVersion.lazy(version));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s|ver=%s", path, version);
|
||||
}
|
||||
}
|
||||
|
||||
private record DefaultCandleInfo(Path path, DottedVersion version, boolean fips) implements CandleInfo {
|
||||
record DefaultCandleInfo(Path path, DottedVersion version, boolean fips) implements CandleInfo {
|
||||
DefaultCandleInfo {
|
||||
Objects.requireNonNull(path);
|
||||
Objects.requireNonNull(version);
|
||||
@ -87,25 +97,42 @@ public enum WixTool {
|
||||
DefaultCandleInfo(ToolInfo info, boolean fips) {
|
||||
this(info.path(), info.version(), fips);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var sb = new StringBuffer();
|
||||
sb.append(path);
|
||||
if (fips) {
|
||||
sb.append("|fips");
|
||||
}
|
||||
sb.append("|ver=").append(version);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static WixToolset createToolset() {
|
||||
return createToolset(WixTool::findWixInstallDirs, true);
|
||||
}
|
||||
|
||||
static WixToolset createToolset(Supplier<List<Path>> wixInstallDirs, boolean searchInPath) {
|
||||
|
||||
Function<List<ToolLookupResult>, Map<WixTool, ToolInfo>> conv = lookupResults -> {
|
||||
return lookupResults.stream().filter(ToolLookupResult::isValid).collect(Collectors.
|
||||
groupingBy(lookupResult -> {
|
||||
return lookupResults.stream().filter(ToolLookupResult::isValid).collect(groupingBy(lookupResult -> {
|
||||
return lookupResult.info().version().toString();
|
||||
})).values().stream().filter(sameVersionLookupResults -> {
|
||||
Set<WixTool> sameVersionTools = sameVersionLookupResults.stream().map(
|
||||
ToolLookupResult::tool).collect(Collectors.toSet());
|
||||
if (sameVersionTools.equals(Set.of(Candle3)) || sameVersionTools.equals(Set.of(
|
||||
Light3))) {
|
||||
var sameVersionTools = sameVersionLookupResults.stream()
|
||||
.map(ToolLookupResult::tool)
|
||||
.collect(toSet());
|
||||
if (sameVersionTools.equals(Set.of(Candle3)) || sameVersionTools.equals(Set.of(Light3))) {
|
||||
// There is only one tool from WiX v3 toolset of some version available. Discard it.
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}).flatMap(List::stream).collect(Collectors.toMap(ToolLookupResult::tool,
|
||||
ToolLookupResult::info, (ToolInfo x, ToolInfo y) -> {
|
||||
}).flatMap(List::stream).collect(toMap(
|
||||
ToolLookupResult::tool,
|
||||
ToolLookupResult::info,
|
||||
(ToolInfo x, ToolInfo y) -> {
|
||||
return Stream.of(x, y).sorted(Comparator.comparing((ToolInfo toolInfo) -> {
|
||||
return toolInfo.version().toComponentsString();
|
||||
}).reversed()).findFirst().get();
|
||||
@ -115,58 +142,53 @@ public enum WixTool {
|
||||
Function<List<ToolLookupResult>, Optional<WixToolset>> createToolset = lookupResults -> {
|
||||
var tools = conv.apply(lookupResults);
|
||||
// Try to build a toolset found in the PATH and in known locations.
|
||||
return Stream.of(WixToolsetType.values()).map(toolsetType -> {
|
||||
return WixToolset.create(toolsetType.getTools(), tools);
|
||||
}).filter(Objects::nonNull).findFirst();
|
||||
return Stream.of(WixToolsetType.values()).flatMap(toolsetType -> {
|
||||
return WixToolset.create(toolsetType, tools).stream();
|
||||
}).findFirst();
|
||||
};
|
||||
|
||||
var toolsInPath = Stream.of(values()).map(tool -> {
|
||||
return ToolLookupResult.lookup(tool, Optional.empty());
|
||||
}).filter(Optional::isPresent).map(Optional::get).toList();
|
||||
final List<ToolLookupResult> toolsInPath;
|
||||
if (searchInPath) {
|
||||
toolsInPath = Stream.of(values()).flatMap(tool -> {
|
||||
return ToolLookupResult.lookup(tool, Optional.empty()).stream();
|
||||
}).toList();
|
||||
} else {
|
||||
toolsInPath = List.of();
|
||||
}
|
||||
|
||||
// Try to build a toolset from tools in the PATH first.
|
||||
var toolset = createToolset.apply(toolsInPath);
|
||||
if (toolset.isPresent()) {
|
||||
return toolset.get();
|
||||
}
|
||||
var toolset = createToolset.apply(toolsInPath).orElseGet(() -> {
|
||||
// Look up for WiX tools in known locations.
|
||||
var toolsInKnownWiXDirs = wixInstallDirs.get().stream().flatMap(dir -> {
|
||||
return Stream.of(values()).flatMap(tool -> {
|
||||
return ToolLookupResult.lookup(tool, Optional.of(dir)).stream();
|
||||
});
|
||||
}).toList();
|
||||
|
||||
// Look up for WiX tools in known locations.
|
||||
var toolsInKnownWiXDirs = findWixInstallDirs().stream().map(dir -> {
|
||||
return Stream.of(values()).map(tool -> {
|
||||
return ToolLookupResult.lookup(tool, Optional.of(dir));
|
||||
// Build a toolset found in the PATH and in known locations.
|
||||
var allValidFoundTools = Stream.of(toolsInPath, toolsInKnownWiXDirs)
|
||||
.flatMap(List::stream)
|
||||
.filter(ToolLookupResult::isValid)
|
||||
.toList();
|
||||
|
||||
return createToolset.apply(allValidFoundTools).orElseThrow(() -> {
|
||||
return new ConfigException(
|
||||
I18N.getString("error.no-wix-tools"),
|
||||
I18N.getString("error.no-wix-tools.advice"));
|
||||
});
|
||||
}).flatMap(Function.identity()).filter(Optional::isPresent).map(Optional::get).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.info().path(),
|
||||
lookupResult.info().version(), lookupResult.tool().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"));
|
||||
}
|
||||
}
|
||||
return toolset;
|
||||
}
|
||||
|
||||
private record ToolLookupResult(WixTool tool, ToolInfo info, boolean versionTooOld) {
|
||||
static List<Path> findWixInstallDirs() {
|
||||
return Stream.of(
|
||||
findWixCurrentInstallDirs(),
|
||||
findWix3InstallDirs()
|
||||
).flatMap(List::stream).toList();
|
||||
}
|
||||
|
||||
private record ToolLookupResult(WixTool tool, ToolInfo info) {
|
||||
|
||||
ToolLookupResult {
|
||||
Objects.requireNonNull(tool);
|
||||
@ -177,58 +199,59 @@ public enum WixTool {
|
||||
Objects.requireNonNull(tool);
|
||||
Objects.requireNonNull(lookupDir);
|
||||
|
||||
final Path toolPath = lookupDir.map(p -> p.resolve(
|
||||
tool.toolFileName)).orElse(tool.toolFileName);
|
||||
final Path toolPath = lookupDir.map(p -> {
|
||||
return 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);
|
||||
|
||||
final var validator = new ToolValidator(toolPath)
|
||||
.setMinimalVersion(tool.minimalVersion)
|
||||
.setToolOldVersionErrorHandler((name, version) -> {
|
||||
tooOld[0] = true;
|
||||
return null;
|
||||
});
|
||||
|
||||
final Function<Stream<String>, String> versionParser;
|
||||
|
||||
if (Set.of(Candle3, Light3).contains(tool)) {
|
||||
final String printVersionArg;
|
||||
if (tool == Candle3) {
|
||||
final var printVersionArg = switch (tool) {
|
||||
case Candle3 -> {
|
||||
// Add '-fips' to make "candle.exe" print help message and return
|
||||
// 0 exit code instead of returning error exit code and printing
|
||||
// "error CNDL0308 : The Federal Information Processing Standard (FIPS) appears to be enabled on the machine..."
|
||||
// error message if FIPS is enabled.
|
||||
// If FIPS is disabled, passing '-fips' parameter still makes
|
||||
// "candle.exe" print help message and return 0 exit code.
|
||||
printVersionArg = "-fips";
|
||||
} else {
|
||||
printVersionArg = "-?";
|
||||
yield "-fips";
|
||||
}
|
||||
validator.setCommandLine(printVersionArg);
|
||||
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("");
|
||||
};
|
||||
}
|
||||
case Light3 -> {
|
||||
yield "-?";
|
||||
}
|
||||
default -> {
|
||||
yield "--version";
|
||||
}
|
||||
};
|
||||
validator.setCommandLine(printVersionArg);
|
||||
|
||||
final Function<Stream<String>, Optional<String>> versionParser = switch (tool) {
|
||||
case Candle3, Light3 -> {
|
||||
yield output -> {
|
||||
return output.findFirst().map(firstLineOfOutput -> {
|
||||
int separatorIdx = firstLineOfOutput.lastIndexOf(' ');
|
||||
if (separatorIdx == -1) {
|
||||
return null;
|
||||
}
|
||||
return firstLineOfOutput.substring(separatorIdx + 1);
|
||||
});
|
||||
};
|
||||
}
|
||||
default -> {
|
||||
yield output -> {
|
||||
return output.findFirst();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
final var parsedVersion = Slot.<String>createEmpty();
|
||||
validator.setVersionParser(output -> {
|
||||
parsedVersion[0] = versionParser.apply(output);
|
||||
return parsedVersion[0];
|
||||
versionParser.apply(output).ifPresent(parsedVersion::set);
|
||||
return parsedVersion.find().orElse(null);
|
||||
});
|
||||
|
||||
if (validator.validate() == null) {
|
||||
// Tool found
|
||||
ToolInfo info = new DefaultToolInfo(toolPath, parsedVersion[0]);
|
||||
ToolInfo info = new DefaultToolInfo(toolPath, parsedVersion.get());
|
||||
if (tool == Candle3) {
|
||||
// Detect FIPS mode
|
||||
var fips = false;
|
||||
@ -242,63 +265,52 @@ public enum WixTool {
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Log.verbose(ex);
|
||||
}
|
||||
info = new DefaultCandleInfo(info, fips);
|
||||
}
|
||||
return Optional.of(new ToolLookupResult(tool, info, tooOld[0]));
|
||||
|
||||
return Optional.of(new ToolLookupResult(tool, info));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
boolean versionTooOld() {
|
||||
return DottedVersion.compareComponents(info.version(), tool.minimalVersion) < 0;
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
return !versionTooOld;
|
||||
return !versionTooOld();
|
||||
}
|
||||
}
|
||||
|
||||
private static Path getSystemDir(String envVar, String knownDir) {
|
||||
return Optional
|
||||
.ofNullable(getEnvVariableAsPath(envVar))
|
||||
.orElseGet(() -> Optional
|
||||
.ofNullable(getEnvVariableAsPath("SystemDrive"))
|
||||
.orElseGet(() -> Path.of("C:")).resolve(knownDir));
|
||||
private static Path getSystemDir(String envVar, Path knownDir) {
|
||||
return getEnvVariableAsPath(envVar).orElseGet(() -> {
|
||||
return getEnvVariableAsPath("SystemDrive").orElseGet(() -> {
|
||||
return Path.of("C:");
|
||||
}).resolve(knownDir);
|
||||
});
|
||||
}
|
||||
|
||||
private static Path getEnvVariableAsPath(String envVar) {
|
||||
String path = System.getenv(envVar);
|
||||
if (path != null) {
|
||||
try {
|
||||
return Path.of(path);
|
||||
} catch (InvalidPathException ex) {
|
||||
Log.error(MessageFormat.format(I18N.getString(
|
||||
"error.invalid-envvar"), envVar));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<Path> findWixInstallDirs() {
|
||||
return Stream.of(findWixCurrentInstallDirs(), findWix3InstallDirs()).
|
||||
flatMap(List::stream).toList();
|
||||
private static Optional<Path> getEnvVariableAsPath(String envVar) {
|
||||
Objects.requireNonNull(envVar);
|
||||
return Optional.ofNullable(Globals.instance().system().getenv(envVar)).flatMap(PathUtils::asPath);
|
||||
}
|
||||
|
||||
private static List<Path> findWixCurrentInstallDirs() {
|
||||
return Stream.of(getEnvVariableAsPath("USERPROFILE"), Optional.ofNullable(System.
|
||||
getProperty("user.home")).map(Path::of).orElse(null)).filter(Objects::nonNull).map(
|
||||
path -> {
|
||||
return path.resolve(".dotnet/tools");
|
||||
}).filter(Files::isDirectory).distinct().toList();
|
||||
return Stream.of(
|
||||
getEnvVariableAsPath("USERPROFILE"),
|
||||
Optional.ofNullable(Globals.instance().system().getProperty("user.home")).flatMap(PathUtils::asPath)
|
||||
).flatMap(Optional::stream).map(path -> {
|
||||
return path.resolve(".dotnet/tools");
|
||||
}).filter(Files::isDirectory).distinct().toList();
|
||||
}
|
||||
|
||||
private static List<Path> findWix3InstallDirs() {
|
||||
PathMatcher wixInstallDirMatcher = FileSystems.getDefault().
|
||||
getPathMatcher(
|
||||
"glob:WiX Toolset v*");
|
||||
var wixInstallDirMatcher = FileSystems.getDefault().getPathMatcher("glob:WiX Toolset v*");
|
||||
|
||||
Path programFiles = getSystemDir("ProgramFiles", "\\Program Files");
|
||||
Path programFilesX86 = getSystemDir("ProgramFiles(x86)",
|
||||
"\\Program Files (x86)");
|
||||
var programFiles = getSystemDir("ProgramFiles", Path.of("Program Files"));
|
||||
var programFilesX86 = getSystemDir("ProgramFiles(x86)", Path.of("Program Files (x86)"));
|
||||
|
||||
// Returns list of WiX install directories ordered by WiX version number.
|
||||
// Newer versions go first.
|
||||
@ -306,13 +318,11 @@ public enum WixTool {
|
||||
try (var paths = Files.walk(path, 1)) {
|
||||
return paths.toList();
|
||||
} catch (IOException ex) {
|
||||
Log.verbose(ex);
|
||||
List<Path> empty = List.of();
|
||||
return empty;
|
||||
return List.<Path>of();
|
||||
}
|
||||
}).flatMap(List::stream)
|
||||
.filter(path -> wixInstallDirMatcher.matches(path.getFileName())).
|
||||
sorted(Comparator.comparing(Path::getFileName).reversed())
|
||||
.filter(path -> wixInstallDirMatcher.matches(path.getFileName()))
|
||||
.sorted(Comparator.comparing(Path::getFileName).reversed())
|
||||
.map(path -> path.resolve("bin"))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -26,14 +26,20 @@ package jdk.jpackage.internal;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.model.DottedVersion;
|
||||
|
||||
final class WixToolset {
|
||||
record WixToolset(Map<WixTool, WixTool.ToolInfo> tools) {
|
||||
|
||||
static enum WixToolsetType {
|
||||
WixToolset {
|
||||
tools = Map.copyOf(tools);
|
||||
}
|
||||
|
||||
enum WixToolsetType {
|
||||
// Wix v4+
|
||||
Wix4(WixTool.Wix4),
|
||||
// Wix v3+
|
||||
@ -50,10 +56,6 @@ final class WixToolset {
|
||||
private final Set<WixTool> tools;
|
||||
}
|
||||
|
||||
private WixToolset(Map<WixTool, WixTool.ToolInfo> tools) {
|
||||
this.tools = tools;
|
||||
}
|
||||
|
||||
WixToolsetType getType() {
|
||||
return Stream.of(WixToolsetType.values()).filter(toolsetType -> {
|
||||
return toolsetType.getTools().equals(tools.keySet());
|
||||
@ -75,16 +77,19 @@ final class WixToolset {
|
||||
.anyMatch(WixTool.CandleInfo::fips);
|
||||
}
|
||||
|
||||
static WixToolset create(Set<WixTool> requiredTools, Map<WixTool, WixTool.ToolInfo> allTools) {
|
||||
static Optional<WixToolset> create(WixToolsetType type, Map<WixTool, WixTool.ToolInfo> allTools) {
|
||||
Objects.requireNonNull(type);
|
||||
Objects.requireNonNull(allTools);
|
||||
|
||||
var requiredTools = type.getTools();
|
||||
|
||||
var filteredTools = allTools.entrySet().stream().filter(e -> {
|
||||
return requiredTools.contains(e.getKey());
|
||||
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
if (filteredTools.keySet().equals(requiredTools)) {
|
||||
return new WixToolset(filteredTools);
|
||||
return Optional.of(new WixToolset(filteredTools));
|
||||
} else {
|
||||
return null;
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<WixTool, WixTool.ToolInfo> tools;
|
||||
}
|
||||
|
||||
@ -36,8 +36,8 @@ resource.launcher-as-service-wix-file=Service installer WiX project file
|
||||
resource.wix-src-conv=XSLT stylesheet converting WiX sources from WiX v3 to WiX v4 format
|
||||
resource.installer-exe=installer executable
|
||||
|
||||
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.no-wix-tools=No usable WiX Toolset installation found
|
||||
error.no-wix-tools.advice=Install the latest WiX v3 from https://github.com/wixtoolset/wix3/releases or WiX v4+ from https://github.com/wixtoolset/wix/releases
|
||||
error.version-string-wrong-format.advice=Set value of --app-version parameter to a valid Windows Installer ProductVersion.
|
||||
error.msi-product-version-components=Version string [{0}] must have between 2 and 4 components.
|
||||
error.msi-product-version-major-out-of-range=Major version must be in the range [0, 255]
|
||||
@ -56,7 +56,6 @@ error.missing-service-installer.advice=Add 'service-installer.exe' service insta
|
||||
|
||||
message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
|
||||
message.tool-version=Detected [{0}] version [{1}].
|
||||
message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required.
|
||||
message.product-code=MSI ProductCode: {0}.
|
||||
message.upgrade-code=MSI UpgradeCode: {0}.
|
||||
message.preparing-msi-config=Preparing MSI config: {0}.
|
||||
|
||||
@ -160,5 +160,5 @@ abstract sealed class MockingToolProvider implements ToolProviderCommandMock {
|
||||
private final String name;
|
||||
private final Iterator<CommandAction> actionIter;
|
||||
|
||||
static ToolProviderCommandMock UNREACHABLE = new MockingToolProvider.NonCompletable("<unreachable>", List.of());
|
||||
static final ToolProviderCommandMock UNREACHABLE = new MockingToolProvider.NonCompletable("<unreachable>", List.of());
|
||||
}
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jpackage.test.stdmock;
|
||||
|
||||
import jdk.jpackage.internal.EnvironmentProvider;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public record EnvironmentProviderMock(
|
||||
Map<String, String> envVariables,
|
||||
Map<String, String> systemProperties) implements EnvironmentProvider {
|
||||
|
||||
public EnvironmentProviderMock {
|
||||
envVariables.keySet().forEach(Objects::requireNonNull);
|
||||
envVariables.values().forEach(Objects::requireNonNull);
|
||||
|
||||
systemProperties.keySet().forEach(Objects::requireNonNull);
|
||||
systemProperties.values().forEach(Objects::requireNonNull);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getenv(String envVarName) {
|
||||
return envVariables.get(Objects.requireNonNull(envVarName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String propertyName) {
|
||||
return systemProperties.get(Objects.requireNonNull(propertyName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var tokens = new ArrayList<String>();
|
||||
if (!envVariables.isEmpty()) {
|
||||
tokens.add(String.format("env=%s", envVariables));
|
||||
}
|
||||
if (!systemProperties.isEmpty()) {
|
||||
tokens.add(String.format("props=%s", systemProperties));
|
||||
}
|
||||
return String.join(", ", tokens);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jpackage.test.stdmock;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import jdk.jpackage.internal.util.PathUtils;
|
||||
import jdk.jpackage.test.mock.CommandActionSpec;
|
||||
import jdk.jpackage.test.mock.CommandActionSpecs;
|
||||
import jdk.jpackage.test.mock.CommandMockSpec;
|
||||
|
||||
public final class WixToolMock {
|
||||
|
||||
public CommandMockSpec create() {
|
||||
Objects.requireNonNull(type);
|
||||
Objects.requireNonNull(version);
|
||||
|
||||
CommandActionSpec action = switch (type) {
|
||||
case CANDLE3 -> {
|
||||
yield candleAction(fips, version);
|
||||
}
|
||||
case LIGHT3 -> {
|
||||
yield lightAction(version);
|
||||
}
|
||||
case WIX4 -> {
|
||||
yield wixAction(version);
|
||||
}
|
||||
};
|
||||
|
||||
var toolPath = Optional.ofNullable(dir).map(d -> {
|
||||
return d.resolve(type.fileName);
|
||||
}).orElse(type.fileName);
|
||||
var mockName = PathUtils.replaceSuffix(toolPath, "");
|
||||
|
||||
return new CommandMockSpec(toolPath, mockName, CommandActionSpecs.build().action(action).create());
|
||||
}
|
||||
|
||||
public WixToolMock fips(Boolean v) {
|
||||
fips = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WixToolMock fips() {
|
||||
return fips(true);
|
||||
}
|
||||
|
||||
public WixToolMock dir(Path v) {
|
||||
dir = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WixToolMock version(String v) {
|
||||
version = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WixToolMock candle(String version) {
|
||||
return type(WixTool.CANDLE3).version(version);
|
||||
}
|
||||
|
||||
public WixToolMock light(String version) {
|
||||
return type(WixTool.LIGHT3).version(version);
|
||||
}
|
||||
|
||||
public WixToolMock wix(String version) {
|
||||
return type(WixTool.WIX4).version(version);
|
||||
}
|
||||
|
||||
private WixToolMock type(WixTool v) {
|
||||
type = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
private static CommandActionSpec candleAction(boolean fips, String version) {
|
||||
Objects.requireNonNull(version);
|
||||
var sb = new StringBuilder();
|
||||
sb.append(version);
|
||||
if (fips) {
|
||||
sb.append("; fips");
|
||||
}
|
||||
return CommandActionSpec.create(sb.toString(), context -> {
|
||||
if (List.of("-?").equals(context.args())) {
|
||||
if (fips) {
|
||||
context.err().println("error CNDL0308 : The Federal Information Processing Standard (FIPS) appears to be enabled on the machine");
|
||||
return Optional.of(308);
|
||||
}
|
||||
} else if (!List.of("-fips").equals(context.args())) {
|
||||
throw context.unexpectedArguments();
|
||||
}
|
||||
|
||||
var out = context.out();
|
||||
List.of(
|
||||
"Windows Installer XML Toolset Compiler version " + version,
|
||||
"Copyright (c) .NET Foundation and contributors. All rights reserved.",
|
||||
"",
|
||||
" usage: candle.exe [-?] [-nologo] [-out outputFile] sourceFile [sourceFile ...] [@responseFile]"
|
||||
).forEach(out::println);
|
||||
|
||||
return Optional.of(0);
|
||||
});
|
||||
}
|
||||
|
||||
private static CommandActionSpec lightAction(String version) {
|
||||
Objects.requireNonNull(version);
|
||||
return CommandActionSpec.create(version, context -> {
|
||||
if (List.of("-?").equals(context.args())) {
|
||||
var out = context.out();
|
||||
List.of(
|
||||
"Windows Installer XML Toolset Linker version " + version,
|
||||
"Copyright (c) .NET Foundation and contributors. All rights reserved.",
|
||||
"",
|
||||
" usage: light.exe [-?] [-b bindPath] [-nologo] [-out outputFile] objectFile [objectFile ...] [@responseFile]"
|
||||
).forEach(out::println);
|
||||
return Optional.of(0);
|
||||
} else {
|
||||
throw context.unexpectedArguments();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static CommandActionSpec wixAction(String version) {
|
||||
Objects.requireNonNull(version);
|
||||
return CommandActionSpec.create(version, context -> {
|
||||
if (List.of("--version").equals(context.args())) {
|
||||
context.out().println(version);
|
||||
return Optional.of(0);
|
||||
} else {
|
||||
throw context.unexpectedArguments();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private enum WixTool {
|
||||
CANDLE3("candle"),
|
||||
LIGHT3("light"),
|
||||
WIX4("wix"),
|
||||
;
|
||||
|
||||
WixTool(String name) {
|
||||
this.fileName = Path.of(Objects.requireNonNull(name) + ".exe");
|
||||
}
|
||||
|
||||
final Path fileName;
|
||||
}
|
||||
|
||||
private Path dir;
|
||||
private WixTool type;
|
||||
private String version;
|
||||
private boolean fips;
|
||||
}
|
||||
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jpackage.internal.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.function.UnaryOperator;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
|
||||
class PathUtilsTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"foo,''",
|
||||
"foo.bar,.bar",
|
||||
"foo..bar,.bar",
|
||||
".bar,.bar",
|
||||
"foo.bar.buz,.buz",
|
||||
".,.",
|
||||
"...,.",
|
||||
"..,.",
|
||||
})
|
||||
void test_getSuffix(Path path, String expected) {
|
||||
|
||||
var suffix = PathUtils.getSuffix(path);
|
||||
|
||||
assertEquals(expected, suffix);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_getSuffix_null() {
|
||||
assertThrowsExactly(NullPointerException.class, () -> {
|
||||
PathUtils.getSuffix(null);
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"foo,'',foo",
|
||||
"a/b/foo.exe,.ico,a/b/foo.exe.ico",
|
||||
"foo,bar,foobar",
|
||||
"'',bar,bar",
|
||||
".,bar,.bar",
|
||||
})
|
||||
void test_addSuffix(Path path, String suffix, Path expected) {
|
||||
|
||||
var newPath = PathUtils.addSuffix(path, suffix);
|
||||
|
||||
assertEquals(expected, newPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_addSuffix_null() {
|
||||
assertThrowsExactly(NullPointerException.class, () -> {
|
||||
PathUtils.addSuffix(null, "foo");
|
||||
});
|
||||
assertThrowsExactly(NullPointerException.class, () -> {
|
||||
PathUtils.addSuffix(Path.of("foo"), null);
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"foo.exe,.ico,foo.ico",
|
||||
"foo.exe,,foo",
|
||||
"foo.exe,'',foo",
|
||||
"a/b/foo.exe,.ico,a/b/foo.ico",
|
||||
"foo,'',foo",
|
||||
"foo,bar,foobar",
|
||||
"'',bar,bar",
|
||||
".,bar,bar",
|
||||
".,.bar,.bar",
|
||||
})
|
||||
void test_replaceSuffix(Path path, String newSuffix, Path expected) {
|
||||
|
||||
var newPath = PathUtils.replaceSuffix(path, newSuffix);
|
||||
|
||||
assertEquals(expected, newPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_replaceSuffix_null() {
|
||||
assertThrowsExactly(NullPointerException.class, () -> {
|
||||
PathUtils.replaceSuffix(null, "foo");
|
||||
});
|
||||
|
||||
assertEquals(Path.of("foo"), PathUtils.replaceSuffix(Path.of("foo.a"), null));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"IDENTITY,a,a",
|
||||
"IDENTITY,,",
|
||||
"RETURN_NULL,a,",
|
||||
"RETURN_NULL,,",
|
||||
"FOO,a,foo",
|
||||
"FOO,,",
|
||||
})
|
||||
void test_mapNullablePath(PathMapper mapper, Path path, Path expected) {
|
||||
|
||||
var newPath = PathUtils.mapNullablePath(mapper, path);
|
||||
|
||||
assertEquals(expected, newPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_mapNullablePath_null() {
|
||||
assertThrowsExactly(NullPointerException.class, () -> {
|
||||
PathUtils.mapNullablePath(null, Path.of(""));
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(nullValues = {"N/A"}, value = {
|
||||
"foo.exe",
|
||||
"N/A",
|
||||
})
|
||||
void test_normalizedAbsolutePath(Path path) {
|
||||
|
||||
var newPath = PathUtils.normalizedAbsolutePath(path);
|
||||
|
||||
var expected = Optional.ofNullable(path).map(v -> {
|
||||
return v.normalize().toAbsolutePath();
|
||||
}).orElse(null);
|
||||
|
||||
assertEquals(expected, newPath);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(nullValues = {"N/A"}, value = {
|
||||
"foo.exe",
|
||||
"N/A",
|
||||
})
|
||||
void test_normalizedAbsolutePathString(Path path) {
|
||||
|
||||
var newPath = PathUtils.normalizedAbsolutePathString(path);
|
||||
|
||||
var expected = Optional.ofNullable(path).map(v -> {
|
||||
return v.normalize().toAbsolutePath().toString();
|
||||
}).orElse(null);
|
||||
|
||||
assertEquals(expected, newPath);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(nullValues = {"N/A"}, value = {
|
||||
"N/A",
|
||||
"foo",
|
||||
"*",
|
||||
":",
|
||||
})
|
||||
void test_asPath(String str) {
|
||||
|
||||
var path = PathUtils.asPath(str);
|
||||
|
||||
var expected = Optional.ofNullable(str).flatMap(v -> {
|
||||
return Result.of(() -> {
|
||||
return Path.of(v);
|
||||
}).value();
|
||||
});
|
||||
|
||||
assertEquals(expected, path);
|
||||
}
|
||||
|
||||
enum PathMapper implements UnaryOperator<Path> {
|
||||
IDENTITY {
|
||||
@Override
|
||||
public Path apply(Path path) {
|
||||
return path;
|
||||
}
|
||||
},
|
||||
RETURN_NULL {
|
||||
@Override
|
||||
public Path apply(Path path) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
FOO {
|
||||
@Override
|
||||
public Path apply(Path path) {
|
||||
return Path.of("foo");
|
||||
}
|
||||
},
|
||||
;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,754 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.WixTool.ToolInfo;
|
||||
import jdk.jpackage.internal.WixToolset.WixToolsetType;
|
||||
import jdk.jpackage.internal.model.ConfigException;
|
||||
import jdk.jpackage.internal.util.TokenReplace;
|
||||
import jdk.jpackage.test.CannedFormattedString;
|
||||
import jdk.jpackage.test.JPackageStringBundle;
|
||||
import jdk.jpackage.test.mock.CommandActionSpecs;
|
||||
import jdk.jpackage.test.mock.CommandMock;
|
||||
import jdk.jpackage.test.mock.CommandMockSpec;
|
||||
import jdk.jpackage.test.mock.Script;
|
||||
import jdk.jpackage.test.stdmock.EnvironmentProviderMock;
|
||||
import jdk.jpackage.test.stdmock.JPackageMockUtils;
|
||||
import jdk.jpackage.test.stdmock.WixToolMock;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
|
||||
class WixToolTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testLookup(TestSpec spec, @TempDir Path workDir) throws IOException {
|
||||
spec.run(workDir);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testLookupDirs(EnvironmentTestSpec spec, @TempDir Path workDir) throws IOException {
|
||||
spec.run(workDir);
|
||||
}
|
||||
|
||||
private static Collection<TestSpec> testLookup() {
|
||||
|
||||
List<TestSpec> testCases = new ArrayList<>();
|
||||
|
||||
Consumer<TestSpec.Builder> appendTestCases = builder -> {
|
||||
testCases.add(builder.create());
|
||||
};
|
||||
|
||||
Stream.of(
|
||||
// Simple WiX3 of a minimal acceptable version
|
||||
TestSpec.build()
|
||||
.expect(toolset().version("3.0").put(WixToolsetType.Wix3, "foo"))
|
||||
.tool(tool("foo").candle("3.0"))
|
||||
.tool(tool("foo").light("3.0")),
|
||||
// Simple WiX3 with FIPS
|
||||
TestSpec.build()
|
||||
.expect(toolset().version("3.14.1.8722").put(WixToolsetType.Wix3, "foo").fips())
|
||||
.tool(tool("foo").candle("3.14.1.8722").fips())
|
||||
.tool(tool("foo").light("3.14.1.8722")),
|
||||
// Simple WiX4+ of a minimal acceptable version
|
||||
TestSpec.build()
|
||||
.expect(toolset().version("4.0.4").put(WixToolsetType.Wix4, "foo"))
|
||||
.tool(tool("foo").wix("4.0.4")),
|
||||
// WiX3 with light and candle from different directories and non-existent directory
|
||||
TestSpec.build()
|
||||
.expect(toolset().version("3.11.2").put(WixTool.Candle3, "foo").put(WixTool.Light3, "bar"))
|
||||
.lookupDir("buz")
|
||||
.tool(tool("foo").candle("3.11.2"))
|
||||
.tool(tool("bar").light("3.11.2"))
|
||||
.tool(tool("bar").candle("3.11.1"))
|
||||
.tool(tool("foo").light("3.11.1")),
|
||||
// WiX3, WiX4+ same directory
|
||||
TestSpec.build()
|
||||
.expect(toolset().version("5.0.2+aa65968c").put(WixToolsetType.Wix4, "foo"))
|
||||
.tool(tool("foo").candle("3.14.1.8722"))
|
||||
.tool(tool("foo").light("3.14.1.8722"))
|
||||
.tool(tool("foo").wix("5.0.2+aa65968c")),
|
||||
// WiX3 (good), WiX4+ (bad version)
|
||||
TestSpec.build()
|
||||
.expect(toolset().version("3.14.1.8722").put(WixToolsetType.Wix3, "foo"))
|
||||
.tool(tool("foo").candle("3.14.1.8722"))
|
||||
.tool(tool("foo").light("3.14.1.8722"))
|
||||
.tool(tool("foo").wix("Blah-blah-blah")),
|
||||
// WiX3 (incomplete), WiX4+ (good)
|
||||
TestSpec.build()
|
||||
.expect(toolset().version("5.0").put(WixToolsetType.Wix4, "foo"))
|
||||
.tool(tool("foo").candle("3.14.1.8722"))
|
||||
.tool(tool("foo").wix("5.0")),
|
||||
// WiX5 in the PATH and in the directory, same version; PATH always wins
|
||||
TestSpec.build()
|
||||
.expect(toolset().version("5.0").put(WixToolsetType.Wix4))
|
||||
.tool(tool().wix("5.0"))
|
||||
.tool(tool("foo").wix("5.0")),
|
||||
// WiX5 in the PATH and in the directory; the one in the directory is newer; PATH always wins
|
||||
TestSpec.build()
|
||||
.expect(toolset().version("5.0").put(WixToolsetType.Wix4))
|
||||
.tool(tool().wix("5.0"))
|
||||
.tool(tool("foo").wix("5.1")),
|
||||
// WiX5 in the PATH and in the directory; the one in the PATH is newer; PATH always wins
|
||||
TestSpec.build()
|
||||
.expect(toolset().version("5.1").put(WixToolsetType.Wix4))
|
||||
.tool(tool().wix("5.1"))
|
||||
.tool(tool("foo").wix("5.0")),
|
||||
// WiX3 in the PATH, WiX3 in the directory; PATH always wins
|
||||
TestSpec.build()
|
||||
.expect(toolset().version("3.20").put(WixToolsetType.Wix3))
|
||||
.tool(tool().candle("3.20"))
|
||||
.tool(tool().light("3.20"))
|
||||
.tool(tool("foo").wix("5.0")),
|
||||
// Old WiX3 in the PATH, WiX3 in the directory
|
||||
TestSpec.build()
|
||||
.expect(toolset().version("3.20").put(WixToolsetType.Wix3, "foo"))
|
||||
.tool(tool().candle("2.9"))
|
||||
.tool(tool().light("2.9"))
|
||||
.tool(tool("foo").candle("3.20"))
|
||||
.tool(tool("foo").light("3.20"))
|
||||
).forEach(appendTestCases);
|
||||
|
||||
for (var oldLightStatus : ToolStatus.values()) {
|
||||
for (var oldCandleStatus : ToolStatus.values()) {
|
||||
for (var newLightStatus : ToolStatus.values()) {
|
||||
for (var newCandleStatus : ToolStatus.values()) {
|
||||
boolean newGood = ToolStatus.isAllGood(newLightStatus, newCandleStatus);
|
||||
if (!ToolStatus.isAllGood(oldLightStatus, oldCandleStatus) && !newGood) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var builder = TestSpec.build();
|
||||
if (newGood) {
|
||||
builder.expect(toolset().version("3.14").put(WixToolsetType.Wix3, "new"));
|
||||
} else {
|
||||
builder.expect(toolset().version("3.11").put(WixToolsetType.Wix3, "old"));
|
||||
}
|
||||
|
||||
oldCandleStatus.map(tool("old").candle("3.11")).ifPresent(builder::tool);
|
||||
oldLightStatus.map(tool("old").light("3.11")).ifPresent(builder::tool);
|
||||
|
||||
newCandleStatus.map(tool("new").candle("3.14")).ifPresent(builder::tool);
|
||||
newLightStatus.map(tool("new").light("3.14")).ifPresent(builder::tool);
|
||||
|
||||
appendTestCases.accept(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Stream.of(
|
||||
// No WiX tools
|
||||
TestSpec.build(),
|
||||
TestSpec.build()
|
||||
.lookupDir("foo"),
|
||||
TestSpec.build()
|
||||
.lookupDir(LOOKUP_IN_PATH),
|
||||
// Incomplete WiX3: missing candle.exe
|
||||
TestSpec.build()
|
||||
.tool(tool("foo").light("3.14.1.8722")),
|
||||
// Incomplete WiX3: missing light.exe
|
||||
TestSpec.build()
|
||||
.tool(tool("foo").candle("3.14.1.8722")),
|
||||
// Incomplete WiX3: version mismatch of light.exe and candle.exe
|
||||
TestSpec.build()
|
||||
.tool(tool("foo").candle("3.14"))
|
||||
.tool(tool("foo").light("3.15")),
|
||||
// WiX3 too old
|
||||
TestSpec.build()
|
||||
.tool(tool("foo").candle("2.9"))
|
||||
.tool(tool("foo").light("2.9")),
|
||||
// WiX4+ too old
|
||||
TestSpec.build()
|
||||
.tool(tool("foo").wix("4.0.3"))
|
||||
).forEach(appendTestCases);
|
||||
|
||||
return testCases;
|
||||
}
|
||||
|
||||
private static Collection<EnvironmentTestSpec> testLookupDirs() {
|
||||
|
||||
List<EnvironmentTestSpec> testCases = new ArrayList<>();
|
||||
|
||||
Stream.of(
|
||||
EnvironmentTestSpec.build()
|
||||
.env(EnvironmentVariable.USERPROFILE, "@@/foo")
|
||||
.expect("@USERPROFILE@/.dotnet/tools"),
|
||||
EnvironmentTestSpec.build()
|
||||
.env(SystemProperty.USER_HOME, "@@/bar")
|
||||
.expect("@user.home@/.dotnet/tools"),
|
||||
// "USERPROFILE" environment variable and "user.home" system property set to different values,
|
||||
// the order should be "USERPROFILE" followed by "user.home".
|
||||
EnvironmentTestSpec.build()
|
||||
.env(EnvironmentVariable.USERPROFILE, "@@/foo")
|
||||
.env(SystemProperty.USER_HOME, "@@/bar")
|
||||
.expect("@USERPROFILE@/.dotnet/tools")
|
||||
.expect("@user.home@/.dotnet/tools"),
|
||||
// "USERPROFILE" environment variable and "user.home" system property set to the same value.
|
||||
EnvironmentTestSpec.build()
|
||||
.env(EnvironmentVariable.USERPROFILE, "@@/buz")
|
||||
.env(SystemProperty.USER_HOME, "@@/buz")
|
||||
.expect("@USERPROFILE@/.dotnet/tools"),
|
||||
// WiX3: newer versions first; 32bit after 64bit
|
||||
EnvironmentTestSpec.build()
|
||||
.standardEnv(EnvironmentVariable.PROGRAM_FILES_X86)
|
||||
.standardEnv(EnvironmentVariable.PROGRAM_FILES)
|
||||
.expect(String.format("@%s@/WiX Toolset v3.11/bin", EnvironmentVariable.PROGRAM_FILES_X86.variableName()))
|
||||
.expect(String.format("@%s@/WiX Toolset v3.10/bin", EnvironmentVariable.PROGRAM_FILES.variableName()))
|
||||
.expect(String.format("@%s@/WiX Toolset v3.10/bin", EnvironmentVariable.PROGRAM_FILES_X86.variableName())),
|
||||
// Malformed installation directory should be accepted
|
||||
EnvironmentTestSpec.build()
|
||||
.standardEnv(EnvironmentVariable.PROGRAM_FILES_X86)
|
||||
.expect(String.format("@%s@/WiX Toolset vb/bin", EnvironmentVariable.PROGRAM_FILES_X86.variableName()))
|
||||
.expect(String.format("@%s@/WiX Toolset va/bin", EnvironmentVariable.PROGRAM_FILES_X86.variableName()))
|
||||
.expect(String.format("@%s@/WiX Toolset v/bin", EnvironmentVariable.PROGRAM_FILES_X86.variableName())),
|
||||
// No directories
|
||||
EnvironmentTestSpec.build()
|
||||
).map(EnvironmentTestSpec.Builder::create).forEach(testCases::add);
|
||||
|
||||
return testCases;
|
||||
}
|
||||
|
||||
private enum ToolStatus {
|
||||
GOOD,
|
||||
MISSING,
|
||||
UNEXPECTED_STDOUT,
|
||||
;
|
||||
|
||||
static boolean isAllGood(ToolStatus... status) {
|
||||
return Stream.of(status).allMatch(Predicate.isEqual(GOOD));
|
||||
}
|
||||
|
||||
Optional<CommandMockSpec> map(WixToolMock builder) {
|
||||
return switch (this) {
|
||||
case MISSING -> {
|
||||
yield Optional.empty();
|
||||
}
|
||||
case UNEXPECTED_STDOUT -> {
|
||||
var mock = builder.create();
|
||||
yield Optional.of(new CommandMockSpec(
|
||||
mock.name(),
|
||||
mock.mockName(),
|
||||
CommandActionSpecs.build().stdout("Blah-Blah-Blah").exit().create()));
|
||||
}
|
||||
case GOOD -> {
|
||||
yield Optional.of(builder.create());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
record TestSpec(
|
||||
Optional<WixToolset> expected,
|
||||
List<Path> lookupDirs,
|
||||
boolean lookupInPATH,
|
||||
Collection<CommandMockSpec> mocks,
|
||||
List<CannedFormattedString> expectedErrors) {
|
||||
|
||||
TestSpec {
|
||||
Objects.requireNonNull(expected);
|
||||
lookupDirs.forEach(Objects::requireNonNull);
|
||||
mocks.forEach(Objects::requireNonNull);
|
||||
expectedErrors.forEach(Objects::requireNonNull);
|
||||
|
||||
if (expected.isEmpty() == expectedErrors.isEmpty()) {
|
||||
// It should be either toolset or errors, not both or non both.
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
lookupDirs.forEach(WixToolTest::assertIsRelative);
|
||||
|
||||
lookupDirs.forEach(path -> {
|
||||
assertNotEquals(LOOKUP_IN_PATH, path);
|
||||
});
|
||||
|
||||
// Ensure tool paths are unique.
|
||||
mocks.stream().map(CommandMockSpec::name).collect(toMap(x -> x, x -> x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var tokens = new ArrayList<String>();
|
||||
expected.map(Object::toString).ifPresent(tokens::add);
|
||||
if (!expectedErrors.isEmpty()) {
|
||||
tokens.add(String.format("errors=%s", expectedErrors));
|
||||
}
|
||||
|
||||
List<Path> lookupPaths;
|
||||
if (lookupInPATH) {
|
||||
lookupPaths = new ArrayList<>();
|
||||
lookupPaths.add(Path.of("${PATH}"));
|
||||
lookupPaths.addAll(lookupDirs);
|
||||
} else {
|
||||
lookupPaths = lookupDirs;
|
||||
}
|
||||
|
||||
if (!lookupPaths.isEmpty()) {
|
||||
tokens.add(String.format("lookup-dirs=%s", lookupPaths));
|
||||
}
|
||||
if (!mocks.isEmpty()) {
|
||||
tokens.add(mocks.toString());
|
||||
}
|
||||
return String.join(", ", tokens);
|
||||
}
|
||||
|
||||
void run(Path workDir) {
|
||||
var scriptBuilder = Script.build().commandMockBuilderMutator(CommandMock.Builder::repeatInfinitely);
|
||||
mocks.stream().map(mockSpec -> {
|
||||
Path toolPath = mockSpec.name();
|
||||
if (toolPath.getNameCount() > 1) {
|
||||
toolPath = workDir.resolve(toolPath);
|
||||
}
|
||||
return new CommandMockSpec(toolPath, mockSpec.mockName(), mockSpec.actions());
|
||||
}).forEach(scriptBuilder::map);
|
||||
|
||||
scriptBuilder.map(_ -> true, CommandMock.ioerror("non-existent"));
|
||||
|
||||
var script = scriptBuilder.createLoop();
|
||||
|
||||
Supplier<WixToolset> createToolset = () -> {
|
||||
return WixTool.createToolset(() -> {
|
||||
return lookupDirs.stream().map(workDir::resolve).toList();
|
||||
}, lookupInPATH());
|
||||
};
|
||||
|
||||
Globals.main(() -> {
|
||||
JPackageMockUtils.buildJPackage()
|
||||
.script(script)
|
||||
.listener(System.out::println)
|
||||
.applyToGlobals();
|
||||
|
||||
expected.ifPresentOrElse(expectedToolset -> {
|
||||
var toolset = createToolset.get();
|
||||
assertEquals(resolveAt(expectedToolset, workDir), toolset);
|
||||
}, () -> {
|
||||
var ex = assertThrows(RuntimeException.class, createToolset::get);
|
||||
assertEquals(expectedErrors.getFirst().getValue(), ex.getMessage());
|
||||
if (ex instanceof ConfigException cfgEx) {
|
||||
assertEquals(expectedErrors.getLast().getValue(), cfgEx.getAdvice());
|
||||
assertEquals(2, expectedErrors.size());
|
||||
} else {
|
||||
assertEquals(1, expectedErrors.size());
|
||||
}
|
||||
});
|
||||
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
static Builder build() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
static final class Builder {
|
||||
|
||||
TestSpec create() {
|
||||
if (expected == null && expectedErrors.isEmpty()) {
|
||||
return copy()
|
||||
.expect("error.no-wix-tools")
|
||||
.expect("error.no-wix-tools.advice")
|
||||
.create();
|
||||
} else {
|
||||
var allLookupDirs = Stream.concat(
|
||||
lookupDirs.stream(),
|
||||
tools.stream().map(CommandMockSpec::name).map(toolPath -> {
|
||||
if (toolPath.getNameCount() == 1) {
|
||||
return LOOKUP_IN_PATH;
|
||||
} else {
|
||||
return toolPath.getParent();
|
||||
}
|
||||
})
|
||||
).distinct().collect(Collectors.toCollection(ArrayList::new));
|
||||
|
||||
var lookupInPATH = allLookupDirs.contains(LOOKUP_IN_PATH);
|
||||
if (lookupInPATH) {
|
||||
allLookupDirs.remove(LOOKUP_IN_PATH);
|
||||
}
|
||||
|
||||
return new TestSpec(
|
||||
Optional.ofNullable(expected),
|
||||
Collections.unmodifiableList(allLookupDirs),
|
||||
lookupInPATH,
|
||||
List.copyOf(tools),
|
||||
List.copyOf(expectedErrors));
|
||||
}
|
||||
}
|
||||
|
||||
Builder copy() {
|
||||
return new Builder(this);
|
||||
}
|
||||
|
||||
private Builder() {
|
||||
expectedErrors = new ArrayList<>();
|
||||
lookupDirs = new ArrayList<>();
|
||||
tools = new ArrayList<>();
|
||||
}
|
||||
|
||||
private Builder(Builder other) {
|
||||
expected = other.expected;
|
||||
expectedErrors = new ArrayList<>(other.expectedErrors);
|
||||
lookupDirs = new ArrayList<>(other.lookupDirs);
|
||||
tools = new ArrayList<>(other.tools);
|
||||
}
|
||||
|
||||
Builder expect(WixToolset v) {
|
||||
expected = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder expect(String formatKey, Object ... args) {
|
||||
expectedErrors.add(JPackageStringBundle.MAIN.cannedFormattedString(formatKey, args));
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder expect(WixToolsetBuilder builder) {
|
||||
return expect(builder.create());
|
||||
}
|
||||
|
||||
Builder lookupDir(String v) {
|
||||
return lookupDir(Path.of(v));
|
||||
}
|
||||
|
||||
Builder lookupDir(Path v) {
|
||||
lookupDirs.add(Objects.requireNonNull(v));
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder tool(CommandMockSpec v) {
|
||||
tools.add(Objects.requireNonNull(v));
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder tool(WixToolMock v) {
|
||||
return tool(v.create());
|
||||
}
|
||||
|
||||
private WixToolset expected;
|
||||
private final List<CannedFormattedString> expectedErrors;
|
||||
private final List<Path> lookupDirs;
|
||||
private final List<CommandMockSpec> tools;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class WixToolsetBuilder {
|
||||
|
||||
WixToolset create() {
|
||||
return new WixToolset(tools.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> {
|
||||
ToolInfo toolInfo = new WixTool.DefaultToolInfo(e.getValue(), version);
|
||||
if (e.getKey() == WixTool.Candle3) {
|
||||
toolInfo = new WixTool.DefaultCandleInfo(toolInfo, fips);
|
||||
}
|
||||
return toolInfo;
|
||||
})));
|
||||
}
|
||||
|
||||
WixToolsetBuilder version(String v) {
|
||||
version = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
WixToolsetBuilder put(WixTool tool, String path) {
|
||||
return put(tool, Path.of(path));
|
||||
}
|
||||
|
||||
WixToolsetBuilder put(WixTool tool, Path path) {
|
||||
tools.put(Objects.requireNonNull(tool), path.resolve(tool.fileName()));
|
||||
return this;
|
||||
}
|
||||
|
||||
WixToolsetBuilder put(WixTool tool) {
|
||||
return put(tool, LOOKUP_IN_PATH);
|
||||
}
|
||||
|
||||
WixToolsetBuilder put(WixToolsetType type, Path path) {
|
||||
type.getTools().forEach(tool -> {
|
||||
put(tool, path);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
WixToolsetBuilder put(WixToolsetType type, String path) {
|
||||
return put(type, Path.of(path));
|
||||
}
|
||||
|
||||
WixToolsetBuilder put(WixToolsetType type) {
|
||||
return put(type, LOOKUP_IN_PATH);
|
||||
}
|
||||
|
||||
WixToolsetBuilder fips(boolean v) {
|
||||
fips = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
WixToolsetBuilder fips() {
|
||||
return fips(true);
|
||||
}
|
||||
|
||||
private Map<WixTool, Path> tools = new HashMap<>();
|
||||
private boolean fips;
|
||||
private String version;
|
||||
}
|
||||
|
||||
enum EnvironmentVariable {
|
||||
USERPROFILE("USERPROFILE"),
|
||||
PROGRAM_FILES("ProgramFiles"),
|
||||
PROGRAM_FILES_X86("ProgramFiles(x86)"),
|
||||
SYSTEM_DRIVE("SystemDrive"),
|
||||
;
|
||||
|
||||
EnvironmentVariable(String variableName) {
|
||||
this.variableName = Objects.requireNonNull(variableName);
|
||||
}
|
||||
|
||||
String variableName() {
|
||||
return variableName;
|
||||
}
|
||||
|
||||
private final String variableName;
|
||||
}
|
||||
|
||||
enum SystemProperty {
|
||||
USER_HOME("user.home"),
|
||||
;
|
||||
|
||||
SystemProperty(String propertyName) {
|
||||
this.propertyName = Objects.requireNonNull(propertyName);
|
||||
}
|
||||
|
||||
String propertyName() {
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
private final String propertyName;
|
||||
}
|
||||
|
||||
record EnvironmentTestSpec(EnvironmentProviderMock env, List<Path> expectedDirs) {
|
||||
|
||||
EnvironmentTestSpec {
|
||||
Objects.requireNonNull(env);
|
||||
expectedDirs.forEach(dir -> {
|
||||
if (dir.isAbsolute()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var tokens = new ArrayList<String>();
|
||||
tokens.add(String.format("expect=%s", expectedDirs));
|
||||
tokens.add(env.toString());
|
||||
return String.join(", ", tokens);
|
||||
}
|
||||
|
||||
void run(Path workDir) throws IOException {
|
||||
var allResolved = resolve(workDir, Stream.of(
|
||||
env.envVariables().entrySet().stream(),
|
||||
env.systemProperties().entrySet().stream(),
|
||||
expectedDirs.stream().map(Path::toString).map(dir -> {
|
||||
return Map.entry(dir, dir);
|
||||
})
|
||||
).flatMap(x -> x).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||
|
||||
Function<Supplier<Map<String, String>>, Map<String, String>> filterAllResolved = filterSupplier -> {
|
||||
var filter = filterSupplier.get();
|
||||
return allResolved.entrySet().stream().filter(e -> {
|
||||
return filter.containsKey(e.getKey());
|
||||
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
};
|
||||
|
||||
var resolvedEnv = new EnvironmentProviderMock(
|
||||
filterAllResolved.apply(env::envVariables),
|
||||
filterAllResolved.apply(env::systemProperties));
|
||||
|
||||
var resolvedDirs = expectedDirs.stream().map(Path::toString).map(allResolved::get).map(Path::of).toList();
|
||||
|
||||
for (var dir : resolvedDirs) {
|
||||
Files.createDirectories(dir);
|
||||
}
|
||||
|
||||
Globals.main(() -> {
|
||||
Globals.instance().system(resolvedEnv);
|
||||
assertEquals(resolvedDirs, WixTool.findWixInstallDirs());
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
static Builder build() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
static final class Builder {
|
||||
|
||||
EnvironmentTestSpec create() {
|
||||
var env = envVariables.entrySet().stream().collect(Collectors.toMap(e -> {
|
||||
return e.getKey().variableName();
|
||||
}, Map.Entry::getValue));
|
||||
|
||||
var props = systemProperties.entrySet().stream().collect(Collectors.toMap(e -> {
|
||||
return e.getKey().propertyName();
|
||||
}, Map.Entry::getValue));
|
||||
|
||||
return new EnvironmentTestSpec(new EnvironmentProviderMock(env, props), List.copyOf(expectedDirs));
|
||||
}
|
||||
|
||||
Builder expect(List<Path> dirs) {
|
||||
expectedDirs.addAll(dirs);
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder expect(Path... dirs) {
|
||||
return expect(List.of(dirs));
|
||||
}
|
||||
|
||||
Builder expect(String... dirs) {
|
||||
return expect(List.of(dirs).stream().map(Path::of).toList());
|
||||
}
|
||||
|
||||
Builder env(SystemProperty k, String v) {
|
||||
systemProperties.put(Objects.requireNonNull(k), Objects.requireNonNull(v));
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder env(EnvironmentVariable k, String v) {
|
||||
envVariables.put(Objects.requireNonNull(k), Objects.requireNonNull(v));
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder standardEnv(EnvironmentVariable k) {
|
||||
var value = switch (k) {
|
||||
case PROGRAM_FILES -> "Program Files";
|
||||
case PROGRAM_FILES_X86 -> "Program Files(x86)";
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
};
|
||||
return env(k, "@@/" + value);
|
||||
}
|
||||
|
||||
private final Map<EnvironmentVariable, String> envVariables = new HashMap<>();
|
||||
private final Map<SystemProperty, String> systemProperties = new HashMap<>();
|
||||
private final List<Path> expectedDirs = new ArrayList<>();
|
||||
}
|
||||
|
||||
private static Map<String, String> resolve(Path workDir, Map<String, String> props) {
|
||||
|
||||
var tokens = new ArrayList<String>();
|
||||
|
||||
Stream.of(
|
||||
Stream.of(EnvironmentVariable.values()).map(EnvironmentVariable::variableName),
|
||||
Stream.of(SystemProperty.values()).map(SystemProperty::propertyName)
|
||||
).flatMap(x -> x).map(str -> {
|
||||
return String.format("@%s@", str);
|
||||
}).forEach(tokens::add);
|
||||
|
||||
tokens.add(TOKEN_WORKDIR);
|
||||
|
||||
var tokenReplace = new TokenReplace(tokens.toArray(String[]::new));
|
||||
|
||||
return props.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> {
|
||||
return tokenReplace.recursiveApplyTo(e.getValue(), token -> {
|
||||
if (token.equals(TOKEN_WORKDIR)) {
|
||||
return workDir;
|
||||
} else {
|
||||
return Objects.requireNonNull(props.get(token.substring(1, token.length() - 1)), () -> {
|
||||
return String.format("Unrecognized token: [%s]", token);
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
static final String TOKEN_WORKDIR = "@@";
|
||||
}
|
||||
|
||||
private static WixToolsetBuilder toolset() {
|
||||
return new WixToolsetBuilder();
|
||||
}
|
||||
|
||||
private static WixToolMock tool() {
|
||||
return new WixToolMock();
|
||||
}
|
||||
|
||||
private static WixToolMock tool(Path dir) {
|
||||
return tool().dir(dir);
|
||||
}
|
||||
|
||||
private static WixToolMock tool(String dir) {
|
||||
return tool(Path.of(dir));
|
||||
}
|
||||
|
||||
private static WixToolset resolveAt(WixToolset toolset, Path root) {
|
||||
return new WixToolset(toolset.tools().entrySet().stream().collect(toMap(Map.Entry::getKey, e -> {
|
||||
var toolInfo = e.getValue();
|
||||
|
||||
assertIsRelative(toolInfo.path());
|
||||
|
||||
if (toolInfo.path().getNameCount() == 1) {
|
||||
// The tool is picked from the PATH.
|
||||
return toolInfo;
|
||||
}
|
||||
|
||||
ToolInfo newToolInfo = new WixTool.DefaultToolInfo(root.resolve(toolInfo.path()), toolInfo.version());
|
||||
if (toolInfo instanceof WixTool.CandleInfo candleInfo) {
|
||||
newToolInfo = new WixTool.DefaultCandleInfo(newToolInfo, candleInfo.fips());
|
||||
}
|
||||
return newToolInfo;
|
||||
})));
|
||||
}
|
||||
|
||||
private static void assertIsRelative(Path path) {
|
||||
if (path.isAbsolute()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
static final Path LOOKUP_IN_PATH = Path.of("");
|
||||
}
|
||||
|
||||
@ -45,3 +45,15 @@
|
||||
* jdk/jpackage/internal/wixui/UISpecTest.java
|
||||
* @run junit jdk.jpackage/jdk.jpackage.internal.wixui.UISpecTest
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Test WiX Toolset lookup algorithm
|
||||
* @requires (os.family == "windows")
|
||||
* @library /test/jdk/tools/jpackage/helpers
|
||||
* @build jdk.jpackage.test.*
|
||||
* @build jdk.jpackage.test.mock.*
|
||||
* @build jdk.jpackage.test.stdmock.*
|
||||
* @compile/module=jdk.jpackage -Xlint:all -Werror
|
||||
* jdk/jpackage/internal/WixToolTest.java
|
||||
* @run junit jdk.jpackage/jdk.jpackage.internal.WixToolTest
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user