Skip to content

Commit

Permalink
8236128: Allow jpackage create installers for services
Browse files Browse the repository at this point in the history
Reviewed-by: almatvee
  • Loading branch information
Alexey Semenyuk committed Apr 27, 2022
1 parent ef27081 commit b675c59
Show file tree
Hide file tree
Showing 64 changed files with 2,995 additions and 268 deletions.
2 changes: 1 addition & 1 deletion make/modules/jdk.jpackage/Java.gmk
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@

COPY += .gif .png .txt .spec .script .prerm .preinst \
.postrm .postinst .list .sh .desktop .copyright .control .plist .template \
.icns .scpt .wxs .wxl .wxi .ico .bmp .tiff
.icns .scpt .wxs .wxl .wxi .ico .bmp .tiff .service

CLEAN += .properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -25,11 +25,8 @@
package jdk.jpackage.internal;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
Expand All @@ -39,8 +36,8 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.imageio.ImageIO;
Expand All @@ -56,16 +53,19 @@
import static jdk.jpackage.internal.StandardBundlerParam.FILE_ASSOCIATIONS;
import static jdk.jpackage.internal.StandardBundlerParam.ICON;
import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE;
import static jdk.jpackage.internal.StandardBundlerParam.SHORTCUT_HINT;;
import static jdk.jpackage.internal.StandardBundlerParam.SHORTCUT_HINT;

/**
* Helper to create files for desktop integration.
*/
final class DesktopIntegration {
final class DesktopIntegration extends ShellCustomAction {

static final String DESKTOP_COMMANDS_INSTALL = "DESKTOP_COMMANDS_INSTALL";
static final String DESKTOP_COMMANDS_UNINSTALL = "DESKTOP_COMMANDS_UNINSTALL";
static final String UTILITY_SCRIPTS = "UTILITY_SCRIPTS";
private static final String COMMANDS_INSTALL = "DESKTOP_COMMANDS_INSTALL";
private static final String COMMANDS_UNINSTALL = "DESKTOP_COMMANDS_UNINSTALL";
private static final String SCRIPTS = "DESKTOP_SCRIPTS";

private static final List<String> REPLACEMENT_STRING_IDS = List.of(
COMMANDS_INSTALL, COMMANDS_UNINSTALL, SCRIPTS);

private DesktopIntegration(PlatformPackage thePackage,
Map<String, ? super Object> params,
Expand Down Expand Up @@ -171,21 +171,28 @@ private DesktopIntegration(PlatformPackage thePackage,
}
}

static DesktopIntegration create(PlatformPackage thePackage,
static ShellCustomAction create(PlatformPackage thePackage,
Map<String, ? super Object> params) throws IOException {
if (StandardBundlerParam.isRuntimeInstaller(params)) {
return null;
return ShellCustomAction.nop(REPLACEMENT_STRING_IDS);
}
return new DesktopIntegration(thePackage, params, null);
}

@Override
List<String> requiredPackages() {
return Stream.of(List.of(this), nestedIntegrations).flatMap(
List::stream).map(DesktopIntegration::requiredPackagesSelf).flatMap(
List::stream).distinct().toList();
}

Map<String, String> create() throws IOException {
@Override
protected List<String> replacementStringIds() {
return REPLACEMENT_STRING_IDS;
}

@Override
protected Map<String, String> createImpl() throws IOException {
associations.forEach(assoc -> assoc.data.verify());

if (iconFile != null) {
Expand Down Expand Up @@ -230,36 +237,26 @@ Map<String, String> create() throws IOException {
// of the additional launchers and append them to the corresponding
// commands of the main launcher.
List<String> installShellCmds = new ArrayList<>(Arrays.asList(
data.get(DESKTOP_COMMANDS_INSTALL)));
data.get(COMMANDS_INSTALL)));
List<String> uninstallShellCmds = new ArrayList<>(Arrays.asList(
data.get(DESKTOP_COMMANDS_UNINSTALL)));
data.get(COMMANDS_UNINSTALL)));
for (var integration: nestedIntegrations) {
if (!integration.associations.isEmpty()) {
needCleanupScripts = true;
}

Map<String, String> launcherData = integration.create();

installShellCmds.add(launcherData.get(DESKTOP_COMMANDS_INSTALL));
uninstallShellCmds.add(launcherData.get(
DESKTOP_COMMANDS_UNINSTALL));
installShellCmds.add(launcherData.get(COMMANDS_INSTALL));
uninstallShellCmds.add(launcherData.get(COMMANDS_UNINSTALL));
}

data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(
installShellCmds));
data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(
uninstallShellCmds));
data.put(COMMANDS_INSTALL, stringifyShellCommands(installShellCmds));
data.put(COMMANDS_UNINSTALL, stringifyShellCommands(uninstallShellCmds));

if (needCleanupScripts) {
// Pull in utils.sh scrips library.
try (InputStream is = OverridableResource.readDefault("utils.sh");
InputStreamReader isr = new InputStreamReader(is);
BufferedReader reader = new BufferedReader(isr)) {
data.put(UTILITY_SCRIPTS, reader.lines().collect(
Collectors.joining(System.lineSeparator())));
}
} else {
data.put(UTILITY_SCRIPTS, "");
// Pull in desktop_utils.sh scrips library.
data.put(SCRIPTS, stringifyTextFile("desktop_utils.sh"));
}

return data;
Expand All @@ -277,17 +274,12 @@ private Map<String, String> createDataForDesktopFile(
Map<String, String> data = new HashMap<>();
data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params));
data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
data.put("APPLICATION_ICON",
iconFile != null ? iconFile.installPath().toString() : null);
data.put("APPLICATION_ICON", Optional.ofNullable(iconFile).map(
f -> f.installPath().toString()).orElse(null));
data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params));

String appLauncher = thePackage.installedApplicationLayout().launchersDirectory().resolve(
LinuxAppImageBuilder.getLauncherName(params)).toString();
if (Pattern.compile("\\s").matcher(appLauncher).find()) {
// Path contains whitespace(s). Enclose in double quotes.
appLauncher = "\"" + appLauncher + "\"";
}
data.put("APPLICATION_LAUNCHER", appLauncher);
data.put("APPLICATION_LAUNCHER", Enquoter.forPropertyValues().applyTo(
thePackage.installedApplicationLayout().launchersDirectory().resolve(
LinuxAppImageBuilder.getLauncherName(params)).toString()));

return data;
}
Expand Down Expand Up @@ -356,13 +348,13 @@ void applyTo(Map<String, String> data) {
cmds.add(registerDesktopFileCmd);
cmds.add(registerFileAssociationsCmd);
cmds.addAll(registerIconCmds);
data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(cmds));
data.put(COMMANDS_INSTALL, stringifyShellCommands(cmds));

cmds.clear();
cmds.add(unregisterDesktopFileCmd);
cmds.add(unregisterFileAssociationsCmd);
cmds.addAll(unregisterIconCmds);
data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(cmds));
data.put(COMMANDS_UNINSTALL, stringifyShellCommands(cmds));
}

private String registerDesktopFileCmd;
Expand All @@ -385,24 +377,25 @@ void applyTo(Map<String, String> data) {
private class DesktopFile {

DesktopFile(String fileName) {
installPath = thePackage
var installPath = thePackage
.installedApplicationLayout()
.destktopIntegrationDirectory().resolve(fileName);
srcPath = thePackage
var srcPath = thePackage
.sourceApplicationLayout()
.destktopIntegrationDirectory().resolve(fileName);
}

private final Path installPath;
private final Path srcPath;
impl = new InstallableFile(srcPath, installPath);
}

Path installPath() {
return installPath;
return impl.installPath();
}

Path srcPath() {
return srcPath;
return impl.srcPath();
}

private final InstallableFile impl;
}

private void appendFileAssociation(XMLStreamWriter xml,
Expand Down Expand Up @@ -526,15 +519,6 @@ private static int normalizeIconSize(int iconSize) {
return commonIconSize;
}

private static String stringifyShellCommands(String... commands) {
return stringifyShellCommands(Arrays.asList(commands));
}

private static String stringifyShellCommands(List<String> commands) {
return String.join(System.lineSeparator(), commands.stream().filter(
s -> s != null && !s.isEmpty()).toList());
}

private static class LinuxFileAssociation {
LinuxFileAssociation(FileAssociation fa) {
this.data = fa;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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.Path;
import java.util.List;
import java.util.Map;
import static jdk.jpackage.internal.OverridableResource.createResource;

/**
* Helper to install launchers as services using "systemd".
*/
public final class LinuxLaunchersAsServices extends UnixLaunchersAsServices {

private LinuxLaunchersAsServices(PlatformPackage thePackage,
Map<String, Object> params) throws IOException {
super(thePackage, REQUIRED_PACKAGES, params, li -> {
return new Launcher(thePackage, li.getName(), params);
});
}

static ShellCustomAction create(PlatformPackage thePackage,
Map<String, Object> params) throws IOException {
if (StandardBundlerParam.isRuntimeInstaller(params)) {
return ShellCustomAction.nop(REPLACEMENT_STRING_IDS);
}
return new LinuxLaunchersAsServices(thePackage, params);
}

public static Path getServiceUnitFileName(String packageName,
String launcherName) {
String baseName = launcherName.replaceAll("[\\s]", "_");
return Path.of(packageName + "-" + baseName + ".service");
}

private static class Launcher extends UnixLauncherAsService {

Launcher(PlatformPackage thePackage, String name,
Map<String, Object> mainParams) {
super(name, mainParams, createResource("unit-template.service",
mainParams).setCategory(I18N.getString(
"resource.systemd-unit-file")));

unitFilename = getServiceUnitFileName(thePackage.name(), getName());

getResource()
.setPublicName(unitFilename)
.addSubstitutionDataEntry("APPLICATION_LAUNCHER",
Enquoter.forPropertyValues().applyTo(
thePackage.installedApplicationLayout().launchersDirectory().resolve(
getName()).toString()));
}

@Override
Path descriptorFilePath(Path root) {
return root.resolve("lib/systemd/system").resolve(unitFilename);
}

private final Path unitFilename;
}

private final static List<String> REQUIRED_PACKAGES = List.of("systemd",
"coreutils" /* /usr/bin/wc */, "grep");
}
Loading

1 comment on commit b675c59

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.