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.artifact.Artifact;
23 import org.apache.maven.artifact.factory.ArtifactFactory;
24 import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
25 import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
26 import org.apache.maven.plugin.MojoExecutionException;
27 import org.apache.maven.plugin.logging.Log;
28 import org.apache.maven.project.MavenProject;
29 import org.apache.maven.shared.utils.io.IOUtil;
30 import org.apache.maven.shared.utils.io.FileUtils;
31 import org.eclipse.aether.AbstractForwardingRepositorySystemSession;
32 import org.eclipse.aether.RepositorySystem;
33 import org.eclipse.aether.RepositorySystemSession;
34 import org.eclipse.aether.artifact.DefaultArtifact;
35 import org.eclipse.aether.repository.RemoteRepository;
36 import org.eclipse.aether.repository.WorkspaceReader;
37 import org.eclipse.aether.resolution.ArtifactRequest;
38 import org.eclipse.aether.resolution.ArtifactResult;
39
40 import java.io.BufferedWriter;
41 import java.io.File;
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.OutputStreamWriter;
46 import java.io.PrintWriter;
47 import java.nio.charset.StandardCharsets;
48 import java.util.Collections;
49 import java.util.HashMap;
50 import java.util.HashSet;
51 import java.util.Map;
52 import java.util.Set;
53 import java.util.jar.Attributes;
54 import java.util.jar.JarFile;
55 import java.util.jar.Manifest;
56 import java.util.zip.ZipEntry;
57
58
59
60
61 class ReferenceBuildinfoUtil
62 {
63 private static final Set<String> JAR_TYPES;
64
65 static
66 {
67 Set<String> types = new HashSet<>();
68 types.add( "jar" );
69 types.add( "test-jar" );
70 types.add( "war" );
71 types.add( "ear" );
72 types.add( "rar" );
73 types.add( "maven-plugin" );
74 JAR_TYPES = Collections.unmodifiableSet( types );
75 }
76
77 private final Log log;
78
79
80
81
82 private final File referenceDir;
83
84 private final Map<Artifact, String> artifacts;
85
86 private final ArtifactFactory artifactFactory;
87
88 private final RepositorySystem repoSystem;
89
90 private final RepositorySystemSession repoSession;
91
92 private final ArtifactHandlerManager artifactHandlerManager;
93
94 ReferenceBuildinfoUtil( Log log, File referenceDir, Map<Artifact, String> artifacts,
95 ArtifactFactory artifactFactory, RepositorySystem repoSystem,
96 RepositorySystemSession repoSession,
97 ArtifactHandlerManager artifactHandlerManager )
98 {
99 this.log = log;
100 this.referenceDir = referenceDir;
101 this.artifacts = artifacts;
102 this.artifactFactory = artifactFactory;
103 this.repoSystem = repoSystem;
104 this.repoSession = repoSession;
105 this.artifactHandlerManager = artifactHandlerManager;
106 }
107
108 File downloadOrCreateReferenceBuildinfo( RemoteRepository repo, MavenProject project, File buildinfoFile,
109 boolean mono )
110 throws MojoExecutionException
111 {
112 File referenceBuildinfo = downloadReferenceBuildinfo( repo, project );
113
114 if ( referenceBuildinfo != null )
115 {
116 log.warn( "dropping downloaded reference buildinfo because it may be generated"
117 + " from different maven-artifact-plugin release..." );
118
119 referenceBuildinfo = null;
120 }
121
122 if ( referenceBuildinfo == null )
123 {
124
125 String javaVersion = null;
126 String osName = null;
127 String currentJavaVersion = null;
128 String currentOsName = null;
129 Map<Artifact, File> referenceArtifacts = new HashMap<>();
130 for ( Artifact artifact : artifacts.keySet() )
131 {
132 try
133 {
134
135 File file = downloadReference( repo, artifact );
136 referenceArtifacts.put( artifact, file );
137
138
139 if ( ( javaVersion == null ) && JAR_TYPES.contains( artifact.getType() ) )
140 {
141 ReproducibleEnv env = extractEnv( file, artifact );
142 if ( ( env != null ) && ( env.javaVersion != null ) )
143 {
144 javaVersion = env.javaVersion;
145 osName = env.osName;
146
147 ReproducibleEnv currentEnv = extractEnv( artifact.getFile(), artifact );
148 currentJavaVersion = currentEnv.javaVersion;
149 currentOsName = currentEnv.osName;
150 }
151 }
152 }
153 catch ( ArtifactNotFoundException e )
154 {
155 log.warn( "Reference artifact not found " + artifact );
156 }
157 }
158
159
160 referenceBuildinfo = getReference( buildinfoFile );
161 try ( PrintWriter p =
162 new PrintWriter( new BufferedWriter( new OutputStreamWriter( new FileOutputStream( referenceBuildinfo ),
163 StandardCharsets.UTF_8 ) ) ) )
164 {
165 BuildInfoWriter bi = new BuildInfoWriter( log, p, mono, artifactHandlerManager );
166
167 if ( javaVersion != null || osName != null )
168 {
169 p.println( "# effective build environment information" );
170 if ( javaVersion != null )
171 {
172 p.println( "java.version=" + javaVersion );
173 log.info( "Reference build java.version: " + javaVersion );
174 if ( !javaVersion.equals( currentJavaVersion ) )
175 {
176 log.error( "Current build java.version: " + currentJavaVersion );
177 }
178 }
179 if ( osName != null )
180 {
181 p.println( "os.name=" + osName );
182 log.info( "Reference build os.name: " + osName );
183
184
185 if ( !osName.equals( currentOsName ) )
186 {
187 log.error( "Current build os.name: " + currentOsName );
188 }
189 String expectedLs = osName.startsWith( "Windows" ) ? "\r\n" : "\n";
190 if ( !expectedLs.equals( System.lineSeparator() ) )
191 {
192 log.warn( "Current System.lineSeparator() does not match reference build OS" );
193
194 String ls = System.getProperty( "line.separator" );
195 if ( !ls.equals( System.lineSeparator() ) )
196 {
197 log.warn( "System.lineSeparator() != System.getProperty( \"line.separator\" ): "
198 + "too late standard system property update..." );
199 }
200 }
201 }
202 }
203
204 for ( Map.Entry<Artifact, String> entry : artifacts.entrySet() )
205 {
206 Artifact artifact = entry.getKey();
207 String prefix = entry.getValue();
208 File referenceFile = referenceArtifacts.get( artifact );
209 if ( referenceFile != null )
210 {
211 bi.printFile( prefix, referenceFile );
212 }
213 }
214
215 if ( p.checkError() )
216 {
217 throw new MojoExecutionException( "Write error to " + referenceBuildinfo );
218 }
219
220 log.info( "Minimal buildinfo generated from downloaded artifacts: " + referenceBuildinfo );
221 }
222 catch ( IOException e )
223 {
224 throw new MojoExecutionException( "Error creating file " + referenceBuildinfo, e );
225 }
226 }
227
228 return referenceBuildinfo;
229 }
230
231 private ReproducibleEnv extractEnv( File file, Artifact artifact )
232 {
233 log.debug( "Guessing java.version and os.name from jar " + file );
234 try ( JarFile jar = new JarFile( file ) )
235 {
236 Manifest manifest = jar.getManifest();
237 if ( manifest != null )
238 {
239 String javaVersion = extractJavaVersion( manifest );
240 String osName = extractOsName( artifact, jar );
241 return new ReproducibleEnv( javaVersion, osName );
242 }
243 else
244 {
245 log.warn( "no MANIFEST.MF found in jar " + file );
246 }
247 }
248 catch ( IOException e )
249 {
250 log.warn( "unable to open jar file " + file, e );
251 }
252 return null;
253 }
254
255 private String extractJavaVersion( Manifest manifest )
256 {
257 Attributes attr = manifest.getMainAttributes();
258
259 String value = attr.getValue( "Build-Jdk-Spec" );
260 if ( value != null )
261 {
262 return value + " (from MANIFEST.MF Build-Jdk-Spec)";
263 }
264
265 value = attr.getValue( "Build-Jdk" );
266 if ( value != null )
267 {
268 return String.valueOf( value ) + " (from MANIFEST.MF Build-Jdk)";
269 }
270
271 return null;
272 }
273
274 private String extractOsName( Artifact a, JarFile jar )
275 {
276 String entryName = "META-INF/maven/" + a.getGroupId() + '/' + a.getArtifactId() + "/pom.properties";
277 ZipEntry zipEntry = jar.getEntry( entryName );
278 if ( zipEntry == null )
279 {
280 return null;
281 }
282 try ( InputStream in = jar.getInputStream( zipEntry ) )
283 {
284 String content = IOUtil.toString( in, StandardCharsets.UTF_8.name() );
285 log.debug( "Manifest content: " + content );
286 if ( content.contains( "\r\n" ) )
287 {
288 return "Windows (from pom.properties newline)";
289 }
290 else if ( content.contains( "\n" ) )
291 {
292 return "Unix (from pom.properties newline)";
293 }
294 }
295 catch ( IOException e )
296 {
297 log.warn( "Unable to read " + entryName + " from " + jar, e );
298 }
299 return null;
300 }
301
302 private File downloadReferenceBuildinfo( RemoteRepository repo, MavenProject project )
303 throws MojoExecutionException
304 {
305 Artifact buildinfo =
306 artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(),
307 project.getVersion(), "buildinfo", "" );
308 try
309 {
310 File file = downloadReference( repo, buildinfo );
311
312 log.info( "Reference buildinfo file found, copied to " + file );
313
314 return file;
315 }
316 catch ( ArtifactNotFoundException e )
317 {
318 log.info( "Reference buildinfo file not found: "
319 + "it will be generated from downloaded reference artifacts" );
320 }
321
322 return null;
323 }
324
325 private File downloadReference( RemoteRepository repo, Artifact artifact )
326 throws MojoExecutionException, ArtifactNotFoundException
327 {
328 try
329 {
330 ArtifactRequest request = new ArtifactRequest();
331 request.setArtifact( new DefaultArtifact( artifact.getGroupId(), artifact.getArtifactId(),
332 artifact.getClassifier(),
333 ( artifact.getArtifactHandler() != null )
334 ? artifact.getArtifactHandler().getExtension()
335 : artifact.getType(),
336 artifact.getVersion() ) );
337 request.setRepositories( Collections.singletonList( repo ) );
338
339 ArtifactResult result =
340 repoSystem.resolveArtifact( new NoWorkspaceRepositorySystemSession( repoSession ), request );
341 File resultFile = result.getArtifact().getFile();
342 File destFile = getReference( resultFile );
343
344 FileUtils.copyFile( resultFile, destFile );
345
346 return destFile;
347 }
348 catch ( org.eclipse.aether.resolution.ArtifactResolutionException are )
349 {
350 if ( are.getResult().isMissing() )
351 {
352 throw new ArtifactNotFoundException( "Artifact not found " + artifact, artifact );
353 }
354 throw new MojoExecutionException( "Error resolving reference artifact " + artifact, are );
355 }
356 catch ( IOException ioe )
357 {
358 throw new MojoExecutionException( "Error copying reference artifact " + artifact, ioe );
359 }
360 }
361
362 private File getReference( File file )
363 {
364 return new File( referenceDir, file.getName() );
365 }
366
367 private static class NoWorkspaceRepositorySystemSession
368 extends AbstractForwardingRepositorySystemSession
369 {
370 private final RepositorySystemSession rss;
371
372 NoWorkspaceRepositorySystemSession( RepositorySystemSession rss )
373 {
374 this.rss = rss;
375 }
376
377 @Override
378 protected RepositorySystemSession getSession()
379 {
380 return rss;
381 }
382
383 @Override
384 public WorkspaceReader getWorkspaceReader()
385 {
386 return null;
387 }
388 }
389
390 private static class ReproducibleEnv
391 {
392 @SuppressWarnings( "checkstyle:visibilitymodifier" )
393 public final String javaVersion;
394
395 @SuppressWarnings( "checkstyle:visibilitymodifier" )
396 public final String osName;
397
398 ReproducibleEnv( String javaVersion, String osName )
399 {
400 this.javaVersion = javaVersion;
401 this.osName = osName;
402 }
403 }
404 }