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