/* * 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 packageName) { this.packageName = packageName; } @Override final public boolean validate(Map 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 params, File outputParentDir) throws PackagerException { IOUtils.writableOutputDir(outputParentDir.toPath()); PlatformPackage thePackage = createMetaPackage(params); Function 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 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 getListOfNeededPackages( Map params) throws IOException { PlatformPackage thePackage = createMetaPackage(params); final List xdgUtilsPackage; if (desktopIntegration != null) { xdgUtilsPackage = desktopIntegration.requiredPackages(); } else { xdgUtilsPackage = Collections.emptyList(); } final List 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 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 createDefaultReplacementData( Map params) throws IOException { Map 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 verifyOutputBundle( Map params, Path packageBundle); abstract protected void initLibProvidersLookup( Map params, LibProvidersLookup libProvidersLookup); abstract protected List getToolValidators( Map params); abstract protected void doValidate(Map params) throws ConfigException; abstract protected Map createReplacementData( Map params) throws IOException; abstract protected File buildPackageBundle( Map replacementData, Map params, File outputParentDir) throws PackagerException, IOException; final protected PlatformPackage createMetaPackage( Map 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 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> associations) throws ConfigException { // only one mime type per association, at least one file extention int assocIdx = 0; for (var assoc : associations) { ++assocIdx; List 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 packageName; private boolean withFindNeededPackages; private DesktopIntegration desktopIntegration; private static final BundlerParamInfo APP_BUNDLER = new StandardBundlerParam<>( "linux.app.bundler", LinuxAppBundler.class, (params) -> new LinuxAppBundler(), null ); }