Add Gradle plugin mapping strategy

This commit adds a mapping strategy for Gradle plugins that don't have
a plugin marker artifact. This is necessary to avoid using a buildScript
section to declare the plugin implementation artifact.

Closes gh-1189
This commit is contained in:
Stephane Nicoll 2021-03-02 07:42:30 +01:00
parent cac36a0d37
commit d1df1d5241
5 changed files with 218 additions and 3 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -16,7 +16,11 @@
package io.spring.initializr.generator.buildsystem.gradle;
import java.util.ArrayList;
import java.util.List;
import io.spring.initializr.generator.buildsystem.BuildSettings;
import io.spring.initializr.generator.buildsystem.Dependency;
/**
* Gradle-specific {@linkplain BuildSettings build settings}.
@ -27,9 +31,12 @@ public class GradleBuildSettings extends BuildSettings {
private final String sourceCompatibility;
private final List<PluginMapping> pluginMappings;
protected GradleBuildSettings(Builder builder) {
super(builder);
this.sourceCompatibility = builder.sourceCompatibility;
this.pluginMappings = new ArrayList<>(builder.pluginMappings);
}
/**
@ -40,6 +47,14 @@ public class GradleBuildSettings extends BuildSettings {
return this.sourceCompatibility;
}
/**
* Return the {@link PluginMapping plugin mappings}, if any.
* @return the plugin mappings
*/
public List<PluginMapping> getPluginMappings() {
return this.pluginMappings;
}
/**
* Builder for {@link GradleBuildSettings}.
*/
@ -47,6 +62,8 @@ public class GradleBuildSettings extends BuildSettings {
private String sourceCompatibility;
private final List<PluginMapping> pluginMappings = new ArrayList<>();
/**
* Set the java version compatibility to use when compiling Java source.
* @param sourceCompatibility java version compatibility
@ -57,6 +74,21 @@ public class GradleBuildSettings extends BuildSettings {
return self();
}
/**
* Map the plugin with the specified id to the specified {@link Dependency}. This
* is mandatory when a plugin does not have an appropriate plugin marker artifact.
* @param id the id of a plugin
* @param pluginDependency the dependency for that plugin
* @return this for method chaining
*/
public Builder mapPlugin(String id, Dependency pluginDependency) {
if (pluginDependency.getVersion() == null || pluginDependency.getVersion().isProperty()) {
throw new IllegalArgumentException("Mapping for plugin '" + id + "' must have a version");
}
this.pluginMappings.add(new PluginMapping(id, pluginDependency));
return this;
}
/**
* Build a {@link GradleBuildSettings} with the current state of this builder.
* @return a {@link GradleBuildSettings}
@ -67,4 +99,36 @@ public class GradleBuildSettings extends BuildSettings {
}
/**
* Map a plugin identifier to a plugin implementation artifact.
*/
public static class PluginMapping {
private final String id;
private final Dependency dependency;
PluginMapping(String id, Dependency dependency) {
this.id = id;
this.dependency = dependency;
}
/**
* Return the id of the plugin.
* @return the plugin id
*/
public String getId() {
return this.id;
}
/**
* Return the plugin implementation dependency.
* @return the plugin implementation
*/
public Dependency getDependency() {
return this.dependency;
}
}
}

View File

@ -16,7 +16,9 @@
package io.spring.initializr.generator.buildsystem.gradle;
import io.spring.initializr.generator.buildsystem.Dependency;
import io.spring.initializr.generator.buildsystem.MavenRepository;
import io.spring.initializr.generator.buildsystem.gradle.GradleBuildSettings.PluginMapping;
import io.spring.initializr.generator.io.IndentingWriter;
/**
@ -41,15 +43,21 @@ public abstract class GradleSettingsWriter {
}
private void writePluginManagement(IndentingWriter writer, GradleBuild build) {
if (build.pluginRepositories().isEmpty()) {
if (build.pluginRepositories().isEmpty() && build.getSettings().getPluginMappings().isEmpty()) {
return;
}
writer.println("pluginManagement {");
writer.indented(() -> writeRepositories(writer, build));
writer.indented(() -> {
writeRepositories(writer, build);
writeResolutionStrategy(writer, build);
});
writer.println("}");
}
private void writeRepositories(IndentingWriter writer, GradleBuild build) {
if (build.pluginRepositories().isEmpty()) {
return;
}
writer.println("repositories {");
writer.indented(() -> {
build.pluginRepositories().items().map(this::repositoryAsString).forEach(writer::println);
@ -58,6 +66,29 @@ public abstract class GradleSettingsWriter {
writer.println("}");
}
private void writeResolutionStrategy(IndentingWriter writer, GradleBuild build) {
if (build.getSettings().getPluginMappings().isEmpty()) {
return;
}
writer.println("resolutionStrategy {");
writer.indented(() -> {
writer.println("eachPlugin {");
writer.indented(() -> build.getSettings().getPluginMappings()
.forEach((pluginMapping) -> writePluginMapping(writer, pluginMapping)));
writer.println("}");
});
writer.println("}");
}
private void writePluginMapping(IndentingWriter writer, PluginMapping pluginMapping) {
writer.println("if (requested.id.id == " + wrapWithQuotes(pluginMapping.getId()) + ") {");
Dependency dependency = pluginMapping.getDependency();
String module = String.format("%s:%s:%s", dependency.getGroupId(), dependency.getArtifactId(),
dependency.getVersion().getValue());
writer.indented(() -> writer.println("useModule(" + wrapWithQuotes(module) + ")"));
writer.println("}");
}
private String repositoryAsString(MavenRepository repository) {
if (MavenRepository.MAVEN_CENTRAL.equals(repository)) {
return "mavenCentral()";

View File

@ -0,0 +1,64 @@
/*
* Copyright 2012-2021 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.buildsystem.gradle;
import io.spring.initializr.generator.buildsystem.Dependency;
import io.spring.initializr.generator.buildsystem.gradle.GradleBuildSettings.Builder;
import io.spring.initializr.generator.version.VersionReference;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link GradleBuildSettings}.
*
* @author Stephane Nicoll
*/
class GradleBuildSettingsTests {
@Test
void mapPluginWithoutVersionIsNotAllowed() {
Builder settingsBuilder = new Builder();
assertThatIllegalArgumentException().isThrownBy(
() -> settingsBuilder.mapPlugin("test", Dependency.withCoordinates("com.example", "plugin").build()))
.withMessage("Mapping for plugin 'test' must have a version");
}
@Test
void mapPluginWithVersionReferenceIsNotAllowed() {
Builder settingsBuilder = new Builder();
assertThatIllegalArgumentException()
.isThrownBy(() -> settingsBuilder.mapPlugin("test",
Dependency.withCoordinates("com.example", "plugin")
.version(VersionReference.ofProperty("test.version")).build()))
.withMessage("Mapping for plugin 'test' must have a version");
}
@Test
void settingsFromBuilderClonePluginMappings() {
Builder settingsBuilder = new Builder();
settingsBuilder.mapPlugin("test",
Dependency.withCoordinates("com.example", "plugin").version(VersionReference.ofValue("1.0.0")).build());
GradleBuildSettings firstSettings = settingsBuilder.build();
settingsBuilder.mapPlugin("another", Dependency.withCoordinates("com.example", "another")
.version(VersionReference.ofValue("2.0.0")).build());
assertThat(firstSettings.getPluginMappings()).singleElement()
.satisfies((pluginMapping) -> assertThat(pluginMapping.getId()).isEqualTo("test"));
}
}

View File

@ -20,8 +20,10 @@ import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import io.spring.initializr.generator.buildsystem.Dependency;
import io.spring.initializr.generator.buildsystem.MavenRepository;
import io.spring.initializr.generator.io.IndentingWriter;
import io.spring.initializr.generator.version.VersionReference;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@ -73,6 +75,32 @@ class GroovyDslGradleSettingsWriterTests {
"}");
}
@Test
void gradleBuildWithPluginMappings() {
GradleBuild build = new GradleBuild();
build.settings()
.mapPlugin("com.example",
Dependency.withCoordinates("com.example", "gradle-plugin")
.version(VersionReference.ofValue("1.0.0")).build())
.mapPlugin("org.acme", Dependency.withCoordinates("org.acme.plugin", "gradle")
.version(VersionReference.ofValue("2.0.0")).build());
List<String> lines = generateSettings(build);
assertThat(lines)
.containsSequence(// @formatter:off
"pluginManagement {",
" resolutionStrategy {",
" eachPlugin {",
" if (requested.id.id == 'com.example') {",
" useModule('com.example:gradle-plugin:1.0.0')",
" }",
" if (requested.id.id == 'org.acme') {",
" useModule('org.acme.plugin:gradle:2.0.0')",
" }",
" }",
" }",
"}"); // @formatter:on
}
@Test
void artifactIdShouldBeUsedAsTheRootProjectName() {
GradleBuild build = new GradleBuild();

View File

@ -20,8 +20,10 @@ import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import io.spring.initializr.generator.buildsystem.Dependency;
import io.spring.initializr.generator.buildsystem.MavenRepository;
import io.spring.initializr.generator.io.IndentingWriter;
import io.spring.initializr.generator.version.VersionReference;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@ -72,6 +74,32 @@ class KotlinDslGradleSettingsWriterTests {
" }", "}");
}
@Test
void gradleBuildWithPluginMappings() {
GradleBuild build = new GradleBuild();
build.settings()
.mapPlugin("com.example",
Dependency.withCoordinates("com.example", "gradle-plugin")
.version(VersionReference.ofValue("1.0.0")).build())
.mapPlugin("org.acme", Dependency.withCoordinates("org.acme.plugin", "gradle")
.version(VersionReference.ofValue("2.0.0")).build());
List<String> lines = generateSettings(build);
assertThat(lines)
.containsSequence(// @formatter:off
"pluginManagement {",
" resolutionStrategy {",
" eachPlugin {",
" if (requested.id.id == \"com.example\") {",
" useModule(\"com.example:gradle-plugin:1.0.0\")",
" }",
" if (requested.id.id == \"org.acme\") {",
" useModule(\"org.acme.plugin:gradle:2.0.0\")",
" }",
" }",
" }",
"}"); // @formatter:on
}
@Test
void artifactIdShouldBeUsedAsTheRootProjectName() {
GradleBuild build = new GradleBuild();