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 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
51
52
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
71 @Parameter( property = "check.buildplan.tasks", defaultValue = "deploy" )
72 private String[] tasks;
73
74
75
76
77
78
79 @Parameter( defaultValue = "${project.build.outputTimestamp}" )
80 private String outputTimestamp;
81
82
83
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
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
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
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 }