blob: 2f3e6da1e6954040b6c3ad20ccc9a66317597612 [file]
/*
* Copyright (c) 2019, 2025, 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.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Stream;
import jdk.jpackage.internal.PackagingPipeline.PackageBuildEnv;
import jdk.jpackage.internal.PackagingPipeline.PackageTaskID;
import jdk.jpackage.internal.PackagingPipeline.PrimaryTaskID;
import jdk.jpackage.internal.model.AppImageLayout;
import jdk.jpackage.internal.model.ConfigException;
import jdk.jpackage.internal.model.LinuxDebPackage;
import jdk.jpackage.internal.model.LinuxPackage;
import jdk.jpackage.internal.model.Package;
import jdk.jpackage.internal.model.PackagerException;
abstract class LinuxPackageBundler extends AbstractBundler {
LinuxPackageBundler(BundlerParamInfo<? extends LinuxPackage> pkgParam) {
this.pkgParam = pkgParam;
customActions = List.of(new CustomActionInstance(
DesktopIntegration::create), new CustomActionInstance(
LinuxLaunchersAsServices::create));
}
@Override
public final boolean validate(Map<String, ? super Object> params)
throws ConfigException {
// Order is important!
LinuxPackage pkg = pkgParam.fetchFrom(params);
var env = BuildEnvFromParams.BUILD_ENV.fetchFrom(params);
for (var validator: getToolValidators()) {
ConfigException ex = validator.validate();
if (ex != null) {
throw ex;
}
}
if (!isDefault()) {
withFindNeededPackages = false;
Log.verbose(MessageFormat.format(I18N.getString(
"message.not-default-bundler-no-dependencies-lookup"),
getName()));
} else {
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(env, pkg);
return true;
}
@Override
public final String getBundleType() {
return "INSTALLER";
}
@Override
public final Path execute(Map<String, ? super Object> params,
Path outputParentDir) throws PackagerException {
IOUtils.writableOutputDir(outputParentDir);
// Order is important!
final LinuxPackage pkg = pkgParam.fetchFrom(params);
final var env = BuildEnvFromParams.BUILD_ENV.fetchFrom(params);
final var pipelineBuilder = LinuxPackagingPipeline.build()
.excludeDirFromCopying(outputParentDir)
.task(PackageTaskID.CREATE_PACKAGE_FILE)
.packageAction(this::buildPackage)
.add();
final var createConfigFilesTaskBuilder = pipelineBuilder
.task(PackageTaskID.CREATE_CONFIG_FILES)
.packageAction(this::buildConfigFiles);
if (pkg instanceof LinuxDebPackage) {
// Build deb config files after app image contents are ready because
// it calculates the size of the image and saves the value in one of the config files.
createConfigFilesTaskBuilder.addDependencies(PrimaryTaskID.BUILD_APPLICATION_IMAGE, PrimaryTaskID.COPY_APP_IMAGE);
}
createConfigFilesTaskBuilder.add();
pipelineBuilder.create().execute(env, pkg, outputParentDir);
return outputParentDir.resolve(pkg.packageFileNameWithSuffix()).toAbsolutePath();
}
private void buildConfigFiles(PackageBuildEnv<LinuxPackage, AppImageLayout> env) throws PackagerException, IOException {
for (var ca : customActions) {
ca.init(env.env(), env.pkg());
}
Map<String, String> data = createDefaultReplacementData(env.env(), env.pkg());
for (var ca : customActions) {
ShellCustomAction.mergeReplacementData(data, ca.instance.create());
}
data.putAll(createReplacementData(env.env(), env.pkg()));
createConfigFiles(Collections.unmodifiableMap(data), env.env(), env.pkg());
}
private void buildPackage(PackageBuildEnv<LinuxPackage, AppImageLayout> env) throws PackagerException, IOException {
Path packageBundle = buildPackageBundle(env.env(), env.pkg(), env.outputDir());
verifyOutputBundle(env.env(), env.pkg(), packageBundle).stream()
.filter(Objects::nonNull)
.forEachOrdered(ex -> {
Log.verbose(ex.getLocalizedMessage());
Log.verbose(ex.getAdvice());
});
}
private List<String> getListOfNeededPackages(BuildEnv env) throws IOException {
final List<String> caPackages = customActions.stream()
.map(ca -> ca.instance)
.map(ShellCustomAction::requiredPackages)
.flatMap(List::stream).toList();
final List<String> neededLibPackages;
if (withFindNeededPackages) {
LibProvidersLookup lookup = new LibProvidersLookup();
initLibProvidersLookup(lookup);
neededLibPackages = lookup.execute(env.appImageDir());
} else {
neededLibPackages = Collections.emptyList();
Log.info(I18N.getString("warning.foreign-app-image"));
}
// Merge all package lists together.
// Filter out empty names, sort and remove duplicates.
List<String> result = Stream.of(caPackages, neededLibPackages).flatMap(
List::stream).filter(Predicate.not(String::isEmpty)).sorted().distinct().toList();
Log.verbose(String.format("Required packages: %s", result));
return result;
}
private Map<String, String> createDefaultReplacementData(BuildEnv env, LinuxPackage pkg) throws IOException {
Map<String, String> data = new HashMap<>();
data.put("APPLICATION_PACKAGE", pkg.packageName());
data.put("APPLICATION_VENDOR", pkg.app().vendor());
data.put("APPLICATION_VERSION", pkg.version());
data.put("APPLICATION_DESCRIPTION", pkg.description());
String defaultDeps = String.join(", ", getListOfNeededPackages(env));
String customDeps = pkg.additionalDependencies().orElse("");
if (!customDeps.isEmpty() && !defaultDeps.isEmpty()) {
customDeps = ", " + customDeps;
}
data.put("PACKAGE_DEFAULT_DEPENDENCIES", defaultDeps);
data.put("PACKAGE_CUSTOM_DEPENDENCIES", customDeps);
return data;
}
protected abstract List<ConfigException> verifyOutputBundle(
BuildEnv env, LinuxPackage pkg, Path packageBundle);
protected abstract void initLibProvidersLookup(LibProvidersLookup libProvidersLookup);
protected abstract List<ToolValidator> getToolValidators();
protected abstract void doValidate(BuildEnv env, LinuxPackage pkg)
throws ConfigException;
protected abstract Map<String, String> createReplacementData(
BuildEnv env, LinuxPackage pkg) throws IOException;
protected abstract void createConfigFiles(
Map<String, String> replacementData,
BuildEnv env, LinuxPackage pkg) throws IOException;
protected abstract Path buildPackageBundle(
BuildEnv env, LinuxPackage pkg, Path outputParentDir) throws
PackagerException, IOException;
private final BundlerParamInfo<? extends LinuxPackage> pkgParam;
private boolean withFindNeededPackages;
private final List<CustomActionInstance> customActions;
private static final class CustomActionInstance {
CustomActionInstance(ShellCustomActionFactory factory) {
this.factory = factory;
}
void init(BuildEnv env, Package pkg) throws IOException {
instance = factory.create(env, pkg);
Objects.requireNonNull(instance);
}
private final ShellCustomActionFactory factory;
ShellCustomAction instance;
}
}