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