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 List<String> ids = Arrays.asList(projectName.split(":"));
230 for (MavenProject project : projects) {
231 if (ids.size() == 1) {
232 if (project.getArtifactId().equals(ids.get(0))) {
233 return true;
234 }
235 } else if (ids.size() == 2) {
236 if (project.getGroupId().equals(ids.get(0))
237 && project.getArtifactId().equals(ids.get(1))) {
238 return true;
239 }
240 } else {
241 assert ids.size() == MAX_NUM_PARTS_IN_DEPENDENCY_DECLARATION;
242 if (project.getGroupId().equals(ids.get(0))
243 && project.getArtifactId().equals(ids.get(1))
244 && project.getPackaging().equals(ids.get(2))) {
245 return true;
246 }
247 }
248 }
249 return false;
250 }
251
252
253
254
255
256
257
258 public static Dependency projectToDependency(MavenProject mavenProject) {
259 Dependency d = new Dependency();
260 d.setGroupId(mavenProject.getGroupId());
261 d.setArtifactId(mavenProject.getArtifactId());
262 d.setVersion(mavenProject.getVersion());
263 d.setType(mavenProject.getPackaging());
264 return d;
265 }
266
267
268
269
270
271
272
273
274
275
276 public static boolean isProjectIncluded(List<String> includes, List<String> excludes,
277 MavenProject mavenProject) {
278 String projectId =
279 String.format("%s:%s:%s", mavenProject.getGroupId(), mavenProject.getArtifactId(),
280 mavenProject.getPackaging());
281
282
283 Predicate<String> predicateForProjectId =
284 s -> projectId.matches(convertStringForMatching(s));
285 return includes.stream().anyMatch(predicateForProjectId)
286 && excludes.stream().noneMatch(predicateForProjectId);
287 }
288
289
290
291
292
293
294
295 void validateAndPrepareParameters() throws EnforcerRuleException {
296 getLog().debug("includes=" + includes);
297 getLog().debug("excludes=" + excludes);
298 getLog().debug("errorIfUnknownProject=" + errorIfUnknownProject);
299 getLog().debug("includeRootProject=" + includeRootProject);
300
301 final List<MavenProject> reactorProjects =
302 mavenSession.getProjectDependencyGraph().getSortedProjects();
303 getLog().debug("reactorProjects=" + reactorProjects);
304
305
306
307
308
309 if ((includes == null) || (includes.size() == 1 && "".equals(includes.get(0)))) {
310 includes = new ArrayList<>();
311 }
312 if (excludes == null) {
313 excludes = new ArrayList<>();
314 } else if (excludes.size() == 1 && "".equals(excludes.get(0))) {
315 excludes = new ArrayList<>();
316 }
317 getLog().debug(String.format("Parameter includes.size: %d", includes.size()));
318 for (String a : includes) {
319 getLog().debug(String.format("Check include '%s'", a));
320 if (a == null) {
321 throw new EnforcerRuleException(
322 "Failure in parameter 'includes'. String is null");
323 }
324 if (a.matches("^[\t\n ]+$")) {
325 throw new EnforcerRuleException(
326 String.format("Failure in parameter 'includes'. String contains only whitespace: '%s'", a));
327 }
328 List<String> ids = Arrays.asList(a.split(":"));
329 if (ids.size() > MAX_NUM_PARTS_IN_DEPENDENCY_DECLARATION) {
330 throw new EnforcerRuleException(
331 "Failure in parameter 'includes'. String is invalid");
332 }
333
334
335
336 if (TRUE.equals(errorIfUnknownProject) && !a.contains("*")
337 && !projectsContains(reactorProjects, a)) {
338 throw new EnforcerRuleException(String.format(
339 "Failure in parameter 'includes'. Project '%s' not found in build", a));
340 }
341 }
342 if (includes.isEmpty()) {
343 includes.add("*");
344 }
345
346 getLog().debug(String.format("Parameter excludes.size: %d", excludes.size()));
347 for (String a : excludes) {
348 getLog().debug(String.format("Check exclude '%s'", a));
349 if (a == null) {
350 throw new EnforcerRuleException(
351 "Failure in parameter 'excludes'. String is null");
352 }
353 if (a.matches("^[\t\n ]+$")) {
354 throw new EnforcerRuleException(
355 String.format("Failure in parameter 'excludes'. String contains only whitespace: '%s'", a));
356 }
357 List<String> ids = Arrays.asList(a.split(":"));
358 if (ids.size() > MAX_NUM_PARTS_IN_DEPENDENCY_DECLARATION) {
359 throw new EnforcerRuleException(
360 "Failure in parameter 'excludes'. String is invalid");
361 }
362
363
364
365 if (TRUE.equals(errorIfUnknownProject) && !a.contains("*")
366 && !projectsContains(reactorProjects, a)) {
367 throw new EnforcerRuleException(String.format(
368 "Failure in parameter 'excludes'. Project '%s' not found in build", a));
369 }
370 }
371
372 if (errorIfUnknownProject == null || errorIfUnknownProject.isEmpty()) {
373 errorIfUnknownProject = FALSE;
374 }
375 if (includeRootProject == null || includeRootProject.isEmpty()) {
376 includeRootProject = FALSE;
377 }
378 if (!TRUE.equals(errorIfUnknownProject) && !FALSE.equals(errorIfUnknownProject)) {
379 throw new EnforcerRuleException(
380 String.format("Failure in parameter 'errorIfUnknownProject'. Must be 'true' or 'false': '%s'", errorIfUnknownProject));
381 }
382 if (!TRUE.equals(includeRootProject) && !FALSE.equals(includeRootProject)) {
383 throw new EnforcerRuleException(
384 String.format("Failure in parameter 'includeRootProject'. Must be 'true' or 'false': '%s'", includeRootProject));
385 }
386
387 getLog().debug("includes(resolved)=" + includes);
388 getLog().debug("excludes(resolved)=" + excludes);
389 getLog().debug("errorIfUnknownProject(resolved)=" + errorIfUnknownProject);
390 getLog().debug("includeRootProject(resolved)=" + includeRootProject);
391 }
392
393
394
395
396
397
398
399
400
401 public void dependOnAllProjects() throws EnforcerRuleException {
402 List<MavenProject> includedProjects = new ArrayList<>();
403 MavenProject currentProject = mavenSession.getCurrentProject();
404 getLog().debug(String.format("Current Project: %s:%s", currentProject.getGroupId(),
405 currentProject.getArtifactId()));
406
407 getLog().debug("Iterate through all projects in Maven Dependency Graph, i.e. the build.");
408 mavenSession.getProjectDependencyGraph().getSortedProjects().forEach(project -> {
409 String projectId =
410 String.format("%s:%s:%s", project.getGroupId(), project.getArtifactId(),
411 project.getVersion());
412 getLog().debug(" " + projectId);
413
414 if (isIncluded(project)) {
415 if (projectsAreEquals(project, currentProject) || (!TRUE.equals(this.includeRootProject)
416 && projectsAreEquals(project, mavenSession.getTopLevelProject()))) {
417 getLog().debug("Filter out project: "
418 + String.format("%s:%s", project.getGroupId(), project.getArtifactId()));
419 } else {
420 includedProjects.add(project);
421 }
422 }
423 });
424 getLog().debug("includedProjects=%s" + includedProjects);
425 List<MavenProject> missingProjects = new ArrayList<>();
426 for (MavenProject project : includedProjects) {
427 @SuppressWarnings("unchecked") List<Dependency> dependencies =
428 currentProject.getDependencies();
429 if (!dependenciesContains(dependencies, project)) {
430 missingProjects.add(project);
431 }
432 }
433 if (!missingProjects.isEmpty()) {
434 List<String> errors = new ArrayList<>(missingProjects.size());
435 for (MavenProject missingProject : missingProjects) {
436 errors.add(String.format("Project '%s:%s' is missing dependency '%s:%s:%s'.",
437 currentProject.getGroupId(), currentProject.getArtifactId(),
438 missingProject.getGroupId(), missingProject.getArtifactId(),
439 missingProject.getPackaging()));
440 }
441 StringBuilder sb = new StringBuilder();
442 sb.append(String.format("Missing definitions from the project '%s:%s':",
443 currentProject.getGroupId(), currentProject.getArtifactId()));
444 sb.append(System.lineSeparator());
445 sb.append("<!-- Created by Maven Enforcer rule dependOnAllProjects --->");
446 sb.append(System.lineSeparator());
447 for (MavenProject missingProject : missingProjects) {
448 sb.append(formatDependency(projectToDependency(missingProject), INDENT_DEPENDENCY));
449 sb.append(System.lineSeparator());
450 }
451 sb.append("<!-- / Created by Maven Enforcer rule dependOnAllProjects --->");
452 errors.add(sb.toString());
453 throw new EnforcerRuleException(String.join("\n", errors));
454 }
455 getLog().debug("End of iterate");
456 }
457
458
459
460
461
462
463 @Override
464 public void execute() throws EnforcerRuleException {
465 MavenProject currentProject = mavenSession.getCurrentProject();
466 getLog().debug(String.format("Current Project: %s:%s", currentProject.getGroupId(),
467 currentProject.getArtifactId()));
468 MavenProject topLevelProject = mavenSession.getTopLevelProject();
469 getLog().debug(String.format("Top Level Project: %s:%s", topLevelProject.getGroupId(),
470 topLevelProject.getArtifactId()));
471
472 validateAndPrepareParameters();
473
474 dependOnAllProjects();
475 }
476
477
478
479
480
481
482
483 @Override
484 public String toString() {
485 return String.format(
486 "DependOnAllProjects[includes=%s;excludes=%s;includeRootProject=%s;errorIfUnknownProject=%s]",
487 includes, excludes, includeRootProject, errorIfUnknownProject);
488 }
489
490
491
492
493
494
495
496 boolean isIncluded(MavenProject mavenProject) {
497 boolean r = isProjectIncluded(this.includes, this.excludes, mavenProject);
498 getLog().debug(String.format("isIncluded(%s:%s:%s:%s): %b", mavenProject.getGroupId(),
499 mavenProject.getArtifactId(), mavenProject.getVersion(), mavenProject.getPackaging(),
500 r));
501 return r;
502 }
503
504 }