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