8289771: jpackage: ResourceEditor error when path is overly long on Windows
Reviewed-by: almatvee
This commit is contained in:
parent
c4c6b1fe06
commit
080f1cc8cd
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2020, 2023, 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -30,6 +30,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -40,6 +41,7 @@ import java.util.Properties;
|
|||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import static jdk.jpackage.internal.OverridableResource.createResource;
|
import static jdk.jpackage.internal.OverridableResource.createResource;
|
||||||
|
import static jdk.jpackage.internal.ShortPathUtils.adjustPath;
|
||||||
import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME;
|
import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME;
|
||||||
import static jdk.jpackage.internal.StandardBundlerParam.COPYRIGHT;
|
import static jdk.jpackage.internal.StandardBundlerParam.COPYRIGHT;
|
||||||
import static jdk.jpackage.internal.StandardBundlerParam.DESCRIPTION;
|
import static jdk.jpackage.internal.StandardBundlerParam.DESCRIPTION;
|
||||||
@ -112,7 +114,7 @@ final class ExecutableRebrander {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void rebrandExecutable(Map<String, ? super Object> params,
|
private void rebrandExecutable(Map<String, ? super Object> params,
|
||||||
Path target, UpdateResourceAction action) throws IOException {
|
final Path target, UpdateResourceAction action) throws IOException {
|
||||||
try {
|
try {
|
||||||
String tempDirectory = TEMP_ROOT.fetchFrom(params)
|
String tempDirectory = TEMP_ROOT.fetchFrom(params)
|
||||||
.toAbsolutePath().toString();
|
.toAbsolutePath().toString();
|
||||||
@ -125,10 +127,11 @@ final class ExecutableRebrander {
|
|||||||
|
|
||||||
target.toFile().setWritable(true, true);
|
target.toFile().setWritable(true, true);
|
||||||
|
|
||||||
long resourceLock = lockResource(target.toString());
|
var shortTargetPath = ShortPathUtils.toShortPath(target);
|
||||||
|
long resourceLock = lockResource(shortTargetPath.orElse(target).toString());
|
||||||
if (resourceLock == 0) {
|
if (resourceLock == 0) {
|
||||||
throw new RuntimeException(MessageFormat.format(
|
throw new RuntimeException(MessageFormat.format(
|
||||||
I18N.getString("error.lock-resource"), target));
|
I18N.getString("error.lock-resource"), shortTargetPath.orElse(target)));
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean resourceUnlockedSuccess;
|
final boolean resourceUnlockedSuccess;
|
||||||
@ -144,6 +147,14 @@ final class ExecutableRebrander {
|
|||||||
resourceUnlockedSuccess = true;
|
resourceUnlockedSuccess = true;
|
||||||
} else {
|
} else {
|
||||||
resourceUnlockedSuccess = unlockResource(resourceLock);
|
resourceUnlockedSuccess = unlockResource(resourceLock);
|
||||||
|
if (shortTargetPath.isPresent()) {
|
||||||
|
// Windows will rename the excuatble in the unlock operation.
|
||||||
|
// Should restore executable's name.
|
||||||
|
var tmpPath = target.getParent().resolve(
|
||||||
|
target.getFileName().toString() + ".restore");
|
||||||
|
Files.move(shortTargetPath.get(), tmpPath);
|
||||||
|
Files.move(tmpPath, target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,6 +247,7 @@ final class ExecutableRebrander {
|
|||||||
|
|
||||||
private static void iconSwapWrapper(long resourceLock,
|
private static void iconSwapWrapper(long resourceLock,
|
||||||
String iconTarget) {
|
String iconTarget) {
|
||||||
|
iconTarget = adjustPath(iconTarget);
|
||||||
if (iconSwap(resourceLock, iconTarget) != 0) {
|
if (iconSwap(resourceLock, iconTarget) != 0) {
|
||||||
throw new RuntimeException(MessageFormat.format(I18N.getString(
|
throw new RuntimeException(MessageFormat.format(I18N.getString(
|
||||||
"error.icon-swap"), iconTarget));
|
"error.icon-swap"), iconTarget));
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("restricted")
|
||||||
|
final class ShortPathUtils {
|
||||||
|
static String adjustPath(String path) {
|
||||||
|
return toShortPath(path).orElse(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Path adjustPath(Path path) {
|
||||||
|
return toShortPath(path).orElse(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Optional<String> toShortPath(String path) {
|
||||||
|
Objects.requireNonNull(path);
|
||||||
|
return toShortPath(Path.of(path)).map(Path::toString);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Optional<Path> toShortPath(Path path) {
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
throw new IllegalArgumentException(String.format("[%s] path does not exist", path));
|
||||||
|
}
|
||||||
|
|
||||||
|
var normPath = path.normalize().toAbsolutePath().toString();
|
||||||
|
if (normPath.length() > MAX_PATH) {
|
||||||
|
return Optional.of(Path.of(getShortPathWrapper(normPath)));
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getShortPathWrapper(final String longPath) {
|
||||||
|
String effectivePath;
|
||||||
|
if (!longPath.startsWith(LONG_PATH_PREFIX)) {
|
||||||
|
effectivePath = LONG_PATH_PREFIX + longPath;
|
||||||
|
} else {
|
||||||
|
effectivePath = longPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.ofNullable(getShortPath(effectivePath)).orElseThrow(
|
||||||
|
() -> new ShortPathException(MessageFormat.format(I18N.getString(
|
||||||
|
"error.short-path-conv-fail"), effectivePath)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class ShortPathException extends RuntimeException {
|
||||||
|
|
||||||
|
ShortPathException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native String getShortPath(String longPath);
|
||||||
|
|
||||||
|
private static final int MAX_PATH = 240;
|
||||||
|
// See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getshortpathnamew
|
||||||
|
private static final String LONG_PATH_PREFIX = "\\\\?\\";
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.loadLibrary("jpackage");
|
||||||
|
}
|
||||||
|
}
|
@ -526,9 +526,10 @@ public class WinMsiBundler extends AbstractBundler {
|
|||||||
"message.preparing-msi-config"), msiOut.toAbsolutePath()
|
"message.preparing-msi-config"), msiOut.toAbsolutePath()
|
||||||
.toString()));
|
.toString()));
|
||||||
|
|
||||||
WixPipeline wixPipeline = new WixPipeline()
|
var wixObjDir = TEMP_ROOT.fetchFrom(params).resolve("wixobj");
|
||||||
.setToolset(wixToolset)
|
|
||||||
.setWixObjDir(TEMP_ROOT.fetchFrom(params).resolve("wixobj"))
|
var wixPipeline = WixPipeline.build()
|
||||||
|
.setWixObjDir(wixObjDir)
|
||||||
.setWorkDir(WIN_APP_IMAGE.fetchFrom(params))
|
.setWorkDir(WIN_APP_IMAGE.fetchFrom(params))
|
||||||
.addSource(CONFIG_ROOT.fetchFrom(params).resolve("main.wxs"),
|
.addSource(CONFIG_ROOT.fetchFrom(params).resolve("main.wxs"),
|
||||||
wixVars);
|
wixVars);
|
||||||
@ -605,13 +606,13 @@ public class WinMsiBundler extends AbstractBundler {
|
|||||||
// Cultures from custom files and a single primary Culture are
|
// Cultures from custom files and a single primary Culture are
|
||||||
// included into "-cultures" list
|
// included into "-cultures" list
|
||||||
for (var wxl : primaryWxlFiles) {
|
for (var wxl : primaryWxlFiles) {
|
||||||
wixPipeline.addLightOptions("-loc", wxl.toAbsolutePath().normalize().toString());
|
wixPipeline.addLightOptions("-loc", wxl.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> cultures = new ArrayList<>();
|
List<String> cultures = new ArrayList<>();
|
||||||
for (var wxl : customWxlFiles) {
|
for (var wxl : customWxlFiles) {
|
||||||
wxl = configDir.resolve(wxl.getFileName());
|
wxl = configDir.resolve(wxl.getFileName());
|
||||||
wixPipeline.addLightOptions("-loc", wxl.toAbsolutePath().normalize().toString());
|
wixPipeline.addLightOptions("-loc", wxl.toString());
|
||||||
cultures.add(getCultureFromWxlFile(wxl));
|
cultures.add(getCultureFromWxlFile(wxl));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -638,7 +639,8 @@ public class WinMsiBundler extends AbstractBundler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wixPipeline.buildMsi(msiOut.toAbsolutePath());
|
Files.createDirectories(wixObjDir);
|
||||||
|
wixPipeline.create(wixToolset).buildMsi(msiOut.toAbsolutePath());
|
||||||
|
|
||||||
return msiOut;
|
return msiOut;
|
||||||
}
|
}
|
||||||
@ -678,14 +680,14 @@ public class WinMsiBundler extends AbstractBundler {
|
|||||||
if (nodes.getLength() != 1) {
|
if (nodes.getLength() != 1) {
|
||||||
throw new IOException(MessageFormat.format(I18N.getString(
|
throw new IOException(MessageFormat.format(I18N.getString(
|
||||||
"error.extract-culture-from-wix-l10n-file"),
|
"error.extract-culture-from-wix-l10n-file"),
|
||||||
wxlPath.toAbsolutePath()));
|
wxlPath.toAbsolutePath().normalize()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodes.item(0).getNodeValue();
|
return nodes.item(0).getNodeValue();
|
||||||
} catch (XPathExpressionException | ParserConfigurationException
|
} catch (XPathExpressionException | ParserConfigurationException
|
||||||
| SAXException ex) {
|
| SAXException ex) {
|
||||||
throw new IOException(MessageFormat.format(I18N.getString(
|
throw new IOException(MessageFormat.format(I18N.getString(
|
||||||
"error.read-wix-l10n-file"), wxlPath.toAbsolutePath()), ex);
|
"error.read-wix-l10n-file"), wxlPath.toAbsolutePath().normalize()), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ abstract class WixFragmentBuilder {
|
|||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
void configureWixPipeline(WixPipeline wixPipeline) {
|
void configureWixPipeline(WixPipeline.Builder wixPipeline) {
|
||||||
wixPipeline.addSource(configRoot.resolve(outputFileName),
|
wixPipeline.addSource(configRoot.resolve(outputFileName),
|
||||||
Optional.ofNullable(wixVariables).map(WixVariables::getValues).orElse(
|
Optional.ofNullable(wixVariables).map(WixVariables::getValues).orElse(
|
||||||
null));
|
null));
|
||||||
|
@ -29,65 +29,130 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import static jdk.jpackage.internal.ShortPathUtils.adjustPath;
|
||||||
import jdk.jpackage.internal.util.PathUtils;
|
import jdk.jpackage.internal.util.PathUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WiX pipeline. Compiles and links WiX sources.
|
* WiX pipeline. Compiles and links WiX sources.
|
||||||
*/
|
*/
|
||||||
public class WixPipeline {
|
final class WixPipeline {
|
||||||
WixPipeline() {
|
|
||||||
sources = new ArrayList<>();
|
static final class Builder {
|
||||||
lightOptions = new ArrayList<>();
|
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<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
WixPipeline setToolset(WixToolset v) {
|
static Builder build() {
|
||||||
toolset = v;
|
return new Builder();
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WixPipeline setWixVariables(Map<String, String> v) {
|
private WixPipeline(WixToolset toolset, Path workDir, Path wixObjDir,
|
||||||
wixVariables = v;
|
Map<String, String> wixVariables, List<String> lightOptions,
|
||||||
return this;
|
List<WixSource> sources) {
|
||||||
}
|
this.toolset = toolset;
|
||||||
|
this.workDir = workDir;
|
||||||
WixPipeline setWixObjDir(Path v) {
|
this.wixObjDir = wixObjDir;
|
||||||
wixObjDir = v;
|
this.wixVariables = wixVariables;
|
||||||
return this;
|
this.lightOptions = lightOptions;
|
||||||
}
|
this.sources = sources;
|
||||||
|
|
||||||
WixPipeline setWorkDir(Path v) {
|
|
||||||
workDir = v;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
WixPipeline addSource(Path source, Map<String, String> wixVariables) {
|
|
||||||
WixSource entry = new WixSource();
|
|
||||||
entry.source = source;
|
|
||||||
entry.variables = wixVariables;
|
|
||||||
sources.add(entry);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
WixPipeline addLightOptions(String ... v) {
|
|
||||||
lightOptions.addAll(List.of(v));
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void buildMsi(Path msi) throws IOException {
|
void buildMsi(Path msi) throws IOException {
|
||||||
Objects.requireNonNull(workDir);
|
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()) {
|
switch (toolset.getType()) {
|
||||||
case Wix3 -> buildMsiWix3(msi);
|
case Wix3 -> buildMsiWix3(transientMsi);
|
||||||
case Wix4 -> buildMsiWix4(msi);
|
case Wix4 -> buildMsiWix4(transientMsi);
|
||||||
default -> throw new IllegalArgumentException();
|
default -> throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IOUtils.copyFile(workDir.resolve(transientMsi), msi);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addWixVariblesToCommandLine(
|
private void addWixVariblesToCommandLine(
|
||||||
@ -141,7 +206,7 @@ public class WixPipeline {
|
|||||||
"build",
|
"build",
|
||||||
"-nologo",
|
"-nologo",
|
||||||
"-pdbtype", "none",
|
"-pdbtype", "none",
|
||||||
"-intermediatefolder", wixObjDir.toAbsolutePath().toString(),
|
"-intermediatefolder", wixObjDir.toString(),
|
||||||
"-ext", "WixToolset.Util.wixext",
|
"-ext", "WixToolset.Util.wixext",
|
||||||
"-arch", WixFragmentBuilder.is64Bit() ? "x64" : "x86"
|
"-arch", WixFragmentBuilder.is64Bit() ? "x64" : "x86"
|
||||||
));
|
));
|
||||||
@ -151,7 +216,7 @@ public class WixPipeline {
|
|||||||
addWixVariblesToCommandLine(mergedSrcWixVars, cmdline);
|
addWixVariblesToCommandLine(mergedSrcWixVars, cmdline);
|
||||||
|
|
||||||
cmdline.addAll(sources.stream().map(wixSource -> {
|
cmdline.addAll(sources.stream().map(wixSource -> {
|
||||||
return wixSource.source.toAbsolutePath().toString();
|
return wixSource.path.toString();
|
||||||
}).toList());
|
}).toList());
|
||||||
|
|
||||||
cmdline.addAll(List.of("-out", msi.toString()));
|
cmdline.addAll(List.of("-out", msi.toString()));
|
||||||
@ -182,15 +247,15 @@ public class WixPipeline {
|
|||||||
|
|
||||||
private Path compileWix3(WixSource wixSource) throws IOException {
|
private Path compileWix3(WixSource wixSource) throws IOException {
|
||||||
Path wixObj = wixObjDir.toAbsolutePath().resolve(PathUtils.replaceSuffix(
|
Path wixObj = wixObjDir.toAbsolutePath().resolve(PathUtils.replaceSuffix(
|
||||||
IOUtils.getFileName(wixSource.source), ".wixobj"));
|
wixSource.path.getFileName(), ".wixobj"));
|
||||||
|
|
||||||
List<String> cmdline = new ArrayList<>(List.of(
|
List<String> cmdline = new ArrayList<>(List.of(
|
||||||
toolset.getToolPath(WixTool.Candle3).toString(),
|
toolset.getToolPath(WixTool.Candle3).toString(),
|
||||||
"-nologo",
|
"-nologo",
|
||||||
wixSource.source.toAbsolutePath().toString(),
|
wixSource.path.toString(),
|
||||||
"-ext", "WixUtilExtension",
|
"-ext", "WixUtilExtension",
|
||||||
"-arch", WixFragmentBuilder.is64Bit() ? "x64" : "x86",
|
"-arch", WixFragmentBuilder.is64Bit() ? "x64" : "x86",
|
||||||
"-out", wixObj.toAbsolutePath().toString()
|
"-out", wixObj.toString()
|
||||||
));
|
));
|
||||||
|
|
||||||
addWixVariblesToCommandLine(wixSource.variables, cmdline);
|
addWixVariblesToCommandLine(wixSource.variables, cmdline);
|
||||||
@ -201,19 +266,19 @@ public class WixPipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void execute(List<String> cmdline) throws IOException {
|
private void execute(List<String> cmdline) throws IOException {
|
||||||
Executor.of(new ProcessBuilder(cmdline).directory(workDir.toFile())).
|
Executor.of(new ProcessBuilder(cmdline).directory(workDir.toFile())).executeExpectSuccess();
|
||||||
executeExpectSuccess();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class WixSource {
|
private record WixSource(Path path, Map<String, String> variables) {
|
||||||
Path source;
|
WixSource overridePath(Path path) {
|
||||||
Map<String, String> variables;
|
return new WixSource(path, variables);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private WixToolset toolset;
|
private final WixToolset toolset;
|
||||||
private Map<String, String> wixVariables;
|
private final Map<String, String> wixVariables;
|
||||||
private List<String> lightOptions;
|
private final List<String> lightOptions;
|
||||||
private Path wixObjDir;
|
private final Path wixObjDir;
|
||||||
private Path workDir;
|
private final Path workDir;
|
||||||
private List<WixSource> sources;
|
private final List<WixSource> sources;
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void configureWixPipeline(WixPipeline wixPipeline) {
|
void configureWixPipeline(WixPipeline.Builder wixPipeline) {
|
||||||
super.configureWixPipeline(wixPipeline);
|
super.configureWixPipeline(wixPipeline);
|
||||||
|
|
||||||
if (withShortcutPromptDlg || withInstallDirChooserDlg || withLicenseDlg) {
|
if (withShortcutPromptDlg || withInstallDirChooserDlg || withLicenseDlg) {
|
||||||
@ -518,7 +518,7 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
|||||||
wxsFileName), wxsFileName);
|
wxsFileName), wxsFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addToWixPipeline(WixPipeline wixPipeline) {
|
void addToWixPipeline(WixPipeline.Builder wixPipeline) {
|
||||||
wixPipeline.addSource(getConfigRoot().toAbsolutePath().resolve(
|
wixPipeline.addSource(getConfigRoot().toAbsolutePath().resolve(
|
||||||
wxsFileName), wixVariables.getValues());
|
wxsFileName), wixVariables.getValues());
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ error.lock-resource=Failed to lock: {0}
|
|||||||
error.unlock-resource=Failed to unlock: {0}
|
error.unlock-resource=Failed to unlock: {0}
|
||||||
error.read-wix-l10n-file=Failed to parse {0} file
|
error.read-wix-l10n-file=Failed to parse {0} file
|
||||||
error.extract-culture-from-wix-l10n-file=Failed to read value of culture from {0} file
|
error.extract-culture-from-wix-l10n-file=Failed to read value of culture from {0} file
|
||||||
|
error.short-path-conv-fail=Failed to get short version of "{0}" path
|
||||||
|
|
||||||
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.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.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
|
message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
|
||||||
|
@ -56,6 +56,7 @@ error.lock-resource=Sperren nicht erfolgreich: {0}
|
|||||||
error.unlock-resource=Aufheben der Sperre nicht erfolgreich: {0}
|
error.unlock-resource=Aufheben der Sperre nicht erfolgreich: {0}
|
||||||
error.read-wix-l10n-file=Datei {0} konnte nicht geparst werden
|
error.read-wix-l10n-file=Datei {0} konnte nicht geparst werden
|
||||||
error.extract-culture-from-wix-l10n-file=Kulturwert konnte nicht aus Datei {0} gelesen werden
|
error.extract-culture-from-wix-l10n-file=Kulturwert konnte nicht aus Datei {0} gelesen werden
|
||||||
|
error.short-path-conv-fail=Failed to get short version of "{0}" path
|
||||||
|
|
||||||
message.icon-not-ico=Das angegebene Symbol "{0}" ist keine ICO-Datei und wird nicht verwendet. Stattdessen wird das Standardsymbol verwendet.
|
message.icon-not-ico=Das angegebene Symbol "{0}" ist keine ICO-Datei und wird nicht verwendet. Stattdessen wird das Standardsymbol verwendet.
|
||||||
message.potential.windows.defender.issue=Warnung: Windows Defender verhindert eventuell die korrekte Ausführung von jpackage. Wenn ein Problem auftritt, deaktivieren Sie das Echtzeitmonitoring, oder fügen Sie einen Ausschluss für das Verzeichnis "{0}" hinzu.
|
message.potential.windows.defender.issue=Warnung: Windows Defender verhindert eventuell die korrekte Ausführung von jpackage. Wenn ein Problem auftritt, deaktivieren Sie das Echtzeitmonitoring, oder fügen Sie einen Ausschluss für das Verzeichnis "{0}" hinzu.
|
||||||
|
@ -56,6 +56,7 @@ error.lock-resource=ロックに失敗しました: {0}
|
|||||||
error.unlock-resource=ロック解除に失敗しました: {0}
|
error.unlock-resource=ロック解除に失敗しました: {0}
|
||||||
error.read-wix-l10n-file={0}ファイルの解析に失敗しました
|
error.read-wix-l10n-file={0}ファイルの解析に失敗しました
|
||||||
error.extract-culture-from-wix-l10n-file={0}ファイルからのカルチャの値の読取りに失敗しました
|
error.extract-culture-from-wix-l10n-file={0}ファイルからのカルチャの値の読取りに失敗しました
|
||||||
|
error.short-path-conv-fail=Failed to get short version of "{0}" path
|
||||||
|
|
||||||
message.icon-not-ico=指定したアイコン"{0}"はICOファイルではなく、使用されません。デフォルト・アイコンがその位置に使用されます。
|
message.icon-not-ico=指定したアイコン"{0}"はICOファイルではなく、使用されません。デフォルト・アイコンがその位置に使用されます。
|
||||||
message.potential.windows.defender.issue=警告: Windows Defenderが原因でjpackageが機能しないことがあります。問題が発生した場合は、リアルタイム・モニタリングを無効にするか、ディレクトリ"{0}"の除外を追加することにより、問題に対処できます。
|
message.potential.windows.defender.issue=警告: Windows Defenderが原因でjpackageが機能しないことがあります。問題が発生した場合は、リアルタイム・モニタリングを無効にするか、ディレクトリ"{0}"の除外を追加することにより、問題に対処できます。
|
||||||
|
@ -56,6 +56,7 @@ error.lock-resource=无法锁定:{0}
|
|||||||
error.unlock-resource=无法解锁:{0}
|
error.unlock-resource=无法解锁:{0}
|
||||||
error.read-wix-l10n-file=无法解析 {0} 文件
|
error.read-wix-l10n-file=无法解析 {0} 文件
|
||||||
error.extract-culture-from-wix-l10n-file=无法从 {0} 文件读取文化值
|
error.extract-culture-from-wix-l10n-file=无法从 {0} 文件读取文化值
|
||||||
|
error.short-path-conv-fail=Failed to get short version of "{0}" path
|
||||||
|
|
||||||
message.icon-not-ico=指定的图标 "{0}" 不是 ICO 文件, 不会使用。将使用默认图标代替。
|
message.icon-not-ico=指定的图标 "{0}" 不是 ICO 文件, 不会使用。将使用默认图标代替。
|
||||||
message.potential.windows.defender.issue=警告:Windows Defender 可能会阻止 jpackage 正常工作。如果存在问题,可以通过禁用实时监视或者为目录 "{0}" 添加排除项来解决。
|
message.potential.windows.defender.issue=警告:Windows Defender 可能会阻止 jpackage 正常工作。如果存在问题,可以通过禁用实时监视或者为目录 "{0}" 添加排除项来解决。
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2020, 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -668,4 +668,23 @@ tstring stripExeSuffix(const tstring& path) {
|
|||||||
return path.substr(0, pos);
|
return path.substr(0, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tstring toShortPath(const tstring& path) {
|
||||||
|
const DWORD len = GetShortPathName(path.c_str(), nullptr, 0);
|
||||||
|
if (0 == len) {
|
||||||
|
JP_THROW(SysError(tstrings::any() << "GetShortPathName("
|
||||||
|
<< path << ") failed", GetShortPathName));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<TCHAR> buf;
|
||||||
|
buf.resize(len);
|
||||||
|
const DWORD copied = GetShortPathName(path.c_str(), buf.data(),
|
||||||
|
static_cast<DWORD>(buf.size()));
|
||||||
|
if (copied != buf.size() - 1) {
|
||||||
|
JP_THROW(SysError(tstrings::any() << "GetShortPathName("
|
||||||
|
<< path << ") failed", GetShortPathName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tstring(buf.data(), buf.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace FileUtils
|
} // namespace FileUtils
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2020, 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -315,6 +315,8 @@ namespace FileUtils {
|
|||||||
std::ofstream tmp;
|
std::ofstream tmp;
|
||||||
tstring dstPath;
|
tstring dstPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tstring toShortPath(const tstring& path);
|
||||||
} // FileUtils
|
} // FileUtils
|
||||||
|
|
||||||
#endif // WINFILEUTILS_H
|
#endif // WINFILEUTILS_H
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
#include "ResourceEditor.h"
|
#include "ResourceEditor.h"
|
||||||
#include "ErrorHandling.h"
|
#include "ErrorHandling.h"
|
||||||
|
#include "FileUtils.h"
|
||||||
|
#include "WinFileUtils.h"
|
||||||
#include "IconSwap.h"
|
#include "IconSwap.h"
|
||||||
#include "VersionInfo.h"
|
#include "VersionInfo.h"
|
||||||
#include "JniUtils.h"
|
#include "JniUtils.h"
|
||||||
@ -162,4 +164,25 @@ extern "C" {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: jdk_jpackage_internal_ShortPathUtils
|
||||||
|
* Method: getShortPath
|
||||||
|
* Signature: (Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
*/
|
||||||
|
JNIEXPORT jstring JNICALL
|
||||||
|
Java_jdk_jpackage_internal_ShortPathUtils_getShortPath(
|
||||||
|
JNIEnv *pEnv, jclass c, jstring jLongPath) {
|
||||||
|
|
||||||
|
JP_TRY;
|
||||||
|
|
||||||
|
const std::wstring longPath = jni::toUnicodeString(pEnv, jLongPath);
|
||||||
|
std::wstring shortPath = FileUtils::toShortPath(longPath);
|
||||||
|
|
||||||
|
return jni::toJString(pEnv, shortPath);
|
||||||
|
|
||||||
|
JP_CATCH_ALL;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
@ -53,7 +53,7 @@ public final class Executor extends CommandArguments<Executor> {
|
|||||||
|
|
||||||
public Executor() {
|
public Executor() {
|
||||||
saveOutputType = new HashSet<>(Set.of(SaveOutputType.NONE));
|
saveOutputType = new HashSet<>(Set.of(SaveOutputType.NONE));
|
||||||
removePath = false;
|
removePathEnvVar = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Executor setExecutable(String v) {
|
public Executor setExecutable(String v) {
|
||||||
@ -85,8 +85,8 @@ public final class Executor extends CommandArguments<Executor> {
|
|||||||
return setExecutable(v.getPath());
|
return setExecutable(v.getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Executor setRemovePath(boolean value) {
|
public Executor setRemovePathEnvVar(boolean value) {
|
||||||
removePath = value;
|
removePathEnvVar = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,7 +348,7 @@ public final class Executor extends CommandArguments<Executor> {
|
|||||||
builder.directory(directory.toFile());
|
builder.directory(directory.toFile());
|
||||||
sb.append(String.format("; in directory [%s]", directory));
|
sb.append(String.format("; in directory [%s]", directory));
|
||||||
}
|
}
|
||||||
if (removePath) {
|
if (removePathEnvVar) {
|
||||||
// run this with cleared Path in Environment
|
// run this with cleared Path in Environment
|
||||||
TKit.trace("Clearing PATH in environment");
|
TKit.trace("Clearing PATH in environment");
|
||||||
builder.environment().remove("PATH");
|
builder.environment().remove("PATH");
|
||||||
@ -478,7 +478,7 @@ public final class Executor extends CommandArguments<Executor> {
|
|||||||
private Path executable;
|
private Path executable;
|
||||||
private Set<SaveOutputType> saveOutputType;
|
private Set<SaveOutputType> saveOutputType;
|
||||||
private Path directory;
|
private Path directory;
|
||||||
private boolean removePath;
|
private boolean removePathEnvVar;
|
||||||
private String winTmpDir = null;
|
private String winTmpDir = null;
|
||||||
|
|
||||||
private static enum SaveOutputType {
|
private static enum SaveOutputType {
|
||||||
|
@ -354,12 +354,12 @@ public final class HelloApp {
|
|||||||
|
|
||||||
if (TKit.isWindows()) {
|
if (TKit.isWindows()) {
|
||||||
// When running app launchers on Windows, clear users environment (JDK-8254920)
|
// When running app launchers on Windows, clear users environment (JDK-8254920)
|
||||||
removePath(true);
|
removePathEnvVar(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppOutputVerifier removePath(boolean v) {
|
public AppOutputVerifier removePathEnvVar(boolean v) {
|
||||||
removePath = v;
|
removePathEnvVar = v;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,7 +455,7 @@ public final class HelloApp {
|
|||||||
Path outputFile = TKit.workDir().resolve(OUTPUT_FILENAME);
|
Path outputFile = TKit.workDir().resolve(OUTPUT_FILENAME);
|
||||||
ThrowingFunction.toFunction(Files::deleteIfExists).apply(outputFile);
|
ThrowingFunction.toFunction(Files::deleteIfExists).apply(outputFile);
|
||||||
|
|
||||||
final Path executablePath;
|
Path executablePath;
|
||||||
if (launcherPath.isAbsolute()) {
|
if (launcherPath.isAbsolute()) {
|
||||||
executablePath = launcherPath;
|
executablePath = launcherPath;
|
||||||
} else {
|
} else {
|
||||||
@ -463,18 +463,27 @@ public final class HelloApp {
|
|||||||
executablePath = Path.of(".").resolve(launcherPath.normalize());
|
executablePath = Path.of(".").resolve(launcherPath.normalize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TKit.isWindows()) {
|
||||||
|
var absExecutablePath = executablePath.toAbsolutePath().normalize();
|
||||||
|
var shortPath = WindowsHelper.toShortPath(absExecutablePath);
|
||||||
|
if (shortPath.isPresent()) {
|
||||||
|
TKit.trace(String.format("Will run [%s] as [%s]", executablePath, shortPath.get()));
|
||||||
|
executablePath = shortPath.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final List<String> launcherArgs = List.of(args);
|
final List<String> launcherArgs = List.of(args);
|
||||||
return new Executor()
|
return new Executor()
|
||||||
.setDirectory(outputFile.getParent())
|
.setDirectory(outputFile.getParent())
|
||||||
.saveOutput(saveOutput)
|
.saveOutput(saveOutput)
|
||||||
.dumpOutput()
|
.dumpOutput()
|
||||||
.setRemovePath(removePath)
|
.setRemovePathEnvVar(removePathEnvVar)
|
||||||
.setExecutable(executablePath)
|
.setExecutable(executablePath)
|
||||||
.addArguments(launcherArgs);
|
.addArguments(launcherArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean launcherNoExit;
|
private boolean launcherNoExit;
|
||||||
private boolean removePath;
|
private boolean removePathEnvVar;
|
||||||
private boolean saveOutput;
|
private boolean saveOutput;
|
||||||
private final Path launcherPath;
|
private final Path launcherPath;
|
||||||
private Path outputFilePath;
|
private Path outputFilePath;
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
package jdk.jpackage.test;
|
package jdk.jpackage.test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -36,7 +37,9 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import static jdk.jpackage.internal.util.function.ExceptionBox.rethrowUnchecked;
|
||||||
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
||||||
|
import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier;
|
||||||
import jdk.jpackage.test.PackageTest.PackageHandlers;
|
import jdk.jpackage.test.PackageTest.PackageHandlers;
|
||||||
|
|
||||||
public class WindowsHelper {
|
public class WindowsHelper {
|
||||||
@ -94,8 +97,9 @@ public class WindowsHelper {
|
|||||||
static PackageHandlers createMsiPackageHandlers() {
|
static PackageHandlers createMsiPackageHandlers() {
|
||||||
BiConsumer<JPackageCommand, Boolean> installMsi = (cmd, install) -> {
|
BiConsumer<JPackageCommand, Boolean> installMsi = (cmd, install) -> {
|
||||||
cmd.verifyIsOfType(PackageType.WIN_MSI);
|
cmd.verifyIsOfType(PackageType.WIN_MSI);
|
||||||
|
var msiPath = TransientMsi.create(cmd).path();
|
||||||
runMsiexecWithRetries(Executor.of("msiexec", "/qn", "/norestart",
|
runMsiexecWithRetries(Executor.of("msiexec", "/qn", "/norestart",
|
||||||
install ? "/i" : "/x").addArgument(cmd.outputBundle().normalize()));
|
install ? "/i" : "/x").addArgument(msiPath));
|
||||||
};
|
};
|
||||||
|
|
||||||
PackageHandlers msi = new PackageHandlers();
|
PackageHandlers msi = new PackageHandlers();
|
||||||
@ -112,6 +116,8 @@ public class WindowsHelper {
|
|||||||
TKit.removeRootFromAbsolutePath(
|
TKit.removeRootFromAbsolutePath(
|
||||||
getInstallationRootDirectory(cmd)));
|
getInstallationRootDirectory(cmd)));
|
||||||
|
|
||||||
|
final Path msiPath = TransientMsi.create(cmd).path();
|
||||||
|
|
||||||
// Put msiexec in .bat file because can't pass value of TARGETDIR
|
// Put msiexec in .bat file because can't pass value of TARGETDIR
|
||||||
// property containing spaces through ProcessBuilder properly.
|
// property containing spaces through ProcessBuilder properly.
|
||||||
// Set folder permissions to allow msiexec unpack msi bundle.
|
// Set folder permissions to allow msiexec unpack msi bundle.
|
||||||
@ -121,7 +127,7 @@ public class WindowsHelper {
|
|||||||
String.join(" ", List.of(
|
String.join(" ", List.of(
|
||||||
"msiexec",
|
"msiexec",
|
||||||
"/a",
|
"/a",
|
||||||
String.format("\"%s\"", cmd.outputBundle().normalize()),
|
String.format("\"%s\"", msiPath),
|
||||||
"/qn",
|
"/qn",
|
||||||
String.format("TARGETDIR=\"%s\"",
|
String.format("TARGETDIR=\"%s\"",
|
||||||
unpackDir.toAbsolutePath().normalize())))));
|
unpackDir.toAbsolutePath().normalize())))));
|
||||||
@ -155,6 +161,49 @@ public class WindowsHelper {
|
|||||||
return msi;
|
return msi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record TransientMsi(Path path) {
|
||||||
|
static TransientMsi create(JPackageCommand cmd) {
|
||||||
|
var outputMsiPath = cmd.outputBundle().normalize();
|
||||||
|
if (isPathTooLong(outputMsiPath)) {
|
||||||
|
return toSupplier(() -> {
|
||||||
|
var transientMsiPath = TKit.createTempDirectory("msi-copy").resolve("a.msi").normalize();
|
||||||
|
TKit.trace(String.format("Copy [%s] to [%s]", outputMsiPath, transientMsiPath));
|
||||||
|
Files.copy(outputMsiPath, transientMsiPath);
|
||||||
|
return new TransientMsi(transientMsiPath);
|
||||||
|
}).get();
|
||||||
|
} else {
|
||||||
|
return new TransientMsi(outputMsiPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum WixType {
|
||||||
|
WIX3,
|
||||||
|
WIX4
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WixType getWixTypeFromVerboseJPackageOutput(Executor.Result result) {
|
||||||
|
return result.getOutput().stream().map(str -> {
|
||||||
|
if (str.contains("[light.exe]")) {
|
||||||
|
return WixType.WIX3;
|
||||||
|
} else if (str.contains("[wix.exe]")) {
|
||||||
|
return WixType.WIX4;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}).filter(Objects::nonNull).reduce((a, b) -> {
|
||||||
|
throw new IllegalArgumentException("Invalid input: multiple invocations of WiX tools");
|
||||||
|
}).orElseThrow(() -> new IllegalArgumentException("Invalid input: no invocations of WiX tools"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Optional<Path> toShortPath(Path path) {
|
||||||
|
if (isPathTooLong(path)) {
|
||||||
|
return Optional.of(ShortPathUtils.toShortPath(path));
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static PackageHandlers createExePackageHandlers() {
|
static PackageHandlers createExePackageHandlers() {
|
||||||
BiConsumer<JPackageCommand, Boolean> installExe = (cmd, install) -> {
|
BiConsumer<JPackageCommand, Boolean> installExe = (cmd, install) -> {
|
||||||
cmd.verifyIsOfType(PackageType.WIN_EXE);
|
cmd.verifyIsOfType(PackageType.WIN_EXE);
|
||||||
@ -303,6 +352,10 @@ public class WindowsHelper {
|
|||||||
return cmd.hasArgument("--win-per-user-install");
|
return cmd.hasArgument("--win-per-user-install");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isPathTooLong(Path path) {
|
||||||
|
return path.toString().length() > WIN_MAX_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
private static class DesktopIntegrationVerifier {
|
private static class DesktopIntegrationVerifier {
|
||||||
|
|
||||||
DesktopIntegrationVerifier(JPackageCommand cmd, String launcherName) {
|
DesktopIntegrationVerifier(JPackageCommand cmd, String launcherName) {
|
||||||
@ -525,6 +578,32 @@ public class WindowsHelper {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class ShortPathUtils {
|
||||||
|
private ShortPathUtils() {
|
||||||
|
try {
|
||||||
|
var shortPathUtilsClass = Class.forName("jdk.jpackage.internal.ShortPathUtils");
|
||||||
|
|
||||||
|
getShortPathWrapper = shortPathUtilsClass.getDeclaredMethod(
|
||||||
|
"getShortPathWrapper", String.class);
|
||||||
|
// Note: this reflection call requires
|
||||||
|
// --add-opens jdk.jpackage/jdk.jpackage.internal=ALL-UNNAMED
|
||||||
|
getShortPathWrapper.setAccessible(true);
|
||||||
|
} catch (ClassNotFoundException | NoSuchMethodException
|
||||||
|
| SecurityException ex) {
|
||||||
|
throw rethrowUnchecked(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Path toShortPath(Path path) {
|
||||||
|
return Path.of(toSupplier(() -> (String) INSTANCE.getShortPathWrapper.invoke(
|
||||||
|
null, path.toString())).get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Method getShortPathWrapper;
|
||||||
|
|
||||||
|
private static final ShortPathUtils INSTANCE = new ShortPathUtils();
|
||||||
|
}
|
||||||
|
|
||||||
static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
|
static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
|
||||||
"bin\\server\\jvm.dll"));
|
"bin\\server\\jvm.dll"));
|
||||||
|
|
||||||
@ -540,4 +619,6 @@ public class WindowsHelper {
|
|||||||
private static final String USER_SHELL_FOLDERS_REGKEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
|
private static final String USER_SHELL_FOLDERS_REGKEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
|
||||||
|
|
||||||
private static final Map<String, String> REGISTRY_VALUES = new HashMap<>();
|
private static final Map<String, String> REGISTRY_VALUES = new HashMap<>();
|
||||||
|
|
||||||
|
private static final int WIN_MAX_PATH = 260;
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,8 @@ import java.util.function.Predicate;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import jdk.jpackage.test.Executor;
|
import jdk.jpackage.test.Executor;
|
||||||
|
import static jdk.jpackage.test.WindowsHelper.WixType.WIX3;
|
||||||
|
import static jdk.jpackage.test.WindowsHelper.getWixTypeFromVerboseJPackageOutput;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @test
|
* @test
|
||||||
@ -109,13 +111,13 @@ public class WinL10nTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Stream<String> getBuildCommandLine(Executor.Result result) {
|
private static Stream<String> getWixCommandLine(Executor.Result result) {
|
||||||
return result.getOutput().stream().filter(createToolCommandLinePredicate("light").or(
|
return result.getOutput().stream().filter(createToolCommandLinePredicate("light").or(
|
||||||
createToolCommandLinePredicate("wix")));
|
createToolCommandLinePredicate("wix")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isWix3(Executor.Result result) {
|
private static boolean isWix3(Executor.Result result) {
|
||||||
return result.getOutput().stream().anyMatch(createToolCommandLinePredicate("light"));
|
return getWixTypeFromVerboseJPackageOutput(result) == WIX3;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static Predicate<String> createToolCommandLinePredicate(String wixToolName) {
|
private final static Predicate<String> createToolCommandLinePredicate(String wixToolName) {
|
||||||
@ -127,10 +129,10 @@ public class WinL10nTest {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<TKit.TextStreamVerifier> createDefaultL10nFilesLocVerifiers(Path tempDir) {
|
private static List<TKit.TextStreamVerifier> createDefaultL10nFilesLocVerifiers(Path wixSrcDir) {
|
||||||
return Arrays.stream(DEFAULT_L10N_FILES).map(loc ->
|
return Arrays.stream(DEFAULT_L10N_FILES).map(loc ->
|
||||||
TKit.assertTextStream("-loc " + tempDir.resolve(
|
TKit.assertTextStream("-loc " + wixSrcDir.resolve(
|
||||||
String.format("config/MsiInstallerStrings_%s.wxl", loc)).normalize()))
|
String.format("MsiInstallerStrings_%s.wxl", loc))))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,16 +185,20 @@ public class WinL10nTest {
|
|||||||
cmd.addArguments("--temp", tempDir);
|
cmd.addArguments("--temp", tempDir);
|
||||||
})
|
})
|
||||||
.addBundleVerifier((cmd, result) -> {
|
.addBundleVerifier((cmd, result) -> {
|
||||||
|
final List<String> wixCmdline = getWixCommandLine(result).toList();
|
||||||
|
|
||||||
|
final var isWix3 = isWix3(result);
|
||||||
|
|
||||||
if (expectedCultures != null) {
|
if (expectedCultures != null) {
|
||||||
String expected;
|
String expected;
|
||||||
if (isWix3(result)) {
|
if (isWix3) {
|
||||||
expected = "-cultures:" + String.join(";", expectedCultures);
|
expected = "-cultures:" + String.join(";", expectedCultures);
|
||||||
} else {
|
} else {
|
||||||
expected = Stream.of(expectedCultures).map(culture -> {
|
expected = Stream.of(expectedCultures).map(culture -> {
|
||||||
return String.join(" ", "-culture", culture);
|
return String.join(" ", "-culture", culture);
|
||||||
}).collect(Collectors.joining(" "));
|
}).collect(Collectors.joining(" "));
|
||||||
}
|
}
|
||||||
TKit.assertTextStream(expected).apply(getBuildCommandLine(result));
|
TKit.assertTextStream(expected).apply(wixCmdline.stream());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expectedErrorMessage != null) {
|
if (expectedErrorMessage != null) {
|
||||||
@ -201,25 +207,27 @@ public class WinL10nTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (wxlFileInitializers != null) {
|
if (wxlFileInitializers != null) {
|
||||||
var wixSrcDir = Path.of(cmd.getArgumentValue("--temp")).resolve("config");
|
var wixSrcDir = Path.of(cmd.getArgumentValue("--temp")).resolve(
|
||||||
|
"config").normalize().toAbsolutePath();
|
||||||
|
|
||||||
if (allWxlFilesValid) {
|
if (allWxlFilesValid) {
|
||||||
for (var v : wxlFileInitializers) {
|
for (var v : wxlFileInitializers) {
|
||||||
if (!v.name.startsWith("MsiInstallerStrings_")) {
|
if (!v.name.startsWith("MsiInstallerStrings_")) {
|
||||||
v.createCmdOutputVerifier(wixSrcDir).apply(getBuildCommandLine(result));
|
v.createCmdOutputVerifier(wixSrcDir).apply(wixCmdline.stream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var tempDir = Path.of(cmd.getArgumentValue("--temp")).toAbsolutePath();
|
|
||||||
for (var v : createDefaultL10nFilesLocVerifiers(tempDir)) {
|
for (var v : createDefaultL10nFilesLocVerifiers(wixSrcDir)) {
|
||||||
v.apply(getBuildCommandLine(result));
|
v.apply(wixCmdline.stream());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Stream.of(wxlFileInitializers)
|
Stream.of(wxlFileInitializers)
|
||||||
.filter(Predicate.not(WixFileInitializer::isValid))
|
.filter(Predicate.not(WixFileInitializer::isValid))
|
||||||
.forEach(v -> v.createCmdOutputVerifier(
|
.forEach(v -> v.createCmdOutputVerifier(
|
||||||
wixSrcDir).apply(result.getOutput().stream()));
|
wixSrcDir).apply(result.getOutput().stream()));
|
||||||
TKit.assertFalse(getBuildCommandLine(result).findAny().isPresent(),
|
TKit.assertTrue(wixCmdline.stream().findAny().isEmpty(),
|
||||||
"Check light.exe was not invoked");
|
String.format("Check %s.exe was not invoked",
|
||||||
|
isWix3 ? "light" : "wix"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -276,10 +284,9 @@ public class WinL10nTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
TKit.TextStreamVerifier createCmdOutputVerifier(Path root) {
|
TKit.TextStreamVerifier createCmdOutputVerifier(Path wixSrcDir) {
|
||||||
return TKit.assertTextStream(String.format(
|
return TKit.assertTextStream(String.format(
|
||||||
"Failed to parse %s file",
|
"Failed to parse %s file", wixSrcDir.resolve("b.wxl")));
|
||||||
root.resolve("b.wxl").toAbsolutePath()));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -297,9 +304,8 @@ public class WinL10nTest {
|
|||||||
+ "\" xmlns=\"http://schemas.microsoft.com/wix/2006/localization\" Codepage=\"1252\"/>"));
|
+ "\" xmlns=\"http://schemas.microsoft.com/wix/2006/localization\" Codepage=\"1252\"/>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TKit.TextStreamVerifier createCmdOutputVerifier(Path root) {
|
TKit.TextStreamVerifier createCmdOutputVerifier(Path wixSrcDir) {
|
||||||
return TKit.assertTextStream(
|
return TKit.assertTextStream("-loc " + wixSrcDir.resolve(name));
|
||||||
"-loc " + root.resolve(name).toAbsolutePath().normalize());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isValid() {
|
boolean isValid() {
|
||||||
|
87
test/jdk/tools/jpackage/windows/WinLongPathTest.java
Normal file
87
test/jdk/tools/jpackage/windows/WinLongPathTest.java
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import jdk.jpackage.test.Annotations.Parameters;
|
||||||
|
import jdk.jpackage.test.PackageTest;
|
||||||
|
import jdk.jpackage.test.PackageType;
|
||||||
|
import jdk.jpackage.test.Annotations.Test;
|
||||||
|
import jdk.jpackage.test.JPackageCommand;
|
||||||
|
import jdk.jpackage.test.RunnablePackageTest.Action;
|
||||||
|
import jdk.jpackage.test.TKit;
|
||||||
|
|
||||||
|
/*
|
||||||
|
/* @test
|
||||||
|
* @bug 8289771
|
||||||
|
* @summary jpackage with long paths on windows
|
||||||
|
* @library /test/jdk/tools/jpackage/helpers
|
||||||
|
* @key jpackagePlatformPackage
|
||||||
|
* @build jdk.jpackage.test.*
|
||||||
|
* @requires (os.family == "windows")
|
||||||
|
* @compile WinLongPathTest.java
|
||||||
|
* @run main/othervm/timeout=540 -Xmx512m jdk.jpackage.test.Main
|
||||||
|
* --jpt-space-subst=*
|
||||||
|
* --jpt-exclude=WinLongPathTest(false,*--temp)
|
||||||
|
* --jpt-run=WinLongPathTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
public record WinLongPathTest(Boolean appImage, String optionName) {
|
||||||
|
|
||||||
|
@Parameters
|
||||||
|
public static List<Object[]> input() {
|
||||||
|
List<Object[]> data = new ArrayList<>();
|
||||||
|
for (var appImage : List.of(Boolean.TRUE, Boolean.FALSE)) {
|
||||||
|
for (var option : List.of("--dest", "--temp")) {
|
||||||
|
data.add(new Object[]{appImage, option});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws IOException {
|
||||||
|
if (appImage) {
|
||||||
|
var cmd = JPackageCommand.helloAppImage();
|
||||||
|
setOptionLongPath(cmd, optionName);
|
||||||
|
cmd.executeAndAssertHelloAppImageCreated();
|
||||||
|
} else {
|
||||||
|
new PackageTest()
|
||||||
|
.forTypes(PackageType.WINDOWS)
|
||||||
|
.configureHelloApp()
|
||||||
|
.addInitializer(cmd -> setOptionLongPath(cmd, optionName))
|
||||||
|
.run(Action.CREATE_AND_UNPACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setOptionLongPath(JPackageCommand cmd, String option) throws IOException {
|
||||||
|
var root = TKit.createTempDirectory("long-path");
|
||||||
|
// 261 characters in total, which alone is above the 260 threshold
|
||||||
|
var longPath = root.resolve(Path.of("a".repeat(80), "b".repeat(90), "c".repeat(91)));
|
||||||
|
Files.createDirectories(longPath);
|
||||||
|
cmd.setArgumentValue(option, longPath);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user