blob: be15b2028771a7242bb6b0621ad041521a91fd88 [file]
/*
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jpackage.internal;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static jdk.jpackage.internal.ShortPathUtils.adjustPath;
import jdk.jpackage.internal.util.PathUtils;
/**
* WiX pipeline. Compiles and links WiX sources.
*/
final class WixPipeline {
static final class Builder {
Builder() {
}
WixPipeline create(WixToolset toolset) {
Objects.requireNonNull(toolset);
Objects.requireNonNull(workDir);
Objects.requireNonNull(wixObjDir);
if (sources.isEmpty()) {
throw new IllegalArgumentException("no sources");
}
final var absWorkDir = workDir.normalize().toAbsolutePath();
final UnaryOperator<Path> normalizePath = path -> {
return path.normalize().toAbsolutePath();
};
final var absObjWorkDir = normalizePath.apply(wixObjDir);
var relSources = sources.stream().map(source -> {
return source.overridePath(normalizePath.apply(source.path));
}).toList();
return new WixPipeline(toolset, adjustPath(absWorkDir), absObjWorkDir,
wixVariables, mapLightOptions(normalizePath), relSources);
}
Builder setWixObjDir(Path v) {
wixObjDir = v;
return this;
}
Builder setWorkDir(Path v) {
workDir = v;
return this;
}
Builder setWixVariables(Map<String, String> v) {
wixVariables.clear();
wixVariables.putAll(v);
return this;
}
Builder addSource(Path source, Map<String, String> wixVariables) {
sources.add(new WixSource(source, wixVariables));
return this;
}
Builder addLightOptions(String ... v) {
lightOptions.addAll(List.of(v));
return this;
}
private List<String> mapLightOptions(UnaryOperator<Path> normalizePath) {
var pathOptions = Set.of("-b", "-loc");
List<String> reply = new ArrayList<>();
boolean convPath = false;
for (var opt : lightOptions) {
if (convPath) {
opt = normalizePath.apply(Path.of(opt)).toString();
convPath = false;
} else if (pathOptions.contains(opt)) {
convPath = true;
}
reply.add(opt);
}
return reply;
}
private Path workDir;
private Path wixObjDir;
private final Map<String, String> wixVariables = new HashMap<>();
private final List<String> lightOptions = new ArrayList<>();
private final List<WixSource> sources = new ArrayList<>();
}
static Builder build() {
return new Builder();
}
private WixPipeline(WixToolset toolset, Path workDir, Path wixObjDir,
Map<String, String> wixVariables, List<String> lightOptions,
List<WixSource> sources) {
this.toolset = toolset;
this.workDir = workDir;
this.wixObjDir = wixObjDir;
this.wixVariables = wixVariables;
this.lightOptions = lightOptions;
this.sources = sources;
}
void buildMsi(Path msi) throws IOException {
Objects.requireNonNull(workDir);
// Use short path to the output msi to workaround
// WiX limitations of handling long paths.
var transientMsi = wixObjDir.resolve("a.msi");
switch (toolset.getType()) {
case Wix3 -> buildMsiWix3(transientMsi);
case Wix4 -> buildMsiWix4(transientMsi);
default -> throw new IllegalArgumentException();
}
IOUtils.copyFile(workDir.resolve(transientMsi), msi);
}
private void addWixVariblesToCommandLine(
Map<String, String> otherWixVariables, List<String> cmdline) {
Stream.of(wixVariables, Optional.ofNullable(otherWixVariables).
orElseGet(Collections::emptyMap)).filter(Objects::nonNull).
reduce((a, b) -> {
a.putAll(b);
return a;
}).ifPresent(wixVars -> {
var entryStream = wixVars.entrySet().stream();
Stream<String> stream;
switch (toolset.getType()) {
case Wix3 -> {
stream = entryStream.map(wixVar -> {
return String.format("-d%s=%s", wixVar.getKey(), wixVar.
getValue());
});
}
case Wix4 -> {
stream = entryStream.map(wixVar -> {
return Stream.of("-d", String.format("%s=%s", wixVar.
getKey(), wixVar.getValue()));
}).flatMap(Function.identity());
}
default -> {
throw new IllegalArgumentException();
}
}
stream.reduce(cmdline, (ctnr, wixVar) -> {
ctnr.add(wixVar);
return ctnr;
}, (x, y) -> {
x.addAll(y);
return x;
});
});
}
private void buildMsiWix4(Path msi) throws IOException {
var mergedSrcWixVars = sources.stream().map(wixSource -> {
return Optional.ofNullable(wixSource.variables).orElseGet(
Collections::emptyMap).entrySet().stream();
}).flatMap(Function.identity()).collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue));
List<String> cmdline = new ArrayList<>(List.of(
toolset.getToolPath(WixTool.Wix4).toString(),
"build",
"-nologo",
"-pdbtype", "none",
"-intermediatefolder", wixObjDir.toString(),
"-ext", "WixToolset.Util.wixext",
"-arch", WixFragmentBuilder.is64Bit() ? "x64" : "x86"
));
cmdline.addAll(lightOptions);
addWixVariblesToCommandLine(mergedSrcWixVars, cmdline);
cmdline.addAll(sources.stream().map(wixSource -> {
return wixSource.path.toString();
}).toList());
cmdline.addAll(List.of("-out", msi.toString()));
execute(cmdline);
}
private void buildMsiWix3(Path msi) throws IOException {
List<Path> wixObjs = new ArrayList<>();
for (var source : sources) {
wixObjs.add(compileWix3(source));
}
List<String> lightCmdline = new ArrayList<>(List.of(
toolset.getToolPath(WixTool.Light3).toString(),
"-nologo",
"-spdb",
"-ext", "WixUtilExtension",
"-out", msi.toString()
));
lightCmdline.addAll(lightOptions);
wixObjs.stream().map(Path::toString).forEach(lightCmdline::add);
Files.createDirectories(IOUtils.getParent(msi));
execute(lightCmdline);
}
private Path compileWix3(WixSource wixSource) throws IOException {
Path wixObj = wixObjDir.toAbsolutePath().resolve(PathUtils.replaceSuffix(
wixSource.path.getFileName(), ".wixobj"));
List<String> cmdline = new ArrayList<>(List.of(
toolset.getToolPath(WixTool.Candle3).toString(),
"-nologo",
wixSource.path.toString(),
"-ext", "WixUtilExtension",
"-arch", WixFragmentBuilder.is64Bit() ? "x64" : "x86",
"-out", wixObj.toString()
));
if (toolset.needFipsParameter()) {
cmdline.add("-fips");
}
addWixVariblesToCommandLine(wixSource.variables, cmdline);
execute(cmdline);
return wixObj;
}
private void execute(List<String> cmdline) throws IOException {
Executor.of(new ProcessBuilder(cmdline).directory(workDir.toFile())).executeExpectSuccess();
}
private record WixSource(Path path, Map<String, String> variables) {
WixSource overridePath(Path path) {
return new WixSource(path, variables);
}
}
private final WixToolset toolset;
private final Map<String, String> wixVariables;
private final List<String> lightOptions;
private final Path wixObjDir;
private final Path workDir;
private final List<WixSource> sources;
}