View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.artifact.buildinfo;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.nio.file.Files;
25  import java.util.HashSet;
26  import java.util.Map;
27  import java.util.Properties;
28  import java.util.Set;
29  
30  import org.apache.maven.artifact.versioning.ArtifactVersion;
31  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
32  import org.apache.maven.execution.MavenSession;
33  import org.apache.maven.lifecycle.LifecycleExecutor;
34  import org.apache.maven.lifecycle.MavenExecutionPlan;
35  import org.apache.maven.model.Plugin;
36  import org.apache.maven.plugin.AbstractMojo;
37  import org.apache.maven.plugin.MojoExecution;
38  import org.apache.maven.plugin.MojoExecutionException;
39  import org.apache.maven.plugins.annotations.Component;
40  import org.apache.maven.plugins.annotations.Mojo;
41  import org.apache.maven.plugins.annotations.Parameter;
42  import org.apache.maven.project.MavenProject;
43  
44  /**
45   * Check from buildplan that plugins used don't have known Reproducible Builds issues.
46   *
47   * @since 3.3.0
48   */
49  @Mojo(name = "check-buildplan", threadSafe = true, requiresProject = true)
50  public class CheckBuildPlanMojo extends AbstractMojo {
51      @Component
52      private MavenProject project;
53  
54      @Component
55      private MavenSession session;
56  
57      @Component
58      private LifecycleExecutor lifecycleExecutor;
59  
60      /** Allow to specify which goals/phases will be used to calculate execution plan. */
61      @Parameter(property = "check.buildplan.tasks", defaultValue = "deploy")
62      private String[] tasks;
63  
64      /**
65       * Timestamp for reproducible output archive entries, either formatted as ISO 8601
66       * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
67       * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
68       */
69      @Parameter(defaultValue = "${project.build.outputTimestamp}")
70      private String outputTimestamp;
71  
72      /**
73       * Provide a plugin issues property file to override plugin's <code>not-reproducible-plugins.properties</code>.
74       */
75      @Parameter(property = "check.plugin-issues")
76      private File pluginIssues;
77  
78      /**
79       * Make build fail if execution plan contains non-reproducible plugins.
80       */
81      @Parameter(property = "check.failOnNonReproducible", defaultValue = "true")
82      private boolean failOnNonReproducible;
83  
84      protected MavenExecutionPlan calculateExecutionPlan() throws MojoExecutionException {
85          try {
86              return lifecycleExecutor.calculateExecutionPlan(session, tasks);
87          } catch (Exception e) {
88              throw new MojoExecutionException("Cannot calculate Maven execution plan" + e.getMessage(), e);
89          }
90      }
91  
92      @Override
93      public void execute() throws MojoExecutionException {
94          boolean fail =
95                  AbstractBuildinfoMojo.hasBadOutputTimestamp(outputTimestamp, getLog(), project, session.getProjects());
96  
97          // TODO check maven-jar-plugin module-info.class?
98  
99          Properties issues = loadIssues();
100 
101         MavenExecutionPlan plan = calculateExecutionPlan();
102 
103         Set<String> plugins = new HashSet<>();
104         for (MojoExecution exec : plan.getMojoExecutions()) {
105             Plugin plugin = exec.getPlugin();
106             String id = plugin.getId();
107 
108             if (plugins.add(id)) {
109                 // check reproducibility status
110                 String issue = issues.getProperty(plugin.getKey());
111                 if (issue == null) {
112                     getLog().info("no known issue with " + id);
113                 } else if (issue.startsWith("fail:")) {
114                     String logMessage = "plugin without solution " + id + ", see " + issue.substring(5);
115                     if (failOnNonReproducible) {
116                         getLog().error(logMessage);
117                     } else {
118                         getLog().warn(logMessage);
119                     }
120                     fail = true;
121 
122                 } else {
123                     ArtifactVersion minimum = new DefaultArtifactVersion(issue);
124                     ArtifactVersion version = new DefaultArtifactVersion(plugin.getVersion());
125                     if (version.compareTo(minimum) < 0) {
126                         String logMessage = "plugin with non-reproducible output: " + id + ", require minimum " + issue;
127                         if (failOnNonReproducible) {
128                             getLog().error(logMessage);
129                         } else {
130                             getLog().warn(logMessage);
131                         }
132                         fail = true;
133                     } else {
134                         getLog().info("no known issue with " + id + " (>= " + issue + ")");
135                     }
136                 }
137             }
138         }
139 
140         if (fail) {
141             getLog().info("current module pom.xml is " + project.getBasedir() + "/pom.xml");
142             MavenProject parent = project;
143             while (true) {
144                 parent = parent.getParent();
145                 if ((parent == null) || !session.getProjects().contains(parent)) {
146                     break;
147                 }
148                 getLog().info("        parent pom.xml is " + parent.getBasedir() + "/pom.xml");
149             }
150             String message = "non-reproducible plugin or configuration found with fix available";
151             if (failOnNonReproducible) {
152                 throw new MojoExecutionException(message);
153             } else {
154                 getLog().warn(message);
155             }
156         }
157     }
158 
159     private Properties loadIssues() throws MojoExecutionException {
160         try (InputStream in = (pluginIssues == null)
161                 ? getClass().getResourceAsStream("not-reproducible-plugins.properties")
162                 : Files.newInputStream(pluginIssues.toPath())) {
163             Properties prop = new Properties();
164             prop.load(in);
165 
166             Properties result = new Properties();
167             for (Map.Entry<Object, Object> entry : prop.entrySet()) {
168                 String plugin = entry.getKey().toString().replace('+', ':');
169                 if (!plugin.contains(":")) {
170                     plugin = "org.apache.maven.plugins:" + plugin;
171                 }
172                 result.put(plugin, entry.getValue());
173             }
174             return result;
175         } catch (IOException ioe) {
176             throw new MojoExecutionException("Cannot load issues file", ioe);
177         }
178     }
179 }