From 69ce392c34e97b233ed8ee48ca927e58aeefc32f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 11 Jan 2022 20:07:53 +0000 Subject: [PATCH] Add support for classifiers when defining a bom Closes gh-29298 --- .../boot/build/bom/BomExtension.java | 23 +++++-- .../boot/build/bom/BomPlugin.java | 40 +++++++++++- .../boot/build/bom/Library.java | 15 +++-- .../build/bom/BomPluginIntegrationTests.java | 65 ++++++++++++++++++- 4 files changed, 130 insertions(+), 13 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java index 7ee1c22dc15..c0511a5073f 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -164,13 +164,18 @@ public class BomExtension { return this.properties; } - String getArtifactVersionProperty(String groupId, String artifactId) { - String coordinates = groupId + ":" + artifactId; + String getArtifactVersionProperty(String groupId, String artifactId, String classifier) { + String coordinates = groupId + ":" + artifactId + ":" + classifier; return this.artifactVersionProperties.get(coordinates); } private void putArtifactVersionProperty(String groupId, String artifactId, String versionProperty) { - String coordinates = groupId + ":" + artifactId; + putArtifactVersionProperty(groupId, artifactId, null, versionProperty); + } + + private void putArtifactVersionProperty(String groupId, String artifactId, String classifier, + String versionProperty) { + String coordinates = groupId + ":" + artifactId + ":" + ((classifier != null) ? classifier : ""); String existing = this.artifactVersionProperties.putIfAbsent(coordinates, versionProperty); if (existing != null) { throw new InvalidUserDataException("Cannot put version property for '" + coordinates @@ -186,7 +191,7 @@ public class BomExtension { } for (Group group : library.getGroups()) { for (Module module : group.getModules()) { - putArtifactVersionProperty(group.getId(), module.getName(), versionProperty); + putArtifactVersionProperty(group.getId(), module.getName(), module.getClassifier(), versionProperty); this.dependencyHandler.getConstraints().add(JavaPlatformPlugin.API_CONFIGURATION_NAME, createDependencyNotation(group.getId(), module.getName(), library.getVersion().getVersion())); } @@ -303,7 +308,7 @@ public class BomExtension { if (arg instanceof Closure) { ModuleHandler moduleHandler = new ModuleHandler(); ConfigureUtil.configure((Closure) arg, moduleHandler); - return new Module(name, moduleHandler.type, moduleHandler.exclusions); + return new Module(name, moduleHandler.type, moduleHandler.classifier, moduleHandler.exclusions); } } throw new InvalidUserDataException("Invalid configuration for module '" + name + "'"); @@ -315,6 +320,8 @@ public class BomExtension { private String type; + private String classifier; + public void exclude(Map exclusion) { this.exclusions.add(new Exclusion(exclusion.get("group"), exclusion.get("module"))); } @@ -323,6 +330,10 @@ public class BomExtension { this.type = type; } + public void setClassifier(String classifier) { + this.classifier = classifier; + } + } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java index d5b1c0206d1..2426939385f 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -109,6 +109,7 @@ public class BomPlugin implements Plugin { Node dependencyManagement = findChild(projectNode, "dependencyManagement"); if (dependencyManagement != null) { addPropertiesBeforeDependencyManagement(projectNode, properties); + addClassifiedManagedDependencies(dependencyManagement); replaceVersionsWithVersionPropertyReferences(dependencyManagement); addExclusionsToManagedDependencies(dependencyManagement); addTypesToManagedDependencies(dependencyManagement); @@ -136,7 +137,9 @@ public class BomPlugin implements Plugin { for (Node dependency : findChildren(dependencies, "dependency")) { String groupId = findChild(dependency, "groupId").text(); String artifactId = findChild(dependency, "artifactId").text(); - String versionProperty = this.bom.getArtifactVersionProperty(groupId, artifactId); + Node classifierNode = findChild(dependency, "classifier"); + String classifier = (classifierNode != null) ? classifierNode.text() : ""; + String versionProperty = this.bom.getArtifactVersionProperty(groupId, artifactId, classifier); if (versionProperty != null) { findChild(dependency, "version").setValue("${" + versionProperty + "}"); } @@ -188,6 +191,39 @@ public class BomPlugin implements Plugin { } } + @SuppressWarnings("unchecked") + private void addClassifiedManagedDependencies(Node dependencyManagement) { + Node dependencies = findChild(dependencyManagement, "dependencies"); + if (dependencies != null) { + for (Node dependency : findChildren(dependencies, "dependency")) { + String groupId = findChild(dependency, "groupId").text(); + String artifactId = findChild(dependency, "artifactId").text(); + String version = findChild(dependency, "version").text(); + Set classifiers = this.bom.getLibraries().stream() + .flatMap((library) -> library.getGroups().stream()) + .filter((group) -> group.getId().equals(groupId)) + .flatMap((group) -> group.getModules().stream()) + .filter((module) -> module.getName().equals(artifactId)).map(Module::getClassifier) + .filter(Objects::nonNull).collect(Collectors.toSet()); + Node target = dependency; + for (String classifier : classifiers) { + if (classifier.length() > 0) { + if (target == null) { + target = new Node(null, "dependency"); + target.appendNode("groupId", groupId); + target.appendNode("artifactId", artifactId); + target.appendNode("version", version); + int index = dependency.parent().children().indexOf(dependency); + dependency.parent().children().add(index + 1, target); + } + target.appendNode("classifier", classifier); + } + target = null; + } + } + } + } + private void addPluginManagement(Node projectNode) { for (Library library : this.bom.getLibraries()) { for (Group group : library.getGroups()) { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java index 31942fb583b..6e629fb29dd 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -189,6 +189,8 @@ public class Library { private final String type; + private final String classifier; + private final List exclusions; public Module(String name) { @@ -196,16 +198,17 @@ public class Library { } public Module(String name, String type) { - this(name, type, Collections.emptyList()); + this(name, type, null, Collections.emptyList()); } public Module(String name, List exclusions) { - this(name, null, exclusions); + this(name, null, null, exclusions); } - public Module(String name, String type, List exclusions) { + public Module(String name, String type, String classifier, List exclusions) { this.name = name; this.type = type; + this.classifier = (classifier != null) ? classifier : ""; this.exclusions = exclusions; } @@ -213,6 +216,10 @@ public class Library { return this.name; } + public String getClassifier() { + return this.classifier; + } + public String getType() { return this.type; } diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java index f7cbea27899..075db3749a3 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,12 +75,14 @@ class BomPluginIntegrationTests { assertThat(dependency).textAtPath("version").isEqualTo("${activemq.version}"); assertThat(dependency).textAtPath("scope").isNullOrEmpty(); assertThat(dependency).textAtPath("type").isNullOrEmpty(); + assertThat(dependency).textAtPath("classifier").isNullOrEmpty(); dependency = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[2]"); assertThat(dependency).textAtPath("groupId").isEqualTo("org.apache.activemq"); assertThat(dependency).textAtPath("artifactId").isEqualTo("activemq-blueprint"); assertThat(dependency).textAtPath("version").isEqualTo("${activemq.version}"); assertThat(dependency).textAtPath("scope").isNullOrEmpty(); assertThat(dependency).textAtPath("type").isNullOrEmpty(); + assertThat(dependency).textAtPath("classifier").isNullOrEmpty(); }); } @@ -135,6 +137,7 @@ class BomPluginIntegrationTests { assertThat(dependency).textAtPath("version").isEqualTo("${jackson-bom.version}"); assertThat(dependency).textAtPath("scope").isEqualTo("import"); assertThat(dependency).textAtPath("type").isEqualTo("pom"); + assertThat(dependency).textAtPath("classifier").isNullOrEmpty(); }); } @@ -164,6 +167,7 @@ class BomPluginIntegrationTests { assertThat(dependency).textAtPath("version").isEqualTo("${mysql.version}"); assertThat(dependency).textAtPath("scope").isNullOrEmpty(); assertThat(dependency).textAtPath("type").isNullOrEmpty(); + assertThat(dependency).textAtPath("classifier").isNullOrEmpty(); NodeAssert exclusion = dependency.nodeAtPath("exclusions/exclusion"); assertThat(exclusion).textAtPath("groupId").isEqualTo("com.google.protobuf"); assertThat(exclusion).textAtPath("artifactId").isEqualTo("protobuf-java"); @@ -196,10 +200,69 @@ class BomPluginIntegrationTests { assertThat(dependency).textAtPath("version").isEqualTo("${elasticsearch.version}"); assertThat(dependency).textAtPath("scope").isNullOrEmpty(); assertThat(dependency).textAtPath("type").isEqualTo("zip"); + assertThat(dependency).textAtPath("classifier").isNullOrEmpty(); assertThat(dependency).nodeAtPath("exclusions").isNull(); }); } + @Test + void moduleClassifiersAreIncludedInDependencyManagementOfGeneratedPom() throws IOException { + try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { + out.println("plugins {"); + out.println(" id 'org.springframework.boot.bom'"); + out.println("}"); + out.println("bom {"); + out.println(" library('Kafka', '2.7.2') {"); + out.println(" group('org.apache.kafka') {"); + out.println(" modules = ["); + out.println(" 'connect-api',"); + out.println(" 'generator',"); + out.println(" 'generator' {"); + out.println(" classifier = 'test'"); + out.println(" },"); + out.println(" 'kafka-tools',"); + out.println(" ]"); + out.println(" }"); + out.println(" }"); + out.println("}"); + } + generatePom((pom) -> { + assertThat(pom).textAtPath("//properties/kafka.version").isEqualTo("2.7.2"); + NodeAssert connectApi = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[1]"); + assertThat(connectApi).textAtPath("groupId").isEqualTo("org.apache.kafka"); + assertThat(connectApi).textAtPath("artifactId").isEqualTo("connect-api"); + assertThat(connectApi).textAtPath("version").isEqualTo("${kafka.version}"); + assertThat(connectApi).textAtPath("scope").isNullOrEmpty(); + assertThat(connectApi).textAtPath("type").isNullOrEmpty(); + assertThat(connectApi).textAtPath("classifier").isNullOrEmpty(); + assertThat(connectApi).nodeAtPath("exclusions").isNull(); + NodeAssert generator = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[2]"); + assertThat(generator).textAtPath("groupId").isEqualTo("org.apache.kafka"); + assertThat(generator).textAtPath("artifactId").isEqualTo("generator"); + assertThat(generator).textAtPath("version").isEqualTo("${kafka.version}"); + assertThat(generator).textAtPath("scope").isNullOrEmpty(); + assertThat(generator).textAtPath("type").isNullOrEmpty(); + assertThat(generator).textAtPath("classifier").isNullOrEmpty(); + assertThat(generator).nodeAtPath("exclusions").isNull(); + NodeAssert generatorTest = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[3]"); + assertThat(generatorTest).textAtPath("groupId").isEqualTo("org.apache.kafka"); + assertThat(generatorTest).textAtPath("artifactId").isEqualTo("generator"); + assertThat(generatorTest).textAtPath("version").isEqualTo("${kafka.version}"); + assertThat(generatorTest).textAtPath("scope").isNullOrEmpty(); + assertThat(generatorTest).textAtPath("type").isNullOrEmpty(); + assertThat(generatorTest).textAtPath("classifier").isEqualTo("test"); + assertThat(generatorTest).nodeAtPath("exclusions").isNull(); + NodeAssert kafkaTools = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[4]"); + assertThat(kafkaTools).textAtPath("groupId").isEqualTo("org.apache.kafka"); + assertThat(kafkaTools).textAtPath("artifactId").isEqualTo("kafka-tools"); + assertThat(kafkaTools).textAtPath("version").isEqualTo("${kafka.version}"); + assertThat(kafkaTools).textAtPath("scope").isNullOrEmpty(); + assertThat(kafkaTools).textAtPath("type").isNullOrEmpty(); + assertThat(kafkaTools).textAtPath("classifier").isNullOrEmpty(); + assertThat(kafkaTools).nodeAtPath("exclusions").isNull(); + }); + } + @Test void libraryNamedSpringBootHasNoVersionProperty() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {