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 org.apache.maven.archiver.MavenArchiver;
23  import org.apache.maven.artifact.Artifact;
24  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
25  import org.apache.maven.execution.MavenSession;
26  import org.apache.maven.plugin.AbstractMojo;
27  import org.apache.maven.plugin.MojoExecutionException;
28  import org.apache.maven.plugins.annotations.Component;
29  import org.apache.maven.plugins.annotations.Parameter;
30  import org.apache.maven.project.MavenProject;
31  import org.apache.maven.shared.utils.io.FileUtils;
32  import org.apache.maven.toolchain.Toolchain;
33  import org.apache.maven.toolchain.ToolchainManager;
34  
35  import java.io.BufferedWriter;
36  import java.io.File;
37  import java.io.FileOutputStream;
38  import java.io.IOException;
39  import java.io.OutputStreamWriter;
40  import java.io.PrintWriter;
41  import java.nio.charset.StandardCharsets;
42  import java.text.SimpleDateFormat;
43  import java.util.Date;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Set;
47  
48  /**
49   * Base buildinfo-generating class, for goals related to Reproducible Builds {@code .buildinfo} files.
50   *
51   * @since 3.2.0
52   */
53  public abstract class AbstractBuildinfoMojo
54      extends AbstractMojo
55  {
56      /**
57       * The Maven project.
58       */
59      @Parameter( defaultValue = "${project}", readonly = true )
60      protected MavenProject project;
61  
62      /**
63       * The reactor projects.
64       */
65      @Parameter( defaultValue = "${reactorProjects}", required = true, readonly = true )
66      protected List<MavenProject> reactorProjects;
67  
68      /**
69       * Location of the generated buildinfo file.
70       */
71      @Parameter( defaultValue = "${project.build.directory}/${project.artifactId}-${project.version}.buildinfo",
72                      required = true, readonly = true )
73      protected File buildinfoFile;
74  
75      /**
76       * Ignore javadoc attached artifacts from buildinfo generation.
77       */
78      @Parameter( property = "buildinfo.ignoreJavadoc", defaultValue = "true" )
79      private boolean ignoreJavadoc;
80  
81      /**
82       * Artifacts to ignore, specified as <code>extension</code> or <code>classifier.extension</code>.
83       */
84      @Parameter( property = "buildinfo.ignore", defaultValue = "" )
85      private Set<String> ignore;
86  
87      /**
88       * Detect projects/modules with install or deploy skipped: avoid taking fingerprints.
89       */
90      @Parameter( property = "buildinfo.detect.skip", defaultValue = "true" )
91      private boolean detectSkip;
92  
93      /**
94       * Makes the generated {@code .buildinfo} file reproducible, by dropping detailed environment recording: OS will be
95       * recorded as "Windows" or "Unix", JVM version as major version only.
96       *
97       * @since 3.1.0
98       */
99      @Parameter( property = "buildinfo.reproducible", defaultValue = "false" )
100     private boolean reproducible;
101 
102     /**
103      * The current build session instance. This is used for toolchain manager API calls.
104      */
105     @Parameter( defaultValue = "${session}", readonly = true, required = true )
106     private MavenSession session;
107 
108     /**
109      * Timestamp for reproducible output archive entries, either formatted as ISO 8601
110      * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
111      * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
112      *
113      * @since 3.2.0
114      */
115     @Parameter( defaultValue = "${project.build.outputTimestamp}" )
116     private String outputTimestamp;
117 
118     /**
119      * To obtain a toolchain if possible.
120      */
121     @Component
122     private ToolchainManager toolchainManager;
123 
124     @Component
125     protected ArtifactHandlerManager artifactHandlerManager;
126 
127     @Override
128     public void execute()
129         throws MojoExecutionException
130     {
131         boolean mono = reactorProjects.size() == 1;
132 
133         MavenArchiver archiver = new MavenArchiver();
134         Date timestamp = archiver.parseOutputTimestamp( outputTimestamp );
135         if ( timestamp == null )
136         {
137             getLog().warn( "Reproducible Build not activated by project.build.outputTimestamp property: "
138                 + "see https://maven.apache.org/guides/mini/guide-reproducible-builds.html" );
139         }
140         else
141         {
142             if ( getLog().isDebugEnabled() )
143             {
144                 getLog().debug( "project.build.outputTimestamp = \"" + outputTimestamp + "\" => "
145                     + new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssXXX" ).format( timestamp ) );
146             }
147 
148             // check if timestamp well defined in a project from reactor
149             MavenProject reactorParent = project;
150             while ( reactorProjects.contains( reactorParent.getParent() ) )
151             {
152                 reactorParent = reactorParent.getParent();
153             }
154             String prop =
155                 reactorParent.getOriginalModel().getProperties().getProperty( "project.build.outputTimestamp" );
156             if ( prop == null )
157             {
158                 getLog().warn( "project.build.outputTimestamp property should not be inherited "
159                     + "but defined in parent POM from reactor " + reactorParent.getFile() );
160             }
161         }
162 
163         if ( !mono )
164         {
165             // if module skips install and/or deploy
166             if ( isSkip( project ) )
167             {
168                 getLog().info( "Skipping goal because module skips install and/or deploy" );
169                 return;
170             }
171             // if multi-module build, generate (aggregate) buildinfo only in last module
172             MavenProject last = getLastProject();
173             if  ( project != last )
174             {
175                 skip( last );
176                 return;
177             }
178         }
179 
180         // generate buildinfo
181         Map<Artifact, String> artifacts = generateBuildinfo( mono );
182         getLog().info( "Saved " + ( mono ? "" : "aggregate " ) + "info on build to " + buildinfoFile );
183 
184         copyAggregateToRoot( buildinfoFile );
185 
186         execute( artifacts );
187     }
188 
189     /**
190      * Execute after buildinfo has been generated for current build (eventually aggregated).
191      *
192      * @param artifacts a Map of artifacts added to the build info with their associated property key prefix
193      *         (<code>outputs.[#module.].#artifact</code>)
194      */
195     abstract void execute( Map<Artifact, String> artifacts )
196         throws MojoExecutionException;
197 
198     protected void skip( MavenProject last )
199         throws MojoExecutionException
200     {
201         getLog().info( "Skipping intermediate goal run, aggregate will be " + last.getArtifactId() );
202     }
203 
204     protected void copyAggregateToRoot( File aggregate )
205         throws MojoExecutionException
206     {
207         if ( reactorProjects.size() == 1 )
208         {
209             // mono-module, no aggregate file to deal with
210             return;
211         }
212 
213         // copy aggregate file to root target directory
214         MavenProject root = getExecutionRoot();
215         String extension = aggregate.getName().substring( aggregate.getName().lastIndexOf( '.' ) );
216         File rootCopy = new File( root.getBuild().getDirectory(),
217                                   root.getArtifactId() + '-' + root.getVersion() + extension );
218         try
219         {
220             FileUtils.copyFile( aggregate, rootCopy );
221             getLog().info( "Aggregate " + extension.substring( 1 ) + " copied to " + rootCopy );
222         }
223         catch ( IOException ioe )
224         {
225             throw new MojoExecutionException( "Could not copy " + aggregate + "to " + rootCopy );
226         }
227     }
228 
229     /**
230      * Generate buildinfo file.
231      *
232      * @param mono is it a mono-module build?
233      * @return a Map of artifacts added to the build info with their associated property key prefix
234      *         (<code>outputs.[#module.].#artifact</code>)
235      * @throws MojoExecutionException
236      */
237     protected Map<Artifact, String> generateBuildinfo( boolean mono )
238         throws MojoExecutionException
239     {
240         MavenProject root = mono ? project : getExecutionRoot();
241 
242         buildinfoFile.getParentFile().mkdirs();
243 
244         try ( PrintWriter p = new PrintWriter( new BufferedWriter(
245                 new OutputStreamWriter( new FileOutputStream( buildinfoFile ), StandardCharsets.UTF_8 ) ) ) )
246         {
247             BuildInfoWriter bi = new BuildInfoWriter( getLog(), p, mono, artifactHandlerManager );
248             bi.setIgnoreJavadoc( ignoreJavadoc );
249             bi.setIgnore( ignore );
250             bi.setToolchain( getToolchain() );
251 
252             bi.printHeader( root, mono ? null : project, reproducible );
253 
254             // artifact(s) fingerprints
255             if ( mono )
256             {
257                 bi.printArtifacts( project );
258             }
259             else
260             {
261                 for ( MavenProject project : reactorProjects )
262                 {
263                     if ( !isSkip( project ) )
264                     {
265                         bi.printArtifacts( project );
266                     }
267                 }
268             }
269 
270             if ( p.checkError() )
271             {
272                 throw new MojoExecutionException( "Write error to " + buildinfoFile );
273             }
274 
275             return bi.getArtifacts();
276         }
277         catch ( IOException e )
278         {
279             throw new MojoExecutionException( "Error creating file " + buildinfoFile, e );
280         }
281     }
282 
283     protected MavenProject getExecutionRoot()
284     {
285         for ( MavenProject p : reactorProjects )
286         {
287             if ( p.isExecutionRoot() )
288             {
289                 return p;
290             }
291         }
292         return null;
293     }
294 
295     private MavenProject getLastProject()
296     {
297         int i = reactorProjects.size();
298         while ( i > 0 )
299         {
300             MavenProject project = reactorProjects.get( --i );
301             if ( !isSkip( project ) )
302             {
303                 return project;
304             }
305         }
306         return null;
307     }
308 
309     private boolean isSkip( MavenProject project )
310     {
311         return detectSkip && PluginUtil.isSkip( project );
312     }
313 
314     private Toolchain getToolchain()
315     {
316         Toolchain tc = null;
317         if ( toolchainManager != null )
318         {
319             tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
320         }
321 
322         return tc;
323     }
324 }