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