@@ -146,6 +146,42 @@ public abstract class AbstractAlignmentReporterMojo extends AbstractMojo {
146146 @ Parameter (property = "excludes" )
147147 private String excludes ;
148148
149+ /**
150+ * A comma-separated list of module names to exclude from processing in multi-module projects, or <code>null</code> to
151+ * include all modules. Each module name pattern supports full and partial <code>*</code> wildcards.
152+ * <p>
153+ * For example, <code>*-test</code> will exclude all modules whose name ends with "-test",
154+ * and <code>flink-*</code> will exclude all modules whose name starts with "flink-".
155+ * </p>
156+ */
157+ @ Parameter (property = "excludeModules" )
158+ private String excludeModules ;
159+
160+ /**
161+ * Enable shade-aware analysis to detect and report on maven-shade-plugin configurations and their alignment.
162+ * When enabled, the plugin will analyze shade configurations across all projects and provide additional
163+ * reporting on shaded dependencies.
164+ */
165+ @ Parameter (property = "analyzeShade" , defaultValue = "false" )
166+ private boolean analyzeShade ;
167+
168+ /**
169+ * Print detailed shade configuration information in the report.
170+ * Only has effect when analyzeShade=true. When enabled, shows all shade plugin configurations
171+ * found in the project with their relocation mappings.
172+ */
173+ @ Parameter (property = "printShadeConfigurations" , defaultValue = "false" )
174+ private boolean printShadeConfigurations ;
175+
176+ /**
177+ * Include transitive dependencies in shade analysis.
178+ * Only has effect when analyzeShade=true. When enabled, analyzes both direct and transitive
179+ * dependencies for shade impact, providing comprehensive coverage of all artifacts that
180+ * could be affected by shading configurations.
181+ */
182+ @ Parameter (property = "includeTransitiveShaded" , defaultValue = "false" )
183+ private boolean includeTransitiveShaded ;
184+
149185 private ArtifactFilter scopeFilter ;
150186
151187 private static void write (final String string , final File file ) throws IOException {
@@ -224,6 +260,37 @@ public void execute() throws MojoExecutionException, MojoFailureException {
224260 String unalignedTransitivesStr = reportUnalignedTransitiveDependenciesSummary (unalignedTransitives );
225261 String unalignedTransitiveDetail = reportUnalignedTransitiveDependencyDetail (alignedDirectDeps , excludeDependencyFilter );
226262
263+ String shadeReport = "" ;
264+ String shadeAlignmentSummary = "" ;
265+
266+ if (analyzeShade ) {
267+ ShadeAwareAlignmentReporter shadeReporter = new ShadeAwareAlignmentReporter (getLog ());
268+
269+ // Collect artifacts for shade analysis
270+ List <Artifact > artifactsForShadeAnalysis ;
271+ List <ArtifactWithPath > artifactsWithPathsForShadeAnalysis = null ;
272+
273+ if (includeTransitiveShaded ) {
274+ artifactsWithPathsForShadeAnalysis = getAllTransitiveDependencyArtifactsWithPaths (directDependencies );
275+ artifactsForShadeAnalysis = artifactsWithPathsForShadeAnalysis .stream ()
276+ .map (ArtifactWithPath ::getArtifact )
277+ .collect (Collectors .toList ());
278+ } else {
279+ artifactsForShadeAnalysis = new ArrayList <>(dependencyArtifacts );
280+ }
281+
282+ ShadeAwareAlignmentReporter .ShadeAlignmentResult shadeResult =
283+ shadeReporter .analyzeShadeAlignment (getReactorProjectsForShadeAnalysis (),
284+ artifactsForShadeAnalysis ,
285+ alignmentPattern ,
286+ artifactsWithPathsForShadeAnalysis );
287+
288+ if (printShadeConfigurations ) {
289+ shadeReport = shadeReporter .generateShadeReport (shadeResult , alignmentPattern );
290+ }
291+ shadeAlignmentSummary = shadeReporter .generateShadeAlignmentSummary (shadeResult , alignmentPattern );
292+ }
293+
227294 if (outputFile != null ) {
228295 String projectTitle = getProjectTitle ();
229296
@@ -233,13 +300,27 @@ public void execute() throws MojoExecutionException, MojoFailureException {
233300 write (unalignedTransitivesStr , outputFile );
234301 write (unalignedTransitiveDetail , outputFile );
235302
303+ if (analyzeShade && !shadeReport .isEmpty ()) {
304+ write (shadeReport , outputFile );
305+ }
306+ if (analyzeShade && !shadeAlignmentSummary .isEmpty ()) {
307+ write (shadeAlignmentSummary , outputFile );
308+ }
309+
236310 getLog ().info (String .format ("Wrote alignment report tree to: %s" ,
237311 outputFile ));
238312 } else {
239313 log (alignedDirectStr , getLog ());
240314 log (unalignedDirectStr , getLog ());
241315 log (unalignedTransitivesStr , getLog ());
242316 log (unalignedTransitiveDetail , getLog ());
317+
318+ if (analyzeShade && !shadeReport .isEmpty ()) {
319+ log (shadeReport , getLog ());
320+ }
321+ if (analyzeShade && !shadeAlignmentSummary .isEmpty ()) {
322+ log (shadeAlignmentSummary , getLog ());
323+ }
243324 }
244325 } catch (IOException exception ) {
245326 throw new MojoExecutionException (
@@ -558,4 +639,121 @@ private ArtifactFilter createExcludeFilter() {
558639 }
559640 return filter ;
560641 }
642+
643+ /**
644+ * Filters reactor projects based on the excludeModules parameter.
645+ *
646+ * @param projects the list of projects to filter
647+ * @return the filtered list of projects
648+ */
649+ protected List <MavenProject > filterModules (final List <MavenProject > projects ) {
650+ if (excludeModules == null || excludeModules .trim ()
651+ .isEmpty ()) {
652+ return projects ;
653+ }
654+
655+ List <String > patterns = Arrays .asList (excludeModules .split ("," ));
656+ getLog ().debug (String .format ("+ Filtering modules by exclude patterns: %s" , patterns ));
657+
658+ return projects .stream ()
659+ .filter (project -> {
660+ String artifactId = project .getArtifactId ();
661+ for (String pattern : patterns ) {
662+ String trimmedPattern = pattern .trim ();
663+ if (matchesPattern (artifactId , trimmedPattern )) {
664+ getLog ().debug (String .format ("+ Excluding module: %s (matched pattern: %s)" , artifactId , trimmedPattern ));
665+ return false ;
666+ }
667+ }
668+ return true ;
669+ })
670+ .collect (Collectors .toList ());
671+ }
672+
673+ /**
674+ * Checks if a string matches a pattern with wildcard support.
675+ *
676+ * @param text the text to match
677+ * @param pattern the pattern (supports * wildcards)
678+ * @return true if the text matches the pattern
679+ */
680+ private boolean matchesPattern (final String text , final String pattern ) {
681+ if (pattern .equals ("*" )) {
682+ return true ;
683+ }
684+
685+ String regex = pattern .replace ("*" , ".*" );
686+ return text .matches (regex );
687+ }
688+
689+ /**
690+ * Gets the reactor projects to use for shade analysis.
691+ * In single-module mode, returns only the current project.
692+ * In aggregate mode, returns filtered reactor projects.
693+ *
694+ * @return the list of projects for shade analysis
695+ */
696+ protected List <MavenProject > getReactorProjectsForShadeAnalysis () {
697+ return filterModules (reactorProjects );
698+ }
699+
700+ /**
701+ * Collects all transitive dependency artifacts from the given direct dependency nodes.
702+ * This method traverses the entire dependency tree and collects all artifacts at all levels.
703+ *
704+ * @param directDependencies the set of direct dependency nodes to traverse
705+ * @return a list of all artifacts (direct + transitive)
706+ */
707+ private List <Artifact > getAllTransitiveDependencyArtifacts (final Set <DependencyNode > directDependencies ) {
708+ List <ArtifactWithPath > allArtifactsWithPaths = getAllTransitiveDependencyArtifactsWithPaths (directDependencies );
709+
710+ List <Artifact > artifacts = allArtifactsWithPaths .stream ()
711+ .map (ArtifactWithPath ::getArtifact )
712+ .collect (Collectors .toList ());
713+
714+ getLog ().debug (String .format ("Collected %d total artifacts (direct + transitive) for shade analysis" , artifacts .size ()));
715+
716+ return artifacts ;
717+ }
718+
719+ /**
720+ * Collects all transitive dependency artifacts with their dependency paths.
721+ *
722+ * @param directDependencies the set of direct dependency nodes to traverse
723+ * @return a list of all artifacts with their paths (direct + transitive)
724+ */
725+ private List <ArtifactWithPath > getAllTransitiveDependencyArtifactsWithPaths (final Set <DependencyNode > directDependencies ) {
726+ Set <ArtifactWithPath > allArtifacts = new HashSet <>();
727+
728+ for (DependencyNode directDependency : directDependencies ) {
729+ collectAllArtifactsWithPathsFromNode (directDependency , new ArrayList <>(), allArtifacts );
730+ }
731+
732+ getLog ().debug (String .format ("Collected %d total artifacts with paths for shade analysis" , allArtifacts .size ()));
733+
734+ return new ArrayList <>(allArtifacts );
735+ }
736+
737+ /**
738+ * Recursively collects all artifacts with their paths from a dependency node and its children.
739+ *
740+ * @param node the dependency node to traverse
741+ * @param currentPath the current dependency path (not including this node)
742+ * @param collector the set to collect artifacts with paths into
743+ */
744+ private void collectAllArtifactsWithPathsFromNode (final DependencyNode node ,
745+ final List <Artifact > currentPath ,
746+ final Set <ArtifactWithPath > collector ) {
747+ // Create the path including this node
748+ List <Artifact > pathWithThisNode = new ArrayList <>(currentPath );
749+ pathWithThisNode .add (node .getArtifact ());
750+
751+ // Add the current node's artifact with its path
752+ collector .add (new ArtifactWithPath (node .getArtifact (), pathWithThisNode ));
753+
754+ // Recursively process all children
755+ for (DependencyNode child : node .getChildren ()) {
756+ collectAllArtifactsWithPathsFromNode (child , pathWithThisNode , collector );
757+ }
758+ }
561759}
0 commit comments