1 package org.apache.maven.plugins.artifact.buildinfo;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
50
51
52
53 public abstract class AbstractBuildinfoMojo
54 extends AbstractMojo
55 {
56
57
58
59 @Parameter( defaultValue = "${project}", readonly = true )
60 protected MavenProject project;
61
62
63
64
65 @Parameter( defaultValue = "${reactorProjects}", required = true, readonly = true )
66 protected List<MavenProject> reactorProjects;
67
68
69
70
71 @Parameter( defaultValue = "${project.build.directory}/${project.artifactId}-${project.version}.buildinfo",
72 required = true, readonly = true )
73 protected File buildinfoFile;
74
75
76
77
78 @Parameter( property = "buildinfo.ignoreJavadoc", defaultValue = "true" )
79 private boolean ignoreJavadoc;
80
81
82
83
84 @Parameter( property = "buildinfo.ignore", defaultValue = "" )
85 private Set<String> ignore;
86
87
88
89
90 @Parameter( property = "buildinfo.detect.skip", defaultValue = "true" )
91 private boolean detectSkip;
92
93
94
95
96
97
98
99 @Parameter( property = "buildinfo.reproducible", defaultValue = "false" )
100 private boolean reproducible;
101
102
103
104
105 @Parameter( defaultValue = "${session}", readonly = true, required = true )
106 private MavenSession session;
107
108
109
110
111
112
113
114
115 @Parameter( defaultValue = "${project.build.outputTimestamp}" )
116 private String outputTimestamp;
117
118
119
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
149 boolean parentInReactor = false;
150 MavenProject reactorParent = project;
151 while ( reactorProjects.contains( reactorParent.getParent() ) )
152 {
153 parentInReactor = true;
154 reactorParent = reactorParent.getParent();
155 }
156 String prop =
157 reactorParent.getOriginalModel().getProperties().getProperty( "project.build.outputTimestamp" );
158 if ( prop == null )
159 {
160 getLog().error( "project.build.outputTimestamp property should not be inherited but defined in "
161 + ( parentInReactor ? "parent POM from reactor " : "POM " ) + reactorParent.getFile() );
162 }
163 }
164
165 if ( !mono )
166 {
167
168 if ( isSkip( project ) )
169 {
170 getLog().info( "Skipping goal because module skips install and/or deploy" );
171 return;
172 }
173
174 MavenProject last = getLastProject();
175 if ( project != last )
176 {
177 skip( last );
178 return;
179 }
180 }
181
182
183 Map<Artifact, String> artifacts = generateBuildinfo( mono );
184 getLog().info( "Saved " + ( mono ? "" : "aggregate " ) + "info on build to " + buildinfoFile );
185
186 copyAggregateToRoot( buildinfoFile );
187
188 execute( artifacts );
189 }
190
191
192
193
194
195
196
197 abstract void execute( Map<Artifact, String> artifacts )
198 throws MojoExecutionException;
199
200 protected void skip( MavenProject last )
201 throws MojoExecutionException
202 {
203 getLog().info( "Skipping intermediate goal run, aggregate will be " + last.getArtifactId() );
204 }
205
206 protected void copyAggregateToRoot( File aggregate )
207 throws MojoExecutionException
208 {
209 if ( reactorProjects.size() == 1 )
210 {
211
212 return;
213 }
214
215
216 MavenProject root = getExecutionRoot();
217 String extension = aggregate.getName().substring( aggregate.getName().lastIndexOf( '.' ) );
218 File rootCopy = new File( root.getBuild().getDirectory(),
219 root.getArtifactId() + '-' + root.getVersion() + extension );
220 try
221 {
222 FileUtils.copyFile( aggregate, rootCopy );
223 getLog().info( "Aggregate " + extension.substring( 1 ) + " copied to " + rootCopy );
224 }
225 catch ( IOException ioe )
226 {
227 throw new MojoExecutionException( "Could not copy " + aggregate + "to " + rootCopy );
228 }
229 }
230
231
232
233
234
235
236
237
238
239 protected Map<Artifact, String> generateBuildinfo( boolean mono )
240 throws MojoExecutionException
241 {
242 MavenProject root = mono ? project : getExecutionRoot();
243
244 buildinfoFile.getParentFile().mkdirs();
245
246 try ( PrintWriter p = new PrintWriter( new BufferedWriter(
247 new OutputStreamWriter( new FileOutputStream( buildinfoFile ), StandardCharsets.UTF_8 ) ) ) )
248 {
249 BuildInfoWriter bi = new BuildInfoWriter( getLog(), p, mono, artifactHandlerManager );
250 bi.setIgnoreJavadoc( ignoreJavadoc );
251 bi.setIgnore( ignore );
252 bi.setToolchain( getToolchain() );
253
254 bi.printHeader( root, mono ? null : project, reproducible );
255
256
257 if ( mono )
258 {
259 bi.printArtifacts( project );
260 }
261 else
262 {
263 for ( MavenProject project : reactorProjects )
264 {
265 if ( !isSkip( project ) )
266 {
267 bi.printArtifacts( project );
268 }
269 }
270 }
271
272 if ( p.checkError() )
273 {
274 throw new MojoExecutionException( "Write error to " + buildinfoFile );
275 }
276
277 return bi.getArtifacts();
278 }
279 catch ( IOException e )
280 {
281 throw new MojoExecutionException( "Error creating file " + buildinfoFile, e );
282 }
283 }
284
285 protected MavenProject getExecutionRoot()
286 {
287 for ( MavenProject p : reactorProjects )
288 {
289 if ( p.isExecutionRoot() )
290 {
291 return p;
292 }
293 }
294 return null;
295 }
296
297 private MavenProject getLastProject()
298 {
299 int i = reactorProjects.size();
300 while ( i > 0 )
301 {
302 MavenProject project = reactorProjects.get( --i );
303 if ( !isSkip( project ) )
304 {
305 return project;
306 }
307 }
308 return null;
309 }
310
311 private boolean isSkip( MavenProject project )
312 {
313 return detectSkip && PluginUtil.isSkip( project );
314 }
315
316 private Toolchain getToolchain()
317 {
318 Toolchain tc = null;
319 if ( toolchainManager != null )
320 {
321 tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
322 }
323
324 return tc;
325 }
326 }