8250950: Allow per-user and system wide configuration of a jpackaged app

Reviewed-by: almatvee
This commit is contained in:
Alexey Semenyuk 2022-06-06 22:17:59 +00:00
parent 124ba45fb8
commit c37c8e5d34
15 changed files with 563 additions and 56 deletions

View File

@ -71,6 +71,18 @@ void launchApp() {
<< _T("lib/runtime")); << _T("lib/runtime"));
} else { } else {
ownerPackage.initAppLauncher(appLauncher); ownerPackage.initAppLauncher(appLauncher);
tstring homeDir;
JP_TRY;
homeDir = SysInfo::getEnvVariable("HOME");
JP_CATCH_ALL;
if (!homeDir.empty()) {
appLauncher.addCfgFileLookupDir(FileUtils::mkpath()
<< homeDir << ".local" << ownerPackage.name());
appLauncher.addCfgFileLookupDir(FileUtils::mkpath()
<< homeDir << "." + ownerPackage.name());
}
} }
const std::string _JPACKAGE_LAUNCHER = "_JPACKAGE_LAUNCHER"; const std::string _JPACKAGE_LAUNCHER = "_JPACKAGE_LAUNCHER";

View File

@ -46,7 +46,7 @@ import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE;
public abstract class MacBaseInstallerBundler extends AbstractBundler { public abstract class MacBaseInstallerBundler extends AbstractBundler {
public final BundlerParamInfo<Path> APP_IMAGE_TEMP_ROOT = private final BundlerParamInfo<Path> APP_IMAGE_TEMP_ROOT =
new StandardBundlerParam<>( new StandardBundlerParam<>(
"mac.app.imageRoot", "mac.app.imageRoot",
Path.class, Path.class,
@ -156,15 +156,25 @@ public abstract class MacBaseInstallerBundler extends AbstractBundler {
} }
protected Path prepareAppBundle(Map<String, ? super Object> params) protected Path prepareAppBundle(Map<String, ? super Object> params)
throws PackagerException { throws PackagerException, IOException {
Path appDir;
Path appImageRoot = APP_IMAGE_TEMP_ROOT.fetchFrom(params);
Path predefinedImage = Path predefinedImage =
StandardBundlerParam.getPredefinedAppImage(params); StandardBundlerParam.getPredefinedAppImage(params);
if (predefinedImage != null) { if (predefinedImage != null) {
return predefinedImage; appDir = appImageRoot.resolve(APP_NAME.fetchFrom(params) + ".app");
IOUtils.copyRecursive(predefinedImage, appDir);
} else {
appDir = appImageBundler.execute(params, appImageRoot);
} }
Path appImageRoot = APP_IMAGE_TEMP_ROOT.fetchFrom(params);
return appImageBundler.execute(params, appImageRoot); if (!StandardBundlerParam.isRuntimeInstaller(params)) {
new PackageFile(APP_NAME.fetchFrom(params)).save(
ApplicationLayout.macAppImage().resolveAt(appDir));
Files.deleteIfExists(AppImageFile.getPathInAppImage(appDir));
}
return appDir;
} }
@Override @Override

View File

@ -277,11 +277,8 @@ public class MacDmgBundler extends MacBaseInstallerBundler {
Path finalDMG = outdir.resolve(MAC_INSTALLER_NAME.fetchFrom(params) Path finalDMG = outdir.resolve(MAC_INSTALLER_NAME.fetchFrom(params)
+ INSTALLER_SUFFIX.fetchFrom(params) + ".dmg"); + INSTALLER_SUFFIX.fetchFrom(params) + ".dmg");
Path srcFolder = APP_IMAGE_TEMP_ROOT.fetchFrom(params); Path srcFolder = appLocation.getParent();
Path predefinedImage = StandardBundlerParam.getPredefinedAppImage(params); if (StandardBundlerParam.isRuntimeInstaller(params)) {
if (predefinedImage != null) {
srcFolder = predefinedImage;
} else if (StandardBundlerParam.isRuntimeInstaller(params)) {
Path newRoot = Files.createTempDirectory(TEMP_ROOT.fetchFrom(params), Path newRoot = Files.createTempDirectory(TEMP_ROOT.fetchFrom(params),
"root-"); "root-");

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2022, 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
@ -26,8 +26,10 @@
#include "AppLauncher.h" #include "AppLauncher.h"
#include "app.h" #include "app.h"
#include "FileUtils.h" #include "FileUtils.h"
#include "PackageFile.h"
#include "UnixSysInfo.h" #include "UnixSysInfo.h"
#include "JvmLauncher.h" #include "JvmLauncher.h"
#include "ErrorHandling.h"
namespace { namespace {
@ -49,17 +51,36 @@ void initJvmLauncher() {
const tstring appImageRoot = FileUtils::dirname(FileUtils::dirname( const tstring appImageRoot = FileUtils::dirname(FileUtils::dirname(
FileUtils::dirname(launcherPath))); FileUtils::dirname(launcherPath)));
const tstring appDirPath = FileUtils::mkpath() << appImageRoot
<< _T("Contents/app");
const PackageFile pkgFile = PackageFile::loadFromAppDir(appDirPath);
// Create JVM launcher and save in global variable. // Create JVM launcher and save in global variable.
jvmLauncher = AppLauncher() AppLauncher appLauncher = AppLauncher()
.setImageRoot(appImageRoot) .setImageRoot(appImageRoot)
.addJvmLibName(_T("Contents/Home/lib/libjli.dylib")) .addJvmLibName(_T("Contents/Home/lib/libjli.dylib"))
// add backup - older version such as JDK11 have it in jli sub-dir // add backup - older version such as JDK11 have it in jli sub-dir
.addJvmLibName(_T("Contents/Home/lib/jli/libjli.dylib")) .addJvmLibName(_T("Contents/Home/lib/jli/libjli.dylib"))
.setAppDir(FileUtils::mkpath() << appImageRoot << _T("Contents/app")) .setAppDir(appDirPath)
.setLibEnvVariableName(_T("DYLD_LIBRARY_PATH")) .setLibEnvVariableName(_T("DYLD_LIBRARY_PATH"))
.setDefaultRuntimePath(FileUtils::mkpath() << appImageRoot .setDefaultRuntimePath(FileUtils::mkpath() << appImageRoot
<< _T("Contents/runtime")) << _T("Contents/runtime"));
.createJvmLauncher();
if (!pkgFile.getPackageName().empty()) {
tstring homeDir;
JP_TRY;
homeDir = SysInfo::getEnvVariable("HOME");
JP_CATCH_ALL;
if (!homeDir.empty()) {
appLauncher.addCfgFileLookupDir(FileUtils::mkpath()
<< homeDir << "Library/Application Support"
<< pkgFile.getPackageName());
}
}
jvmLauncher = appLauncher.createJvmLauncher();
// Kick start JVM launching. The function wouldn't return! // Kick start JVM launching. The function wouldn't return!
launchJvm(); launchJvm();

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* 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.Objects;
import java.util.Optional;
public final class PackageFile {
/**
* Returns path to package file.
* @param appImageDir - path to application image
*/
public static Path getPathInAppImage(Path appImageDir) {
return ApplicationLayout.platformAppImage()
.resolveAt(appImageDir)
.appDirectory()
.resolve(FILENAME);
}
PackageFile(String packageName) {
Objects.requireNonNull(packageName);
this.packageName = packageName;
}
void save(ApplicationLayout appLayout) throws IOException {
Path dst = Optional.ofNullable(appLayout.appDirectory()).map(appDir -> {
return appDir.resolve(FILENAME);
}).orElse(null);
if (dst != null) {
Files.createDirectories(dst.getParent());
Files.writeString(dst, packageName);
}
}
private final String packageName;
private final static String FILENAME = ".package";
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2022, 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
@ -110,9 +110,7 @@ bool AppLauncher::libEnvVariableContainsAppDir() const {
} }
Jvm* AppLauncher::createJvmLauncher() const { Jvm* AppLauncher::createJvmLauncher() const {
const tstring cfgFilePath = FileUtils::mkpath() const tstring cfgFilePath = getCfgFilePath();
<< appDirPath << FileUtils::stripExeSuffix(
FileUtils::basename(launcherPath)) + _T(".cfg");
LOG_TRACE(tstrings::any() << "Launcher config file path: \"" LOG_TRACE(tstrings::any() << "Launcher config file path: \""
<< cfgFilePath << "\""); << cfgFilePath << "\"");
@ -160,3 +158,20 @@ Jvm* AppLauncher::createJvmLauncher() const {
void AppLauncher::launch() const { void AppLauncher::launch() const {
std::unique_ptr<Jvm>(createJvmLauncher())->launch(); std::unique_ptr<Jvm>(createJvmLauncher())->launch();
} }
tstring AppLauncher::getCfgFilePath() const {
tstring_array::const_iterator it = cfgFileLookupDirs.begin();
tstring_array::const_iterator end = cfgFileLookupDirs.end();
const tstring cfgFileName = FileUtils::stripExeSuffix(
FileUtils::basename(launcherPath)) + _T(".cfg");
for (; it != end; ++it) {
const tstring cfgFilePath = FileUtils::mkpath() << *it << cfgFileName;
LOG_TRACE(tstrings::any() << "Check [" << cfgFilePath << "] file exit");
if (FileUtils::isFileExists(cfgFilePath)) {
return cfgFilePath;
}
}
return FileUtils::mkpath() << appDirPath << cfgFileName;
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2022, 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
@ -45,6 +45,11 @@ public:
return *this; return *this;
} }
AppLauncher& addCfgFileLookupDir(const tstring& v) {
cfgFileLookupDirs.push_back(v);
return *this;
}
AppLauncher& setAppDir(const tstring& v) { AppLauncher& setAppDir(const tstring& v) {
appDirPath = v; appDirPath = v;
return *this; return *this;
@ -71,6 +76,9 @@ public:
void launch() const; void launch() const;
private:
tstring getCfgFilePath() const;
private: private:
tstring_array args; tstring_array args;
tstring launcherPath; tstring launcherPath;
@ -79,6 +87,7 @@ private:
tstring libEnvVarName; tstring libEnvVarName;
tstring imageRoot; tstring imageRoot;
tstring_array jvmLibNames; tstring_array jvmLibNames;
tstring_array cfgFileLookupDirs;
bool initJvmFromCmdlineOnly; bool initJvmFromCmdlineOnly;
}; };

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* 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.
*/
#include "kludge_c++11.h"
#include <fstream>
#include "PackageFile.h"
#include "Log.h"
#include "FileUtils.h"
#include "ErrorHandling.h"
PackageFile::PackageFile(const tstring& v): packageName(v) {
}
PackageFile PackageFile::loadFromAppDir(const tstring& appDirPath) {
tstring packageName;
const tstring packageFilePath =
FileUtils::mkpath() << appDirPath << _T(".package");
if (FileUtils::isFileExists(packageFilePath)) {
LOG_TRACE(tstrings::any() << "Read \"" << packageFilePath
<< "\" package file");
std::ifstream input(packageFilePath);
if (!input.good()) {
JP_THROW(tstrings::any() << "Error opening \"" << packageFilePath
<< "\" file: " << lastCRTError());
}
std::string utf8line;
if (std::getline(input, utf8line)) {
LOG_TRACE(tstrings::any()
<< "Package name is [" << utf8line << "]");
packageName = tstrings::any(utf8line).tstr();
}
}
return PackageFile(packageName);
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* 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.
*/
#ifndef PackageFile_h
#define PackageFile_h
#include "tstrings.h"
class PackageFile {
public:
static PackageFile loadFromAppDir(const tstring& appDirPath);
tstring getPackageName() const {
return packageName;
}
private:
PackageFile(const tstring& packageName);
private:
tstring packageName;
};
#endif // PackageFile_h

View File

@ -382,10 +382,12 @@ public class WinMsiBundler extends AbstractBundler {
.runtimeDirectory() .runtimeDirectory()
.resolve(Path.of("bin", "java.exe")); .resolve(Path.of("bin", "java.exe"));
} else { } else {
installerIcon = ApplicationLayout.windowsAppImage() var appLayout = ApplicationLayout.windowsAppImage().resolveAt(appDir);
.resolveAt(appDir)
.launchersDirectory() installerIcon = appLayout.launchersDirectory()
.resolve(appName + ".exe"); .resolve(appName + ".exe");
new PackageFile(appName).save(appLayout);
} }
installerIcon = installerIcon.toAbsolutePath(); installerIcon = installerIcon.toAbsolutePath();

View File

@ -35,6 +35,7 @@
#include "WinApp.h" #include "WinApp.h"
#include "Toolbox.h" #include "Toolbox.h"
#include "FileUtils.h" #include "FileUtils.h"
#include "PackageFile.h"
#include "UniqueHandle.h" #include "UniqueHandle.h"
#include "ErrorHandling.h" #include "ErrorHandling.h"
#include "WinSysInfo.h" #include "WinSysInfo.h"
@ -133,6 +134,22 @@ tstring getJvmLibPath(const Jvm& jvm) {
} }
void addCfgFileLookupDirForEnvVariable(
const PackageFile& pkgFile, AppLauncher& appLauncher,
const tstring& envVarName) {
tstring path;
JP_TRY;
path = SysInfo::getEnvVariable(envVarName);
JP_CATCH_ALL;
if (!path.empty()) {
appLauncher.addCfgFileLookupDir(FileUtils::mkpath() << path
<< pkgFile.getPackageName());
}
}
void launchApp() { void launchApp() {
// [RT-31061] otherwise UI can be left in back of other windows. // [RT-31061] otherwise UI can be left in back of other windows.
::AllowSetForegroundWindow(ASFW_ANY); ::AllowSetForegroundWindow(ASFW_ANY);
@ -141,13 +158,20 @@ void launchApp() {
const tstring appImageRoot = FileUtils::dirname(launcherPath); const tstring appImageRoot = FileUtils::dirname(launcherPath);
const tstring appDirPath = FileUtils::mkpath() << appImageRoot << _T("app"); const tstring appDirPath = FileUtils::mkpath() << appImageRoot << _T("app");
const AppLauncher appLauncher = AppLauncher().setImageRoot(appImageRoot) const PackageFile pkgFile = PackageFile::loadFromAppDir(appDirPath);
AppLauncher appLauncher = AppLauncher().setImageRoot(appImageRoot)
.addJvmLibName(_T("bin\\jli.dll")) .addJvmLibName(_T("bin\\jli.dll"))
.setAppDir(appDirPath) .setAppDir(appDirPath)
.setLibEnvVariableName(_T("PATH")) .setLibEnvVariableName(_T("PATH"))
.setDefaultRuntimePath(FileUtils::mkpath() << appImageRoot .setDefaultRuntimePath(FileUtils::mkpath() << appImageRoot
<< _T("runtime")); << _T("runtime"));
if (!pkgFile.getPackageName().empty()) {
addCfgFileLookupDirForEnvVariable(pkgFile, appLauncher, _T("LOCALAPPDATA"));
addCfgFileLookupDirForEnvVariable(pkgFile, appLauncher, _T("APPDATA"));
}
const bool restart = !appLauncher.libEnvVariableContainsAppDir(); const bool restart = !appLauncher.libEnvVariableContainsAppDir();
std::unique_ptr<Jvm> jvm(appLauncher.createJvmLauncher()); std::unique_ptr<Jvm> jvm(appLauncher.createJvmLauncher());

View File

@ -47,6 +47,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import jdk.jpackage.internal.AppImageFile; import jdk.jpackage.internal.AppImageFile;
import jdk.jpackage.internal.ApplicationLayout; import jdk.jpackage.internal.ApplicationLayout;
import jdk.jpackage.internal.PackageFile;
import static jdk.jpackage.test.AdditionalLauncher.forEachAdditionalLauncher; import static jdk.jpackage.test.AdditionalLauncher.forEachAdditionalLauncher;
import jdk.jpackage.test.Functional.ThrowingConsumer; import jdk.jpackage.test.Functional.ThrowingConsumer;
import jdk.jpackage.test.Functional.ThrowingFunction; import jdk.jpackage.test.Functional.ThrowingFunction;
@ -761,38 +762,8 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
} }
JPackageCommand assertAppLayout() { JPackageCommand assertAppLayout() {
if (isPackageUnpacked() || isImagePackageType()) { assertAppImageFile();
final Path rootDir = isPackageUnpacked() ? pathToUnpackedPackageFile( assertPackageFile();
appInstallationDirectory()) : outputBundle();
final Path appImageFileName = AppImageFile.getPathInAppImage(
Path.of("")).getFileName();
try (Stream<Path> walk = ThrowingSupplier.toSupplier(
() -> Files.walk(rootDir)).get()) {
List<String> appImageFiles = walk
.filter(path -> path.getFileName().equals(appImageFileName))
.map(Path::toString)
.collect(Collectors.toList());
if (isImagePackageType() || (TKit.isOSX() && !isRuntime())) {
List<String> expected = List.of(
AppImageFile.getPathInAppImage(rootDir).toString());
TKit.assertStringListEquals(expected, appImageFiles,
String.format(
"Check there is only one file with [%s] name in the package",
appImageFileName));
} else {
TKit.assertStringListEquals(List.of(), appImageFiles,
String.format(
"Check there are no files with [%s] name in the package",
appImageFileName));
}
}
} else if (TKit.isOSX() && !isRuntime()) {
TKit.assertFileExists(AppImageFile.getPathInAppImage(
appInstallationDirectory()));
} else {
TKit.assertPathExists(AppImageFile.getPathInAppImage(
appInstallationDirectory()), false);
}
TKit.assertDirectoryExists(appRuntimeDirectory()); TKit.assertDirectoryExists(appRuntimeDirectory());
@ -809,6 +780,54 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
return this; return this;
} }
private void assertAppImageFile() {
final Path lookupPath = AppImageFile.getPathInAppImage(Path.of(""));
if (isRuntime() || !isImagePackageType()) {
assertFileInAppImage(lookupPath, null);
} else {
assertFileInAppImage(lookupPath, lookupPath);
}
}
private void assertPackageFile() {
final Path lookupPath = PackageFile.getPathInAppImage(Path.of(""));
if (isRuntime() || isImagePackageType() || TKit.isLinux()) {
assertFileInAppImage(lookupPath, null);
} else {
assertFileInAppImage(lookupPath, lookupPath);
}
}
private void assertFileInAppImage(Path filename, Path expectedPath) {
if (filename.getNameCount() > 1) {
assertFileInAppImage(filename.getFileName(), expectedPath);
return;
}
final Path rootDir = isImagePackageType() ? outputBundle() : pathToUnpackedPackageFile(
appInstallationDirectory());
try ( Stream<Path> walk = ThrowingSupplier.toSupplier(() -> Files.walk(
rootDir)).get()) {
List<String> files = walk.filter(path -> path.getFileName().equals(
filename)).map(Path::toString).toList();
if (expectedPath == null) {
TKit.assertStringListEquals(List.of(), files, String.format(
"Check there are no files with [%s] name in the package",
filename));
} else {
List<String> expected = List.of(
rootDir.resolve(expectedPath).toString());
TKit.assertStringListEquals(expected, files, String.format(
"Check there is only one file with [%s] name in the package",
filename));
}
}
}
JPackageCommand setUnpackedPackageLocation(Path path) { JPackageCommand setUnpackedPackageLocation(Path path) {
verifyIsOfType(PackageType.NATIVE); verifyIsOfType(PackageType.NATIVE);
if (path != null) { if (path != null) {

View File

@ -22,6 +22,7 @@
*/ */
package jdk.jpackage.test; package jdk.jpackage.test;
import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@ -46,6 +47,7 @@ import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -769,6 +771,34 @@ final public class TKit {
} }
} }
/**
* Creates a directory by creating all nonexistent parent directories first
* just like java.nio.file.Files#createDirectories() and returns
* java.io.Closeable that will delete all created nonexistent parent
* directories.
*/
public static Closeable createDirectories(Path dir) throws IOException {
Objects.requireNonNull(dir);
Collection<Path> dirsToDelete = new ArrayList<>();
Path curDir = dir;
while (!Files.exists(curDir)) {
dirsToDelete.add(curDir);
curDir = curDir.getParent();
}
Files.createDirectories(dir);
return new Closeable() {
@Override
public void close() throws IOException {
for (var dirToDelete : dirsToDelete) {
Files.deleteIfExists(dirToDelete);
}
}
};
}
public final static class TextStreamVerifier { public final static class TextStreamVerifier {
TextStreamVerifier(String value) { TextStreamVerifier(String value) {
this.value = value; this.value = value;

View File

@ -254,6 +254,14 @@ if [ -z "$run_all_tests" ]; then
jtreg_args+=(-Djpackage.test.SQETest=yes) jtreg_args+=(-Djpackage.test.SQETest=yes)
fi fi
if [ -n "$APPDATA" ]; then
# Looks like this is Windows.
# Explicitly add LOCALAPPDATA and APPDATA environment variables to the list
# of environment variables jtreg will pass to tests as by default it will not.
# This is needed for PerUserCfgTest test.
jtreg_args+=("-e:LOCALAPPDATA,APPDATA")
fi
jtreg_args+=("$test_actions") jtreg_args+=("$test_actions")
# Drop arguments separator # Drop arguments separator

View File

@ -0,0 +1,185 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* 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.List;
import java.util.Optional;
import jdk.jpackage.test.AdditionalLauncher;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Functional.ThrowingConsumer;
import jdk.jpackage.test.HelloApp;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.LinuxHelper;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.TKit;
/**
* Test per-user configuration of app launchers created by jpackage.
*/
/*
* @test
* @summary pre-user configuration of app launchers
* @library ../helpers
* @key jpackagePlatformPackage
* @requires jpackage.test.SQETest == null
* @build jdk.jpackage.test.*
* @compile PerUserCfgTest.java
* @modules jdk.jpackage/jdk.jpackage.internal
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
* --jpt-run=PerUserCfgTest
*/
public class PerUserCfgTest {
@Test
public static void test() throws IOException {
// Create a number of .cfg files with different startup args
JPackageCommand cfgCmd = JPackageCommand.helloAppImage().setFakeRuntime()
.setArgumentValue("--dest", TKit.createTempDirectory("cfg-files").toString());
addLauncher(cfgCmd, "a");
addLauncher(cfgCmd, "b");
cfgCmd.execute();
new PackageTest().configureHelloApp().addInstallVerifier(cmd -> {
if (cmd.isPackageUnpacked("Not running per-user configuration tests")) {
return;
}
Path launcherPath = cmd.appLauncherPath();
if (!cmd.canRunLauncher(String.format(
"Not running %s launcher and per-user configuration tests",
launcherPath))) {
return;
}
final PackageType type = cmd.packageType();
if (PackageType.MAC.contains(type)) {
withConfigFile(cmd, cfgCmd.appLauncherCfgPath("a"),
getUserHomeDir().resolve("Library/Application Support").resolve(
cmd.name()), theCmd -> {
runMainLauncher(cmd, "a");
});
} else if (PackageType.LINUX.contains(type)) {
final String pkgName = LinuxHelper.getPackageName(cmd);
final Path homeDir = getUserHomeDir();
withConfigFile(cmd, cfgCmd.appLauncherCfgPath("a"),
homeDir.resolve(".local").resolve(pkgName), theCmd -> {
runMainLauncher(cmd, "a");
});
withConfigFile(cmd, cfgCmd.appLauncherCfgPath("b"),
homeDir.resolve("." + pkgName), theCmd -> {
runMainLauncher(cmd, "b");
});
withConfigFile(cmd, cfgCmd.appLauncherCfgPath("b"),
homeDir.resolve("." + pkgName), theCmd -> {
runMainLauncher(cmd, "b");
withConfigFile(cmd, cfgCmd.appLauncherCfgPath("a"),
homeDir.resolve(".local").resolve(pkgName),
theCmd2 -> {
runMainLauncher(cmd, "a");
});
});
} else if (PackageType.WINDOWS.contains(type)) {
final Path appData = getDirFromEnvVariable("APPDATA");
final Path localAppData = getDirFromEnvVariable("LOCALAPPDATA");
if (appData == null || localAppData == null) {
TKit.trace(String.format(
"Not running per-user configuration tests because some of the environment varibles are not set. "
+ "Run jtreg with -e:APPDATA,LOCALAPPDATA option to fix the problem"));
} else {
withConfigFile(cmd, cfgCmd.appLauncherCfgPath("a"),
appData.resolve(cmd.name()), theCmd -> {
runMainLauncher(cmd, "a");
});
withConfigFile(cmd, cfgCmd.appLauncherCfgPath("b"),
localAppData.resolve(cmd.name()), theCmd -> {
runMainLauncher(cmd, "b");
});
withConfigFile(cmd, cfgCmd.appLauncherCfgPath("b"),
appData.resolve(cmd.name()), theCmd -> {
runMainLauncher(cmd, "b");
withConfigFile(cmd, cfgCmd.appLauncherCfgPath("a"),
localAppData.resolve(cmd.name()),
theCmd2 -> {
runMainLauncher(cmd, "a");
});
});
}
}
runMainLauncher(cmd);
}).run();
}
private static void addLauncher(JPackageCommand cmd, String name) {
new AdditionalLauncher(name) {
@Override
protected void verify(JPackageCommand cmd) {}
}.setDefaultArguments(name).applyTo(cmd);
}
private static Path getUserHomeDir() {
return getDirFromEnvVariable("HOME");
}
private static Path getDirFromEnvVariable(String envVariableName) {
return Optional.ofNullable(System.getenv(envVariableName)).map(Path::of).orElse(
null);
}
private static void withConfigFile(JPackageCommand cmd, Path srcCfgFile,
Path outputCfgFileDir, ThrowingConsumer<JPackageCommand> action) throws
Throwable {
Path targetCfgFile = outputCfgFileDir.resolve(cmd.appLauncherCfgPath(
null).getFileName());
TKit.assertPathExists(targetCfgFile, false);
try (var dirCleaner = TKit.createDirectories(targetCfgFile.getParent())) {
Files.copy(srcCfgFile, targetCfgFile);
try {
TKit.traceFileContents(targetCfgFile, "cfg file");
action.accept(cmd);
} finally {
Files.deleteIfExists(targetCfgFile);
}
}
}
private static void runMainLauncher(JPackageCommand cmd,
String... expectedArgs) {
HelloApp.assertApp(cmd.appLauncherPath()).addDefaultArguments(List.of(
expectedArgs)).executeAndVerifyOutput();
}
}