1 package com.github.mikkoi.maven.enforcer.rules;
2
3 import javax.annotation.Nullable;
4 import javax.inject.Inject;
5 import javax.inject.Named;
6
7 import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule;
8 import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
9 import org.apache.maven.execution.MavenSession;
10 import org.apache.maven.model.Dependency;
11 import org.apache.maven.project.MavenProject;
12
13 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.List;
17 import java.util.Objects;
18 import java.util.function.Predicate;
19
20
21
22
23 @Named("dependOnAllProjects")
24 public class DependOnAllProjects extends AbstractEnforcerRule {
25
26
27
28
29 private static final String INDENT_DEPENDENCY = " ";
30
31
32
33 private static final int MAX_NUM_PARTS_IN_DEPENDENCY_DECLARATION = 3;
34
35
36
37 private static final String FALSE = "false";
38
39
40
41 private static final String TRUE = "true";
42
43
44
45 private final MavenSession mavenSession;
46
47
48
49
50 private List<String> includes;
51
52
53
54
55
56
57 private List<String> excludes;
58
59
60
61
62 @SuppressWarnings("unused")
63 private String errorIfUnknownProject;
64
65
66
67 @SuppressWarnings("unused")
68 private String includeRootProject;
69
70
71
72
73
74
75 @Inject
76 @SuppressFBWarnings
77 public DependOnAllProjects(MavenSession session) {
78 this.mavenSession = Objects.requireNonNull(session);
79 }
80
81
82
83
84
85 @Inject
86 public void setIncludes(@Nullable List<String> includes) {
87 if (includes == null) {
88 this.includes = new ArrayList<>();
89 } else {
90 this.includes = new ArrayList<>(includes);
91 }
92 }
93
94
95
96
97
98 @Inject
99 public void setExcludes(@Nullable List<String> excludes) {
100 if (excludes == null) {
101 this.excludes = new ArrayList<>();
102 } else {
103 this.excludes = new ArrayList<>(excludes);
104 }
105 }
106
107
108
109
110
111 @Inject
112 public void setErrorIfUnknownProject(String errorIfUnknownProject) {
113 if (errorIfUnknownProject != null) {
114 this.errorIfUnknownProject = errorIfUnknownProject;
115 } else {
116 this.errorIfUnknownProject = FALSE;
117 }
118 }
119
120
121
122
123
124 @Inject
125 public void setIncludeRootProject(String includeRootProject) {
126 if (includeRootProject != null) {
127 this.includeRootProject = includeRootProject;
128 } else {
129 this.includeRootProject = FALSE;
130 }
131 }
132
133
134
135
136
137
138
139
140 public static String formatDependency(Dependency dependency, String indent) {
141 final String newLine = System.lineSeparator();
142 final StringBuilder sb = new StringBuilder();
143 sb.append("<dependency>").append(newLine);
144 sb.append(indent).append(String.format("<groupId>%s</groupId>", dependency.getGroupId()))
145 .append(newLine);
146 sb.append(indent)
147 .append(String.format("<artifactId>%s</artifactId>", dependency.getArtifactId()))
148 .append(newLine);
149 if (!"jar".equals(dependency.getType())) {
150 sb.append(indent).append(String.format("<type>%s</type>", dependency.getType()))
151 .append(newLine);
152 }
153 sb.append("</dependency>");
154 return sb.toString();
155 }
156
157
158
159
160
161
162
163 public static String convertStringForMatching(String s) {
164 String t = s.replace(".", "\\.");
165 t = t.replace("*", ".*");
166 if (!t.contains(":")) {
167 t = ".*:" + t + ":.*";
168 }
169 if (Arrays.stream(t.split(":")).count() < MAX_NUM_PARTS_IN_DEPENDENCY_DECLARATION) {
170 t = t + ":.*";
171 }
172 return t;
173 }
174
175
176
177
178
179
180
181
182
183 public static boolean projectsAreEquals(MavenProject a, MavenProject b) {
184 return a.getGroupId().equals(b.getGroupId())
185 && a.getArtifactId().equals(b.getArtifactId()) && a.getVersion().equals(b.getVersion())
186 && a.getPackaging().equals(b.getPackaging());
187 }
188
189
190
191
192
193
194
195
196
197 public static boolean dependenciesAreEquals(Dependency a, Dependency b) {
198 return a.getGroupId().equals(b.getGroupId())
199 && a.getArtifactId().equals(b.getArtifactId()) && a.getVersion().equals(b.getVersion())
200 && a.getType().equals(b.getType());
201 }
202
203
204
205
206
207
208
209
210 public static boolean dependenciesContains(Iterable<Dependency> projects,
211 MavenProject project) {
212 for (Dependency p : projects) {
213 if (dependenciesAreEquals(p, projectToDependency(project))) {
214 return true;
215 }
216 }
217 return false;
218 }
219
220
221
222
223
224
225
226
227
228 public static boolean projectsContains(Iterable<MavenProject> projects, String projectName) {
229 for (MavenProject project : projects) {
230 if (projectMatchesWithDefinition(project, projectName)) {
231 return true;
232 }
233 }
234 return false;
235 }
236
237
238
239
240
241
242
243
244
245
246
247
248 public static boolean projectMatchesWithDefinition(MavenProject project, String projectDefinition) {
249 List<String> ids = Arrays.asList(projectDefinition.split(":"));
250 if (ids.size() == 1) {
251 return project.getArtifactId().equals(ids.get(0));
252 } else if (ids.size() == 2) {
253 return project.getGroupId().equals(ids.get(0))
254 && project.getArtifactId().equals(ids.get(1));
255 } else {
256
257 return project.getGroupId().equals(ids.get(0))
258 && project.getArtifactId().equals(ids.get(1))
259 && project.getPackaging().equals(ids.get(2));
260 }
261 }
262
263
264
265
266
267
268
269 public static Dependency projectToDependency(MavenProject mavenProject) {
270 Dependency d = new Dependency();
271 d.setGroupId(mavenProject.getGroupId());
272 d.setArtifactId(mavenProject.getArtifactId());
273 d.setVersion(mavenProject.getVersion());
274 d.setType(mavenProject.getPackaging());
275 return d;
276 }
277
278
279
280
281
282
283
284
285
286
287 public static boolean isProjectIncluded(List<String> includes, List<String> excludes,
288 MavenProject mavenProject) {
289 String projectId =
290 String.format("%s:%s:%s", mavenProject.getGroupId(), mavenProject.getArtifactId(),
291 mavenProject.getPackaging());
292
293
294 Predicate<String> predicateForProjectId =
295 s -> projectId.matches(convertStringForMatching(s));
296 return includes.stream().anyMatch(predicateForProjectId)
297 && excludes.stream().noneMatch(predicateForProjectId);
298 }
299
300
301
302
303
304
305
306 void validateAndPrepareParameters() throws EnforcerRuleException {
307 getLog().debug("includes=" + includes);
308 getLog().debug("excludes=" + excludes);
309 getLog().debug("errorIfUnknownProject=" + errorIfUnknownProject);
310 getLog().debug("includeRootProject=" + includeRootProject);
311
312 final List<MavenProject> reactorProjects =
313 mavenSession.getProjectDependencyGraph().getSortedProjects();
314 getLog().debug("reactorProjects=" + reactorProjects);
315
316
317
318
319
320 if ((includes == null) || (includes.size() == 1 && "".equals(includes.get(0)))) {
321 includes = new ArrayList<>();
322 }
323 if (excludes == null) {
324 excludes = new ArrayList<>();
325 } else if (excludes.size() == 1 && "".equals(excludes.get(0))) {
326 excludes = new ArrayList<>();
327 }
328 getLog().debug(String.format("Parameter includes.size: %d", includes.size()));
329 for (String a : includes) {
330 getLog().debug(String.format("Check include '%s'", a));
331 if (a == null) {
332 throw new EnforcerRuleException(
333 "Failure in parameter 'includes'. String is null");
334 }
335 if (a.matches("^[\t\n ]+$")) {
336 throw new EnforcerRuleException(
337 String.format("Failure in parameter 'includes'. String contains only whitespace: '%s'", a));
338 }
339 List<String> ids = Arrays.asList(a.split(":"));
340 if (ids.size() > MAX_NUM_PARTS_IN_DEPENDENCY_DECLARATION) {
341 throw new EnforcerRuleException(
342 "Failure in parameter 'includes'. String is invalid");
343 }
344
345
346
347 if (TRUE.equals(errorIfUnknownProject) && !a.contains("*")
348 && !projectsContains(reactorProjects, a)) {
349 throw new EnforcerRuleException(String.format(
350 "Failure in parameter 'includes'. Project '%s' not found in build", a));
351 }
352 }
353 if (includes.isEmpty()) {
354 includes.add("*");
355 }
356
357 getLog().debug(String.format("Parameter excludes.size: %d", excludes.size()));
358 for (String a : excludes) {
359 getLog().debug(String.format("Check exclude '%s'", a));
360 if (a == null) {
361 throw new EnforcerRuleException(
362 "Failure in parameter 'excludes'. String is null");
363 }
364 if (a.matches("^[\t\n ]+$")) {
365 throw new EnforcerRuleException(
366 String.format("Failure in parameter 'excludes'. String contains only whitespace: '%s'", a));
367 }
368 List<String> ids = Arrays.asList(a.split(":"));
369 if (ids.size() > MAX_NUM_PARTS_IN_DEPENDENCY_DECLARATION) {
370 throw new EnforcerRuleException(
371 "Failure in parameter 'excludes'. String is invalid");
372 }
373
374
375
376 if (TRUE.equals(errorIfUnknownProject) && !a.contains("*")
377 && !projectsContains(reactorProjects, a)) {
378 throw new EnforcerRuleException(String.format(
379 "Failure in parameter 'excludes'. Project '%s' not found in build", a));
380 }
381 }
382
383 if (errorIfUnknownProject == null || errorIfUnknownProject.isEmpty()) {
384 errorIfUnknownProject = FALSE;
385 }
386 if (includeRootProject == null || includeRootProject.isEmpty()) {
387 includeRootProject = FALSE;
388 }
389 if (!TRUE.equals(errorIfUnknownProject) && !FALSE.equals(errorIfUnknownProject)) {
390 throw new EnforcerRuleException(
391 String.format("Failure in parameter 'errorIfUnknownProject'. Must be 'true' or 'false': '%s'", errorIfUnknownProject));
392 }
393 if (!TRUE.equals(includeRootProject) && !FALSE.equals(includeRootProject)) {
394 throw new EnforcerRuleException(
395 String.format("Failure in parameter 'includeRootProject'. Must be 'true' or 'false': '%s'", includeRootProject));
396 }
397
398 getLog().debug("includes(resolved)=" + includes);
399 getLog().debug("excludes(resolved)=" + excludes);
400 getLog().debug("errorIfUnknownProject(resolved)=" + errorIfUnknownProject);
401 getLog().debug("includeRootProject(resolved)=" + includeRootProject);
402 }
403
404
405
406
407
408
409
410
411
412 public void dependOnAllProjects() throws EnforcerRuleException {
413 List<MavenProject> includedProjects = new ArrayList<>();
414 MavenProject currentProject = mavenSession.getCurrentProject();
415 getLog().debug(String.format("Current Project: %s:%s", currentProject.getGroupId(),
416 currentProject.getArtifactId()));
417
418 getLog().debug("Iterate through all projects in Maven Dependency Graph, i.e. the build.");
419 mavenSession.getProjectDependencyGraph().getSortedProjects().forEach(project -> {
420 String projectId =
421 String.format("%s:%s:%s", project.getGroupId(), project.getArtifactId(),
422 project.getVersion());
423 getLog().debug(" " + projectId);
424
425 if (isIncluded(project)) {
426
427 if (projectsAreEquals(project, currentProject) || (!TRUE.equals(this.includeRootProject)
428 && projectsAreEquals(project, mavenSession.getTopLevelProject()))) {
429 getLog().debug("Filter out project: "
430 + String.format("%s:%s", project.getGroupId(), project.getArtifactId()));
431 } else {
432 includedProjects.add(project);
433 }
434 }
435 });
436 getLog().debug("includedProjects=%s" + includedProjects);
437 List<MavenProject> missingProjects = new ArrayList<>();
438 for (MavenProject project : includedProjects) {
439 List<Dependency> dependencies =
440 currentProject.getDependencies();
441 if (!dependenciesContains(dependencies, project)) {
442 missingProjects.add(project);
443 }
444 }
445 if (!missingProjects.isEmpty()) {
446 List<String> errors = new ArrayList<>(missingProjects.size());
447 for (MavenProject missingProject : missingProjects) {
448 errors.add(String.format("Project '%s:%s' is missing dependency '%s:%s:%s'.",
449 currentProject.getGroupId(), currentProject.getArtifactId(),
450 missingProject.getGroupId(), missingProject.getArtifactId(),
451 missingProject.getPackaging()));
452 }
453 StringBuilder sb = new StringBuilder();
454 sb.append(String.format("Missing definitions from the project '%s:%s':",
455 currentProject.getGroupId(), currentProject.getArtifactId()));
456 sb.append(System.lineSeparator());
457 sb.append("<!-- Created by Maven Enforcer rule dependOnAllProjects --->");
458 sb.append(System.lineSeparator());
459 for (MavenProject missingProject : missingProjects) {
460 sb.append(formatDependency(projectToDependency(missingProject), INDENT_DEPENDENCY));
461 sb.append(System.lineSeparator());
462 }
463 sb.append("<!-- / Created by Maven Enforcer rule dependOnAllProjects --->");
464 errors.add(sb.toString());
465 throw new EnforcerRuleException(String.join("\n", errors));
466 }
467 getLog().debug("End of iterate");
468 }
469
470
471
472
473
474
475 @Override
476 public void execute() throws EnforcerRuleException {
477 MavenProject currentProject = mavenSession.getCurrentProject();
478 getLog().debug(String.format("Current Project: %s:%s", currentProject.getGroupId(),
479 currentProject.getArtifactId()));
480 MavenProject topLevelProject = mavenSession.getTopLevelProject();
481 getLog().debug(String.format("Top Level Project: %s:%s", topLevelProject.getGroupId(),
482 topLevelProject.getArtifactId()));
483
484 validateAndPrepareParameters();
485
486 dependOnAllProjects();
487 }
488
489
490
491
492
493
494
495 @Override
496 public String toString() {
497 return String.format(
498 "DependOnAllProjects[includes=%s;excludes=%s;includeRootProject=%s;errorIfUnknownProject=%s]",
499 includes, excludes, includeRootProject, errorIfUnknownProject);
500 }
501
502
503
504
505
506
507
508 boolean isIncluded(MavenProject mavenProject) {
509 boolean r = isProjectIncluded(this.includes, this.excludes, mavenProject);
510 getLog().debug(String.format("isIncluded(%s:%s:%s:%s): %b", mavenProject.getGroupId(),
511 mavenProject.getArtifactId(), mavenProject.getVersion(), mavenProject.getPackaging(),
512 r));
513 return r;
514 }
515
516 }