1616
1717package org .springframework .boot .build .bom ;
1818
19+ import java .io .File ;
1920import java .util .ArrayList ;
2021import java .util .Arrays ;
2122import java .util .Collections ;
3132import java .util .regex .Pattern ;
3233import java .util .stream .Stream ;
3334
35+ import javax .xml .parsers .DocumentBuilder ;
36+ import javax .xml .parsers .DocumentBuilderFactory ;
37+ import javax .xml .xpath .XPath ;
38+ import javax .xml .xpath .XPathFactory ;
39+
3440import org .apache .maven .artifact .versioning .DefaultArtifactVersion ;
3541import org .apache .maven .artifact .versioning .VersionRange ;
3642import org .gradle .api .Project ;
3743import org .gradle .api .artifacts .Configuration ;
3844import org .gradle .api .artifacts .Dependency ;
3945import org .gradle .api .artifacts .result .DependencyResult ;
4046import org .gradle .api .artifacts .result .ResolutionResult ;
47+ import org .w3c .dom .Document ;
4148
4249import org .springframework .boot .build .bom .bomr .version .DependencyVersion ;
4350
@@ -406,10 +413,18 @@ public String getArtifactId() {
406413
407414 }
408415
416+ public interface VersionAlignment {
417+
418+ Set <String > resolve ();
419+
420+ }
421+
409422 /**
410- * Version alignment for a library.
423+ * Version alignment for a library based on a dependency of another module .
411424 */
412- public static class VersionAlignment {
425+ public static class DependencyVersionAlignment implements VersionAlignment {
426+
427+ private final String dependency ;
413428
414429 private final String from ;
415430
@@ -423,35 +438,44 @@ public static class VersionAlignment {
423438
424439 private Set <String > alignedVersions ;
425440
426- VersionAlignment (String from , String managedBy , Project project , List <Library > libraries , List <Group > groups ) {
441+ DependencyVersionAlignment (String dependency , String from , String managedBy , Project project ,
442+ List <Library > libraries , List <Group > groups ) {
443+ this .dependency = dependency ;
427444 this .from = from ;
428445 this .managedBy = managedBy ;
429446 this .project = project ;
430447 this .libraries = libraries ;
431448 this .groups = groups ;
432449 }
433450
451+ @ Override
434452 public Set <String > resolve () {
435453 if (this .alignedVersions != null ) {
436454 return this .alignedVersions ;
437455 }
438456 Map <String , String > versions = resolveAligningDependencies ();
439- Set <String > versionsInLibrary = new HashSet <>();
440- for (Group group : this .groups ) {
441- for (Module module : group .getModules ()) {
442- String version = versions .get (group .getId () + ":" + module .getName ());
443- if (version != null ) {
444- versionsInLibrary .add (version );
457+ if (this .dependency != null ) {
458+ String version = versions .get (this .dependency );
459+ this .alignedVersions = (version != null ) ? Set .of (version ) : Collections .emptySet ();
460+ }
461+ else {
462+ Set <String > versionsInLibrary = new HashSet <>();
463+ for (Group group : this .groups ) {
464+ for (Module module : group .getModules ()) {
465+ String version = versions .get (group .getId () + ":" + module .getName ());
466+ if (version != null ) {
467+ versionsInLibrary .add (version );
468+ }
445469 }
446- }
447- for ( String plugin : group .getPlugins ()) {
448- String version = versions . get ( group . getId () + ":" + plugin );
449- if (version != null ) {
450- versionsInLibrary . add ( version );
470+ for ( String plugin : group . getPlugins ()) {
471+ String version = versions . get ( group .getId () + ":" + plugin );
472+ if ( version != null ) {
473+ versionsInLibrary . add (version );
474+ }
451475 }
452476 }
477+ this .alignedVersions = versionsInLibrary ;
453478 }
454- this .alignedVersions = versionsInLibrary ;
455479 return this .alignedVersions ;
456480 }
457481
@@ -539,6 +563,100 @@ public String toString() {
539563
540564 }
541565
566+ /**
567+ * Version alignment for a library based on a property in the pom of another module.
568+ */
569+ public static class PomPropertyVersionAlignment implements VersionAlignment {
570+
571+ private final String name ;
572+
573+ private final String from ;
574+
575+ private final String managedBy ;
576+
577+ private final Project project ;
578+
579+ private final List <Library > libraries ;
580+
581+ private Set <String > alignedVersions ;
582+
583+ PomPropertyVersionAlignment (String name , String from , String managedBy , Project project ,
584+ List <Library > libraries ) {
585+ this .name = name ;
586+ this .from = from ;
587+ this .managedBy = managedBy ;
588+ this .project = project ;
589+ this .libraries = libraries ;
590+ }
591+
592+ @ Override
593+ public Set <String > resolve () {
594+ if (this .alignedVersions != null ) {
595+ return this .alignedVersions ;
596+ }
597+ Configuration alignmentConfiguration = this .project .getConfigurations ()
598+ .detachedConfiguration (getAligningDependencies ().toArray (new Dependency [0 ]));
599+ Set <File > files = alignmentConfiguration .resolve ();
600+ if (files .size () != 1 ) {
601+ throw new IllegalStateException (
602+ "Expected a single file when resolving the pom of " + this .from + " but found " + files .size ());
603+ }
604+ File pomFile = files .iterator ().next ();
605+ return Set .of (propertyFrom (pomFile ));
606+ }
607+
608+ private List <Dependency > getAligningDependencies () {
609+ Library managingLibrary = findManagingLibrary ();
610+ List <Dependency > boms = getBomDependencies (managingLibrary );
611+ List <Dependency > dependencies = new ArrayList <>();
612+ dependencies .addAll (boms );
613+ dependencies .add (this .project .getDependencies ().create (this .from + "@pom" ));
614+ return dependencies ;
615+ }
616+
617+ private Library findManagingLibrary () {
618+ if (this .managedBy == null ) {
619+ return null ;
620+ }
621+ return this .libraries .stream ()
622+ .filter ((candidate ) -> this .managedBy .equals (candidate .getName ()))
623+ .findFirst ()
624+ .orElseThrow (() -> new IllegalStateException ("Managing library '" + this .managedBy + "' not found." ));
625+ }
626+
627+ private List <Dependency > getBomDependencies (Library manager ) {
628+ return manager .getGroups ()
629+ .stream ()
630+ .flatMap ((group ) -> group .getBoms ()
631+ .stream ()
632+ .map ((bom ) -> this .project .getDependencies ()
633+ .platform (group .getId () + ":" + bom .name () + ":" + manager .getVersion ().getVersion ())))
634+ .toList ();
635+ }
636+
637+ private String propertyFrom (File pomFile ) {
638+ try {
639+ DocumentBuilder documentBuilder = DocumentBuilderFactory .newInstance ().newDocumentBuilder ();
640+ Document document = documentBuilder .parse (pomFile );
641+ XPath xpath = XPathFactory .newInstance ().newXPath ();
642+ return xpath .evaluate ("/project/properties/" + this .name + "/text()" , document );
643+ }
644+ catch (Exception ex ) {
645+ throw new RuntimeException (ex );
646+ }
647+ }
648+
649+ @ Override
650+ public String toString () {
651+ String result = "version from properties of " + this .from ;
652+ if (this .managedBy != null ) {
653+ result += " that is managed by " + this .managedBy ;
654+ }
655+ return result ;
656+ }
657+
658+ }
659+
542660 public record Link (String rootName , Function <LibraryVersion , String > factory , List <String > packages ) {
543661
544662 private static final Pattern PACKAGE_EXPAND = Pattern .compile ("^(.*)\\ [(.*)\\ ]$" );
0 commit comments