--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxPackageBundler.java Fri Nov 08 14:53:03 2019 -0500
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2019, 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.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static jdk.incubator.jpackage.internal.DesktopIntegration.*;
+import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
+import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+
+abstract class LinuxPackageBundler extends AbstractBundler {
+
+ LinuxPackageBundler(BundlerParamInfo<String> packageName) {
+ this.packageName = packageName;
+ }
+
+ @Override
+ final public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+
+ // run basic validation to ensure requirements are met
+ // we are not interested in return code, only possible exception
+ APP_BUNDLER.fetchFrom(params).validate(params);
+
+ validateInstallDir(LINUX_INSTALL_DIR.fetchFrom(params));
+
+ validateFileAssociations(FILE_ASSOCIATIONS.fetchFrom(params));
+
+ // If package name has some restrictions, the string converter will
+ // throw an exception if invalid
+ packageName.getStringConverter().apply(packageName.fetchFrom(params),
+ params);
+
+ for (var validator: getToolValidators(params)) {
+ ConfigException ex = validator.validate();
+ if (ex != null) {
+ throw ex;
+ }
+ }
+
+ withFindNeededPackages = LibProvidersLookup.supported();
+ if (!withFindNeededPackages) {
+ final String advice;
+ if ("deb".equals(getID())) {
+ advice = "message.deb-ldd-not-available.advice";
+ } else {
+ advice = "message.rpm-ldd-not-available.advice";
+ }
+ // Let user know package dependencies will not be generated.
+ Log.error(String.format("%s\n%s", I18N.getString(
+ "message.ldd-not-available"), I18N.getString(advice)));
+ }
+
+ // Packaging specific validation
+ doValidate(params);
+
+ return true;
+ }
+
+ @Override
+ final public String getBundleType() {
+ return "INSTALLER";
+ }
+
+ @Override
+ final public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ IOUtils.writableOutputDir(outputParentDir.toPath());
+
+ PlatformPackage thePackage = createMetaPackage(params);
+
+ Function<File, ApplicationLayout> initAppImageLayout = imageRoot -> {
+ ApplicationLayout layout = appImageLayout(params);
+ layout.pathGroup().setPath(new Object(),
+ AppImageFile.getPathInAppImage(Path.of("")));
+ return layout.resolveAt(imageRoot.toPath());
+ };
+
+ try {
+ File appImage = StandardBundlerParam.getPredefinedAppImage(params);
+
+ // we either have an application image or need to build one
+ if (appImage != null) {
+ initAppImageLayout.apply(appImage).copy(
+ thePackage.sourceApplicationLayout());
+ } else {
+ appImage = APP_BUNDLER.fetchFrom(params).doBundle(params,
+ thePackage.sourceRoot().toFile(), true);
+ ApplicationLayout srcAppLayout = initAppImageLayout.apply(
+ appImage);
+ if (appImage.equals(PREDEFINED_RUNTIME_IMAGE.fetchFrom(params))) {
+ // Application image points to run-time image.
+ // Copy it.
+ srcAppLayout.copy(thePackage.sourceApplicationLayout());
+ } else {
+ // Application image is a newly created directory tree.
+ // Move it.
+ srcAppLayout.move(thePackage.sourceApplicationLayout());
+ if (appImage.exists()) {
+ // Empty app image directory might remain after all application
+ // directories have been moved.
+ appImage.delete();
+ }
+ }
+ }
+
+ if (!StandardBundlerParam.isRuntimeInstaller(params)) {
+ desktopIntegration = new DesktopIntegration(thePackage, params);
+ } else {
+ desktopIntegration = null;
+ }
+
+ Map<String, String> data = createDefaultReplacementData(params);
+ if (desktopIntegration != null) {
+ data.putAll(desktopIntegration.create());
+ } else {
+ Stream.of(DESKTOP_COMMANDS_INSTALL, DESKTOP_COMMANDS_UNINSTALL,
+ UTILITY_SCRIPTS).forEach(v -> data.put(v, ""));
+ }
+
+ data.putAll(createReplacementData(params));
+
+ File packageBundle = buildPackageBundle(Collections.unmodifiableMap(
+ data), params, outputParentDir);
+
+ verifyOutputBundle(params, packageBundle.toPath()).stream()
+ .filter(Objects::nonNull)
+ .forEachOrdered(ex -> {
+ Log.verbose(ex.getLocalizedMessage());
+ Log.verbose(ex.getAdvice());
+ });
+
+ return packageBundle;
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ private List<String> getListOfNeededPackages(
+ Map<String, ? super Object> params) throws IOException {
+
+ PlatformPackage thePackage = createMetaPackage(params);
+
+ final List<String> xdgUtilsPackage;
+ if (desktopIntegration != null) {
+ xdgUtilsPackage = desktopIntegration.requiredPackages();
+ } else {
+ xdgUtilsPackage = Collections.emptyList();
+ }
+
+ final List<String> neededLibPackages;
+ if (withFindNeededPackages) {
+ LibProvidersLookup lookup = new LibProvidersLookup();
+ initLibProvidersLookup(params, lookup);
+
+ neededLibPackages = lookup.execute(thePackage.sourceRoot());
+ } else {
+ neededLibPackages = Collections.emptyList();
+ }
+
+ // Merge all package lists together.
+ // Filter out empty names, sort and remove duplicates.
+ List<String> result = Stream.of(xdgUtilsPackage, neededLibPackages).flatMap(
+ List::stream).filter(Predicate.not(String::isEmpty)).sorted().distinct().collect(
+ Collectors.toList());
+
+ Log.verbose(String.format("Required packages: %s", result));
+
+ return result;
+ }
+
+ private Map<String, String> createDefaultReplacementData(
+ Map<String, ? super Object> params) throws IOException {
+ Map<String, String> data = new HashMap<>();
+
+ data.put("APPLICATION_PACKAGE", createMetaPackage(params).name());
+ data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params));
+ data.put("APPLICATION_VERSION", VERSION.fetchFrom(params));
+ data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
+ data.put("APPLICATION_RELEASE", RELEASE.fetchFrom(params));
+
+ String defaultDeps = String.join(", ", getListOfNeededPackages(params));
+ String customDeps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params).strip();
+ if (!customDeps.isEmpty() && !defaultDeps.isEmpty()) {
+ customDeps = ", " + customDeps;
+ }
+ data.put("PACKAGE_DEFAULT_DEPENDENCIES", defaultDeps);
+ data.put("PACKAGE_CUSTOM_DEPENDENCIES", customDeps);
+
+ return data;
+ }
+
+ abstract protected List<ConfigException> verifyOutputBundle(
+ Map<String, ? super Object> params, Path packageBundle);
+
+ abstract protected void initLibProvidersLookup(
+ Map<String, ? super Object> params,
+ LibProvidersLookup libProvidersLookup);
+
+ abstract protected List<ToolValidator> getToolValidators(
+ Map<String, ? super Object> params);
+
+ abstract protected void doValidate(Map<String, ? super Object> params)
+ throws ConfigException;
+
+ abstract protected Map<String, String> createReplacementData(
+ Map<String, ? super Object> params) throws IOException;
+
+ abstract protected File buildPackageBundle(
+ Map<String, String> replacementData,
+ Map<String, ? super Object> params, File outputParentDir) throws
+ PackagerException, IOException;
+
+ final protected PlatformPackage createMetaPackage(
+ Map<String, ? super Object> params) {
+ return new PlatformPackage() {
+ @Override
+ public String name() {
+ return packageName.fetchFrom(params);
+ }
+
+ @Override
+ public Path sourceRoot() {
+ return IMAGES_ROOT.fetchFrom(params).toPath().toAbsolutePath();
+ }
+
+ @Override
+ public ApplicationLayout sourceApplicationLayout() {
+ return appImageLayout(params).resolveAt(
+ applicationInstallDir(sourceRoot()));
+ }
+
+ @Override
+ public ApplicationLayout installedApplicationLayout() {
+ return appImageLayout(params).resolveAt(
+ applicationInstallDir(Path.of("/")));
+ }
+
+ private Path applicationInstallDir(Path root) {
+ Path installDir = Path.of(LINUX_INSTALL_DIR.fetchFrom(params),
+ name());
+ if (installDir.isAbsolute()) {
+ installDir = Path.of("." + installDir.toString()).normalize();
+ }
+ return root.resolve(installDir);
+ }
+ };
+ }
+
+ private ApplicationLayout appImageLayout(
+ Map<String, ? super Object> params) {
+ if (StandardBundlerParam.isRuntimeInstaller(params)) {
+ return ApplicationLayout.javaRuntime();
+ }
+ return ApplicationLayout.linuxAppImage();
+ }
+
+ private static void validateInstallDir(String installDir) throws
+ ConfigException {
+ if (installDir.startsWith("/usr/") || installDir.equals("/usr")) {
+ throw new ConfigException(MessageFormat.format(I18N.getString(
+ "error.unsupported-install-dir"), installDir), null);
+ }
+
+ if (installDir.isEmpty()) {
+ throw new ConfigException(MessageFormat.format(I18N.getString(
+ "error.invalid-install-dir"), "/"), null);
+ }
+
+ boolean valid = false;
+ try {
+ final Path installDirPath = Path.of(installDir);
+ valid = installDirPath.isAbsolute();
+ if (valid && !installDirPath.normalize().toString().equals(
+ installDirPath.toString())) {
+ // Don't allow '/opt/foo/..' or /opt/.
+ valid = false;
+ }
+ } catch (InvalidPathException ex) {
+ }
+
+ if (!valid) {
+ throw new ConfigException(MessageFormat.format(I18N.getString(
+ "error.invalid-install-dir"), installDir), null);
+ }
+ }
+
+ private static void validateFileAssociations(
+ List<Map<String, ? super Object>> associations) throws
+ ConfigException {
+ // only one mime type per association, at least one file extention
+ int assocIdx = 0;
+ for (var assoc : associations) {
+ ++assocIdx;
+ List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc);
+ if (mimes == null || mimes.isEmpty()) {
+ String msgKey = "error.no-content-types-for-file-association";
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(msgKey), assocIdx),
+ I18N.getString(msgKey + ".advise"));
+
+ }
+
+ if (mimes.size() > 1) {
+ String msgKey = "error.too-many-content-types-for-file-association";
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(msgKey), assocIdx),
+ I18N.getString(msgKey + ".advise"));
+ }
+ }
+ }
+
+ private final BundlerParamInfo<String> packageName;
+ private boolean withFindNeededPackages;
+ private DesktopIntegration desktopIntegration;
+
+ private static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER =
+ new StandardBundlerParam<>(
+ "linux.app.bundler",
+ LinuxAppBundler.class,
+ (params) -> new LinuxAppBundler(),
+ null
+ );
+
+}