All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jreleaser.packagers.AbstractPackagerProcessor Maven / Gradle / Ivy

The newest version!
/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright 2020-2024 The JReleaser authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jreleaser.packagers;

import org.jreleaser.bundle.RB;
import org.jreleaser.model.Stereotype;
import org.jreleaser.model.internal.JReleaserContext;
import org.jreleaser.model.internal.common.Artifact;
import org.jreleaser.model.internal.distributions.Distribution;
import org.jreleaser.model.internal.packagers.Packager;
import org.jreleaser.model.internal.util.Artifacts;
import org.jreleaser.model.spi.packagers.PackagerProcessingException;
import org.jreleaser.model.spi.packagers.PackagerProcessor;
import org.jreleaser.mustache.TemplateContext;
import org.jreleaser.sdk.command.Command;
import org.jreleaser.sdk.command.CommandException;
import org.jreleaser.sdk.command.CommandExecutor;
import org.jreleaser.util.Algorithm;
import org.jreleaser.util.FileType;
import org.jreleaser.util.FileUtils;
import org.jreleaser.util.IoUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;

import static java.util.stream.Collectors.toList;
import static org.jreleaser.model.Constants.KEY_ARTIFACT_ARCH;
import static org.jreleaser.model.Constants.KEY_ARTIFACT_FILE;
import static org.jreleaser.model.Constants.KEY_ARTIFACT_FILE_EXTENSION;
import static org.jreleaser.model.Constants.KEY_ARTIFACT_FILE_FORMAT;
import static org.jreleaser.model.Constants.KEY_ARTIFACT_FILE_NAME;
import static org.jreleaser.model.Constants.KEY_ARTIFACT_NAME;
import static org.jreleaser.model.Constants.KEY_ARTIFACT_OS;
import static org.jreleaser.model.Constants.KEY_ARTIFACT_PLATFORM;
import static org.jreleaser.model.Constants.KEY_ARTIFACT_PLATFORM_REPLACED;
import static org.jreleaser.model.Constants.KEY_ARTIFACT_ROOT_ENTRY_NAME;
import static org.jreleaser.model.Constants.KEY_ARTIFACT_SIZE;
import static org.jreleaser.model.Constants.KEY_ARTIFACT_VERSION;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT_ARCH;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT_FILE;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT_FILE_EXTENSION;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT_FILE_FORMAT;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT_FILE_NAME;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT_NAME;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT_OS;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT_PLATFORM;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT_PLATFORM_REPLACED;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT_ROOT_ENTRY_NAME;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT_SIZE;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ARTIFACT_VERSION;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_PACKAGE_DIRECTORY;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_PREPARE_DIRECTORY;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_SHA_256;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_SIZE;
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_URL;
import static org.jreleaser.model.Constants.KEY_REVERSE_REPO_HOST;
import static org.jreleaser.mustache.MustacheUtils.applyTemplates;
import static org.jreleaser.util.FileUtils.resolveRootEntryName;
import static org.jreleaser.util.StringUtils.capitalize;
import static org.jreleaser.util.StringUtils.getFilename;
import static org.jreleaser.util.StringUtils.isBlank;
import static org.jreleaser.util.StringUtils.isNotBlank;

/**
 * @author Andres Almiray
 * @since 0.1.0
 */
public abstract class AbstractPackagerProcessor> implements PackagerProcessor {
    private static final String ARTIFACT = "artifact";
    private static final String DISTRIBUTION = "distribution";
    private static final String NAME = "Name";
    private static final String VERSION = "Version";
    private static final String OS = "Os";
    private static final String ARCH = "Arch";
    private static final String FILE = "File";
    private static final String SIZE = "Size";
    private static final String FILE_NAME = "FileName";
    private static final String ROOT_ENTRY_NAME = "RootEntryName";
    private static final String FILE_EXTENSION = "FileExtension";
    private static final String FILE_FORMAT = "FileFormat";
    private static final String CHECKSUM = "Checksum";
    private static final String URL = "Url";

    protected final JReleaserContext context;
    protected T packager;

    protected AbstractPackagerProcessor(JReleaserContext context) {
        this.context = context;
    }

    @Override
    public T getPackager() {
        return packager;
    }

    @Override
    public void setPackager(T packager) {
        this.packager = packager;
    }

    @Override
    public String getPackagerName() {
        return packager.getType();
    }

    @Override
    public boolean supportsDistribution(Distribution distribution) {
        return true;
    }

    @Override
    public void prepareDistribution(Distribution distribution, TemplateContext props) throws PackagerProcessingException {
        try {
            String distributionName = distribution.getName();
            context.getLogger().debug(RB.$("packager.create.properties"), distributionName, getPackagerName());
            TemplateContext newProps = fillProps(distribution, props);
            if (newProps.isEmpty()) {
                context.getLogger().warn(RB.$("packager.skip.distribution"), distributionName);
                return;
            }

            doPrepareDistribution(distribution, newProps);
        } catch (RuntimeException e) {
            throw new PackagerProcessingException(e);
        }
    }

    protected abstract void doPrepareDistribution(Distribution distribution, TemplateContext props) throws PackagerProcessingException;

    @Override
    public void packageDistribution(Distribution distribution, TemplateContext props) throws PackagerProcessingException {
        try {
            String distributionName = distribution.getName();
            context.getLogger().debug(RB.$("packager.create.properties"), distributionName, getPackagerName());
            TemplateContext newProps = fillProps(distribution, props);
            if (newProps.isEmpty()) {
                context.getLogger().warn(RB.$("packager.skip.distribution"), distributionName);
                return;
            }

            doPackageDistribution(distribution, newProps);
        } catch (IllegalArgumentException e) {
            throw new PackagerProcessingException(e);
        }
    }

    @Override
    public void publishDistribution(Distribution distribution, TemplateContext props) throws PackagerProcessingException {
        if (context.getModel().getProject().isSnapshot() && !packager.isSnapshotSupported()) {
            context.getLogger().info(RB.$("packager.publish.snapshot.not.supported"));
            return;
        }

        try {
            String distributionName = distribution.getName();
            context.getLogger().debug(RB.$("packager.create.properties"), distributionName, getPackagerName());
            TemplateContext newProps = fillProps(distribution, props);
            if (newProps.isEmpty()) {
                context.getLogger().warn(RB.$("packager.skip.distribution"), distributionName);
                return;
            }

            doPublishDistribution(distribution, newProps);
        } catch (RuntimeException e) {
            throw new PackagerProcessingException(e);
        }
    }

    protected abstract void doPackageDistribution(Distribution distribution, TemplateContext props) throws PackagerProcessingException;

    protected abstract void doPublishDistribution(Distribution distribution, TemplateContext props) throws PackagerProcessingException;

    protected TemplateContext fillProps(Distribution distribution, TemplateContext props) {
        TemplateContext newProps = new TemplateContext(props);
        context.getLogger().debug(RB.$("packager.fill.distribution.properties"));
        fillDistributionProperties(newProps, distribution);
        context.getLogger().debug(RB.$("packager.fill.git.properties"));
        context.getModel().getRelease().getReleaser().fillProps(newProps, context.getModel());
        context.getLogger().debug(RB.$("packager.fill.artifact.properties"));
        if (!verifyAndAddArtifacts(newProps, distribution)) {
            // we can't continue with this packager
            return TemplateContext.empty();
        }
        context.getLogger().debug(RB.$("packager.fill.packager.properties"));
        fillPackagerProperties(newProps, distribution);
        applyTemplates(newProps, packager.resolvedExtraProperties());
        if (isBlank(context.getModel().getRelease().getReleaser().getReverseRepoHost())) {
            newProps.set(KEY_REVERSE_REPO_HOST,
                packager.getExtraProperties().get(KEY_REVERSE_REPO_HOST));
        }
        applyTemplates(newProps, newProps);
        return newProps;
    }

    protected void fillDistributionProperties(TemplateContext props, Distribution distribution) {
        props.setAll(distribution.props());
    }

    protected abstract void fillPackagerProperties(TemplateContext props, Distribution distribution);

    protected Command.Result executeCommand(Path directory, Command command) throws PackagerProcessingException {
        try {
            Command.Result result = new CommandExecutor(context.getLogger())
                .executeCommand(directory, command);
            if (result.getExitValue() != 0) {
                throw new CommandException(RB.$("ERROR_command_execution_exit_value", result.getExitValue()));
            }
            return result;
        } catch (CommandException e) {
            throw new PackagerProcessingException(RB.$("ERROR_unexpected_error"), e);
        }
    }

    protected Command.Result executeCommand(Command command) throws PackagerProcessingException {
        try {
            Command.Result result = new CommandExecutor(context.getLogger())
                .executeCommand(command);
            if (result.getExitValue() != 0) {
                throw new CommandException(RB.$("ERROR_command_execution_exit_value", result.getExitValue()));
            }
            return result;
        } catch (CommandException e) {
            throw new PackagerProcessingException(RB.$("ERROR_unexpected_error"), e);
        }
    }

    protected Command.Result executeCommand(Command command, InputStream in) throws PackagerProcessingException {
        try {
            Command.Result result = new CommandExecutor(context.getLogger())
                .executeCommand(command, in);
            if (result.getExitValue() != 0) {
                throw new CommandException(RB.$("ERROR_command_execution_exit_value", result.getExitValue()));
            }
            return result;
        } catch (CommandException e) {
            throw new PackagerProcessingException(RB.$("ERROR_unexpected_error"), e);
        }
    }

    protected void copyPreparedFiles(TemplateContext props) throws PackagerProcessingException {
        Path prepareDirectory = getPrepareDirectory(props);
        Path packageDirectory = getPackageDirectory(props);
        copyFiles(prepareDirectory, packageDirectory);
    }

    protected void copyFiles(Path src, Path dest) throws PackagerProcessingException {
        try {
            if (!Files.exists(dest)) {
                Files.createDirectories(dest);
            }

            if (!FileUtils.copyFilesRecursive(context.getLogger(), src, dest)) {
                throw new PackagerProcessingException(RB.$("ERROR_copy_files_from_to",
                    context.relativizeToBasedir(src),
                    context.relativizeToBasedir(dest)));
            }
        } catch (IOException e) {
            throw new PackagerProcessingException(RB.$("ERROR_unexpected_copy_files_from_to",
                context.relativizeToBasedir(src),
                context.relativizeToBasedir(dest)), e);
        }
    }

    protected boolean verifyAndAddArtifacts(TemplateContext props,
                                            Distribution distribution) {
        return verifyAndAddArtifacts(props, distribution, collectArtifacts(distribution));
    }

    protected boolean verifyAndAddArtifacts(TemplateContext props,
                                            Distribution distribution,
                                            List artifacts) {
        List activeArtifacts = artifacts.stream()
            .filter(Artifact::isActiveAndSelected)
            .collect(toList());

        if (activeArtifacts.isEmpty()) {
            // we can't proceed
            context.getLogger().warn(RB.$("packager.no.matching.artifacts"),
                distribution.getName(), capitalize(packager.getType()));
            return false;
        }

        int count = 0;
        for (Artifact artifact : activeArtifacts) {
            String artifactUrl = Artifacts.resolveDownloadUrl(context, packager.getType(), distribution, artifact);
            if (isBlank(artifactUrl)) continue;
            count++;

            String platform = artifact.getPlatform();
            String artifactPlatform = isNotBlank(platform) ? capitalize(platform) : "";
            String platformReplaced = distribution.getPlatform().applyReplacements(platform);
            String artifactPlatformReplaced = isNotBlank(platformReplaced) ? capitalize(platformReplaced) : "";
            // add extra properties without clobbering existing keys
            Map artifactProps = artifact.resolvedExtraProperties(ARTIFACT + artifactPlatform);
            artifactProps.keySet().stream()
                .filter(k -> !props.contains(k))
                .forEach(k -> props.set(k, artifactProps.get(k)));

            Path artifactPath = artifact.getEffectivePath(context, distribution);

            long artifactSize = 0;
            try {
                artifactSize = Files.size(artifactPath);
            } catch (IOException ignored) {
                // this would be strange
                context.getLogger().trace(ignored);
            }

            String artifactFile = artifact.getEffectivePath().getFileName().toString();
            String artifactFileName = getFilename(artifactFile, FileType.getSupportedExtensions());
            String artifactRootEntryName = resolveRootEntryName(artifact.getEffectivePath());
            String artifactFileExtension = "";
            String artifactFileFormat = "";

            if (!artifactFile.equals(artifactFileName)) {
                artifactFileExtension = artifactFile.substring(artifactFileName.length());
                artifactFileFormat = artifactFileExtension.substring(1);
            }

            String artifactName = "";
            String artifactVersion = "";
            String projectVersion = context.getModel().getProject().getEffectiveVersion();
            if (isNotBlank(projectVersion) && artifactFileName.contains(projectVersion)) {
                artifactName = artifactFileName.substring(0, artifactFileName.indexOf(projectVersion));
                if (artifactName.endsWith("-")) {
                    artifactName = artifactName.substring(0, artifactName.length() - 1);
                }
                artifactVersion = projectVersion;
            }
            projectVersion = context.getModel().getProject().getVersion();
            if (isBlank(artifactName) && isNotBlank(projectVersion) && artifactFileName.contains(projectVersion)) {
                artifactName = artifactFileName.substring(0, artifactFileName.indexOf(projectVersion));
                if (artifactName.endsWith("-")) {
                    artifactName = artifactName.substring(0, artifactName.length() - 1);
                }
                artifactVersion = projectVersion;
            }

            String artifactOs = "";
            String artifactArch = "";
            if (isNotBlank(platform) && platform.contains("-")) {
                String[] parts = platform.split("-");
                artifactOs = parts[0];
                artifactArch = parts[1];
            }

            safePut(props, ARTIFACT + artifactPlatform + NAME, artifactName);
            safePut(props, ARTIFACT + artifactPlatform + VERSION, artifactVersion);
            safePut(props, ARTIFACT + artifactPlatform + OS, artifactOs);
            safePut(props, ARTIFACT + artifactPlatform + ARCH, artifactArch);
            safePut(props, ARTIFACT + artifactPlatform + FILE, artifactFile);
            safePut(props, ARTIFACT + artifactPlatform + SIZE, artifactSize);
            safePut(props, ARTIFACT + artifactPlatform + FILE_NAME, artifactFileName);
            safePut(props, ARTIFACT + artifactPlatform + ROOT_ENTRY_NAME, artifactRootEntryName);
            safePut(props, ARTIFACT + artifactPlatform + FILE_EXTENSION, artifactFileExtension);
            safePut(props, ARTIFACT + artifactPlatform + FILE_FORMAT, artifactFileFormat);

            safePut(props, ARTIFACT + artifactPlatformReplaced + NAME, artifactName);
            safePut(props, ARTIFACT + artifactPlatformReplaced + VERSION, artifactVersion);
            safePut(props, ARTIFACT + artifactPlatformReplaced + OS, artifactOs);
            safePut(props, ARTIFACT + artifactPlatformReplaced + ARCH, artifactArch);
            safePut(props, ARTIFACT + artifactPlatformReplaced + FILE, artifactFile);
            safePut(props, ARTIFACT + artifactPlatformReplaced + SIZE, artifactSize);
            safePut(props, ARTIFACT + artifactPlatformReplaced + FILE_NAME, artifactFileName);
            safePut(props, ARTIFACT + artifactPlatformReplaced + ROOT_ENTRY_NAME, artifactRootEntryName);
            safePut(props, ARTIFACT + artifactPlatformReplaced + FILE_EXTENSION, artifactFileExtension);
            safePut(props, ARTIFACT + artifactPlatformReplaced + FILE_FORMAT, artifactFileFormat);

            for (Algorithm algorithm : context.getModel().getChecksum().getAlgorithms()) {
                safePut(props, ARTIFACT + artifactPlatform + CHECKSUM + capitalize(algorithm.formatted()), artifact.getHash(algorithm));
                safePut(props, ARTIFACT + artifactPlatformReplaced + CHECKSUM + capitalize(algorithm.formatted()), artifact.getHash(algorithm));
            }

            safePut(props, ARTIFACT + artifactPlatform + URL, artifactUrl);
            safePut(props, ARTIFACT + artifactPlatformReplaced + URL, artifactUrl);
            props.setAll(context.getModel().getUpload()
                .resolveDownloadUrls(context, distribution, artifact, ARTIFACT + artifactPlatform));
            props.setAll(context.getModel().getUpload()
                .resolveDownloadUrls(context, distribution, artifact, ARTIFACT + artifactPlatformReplaced));

            if (count == 1) {
                props.setAll(context.getModel().getUpload()
                    .resolveDownloadUrls(context, distribution, artifact, DISTRIBUTION));
                safePut(props, KEY_DISTRIBUTION_ARTIFACT, artifact);
                safePut(props, KEY_DISTRIBUTION_URL, artifactUrl);
                safePut(props, KEY_DISTRIBUTION_SIZE, artifactSize);
                safePut(props, KEY_DISTRIBUTION_SHA_256, artifact.getHash(Algorithm.SHA_256));
                for (Algorithm algorithm : context.getModel().getChecksum().getAlgorithms()) {
                    safePut(props, DISTRIBUTION + CHECKSUM + capitalize(algorithm.formatted()), artifact.getHash(algorithm));
                }

                safePut(props, KEY_DISTRIBUTION_ARTIFACT_PLATFORM, platform);
                safePut(props, KEY_DISTRIBUTION_ARTIFACT_PLATFORM_REPLACED, platformReplaced);
                safePut(props, KEY_DISTRIBUTION_ARTIFACT_NAME, artifactName);
                safePut(props, KEY_DISTRIBUTION_ARTIFACT_VERSION, artifactVersion);
                safePut(props, KEY_DISTRIBUTION_ARTIFACT_OS, artifactOs);
                safePut(props, KEY_DISTRIBUTION_ARTIFACT_ARCH, artifactArch);
                safePut(props, KEY_DISTRIBUTION_ARTIFACT_SIZE, artifactSize);
                safePut(props, KEY_DISTRIBUTION_ARTIFACT_FILE, artifactFile);
                safePut(props, KEY_DISTRIBUTION_ARTIFACT_FILE_NAME, artifactFileName);
                safePut(props, KEY_DISTRIBUTION_ARTIFACT_ROOT_ENTRY_NAME, artifactRootEntryName);
                safePut(props, KEY_DISTRIBUTION_ARTIFACT_FILE_EXTENSION, artifactFileExtension);
                safePut(props, KEY_DISTRIBUTION_ARTIFACT_FILE_FORMAT, artifactFileFormat);

                safePut(props, KEY_ARTIFACT_PLATFORM, platform);
                safePut(props, KEY_ARTIFACT_PLATFORM_REPLACED, platformReplaced);
                safePut(props, KEY_ARTIFACT_NAME, artifactName);
                safePut(props, KEY_ARTIFACT_VERSION, artifactVersion);
                safePut(props, KEY_ARTIFACT_OS, artifactOs);
                safePut(props, KEY_ARTIFACT_ARCH, artifactArch);
                safePut(props, KEY_ARTIFACT_SIZE, artifactSize);
                safePut(props, KEY_ARTIFACT_FILE, artifactFile);
                safePut(props, KEY_ARTIFACT_FILE_NAME, artifactFileName);
                safePut(props, KEY_ARTIFACT_ROOT_ENTRY_NAME, artifactRootEntryName);
                safePut(props, KEY_ARTIFACT_FILE_EXTENSION, artifactFileExtension);
                safePut(props, KEY_ARTIFACT_FILE_FORMAT, artifactFileFormat);

                // add extra properties without clobbering existing keys
                Map aprops = artifact.resolvedExtraProperties();
                TemplateContext bprops = new TemplateContext(aprops);
                applyTemplates(aprops, bprops);
                aprops.keySet().stream()
                    .filter(k -> !props.contains(k))
                    .forEach(k -> props.set(k, aprops.get(k)));
            }
        }

        return count > 0;
    }

    protected List collectArtifacts(Distribution distribution) {
        return packager.resolveArtifacts(context, distribution);
    }

    protected void info(ByteArrayOutputStream out) {
        log(out, context.getLogger()::info);
    }

    protected void error(ByteArrayOutputStream err) {
        log(err, context.getLogger()::error);
    }

    private void log(ByteArrayOutputStream stream, Consumer consumer) {
        String str = IoUtils.toString(stream);
        if (isBlank(str)) return;

        Arrays.stream(str.split(System.lineSeparator()))
            .forEach(consumer);
    }

    protected Path getPrepareDirectory(TemplateContext props) {
        return props.get(KEY_DISTRIBUTION_PREPARE_DIRECTORY);
    }

    protected Path getPackageDirectory(TemplateContext props) {
        return props.get(KEY_DISTRIBUTION_PACKAGE_DIRECTORY);
    }

    protected void safePut(TemplateContext dest, String key, Object value) {
        if (value instanceof CharSequence && isNotBlank(String.valueOf(value)) || null != value) {
            dest.set(key, value);
        }
    }

    protected Optional resolveStereotype(String fileName) {
        for (Stereotype stereotype : packager.getSupportedStereotypes()) {
            if (fileName.startsWith(stereotype.formatted() + "-")) {
                return Optional.of(stereotype);
            }
        }

        return Optional.empty();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy