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

org.apache.maven.plugins.artifact.buildinfo.CheckBuildPlanMojo Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 *
 *   http://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.apache.maven.plugins.artifact.buildinfo;

import javax.inject.Inject;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.LifecycleExecutor;
import org.apache.maven.lifecycle.MavenExecutionPlan;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.util.version.GenericVersionScheme;
import org.eclipse.aether.version.InvalidVersionSpecificationException;
import org.eclipse.aether.version.Version;
import org.eclipse.aether.version.VersionScheme;

/**
 * Check from buildplan that plugins used don't have known Reproducible Builds issues.
 *
 * @since 3.3.0
 */
@Mojo(name = "check-buildplan", threadSafe = true, requiresProject = true)
public class CheckBuildPlanMojo extends AbstractMojo {
    private final MavenProject project;

    private final MavenSession session;

    private final LifecycleExecutor lifecycleExecutor;

    /** Allow to specify which goals/phases will be used to calculate execution plan. */
    @Parameter(property = "check.buildplan.tasks", defaultValue = "deploy")
    private String[] tasks;

    /**
     * Timestamp for reproducible output archive entries, either formatted as ISO 8601
     * yyyy-MM-dd'T'HH:mm:ssXXX or as an int representing seconds since the epoch (like
     * SOURCE_DATE_EPOCH).
     */
    @Parameter(defaultValue = "${project.build.outputTimestamp}")
    private String outputTimestamp;

    /**
     * Diagnose {@code outputTimestamp} effective value based on execution context.
     *
     * @since 3.5.2
     */
    @Parameter(property = "diagnose", defaultValue = "false")
    private boolean diagnose;

    /**
     * Provide a plugin issues property file to override plugin's not-reproducible-plugins.properties.
     */
    @Parameter(property = "check.plugin-issues")
    private File pluginIssues;

    /**
     * Make build fail if execution plan contains non-reproducible plugins.
     */
    @Parameter(property = "check.failOnNonReproducible", defaultValue = "true")
    private boolean failOnNonReproducible;

    private final VersionScheme versionScheme = new GenericVersionScheme();

    @Inject
    public CheckBuildPlanMojo(MavenProject project, MavenSession session, LifecycleExecutor lifecycleExecutor) {
        this.project = project;
        this.session = session;
        this.lifecycleExecutor = lifecycleExecutor;
    }

    protected MavenExecutionPlan calculateExecutionPlan() throws MojoExecutionException {
        try {
            return lifecycleExecutor.calculateExecutionPlan(session, tasks);
        } catch (Exception e) {
            throw new MojoExecutionException("Cannot calculate Maven execution plan" + e.getMessage(), e);
        }
    }

    @Override
    public void execute() throws MojoExecutionException {
        boolean fail =
                AbstractBuildinfoMojo.hasBadOutputTimestamp(outputTimestamp, getLog(), project, session, diagnose);

        // TODO check maven-jar-plugin module-info.class?

        Properties issues = loadIssues();

        MavenExecutionPlan plan = calculateExecutionPlan();

        Set plugins = new HashSet<>();
        int okCount = 0;
        for (MojoExecution exec : plan.getMojoExecutions()) {
            Plugin plugin = exec.getPlugin();
            String id = plugin.getId();

            if (plugins.add(id)) {
                // check reproducibility status
                String issue = issues.getProperty(plugin.getKey());
                if (issue == null) {
                    okCount++;
                    getLog().debug("No known issue with " + id);
                } else if (issue.startsWith("fail:")) {
                    String logMessage = "plugin without solution " + id + ", see " + issue.substring(5);
                    if (failOnNonReproducible) {
                        getLog().error(logMessage);
                    } else {
                        getLog().warn(logMessage);
                    }
                    fail = true;
                } else {
                    try {
                        Version minimum = versionScheme.parseVersion(issue);
                        Version version = versionScheme.parseVersion(plugin.getVersion());
                        if (version.compareTo(minimum) < 0) {
                            String logMessage =
                                    "plugin with non-reproducible output: " + id + ", require minimum " + issue;
                            if (failOnNonReproducible) {
                                getLog().error(logMessage);
                            } else {
                                getLog().warn(logMessage);
                            }
                            fail = true;
                        } else {
                            okCount++;
                            getLog().debug("No known issue with " + id + " (>= " + issue + ")");
                        }
                    } catch (InvalidVersionSpecificationException e) {
                        throw new MojoExecutionException(e);
                    }
                }
            }
        }
        if (okCount > 0) {
            getLog().info("No known issue in " + okCount + " plugins");
        }

        if (fail) {
            getLog().info("current module pom.xml is " + project.getBasedir() + "/pom.xml");
            MavenProject parent = project;
            while (true) {
                parent = parent.getParent();
                if ((parent == null) || !session.getProjects().contains(parent)) {
                    break;
                }
                getLog().info("        parent pom.xml is " + parent.getBasedir() + "/pom.xml");
            }
            String message = "non-reproducible plugin or configuration found with fix available";
            if (failOnNonReproducible) {
                throw new MojoExecutionException(message);
            } else {
                getLog().warn(message);
            }
        }
    }

    private Properties loadIssues() throws MojoExecutionException {
        try (InputStream in = (pluginIssues == null)
                ? getClass().getResourceAsStream("not-reproducible-plugins.properties")
                : Files.newInputStream(pluginIssues.toPath())) {
            Properties prop = new Properties();
            prop.load(in);

            Properties result = new Properties();
            for (Map.Entry entry : prop.entrySet()) {
                String plugin = entry.getKey().toString().replace('+', ':');
                if (!plugin.contains(":")) {
                    plugin = "org.apache.maven.plugins:" + plugin;
                }
                result.put(plugin, entry.getValue());
            }
            return result;
        } catch (IOException ioe) {
            throw new MojoExecutionException("Cannot load issues file", ioe);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy