15a45a7e6afdc0947f140b13c984404ffde5abb7
[sdc.git] /
1 /*
2  * Copyright © 2018 European Support Limited
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on a "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.openecomp.sdc.onboarding;
18
19 import static org.openecomp.sdc.onboarding.BuildHelper.getArtifactPathInLocalRepo;
20 import static org.openecomp.sdc.onboarding.BuildHelper.getChecksum;
21 import static org.openecomp.sdc.onboarding.BuildHelper.getSourceChecksum;
22 import static org.openecomp.sdc.onboarding.BuildHelper.readState;
23 import static org.openecomp.sdc.onboarding.Constants.ANY_EXT;
24 import static org.openecomp.sdc.onboarding.Constants.COLON;
25 import static org.openecomp.sdc.onboarding.Constants.DOT;
26 import static org.openecomp.sdc.onboarding.Constants.EMPTY_JAR;
27 import static org.openecomp.sdc.onboarding.Constants.GENERATED_SOURCE_CHECKSUM;
28 import static org.openecomp.sdc.onboarding.Constants.INSTRUMENT_ONLY;
29 import static org.openecomp.sdc.onboarding.Constants.INSTRUMENT_WITH_TEST_ONLY;
30 import static org.openecomp.sdc.onboarding.Constants.JAR;
31 import static org.openecomp.sdc.onboarding.Constants.JAVA_EXT;
32 import static org.openecomp.sdc.onboarding.Constants.MAIN;
33 import static org.openecomp.sdc.onboarding.Constants.MAIN_CHECKSUM;
34 import static org.openecomp.sdc.onboarding.Constants.MAIN_SOURCE_CHECKSUM;
35 import static org.openecomp.sdc.onboarding.Constants.RESOURCES_CHANGED;
36 import static org.openecomp.sdc.onboarding.Constants.RESOURCE_CHECKSUM;
37 import static org.openecomp.sdc.onboarding.Constants.RESOURCE_ONLY;
38 import static org.openecomp.sdc.onboarding.Constants.RESOURCE_WITH_TEST_ONLY;
39 import static org.openecomp.sdc.onboarding.Constants.SHA1;
40 import static org.openecomp.sdc.onboarding.Constants.SKIP_INSTALL;
41 import static org.openecomp.sdc.onboarding.Constants.SKIP_MAIN_SOURCE_COMPILE;
42 import static org.openecomp.sdc.onboarding.Constants.SKIP_PMD;
43 import static org.openecomp.sdc.onboarding.Constants.SKIP_RESOURCE_COLLECTION;
44 import static org.openecomp.sdc.onboarding.Constants.SKIP_TEST_RUN;
45 import static org.openecomp.sdc.onboarding.Constants.SKIP_TEST_SOURCE_COMPILE;
46 import static org.openecomp.sdc.onboarding.Constants.TEST;
47 import static org.openecomp.sdc.onboarding.Constants.TEST_CHECKSUM;
48 import static org.openecomp.sdc.onboarding.Constants.TEST_ONLY;
49 import static org.openecomp.sdc.onboarding.Constants.TEST_RESOURCE_ONLY;
50 import static org.openecomp.sdc.onboarding.Constants.TEST_SOURCE_CHECKSUM;
51 import static org.openecomp.sdc.onboarding.Constants.UNICORN;
52
53 import java.io.File;
54 import java.io.IOException;
55 import java.io.UncheckedIOException;
56 import java.nio.file.Files;
57 import java.nio.file.Paths;
58 import java.nio.file.StandardOpenOption;
59 import java.security.NoSuchAlgorithmException;
60 import java.util.Arrays;
61 import java.util.Collection;
62 import java.util.HashMap;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.Optional;
66 import java.util.Set;
67 import org.apache.maven.artifact.Artifact;
68 import org.apache.maven.execution.MavenSession;
69 import org.apache.maven.model.Plugin;
70 import org.apache.maven.model.PluginExecution;
71 import org.apache.maven.plugin.AbstractMojo;
72 import org.apache.maven.plugin.InvalidPluginDescriptorException;
73 import org.apache.maven.plugin.MavenPluginManager;
74 import org.apache.maven.plugin.MojoExecutionException;
75 import org.apache.maven.plugin.MojoFailureException;
76 import org.apache.maven.plugin.MojoNotFoundException;
77 import org.apache.maven.plugin.PluginDescriptorParsingException;
78 import org.apache.maven.plugin.PluginResolutionException;
79 import org.apache.maven.plugin.descriptor.MojoDescriptor;
80 import org.apache.maven.plugins.annotations.Component;
81 import org.apache.maven.plugins.annotations.LifecyclePhase;
82 import org.apache.maven.plugins.annotations.Mojo;
83 import org.apache.maven.plugins.annotations.Parameter;
84 import org.apache.maven.plugins.annotations.ResolutionScope;
85 import org.apache.maven.project.MavenProject;
86
87
88 @Mojo(name = "pre-compile-helper", threadSafe = true, defaultPhase = LifecyclePhase.GENERATE_RESOURCES,
89         requiresDependencyResolution = ResolutionScope.TEST)
90 public class PreCompileHelperMojo extends AbstractMojo {
91
92     @Parameter(defaultValue = "${project}")
93     private MavenProject project;
94     @Parameter(defaultValue = "${project.artifact.groupId}:${project.artifact.artifactId}")
95     private String moduleCoordinates;
96     @Parameter(defaultValue = "${session}")
97     private MavenSession session;
98     @Parameter
99     private String excludePackaging;
100     @Parameter
101     private List<String> excludeDependencies;
102     @Parameter
103     private BuildState buildState;
104     @Parameter
105     private File mainSourceLocation;
106     @Parameter
107     private File testSourceLocation;
108     @Parameter
109     private File generatedSourceLocation;
110     @Component
111     private MavenPluginManager pluginManager;
112     @Parameter
113     private File mainResourceLocation;
114     @Parameter
115     private File testResourceLocation;
116     private Map<String, Object> resourceBuildData;
117
118     private static Map<String, String> checksumMap;
119     private long mainChecksum = 0;
120     private long testChecksum = 0;
121     private long resourceChecksum = 0;
122     Optional<String> artifactPath;
123
124     static {
125         checksumMap = readCurrentPMDState("pmd.dat");
126     }
127
128     public void execute() throws MojoExecutionException, MojoFailureException {
129
130
131         if (project.getPackaging().equals(excludePackaging)) {
132             return;
133         }
134         init();
135         processPMDCheck();
136         project.getProperties().setProperty(EMPTY_JAR, "");
137         if (!System.getProperties().containsKey(UNICORN)) {
138             return;
139         }
140         resourceChecksum = getChecksum(mainResourceLocation, ANY_EXT);
141         project.getProperties().setProperty(RESOURCE_CHECKSUM, String.valueOf(resourceChecksum));
142         byte[] sourceChecksum = calculateChecksum(mainChecksum, resourceChecksum).getBytes();
143         boolean instrumented = isCurrentModuleInstrumented();
144         artifactPath = getArtifactPathInLocalRepo(session.getLocalRepository().getUrl(), project, sourceChecksum);
145
146         boolean isFirstBuild = buildState.getBuildTime(moduleCoordinates) == 0 || !artifactPath.isPresent();
147
148         Map<String, Object> moduleBuildData = getCurrentModuleBuildData();
149         Map<String, Object> lastTimeModuleBuildData = buildState.readModuleBuildData();
150         resourceBuildData = getCurrentResourceBuildData();
151         Map<String, Object> lastTimeResourceBuildData = buildState.readResourceBuildData();
152
153         boolean buildDataSameWithPreviousBuild =
154                 isBuildDataSameWithPreviousBuild(lastTimeModuleBuildData, moduleBuildData);
155         boolean resourceMainBuildDataSameWithPreviousBuild =
156                 isResourceMainBuildDataSameWithPreviousBuild(lastTimeResourceBuildData);
157
158         boolean mainToBeCompiled = isCompileNeeded(HashMap.class.cast(moduleBuildData.get(MAIN)).keySet(), isFirstBuild,
159                 buildDataSameWithPreviousBuild);
160
161         boolean resourceDataSame = resourceBuildData.equals(lastTimeResourceBuildData);
162
163         boolean testToBeCompiled =
164                 lastTimeModuleBuildData == null || !moduleBuildData.get(TEST).equals(lastTimeModuleBuildData.get(TEST));
165         setMainBuildAttribute(mainToBeCompiled, testToBeCompiled);
166         generateSignature(sourceChecksum);
167         setTestBuild(resourceDataSame, resourceMainBuildDataSameWithPreviousBuild, testToBeCompiled, mainToBeCompiled);
168         setInstrumentedBuild(testToBeCompiled, mainToBeCompiled, instrumented);
169
170         if (!moduleBuildData.equals(lastTimeModuleBuildData) || isFirstBuild) {
171             buildState.addModuleBuildData(moduleCoordinates, moduleBuildData);
172         }
173         setResourceBuild(resourceMainBuildDataSameWithPreviousBuild, mainToBeCompiled, testToBeCompiled);
174         setJarFlags(mainToBeCompiled, instrumented, !resourceMainBuildDataSameWithPreviousBuild);
175         setInstallFlags(mainToBeCompiled, instrumented, project.getPackaging(),
176                 !resourceMainBuildDataSameWithPreviousBuild);
177
178         setArtifactPath(mainToBeCompiled, instrumented, JAR.equals(project.getPackaging()),
179                 resourceMainBuildDataSameWithPreviousBuild);
180     }
181
182     private void generateSignature(byte[] sourceChecksum) {
183         try {
184             Paths.get(project.getBuild().getDirectory()).toFile().mkdirs();
185             Files.write(Paths.get(project.getBuild().getDirectory(), project.getBuild().getFinalName() + DOT + UNICORN),
186                     sourceChecksum, StandardOpenOption.CREATE);
187         } catch (IOException e) {
188             throw new UncheckedIOException(e);
189         }
190     }
191
192     private String calculateChecksum(long mainChecksum, long resourceChecksum) throws MojoExecutionException {
193         try {
194             return getSourceChecksum(mainChecksum + COLON + resourceChecksum, SHA1);
195         } catch (NoSuchAlgorithmException e) {
196             throw new MojoExecutionException(e.getMessage(), e);
197         }
198     }
199
200     private boolean isResourceMainBuildDataSameWithPreviousBuild(Map<String, Object> lastTimeResourceBuildData) {
201         return lastTimeResourceBuildData != null && (lastTimeResourceBuildData.get(MAIN) != null && resourceBuildData
202                                                                                                             .get(MAIN)
203                                                                                                             .equals(lastTimeResourceBuildData
204                                                                                                                             .get(MAIN)));
205     }
206
207     private boolean isBuildDataSameWithPreviousBuild(Map<String, Object> lastTimeModuleBuildData,
208             Map<String, Object> moduleBuildData) {
209         return lastTimeModuleBuildData != null && (lastTimeModuleBuildData.get(MAIN) != null && moduleBuildData
210                                                                                                         .get(MAIN)
211                                                                                                         .equals(lastTimeModuleBuildData
212                                                                                                                         .get(MAIN)));
213     }
214
215     private void setInstrumentedBuild(boolean testToBeCompiled, boolean mainToBeCompiled, boolean instrumented) {
216         if (!testToBeCompiled && !mainToBeCompiled && instrumented) {
217             project.getProperties().setProperty(INSTRUMENT_ONLY, Boolean.TRUE.toString());
218             project.getProperties().remove(SKIP_MAIN_SOURCE_COMPILE);
219             project.getProperties().remove(SKIP_TEST_SOURCE_COMPILE);
220         }
221         if (testToBeCompiled && !mainToBeCompiled && instrumented) {
222             project.getProperties().setProperty(INSTRUMENT_WITH_TEST_ONLY, Boolean.TRUE.toString());
223             project.getProperties().remove(SKIP_MAIN_SOURCE_COMPILE);
224         }
225         if (instrumented) {
226             buildState.markTestsMandatoryModule(moduleCoordinates);
227             project.getProperties().setProperty(SKIP_TEST_RUN, Boolean.FALSE.toString());
228         }
229     }
230
231     private void setArtifactPath(boolean mainToBeCompiled, boolean instrumented, boolean isJar,
232             boolean resourceDataSame) {
233         if (!mainToBeCompiled && !instrumented && isJar && resourceDataSame) {
234             project.getProperties().setProperty("artifactPathToCopy", artifactPath.orElse(null));
235         }
236     }
237
238     private void setResourceBuild(boolean resourceMainBuildDataSameWithPreviousBuild, boolean mainToBeCompiled,
239             boolean testToBeCompiled) {
240         if (resourceMainBuildDataSameWithPreviousBuild) {
241             project.getProperties().setProperty(SKIP_RESOURCE_COLLECTION, Boolean.TRUE.toString());
242         } else {
243             project.getProperties().setProperty(RESOURCES_CHANGED, Boolean.TRUE.toString());
244         }
245         if (!resourceMainBuildDataSameWithPreviousBuild && !mainToBeCompiled) {
246             project.getProperties().remove(SKIP_MAIN_SOURCE_COMPILE);
247             if (!testToBeCompiled) {
248                 project.getProperties().remove(SKIP_TEST_SOURCE_COMPILE);
249                 project.getProperties().setProperty(RESOURCE_ONLY, Boolean.TRUE.toString());
250             } else {
251                 project.getProperties().setProperty(RESOURCE_WITH_TEST_ONLY, Boolean.TRUE.toString());
252             }
253         }
254     }
255
256     private void setTestBuild(boolean resourceDataSame, boolean resourceMainBuildDataSameWithPreviousBuild,
257             boolean testToBeCompiled, boolean mainToBeCompiled) {
258         if (!resourceDataSame) {
259             buildState.addResourceBuildData(moduleCoordinates, resourceBuildData);
260             project.getProperties().setProperty(SKIP_TEST_RUN, Boolean.FALSE.toString());
261             if (resourceMainBuildDataSameWithPreviousBuild && !testToBeCompiled && !mainToBeCompiled) {
262                 project.getProperties().setProperty(TEST_RESOURCE_ONLY, Boolean.TRUE.toString());
263                 project.getProperties().remove(SKIP_MAIN_SOURCE_COMPILE);
264                 project.getProperties().remove(SKIP_TEST_SOURCE_COMPILE);
265             }
266         }
267     }
268
269     private void setMainBuildAttribute(boolean mainToBeCompiled, boolean testToBeCompiled) {
270         if (!mainToBeCompiled) {
271             project.getProperties().setProperty(SKIP_MAIN_SOURCE_COMPILE, Boolean.TRUE.toString());
272         }
273         if (testToBeCompiled && !mainToBeCompiled) {
274             project.getProperties().setProperty(TEST_ONLY, Boolean.TRUE.toString());
275             project.getProperties().remove(SKIP_MAIN_SOURCE_COMPILE);
276         }
277
278         if (mainToBeCompiled || testToBeCompiled) {
279             project.getProperties().setProperty(SKIP_TEST_RUN, Boolean.FALSE.toString());
280         } else {
281             project.getProperties().setProperty(SKIP_TEST_SOURCE_COMPILE, Boolean.TRUE.toString());
282         }
283     }
284
285     private void setJarFlags(boolean compile, boolean instrumented, boolean resourceChanged) {
286         if (compile || instrumented || resourceChanged) {
287             project.getProperties().setProperty(EMPTY_JAR, "");
288         } else {
289             project.getProperties().setProperty(EMPTY_JAR, "**/*");
290             project.getProperties().setProperty("mvnDsc", "false");
291         }
292     }
293
294     private void setInstallFlags(boolean compile, boolean instrumented, String packaging, boolean resourceChanged) {
295         if (!compile && !instrumented && !resourceChanged && JAR.equals(packaging)) {
296             project.getProperties().setProperty(SKIP_INSTALL, Boolean.TRUE.toString());
297         }
298     }
299
300     private boolean isCompileNeeded(Collection<String> dependencyCoordinates, boolean isFirstBuild,
301             boolean buildDataSame) {
302         return isFirstBuild || !buildDataSame || buildState.isCompileMust(moduleCoordinates, dependencyCoordinates);
303     }
304
305     private boolean isCurrentModuleInstrumented() {
306         try {
307             return scanModuleFor(LifecyclePhase.PROCESS_CLASSES.id(), LifecyclePhase.PROCESS_TEST_CLASSES.id(),
308                     LifecyclePhase.COMPILE.id(), LifecyclePhase.TEST_COMPILE.id());
309         } catch (Exception e) {
310             getLog().debug(e);
311             return true;
312         }
313     }
314
315     boolean isCodeGenerator() {
316         try {
317             return scanModuleFor(LifecyclePhase.GENERATE_RESOURCES.id(), LifecyclePhase.GENERATE_SOURCES.id(),
318                     LifecyclePhase.GENERATE_TEST_RESOURCES.id(), LifecyclePhase.GENERATE_TEST_SOURCES.id());
319         } catch (Exception e) {
320             getLog().debug(e);
321             return true;
322         }
323     }
324
325     private Map<String, Object> getCurrentModuleBuildData() {
326         Map<String, Object> moduleBuildData = new HashMap<>();
327         moduleBuildData.put(MAIN, new HashMap<String, String>());
328         moduleBuildData.put(TEST, new HashMap<String, String>());
329         HashMap.class.cast(moduleBuildData.get(MAIN))
330                      .put(MAIN_SOURCE_CHECKSUM, project.getProperties().getProperty(MAIN_CHECKSUM));
331         HashMap.class.cast(moduleBuildData.get(TEST))
332                      .put(TEST_SOURCE_CHECKSUM, project.getProperties().getProperty(TEST_CHECKSUM));
333         if (isCodeGenerator()) {
334             HashMap.class.cast(moduleBuildData.get(MAIN))
335                          .put(GENERATED_SOURCE_CHECKSUM, getChecksum(generatedSourceLocation, JAVA_EXT));
336         }
337         if (project.getArtifacts() == null || project.getArtifacts().isEmpty()) {
338             return moduleBuildData;
339         }
340         for (Artifact dependency : project.getArtifacts()) {
341             String fileNme = dependency.getFile().getName();
342             if (excludeDependencies.contains(dependency.getScope())) {
343                 HashMap.class.cast(moduleBuildData.get(TEST))
344                              .put(dependency.getGroupId() + COLON + dependency.getArtifactId(),
345                                      fileNme.endsWith(dependency.getVersion() + DOT + JAR) ? dependency.getVersion() :
346                                              fileNme);
347                 continue;
348             }
349             HashMap.class.cast(moduleBuildData.get(MAIN))
350                          .put(dependency.getGroupId() + COLON + dependency.getArtifactId(),
351                                  fileNme.endsWith(dependency.getVersion() + DOT + JAR) ? dependency.getVersion() :
352                                          fileNme);
353         }
354         return moduleBuildData;
355     }
356
357     private static Map<String, String> readCurrentPMDState(String fileName) {
358         Optional<HashMap> val = readState(fileName, HashMap.class);
359         return val.orElseGet(HashMap::new);
360     }
361
362     private boolean isPMDMandatory(Set<Artifact> dependencies) {
363         for (Artifact artifact : dependencies) {
364             if (BuildState.isPMDRun(artifact.getGroupId() + COLON + artifact.getArtifactId())) {
365                 return true;
366             }
367         }
368         return false;
369     }
370
371     private boolean scanModuleFor(String... types)
372             throws InvalidPluginDescriptorException, PluginResolutionException, MojoNotFoundException,
373                            PluginDescriptorParsingException {
374         for (Plugin plugin : project.getBuildPlugins()) {
375             if (!("org.apache.maven.plugins".equals(plugin.getGroupId()) && plugin.getArtifactId().startsWith("maven"))
376                         && !plugin.getGroupId().startsWith("org.openecomp.sdc")) {
377                 boolean success = scanPlugin(plugin, types);
378                 if (success) {
379                     return true;
380                 }
381             }
382         }
383         return false;
384     }
385
386     private boolean scanPlugin(Plugin plugin, String... types)
387             throws InvalidPluginDescriptorException, PluginDescriptorParsingException, MojoNotFoundException,
388                            PluginResolutionException {
389         for (PluginExecution pluginExecution : plugin.getExecutions()) {
390             if (pluginExecution.getPhase() != null) {
391                 boolean phaseAvailable = Arrays.asList(types).contains(pluginExecution.getPhase());
392                 if (phaseAvailable) {
393                     return true;
394                 }
395             }
396             for (String goal : pluginExecution.getGoals()) {
397                 MojoDescriptor md = pluginManager.getMojoDescriptor(plugin, goal, project.getRemotePluginRepositories(),
398                         session.getRepositorySession());
399                 if (Arrays.asList(types).contains(md.getPhase())) {
400                     return true;
401                 }
402             }
403         }
404         return false;
405     }
406
407     private Map<String, Object> getCurrentResourceBuildData() {
408         HashMap<String, Object> resourceBuildStateData = new HashMap<>();
409         resourceBuildStateData.put(MAIN, project.getProperties().getProperty(RESOURCE_CHECKSUM));
410         resourceBuildStateData.put(TEST, getChecksum(testResourceLocation, ANY_EXT));
411         resourceBuildStateData.put("dependency", getDependencies().hashCode());
412         return resourceBuildStateData;
413     }
414
415     private Map<String, String> getDependencies() {
416         Map<String, String> dependencies = new HashMap<>();
417         for (Artifact d : project.getArtifacts()) {
418             dependencies.put(d.getGroupId() + COLON + d.getArtifactId(), d.getVersion());
419         }
420         return dependencies;
421     }
422
423     private void init() {
424         if (mainSourceLocation == null) {
425             mainSourceLocation = Paths.get(project.getBuild().getSourceDirectory()).toFile();
426         }
427         if (testSourceLocation == null) {
428             testSourceLocation = Paths.get(project.getBuild().getTestSourceDirectory()).toFile();
429         }
430         if (mainResourceLocation == null) {
431             mainResourceLocation = Paths.get(project.getBuild().getResources().get(0).getDirectory()).toFile();
432         }
433         if (testResourceLocation == null) {
434             testResourceLocation = Paths.get(project.getBuild().getTestResources().get(0).getDirectory()).toFile();
435         }
436     }
437
438     private void processPMDCheck() {
439         mainChecksum = getChecksum(mainSourceLocation, JAVA_EXT);
440         testChecksum = getChecksum(testSourceLocation, JAVA_EXT);
441         project.getProperties().setProperty(MAIN_CHECKSUM, String.valueOf(mainChecksum));
442         project.getProperties().setProperty(TEST_CHECKSUM, String.valueOf(testChecksum));
443         String checksum = mainChecksum + COLON + testChecksum;
444         if (!checksum.equals(checksumMap.get(moduleCoordinates)) || isPMDMandatory(project.getArtifacts())) {
445             project.getProperties().setProperty(SKIP_PMD, Boolean.FALSE.toString());
446             BuildState.recordPMDRun(moduleCoordinates);
447         }
448     }
449 }