Add Gradle build support

This commit provides a Gradle build system implementation with a writer
that can generate `build.gradle` and `settings.gradle` files based on a
configurable model.

See gh-814
This commit is contained in:
Andy Wilkinson 2019-02-07 15:22:15 +01:00 committed by Stephane Nicoll
parent 1dbed8cdf8
commit 2bd64ed6ed
11 changed files with 1360 additions and 1 deletions

View File

@ -0,0 +1,241 @@
/*
* Copyright 2012-2019 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
*
* http://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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Consumer;
import io.spring.initializr.generator.buildsystem.Build;
import io.spring.initializr.generator.buildsystem.BuildItemResolver;
/**
* Gradle build configuration for a project.
*
* @author Andy Wilkinson
*/
public class GradleBuild extends Build {
private String sourceCompatibility;
private final Map<String, String> ext = new TreeMap<>();
private final List<GradlePlugin> plugins = new ArrayList<>();
private final List<String> appliedPlugins = new ArrayList<>();
private final Map<String, ConfigurationCustomization> configurationCustomizations = new LinkedHashMap<>();
private final Map<String, TaskCustomization> taskCustomizations = new LinkedHashMap<>();
private final Buildscript buildscript = new Buildscript();
public GradleBuild(BuildItemResolver buildItemResolver) {
super(buildItemResolver);
}
public GradleBuild() {
this(null);
}
public void setSourceCompatibility(String sourceCompatibility) {
this.sourceCompatibility = sourceCompatibility;
}
public String getSourceCompatibility() {
return this.sourceCompatibility;
}
public GradleBuild ext(String key, String value) {
this.ext.put(key, value);
return this;
}
public Map<String, String> getExt() {
return Collections.unmodifiableMap(this.ext);
}
public GradlePlugin addPlugin(String id) {
return this.addPlugin(id, null);
}
public GradlePlugin addPlugin(String id, String version) {
GradlePlugin plugin = new GradlePlugin(id, version);
this.plugins.add(plugin);
return plugin;
}
public void applyPlugin(String id) {
this.appliedPlugins.add(id);
}
public List<GradlePlugin> getPlugins() {
return Collections.unmodifiableList(this.plugins);
}
public List<String> getAppliedPlugins() {
return Collections.unmodifiableList(this.appliedPlugins);
}
public void buildscript(Consumer<Buildscript> customizer) {
customizer.accept(this.buildscript);
}
public Buildscript getBuildscript() {
return this.buildscript;
}
public void customizeConfiguration(String configurationName,
Consumer<ConfigurationCustomization> customizer) {
customizer.accept(this.configurationCustomizations.computeIfAbsent(
configurationName, (name) -> new ConfigurationCustomization()));
}
public void addConfiguration(String configurationName) {
customizeConfiguration(configurationName, (configuration) -> {
});
}
public Map<String, ConfigurationCustomization> getConfigurationCustomizations() {
return Collections.unmodifiableMap(this.configurationCustomizations);
}
public void customizeTask(String taskName, Consumer<TaskCustomization> customizer) {
customizer.accept(this.taskCustomizations.computeIfAbsent(taskName,
(name) -> new TaskCustomization()));
}
public Map<String, TaskCustomization> getTaskCustomizations() {
return Collections.unmodifiableMap(this.taskCustomizations);
}
/**
* The {@code buildscript} block in the {@code build.gradle} file.
*/
public static class Buildscript {
private final List<String> dependencies = new ArrayList<>();
private final Map<String, String> ext = new LinkedHashMap<>();
public Buildscript dependency(String coordinates) {
this.dependencies.add(coordinates);
return this;
}
public Buildscript ext(String key, String value) {
this.ext.put(key, value);
return this;
}
public List<String> getDependencies() {
return Collections.unmodifiableList(this.dependencies);
}
public Map<String, String> getExt() {
return Collections.unmodifiableMap(this.ext);
}
}
/**
* Customization of a configuration in a Gradle build.
*/
public static class ConfigurationCustomization {
private final Set<String> extendsFrom = new LinkedHashSet<>();
public void extendsFrom(String configurationName) {
this.extendsFrom.add(configurationName);
}
public Set<String> getExtendsFrom() {
return Collections.unmodifiableSet(this.extendsFrom);
}
}
/**
* Customization of a task in a Gradle build.
*/
public static class TaskCustomization {
private final List<Invocation> invocations = new ArrayList<>();
private final Map<String, String> assignments = new LinkedHashMap<>();
private final Map<String, TaskCustomization> nested = new LinkedHashMap<>();
public void nested(String property, Consumer<TaskCustomization> customizer) {
customizer.accept(this.nested.computeIfAbsent(property,
(name) -> new TaskCustomization()));
}
public Map<String, TaskCustomization> getNested() {
return this.nested;
}
public void invoke(String target, String... arguments) {
this.invocations.add(new Invocation(target, Arrays.asList(arguments)));
}
public List<Invocation> getInvocations() {
return Collections.unmodifiableList(this.invocations);
}
public void set(String target, String value) {
this.assignments.put(target, value);
}
public Map<String, String> getAssignments() {
return Collections.unmodifiableMap(this.assignments);
}
/**
* An invocation of a method that customizes a task.
*/
public static class Invocation {
private final String target;
private final List<String> arguments;
Invocation(String target, List<String> arguments) {
this.target = target;
this.arguments = arguments;
}
public String getTarget() {
return this.target;
}
public List<String> getArguments() {
return this.arguments;
}
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2012-2019 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
*
* http://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.BuildSystem;
/**
* Gradle {@link BuildSystem}.
*
* @author Andy Wilkinson
*/
public final class GradleBuildSystem implements BuildSystem {
/**
* Gradle {@link BuildSystem} identifier.
*/
public static final String ID = "gradle";
@Override
public String id() {
return ID;
}
@Override
public String toString() {
return id();
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2012-2019 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
*
* http://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.BuildSystem;
import io.spring.initializr.generator.buildsystem.BuildSystemFactory;
/**
* {@link BuildSystemFactory Factory} for {@link GradleBuildSystem}.
*
* @author Andy Wilkinson
*/
class GradleBuildSystemFactory implements BuildSystemFactory {
@Override
public BuildSystem createBuildSystem(String id) {
if (GradleBuildSystem.ID.equals(id)) {
return new GradleBuildSystem();
}
return null;
}
}

View File

@ -0,0 +1,367 @@
/*
* Copyright 2012-2019 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
*
* http://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 java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import io.spring.initializr.generator.buildsystem.BillOfMaterials;
import io.spring.initializr.generator.buildsystem.Dependency;
import io.spring.initializr.generator.buildsystem.DependencyComparator;
import io.spring.initializr.generator.buildsystem.DependencyContainer;
import io.spring.initializr.generator.buildsystem.DependencyScope;
import io.spring.initializr.generator.buildsystem.MavenRepository;
import io.spring.initializr.generator.buildsystem.gradle.GradleBuild.ConfigurationCustomization;
import io.spring.initializr.generator.buildsystem.gradle.GradleBuild.TaskCustomization;
import io.spring.initializr.generator.io.IndentingWriter;
import io.spring.initializr.generator.version.VersionProperty;
import io.spring.initializr.generator.version.VersionReference;
/**
* A {@link GradleBuild} writer for {@code build.gradle}.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
*/
public class GradleBuildWriter {
public void writeTo(IndentingWriter writer, GradleBuild build) throws IOException {
boolean buildScriptWritten = writeBuildscript(writer, build);
writePlugins(writer, build, buildScriptWritten);
writeProperty(writer, "group", build.getGroup());
writeProperty(writer, "version", build.getVersion());
writeProperty(writer, "sourceCompatibility", build.getSourceCompatibility());
writeConfigurations(writer, build);
writeRepositories(writer, build, writer::println);
writeProperties(writer, build);
writeDependencies(writer, build);
writeBoms(writer, build);
writeTaskCustomizations(writer, build);
}
private boolean writeBuildscript(IndentingWriter writer, GradleBuild build) {
List<String> dependencies = build.getBuildscript().getDependencies();
Map<String, String> ext = build.getBuildscript().getExt();
if (dependencies.isEmpty() && ext.isEmpty()) {
return false;
}
writer.println("buildscript {");
writer.indented(() -> {
writeBuildscriptExt(writer, build);
writeBuildscriptRepositories(writer, build);
writeBuildscriptDependencies(writer, build);
});
writer.println("}");
return true;
}
private void writeBuildscriptExt(IndentingWriter writer, GradleBuild build) {
writeNestedMap(writer, "ext", build.getBuildscript().getExt(),
(key, value) -> key + " = " + value);
}
private void writeBuildscriptRepositories(IndentingWriter writer, GradleBuild build) {
writeRepositories(writer, build);
}
private void writeBuildscriptDependencies(IndentingWriter writer, GradleBuild build) {
writeNestedCollection(writer, "dependencies",
build.getBuildscript().getDependencies(),
(dependency) -> "classpath '" + dependency + "'");
}
private void writePlugins(IndentingWriter writer, GradleBuild build,
boolean buildScriptWritten) {
writeNestedCollection(writer, "plugins", build.getPlugins(), this::pluginAsString,
determineBeforeWriting(buildScriptWritten, writer));
writeCollection(writer, build.getAppliedPlugins(),
(plugin) -> "apply plugin: '" + plugin + "'", writer::println);
writer.println();
}
private Runnable determineBeforeWriting(boolean buildScriptWritten,
IndentingWriter writer) {
if (buildScriptWritten) {
return writer::println;
}
return null;
}
private String pluginAsString(GradlePlugin plugin) {
String string = "id '" + plugin.getId() + "'";
if (plugin.getVersion() != null) {
string += " version '" + plugin.getVersion() + "'";
}
return string;
}
private void writeConfigurations(IndentingWriter writer, GradleBuild build) {
Map<String, ConfigurationCustomization> configurationCustomizations = build
.getConfigurationCustomizations();
if (configurationCustomizations.isEmpty()) {
return;
}
writer.println();
writer.println("configurations {");
writer.indented(() -> {
configurationCustomizations.forEach((name, customization) -> {
writeConfiguration(writer, name, customization);
});
});
writer.println("}");
}
private void writeConfiguration(IndentingWriter writer, String configurationName,
ConfigurationCustomization configurationCustomization) {
if (configurationCustomization.getExtendsFrom().isEmpty()) {
writer.println(configurationName);
}
else {
writer.println(configurationName + " {");
writer.indented(() -> writer.println(String.format("extendsFrom %s",
String.join(",", configurationCustomization.getExtendsFrom()))));
writer.println("}");
}
}
private void writeRepositories(IndentingWriter writer, GradleBuild build) {
writeRepositories(writer, build, null);
}
private void writeRepositories(IndentingWriter writer, GradleBuild build,
Runnable beforeWriting) {
writeNestedCollection(writer, "repositories",
build.repositories().items().collect(Collectors.toList()),
this::repositoryAsString, beforeWriting);
}
private String repositoryAsString(MavenRepository repository) {
if (MavenRepository.MAVEN_CENTRAL.equals(repository)) {
return "mavenCentral()";
}
return "maven { url '" + repository.getUrl() + "' }";
}
private void writeProperties(IndentingWriter writer, GradleBuild build) {
if (build.getExt().isEmpty() && build.getVersionProperties().isEmpty()) {
return;
}
Map<String, String> allProperties = new LinkedHashMap<>(build.getExt());
build.getVersionProperties().entrySet().forEach((entry) -> allProperties
.put(getVersionPropertyKey(entry), entry.getValue()));
writeNestedCollection(writer, "ext", allProperties.entrySet(),
(e) -> getFormattedProperty(e.getKey(), e.getValue()), writer::println);
}
private String getVersionPropertyKey(Entry<VersionProperty, String> entry) {
return entry.getKey().isInternal() ? entry.getKey().toCamelCaseFormat()
: entry.getKey().toStandardFormat();
}
private String getFormattedProperty(String key, String value) {
return String.format("set('%s', '%s')", key, value);
}
private void writeDependencies(IndentingWriter writer, GradleBuild build) {
Set<Dependency> sortedDependencies = new LinkedHashSet<>();
DependencyContainer dependencies = build.dependencies();
sortedDependencies
.addAll(filterDependencies(dependencies, DependencyScope.COMPILE));
sortedDependencies
.addAll(filterDependencies(dependencies, DependencyScope.COMPILE_ONLY));
sortedDependencies
.addAll(filterDependencies(dependencies, DependencyScope.RUNTIME));
sortedDependencies.addAll(
filterDependencies(dependencies, DependencyScope.ANNOTATION_PROCESSOR));
sortedDependencies.addAll(
filterDependencies(dependencies, DependencyScope.PROVIDED_RUNTIME));
sortedDependencies
.addAll(filterDependencies(dependencies, DependencyScope.TEST_COMPILE));
sortedDependencies
.addAll(filterDependencies(dependencies, DependencyScope.TEST_RUNTIME));
writeNestedCollection(writer, "dependencies", sortedDependencies,
this::dependencyAsString, writer::println);
}
private String dependencyAsString(Dependency dependency) {
String quoteStyle = determineQuoteStyle(dependency.getVersion());
String version = determineVersion(dependency.getVersion());
String type = dependency.getType();
return configurationForScope(dependency.getScope()) + " " + quoteStyle
+ dependency.getGroupId() + ":" + dependency.getArtifactId()
+ ((version != null) ? ":" + version : "")
+ ((type != null) ? "@" + type : "") + quoteStyle;
}
private void writeBoms(IndentingWriter writer, GradleBuild build) {
if (build.boms().isEmpty()) {
return;
}
List<BillOfMaterials> boms = build.boms().items()
.sorted(Comparator.comparingInt(BillOfMaterials::getOrder).reversed())
.collect(Collectors.toList());
writer.println();
writer.println("dependencyManagement {");
writer.indented(
() -> writeNestedCollection(writer, "imports", boms, this::bomAsString));
writer.println("}");
}
private String bomAsString(BillOfMaterials bom) {
String quoteStyle = determineQuoteStyle(bom.getVersion());
String version = determineVersion(bom.getVersion());
return "mavenBom " + quoteStyle + bom.getGroupId() + ":" + bom.getArtifactId()
+ ":" + version + quoteStyle;
}
private String determineQuoteStyle(VersionReference versionReference) {
return (versionReference != null && versionReference.isProperty()) ? "\"" : "'";
}
private String determineVersion(VersionReference versionReference) {
if (versionReference != null) {
if (versionReference.isProperty()) {
VersionProperty property = versionReference.getProperty();
return "${"
+ (property.isInternal() ? property.toCamelCaseFormat()
: "property('" + property.toStandardFormat() + "')")
+ "}";
}
return versionReference.getValue();
}
return null;
}
private void writeTaskCustomizations(IndentingWriter writer, GradleBuild build) {
Map<String, TaskCustomization> taskCustomizations = build.getTaskCustomizations();
taskCustomizations.forEach((name, customization) -> {
writer.println();
writer.println(name + " {");
writer.indented(() -> writeTaskCustomization(writer, customization));
writer.println("}");
});
}
private void writeTaskCustomization(IndentingWriter writer,
TaskCustomization customization) {
writeCollection(writer, customization.getInvocations(),
(invocation) -> invocation.getTarget() + " "
+ String.join(", ", invocation.getArguments()));
writeMap(writer, customization.getAssignments(),
(key, value) -> key + " = " + value);
customization.getNested().forEach((property, nestedCustomization) -> {
writer.println(property + " {");
writer.indented(() -> writeTaskCustomization(writer, nestedCustomization));
writer.println("}");
});
}
private <T> void writeNestedCollection(IndentingWriter writer, String name,
Collection<T> collection, Function<T, String> itemToStringConverter) {
this.writeNestedCollection(writer, name, collection, itemToStringConverter, null);
}
private <T> void writeNestedCollection(IndentingWriter writer, String name,
Collection<T> collection, Function<T, String> converter,
Runnable beforeWriting) {
if (!collection.isEmpty()) {
if (beforeWriting != null) {
beforeWriting.run();
}
writer.println(name + " {");
writer.indented(() -> writeCollection(writer, collection, converter));
writer.println("}");
}
}
private <T> void writeCollection(IndentingWriter writer, Collection<T> collection,
Function<T, String> converter) {
writeCollection(writer, collection, converter, null);
}
private <T> void writeCollection(IndentingWriter writer, Collection<T> collection,
Function<T, String> itemToStringConverter, Runnable beforeWriting) {
if (!collection.isEmpty()) {
if (beforeWriting != null) {
beforeWriting.run();
}
collection.stream().map(itemToStringConverter).forEach(writer::println);
}
}
private <T, U> void writeNestedMap(IndentingWriter writer, String name, Map<T, U> map,
BiFunction<T, U, String> converter) {
if (!map.isEmpty()) {
writer.println(name + " {");
writer.indented(() -> writeMap(writer, map, converter));
writer.println("}");
}
}
private <T, U> void writeMap(IndentingWriter writer, Map<T, U> map,
BiFunction<T, U, String> converter) {
map.forEach((key, value) -> writer.println(converter.apply(key, value)));
}
private void writeProperty(IndentingWriter writer, String name, String value) {
if (value != null) {
writer.println(String.format("%s = '%s'", name, value));
}
}
private static Collection<Dependency> filterDependencies(
DependencyContainer dependencies, DependencyScope... types) {
List<DependencyScope> candidates = Arrays.asList(types);
return dependencies.items().filter((dep) -> candidates.contains(dep.getScope()))
.sorted(DependencyComparator.INSTANCE).collect(Collectors.toList());
}
private String configurationForScope(DependencyScope type) {
switch (type) {
case ANNOTATION_PROCESSOR:
return "annotationProcessor";
case COMPILE:
return "implementation";
case COMPILE_ONLY:
return "compileOnly";
case PROVIDED_RUNTIME:
return "providedRuntime";
case RUNTIME:
return "runtimeOnly";
case TEST_COMPILE:
return "testImplementation";
case TEST_RUNTIME:
return "testRuntimeOnly";
default:
throw new IllegalStateException(
"Unrecognized dependency type '" + type + "'");
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2012-2019 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
*
* http://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;
/**
* A plugin in a {@link GradleBuild}.
*
* @author Andy Wilkinson
*/
public class GradlePlugin {
private final String id;
private final String version;
public GradlePlugin(String id, String version) {
this.id = id;
this.version = version;
}
public String getId() {
return this.id;
}
public String getVersion() {
return this.version;
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2012-2019 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
*
* http://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 java.io.IOException;
import io.spring.initializr.generator.buildsystem.MavenRepository;
import io.spring.initializr.generator.io.IndentingWriter;
/**
* A {@link GradleBuild} writer for {@code settings.gradle}.
*
* @author Andy Wilkinson
*/
public class GradleSettingsWriter {
public void writeTo(IndentingWriter writer, GradleBuild build) throws IOException {
writePluginManagement(writer, build);
writer.println("rootProject.name = '" + build.getArtifact() + "'");
}
private void writePluginManagement(IndentingWriter writer, GradleBuild build) {
writer.println("pluginManagement {");
writer.indented(() -> {
writeRepositories(writer, build);
writeResolutionStrategyIfNecessary(writer, build);
});
writer.println("}");
}
private void writeRepositories(IndentingWriter writer, GradleBuild build) {
writer.println("repositories {");
writer.indented(() -> {
build.pluginRepositories().items().map(this::repositoryAsString)
.forEach(writer::println);
writer.println("gradlePluginPortal()");
});
writer.println("}");
}
private void writeResolutionStrategyIfNecessary(IndentingWriter writer,
GradleBuild build) {
if (build.pluginRepositories().items()
.allMatch(MavenRepository.MAVEN_CENTRAL::equals)) {
return;
}
writer.println("resolutionStrategy {");
writer.indented(() -> {
writer.println("eachPlugin {");
writer.indented(() -> {
writer.println("if(requested.id.id == 'org.springframework.boot') {");
writer.indented(() -> writer.println(
"useModule(\"org.springframework.boot:spring-boot-gradle-plugin:${requested.version}\")"));
writer.println("}");
});
writer.println("}");
});
writer.println("}");
}
private String repositoryAsString(MavenRepository repository) {
if (MavenRepository.MAVEN_CENTRAL.equals(repository)) {
return "mavenCentral()";
}
return "maven { url '" + repository.getUrl() + "' }";
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright 2012-2019 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
*
* http://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.
*/
/**
* Gradle build system. Provides a {@link io.spring.initializr.generator.buildsystem.Build
* Build} abstraction and a writer for {@code build.gradle} and {@code settings.gradle}.
*/
package io.spring.initializr.generator.buildsystem.gradle;

View File

@ -1,3 +1,6 @@
io.spring.initializr.generator.buildsystem.BuildSystemFactory=\
io.spring.initializr.generator.buildsystem.gradle.GradleBuildSystemFactory
io.spring.initializr.generator.language.LanguageFactory=\
io.spring.initializr.generator.language.groovy.GroovyLanguageFactory,\
io.spring.initializr.generator.language.java.JavaLanguageFactory,\

View File

@ -16,8 +16,15 @@
package io.spring.initializr.generator.buildsystem;
import org.junit.jupiter.api.Test;
import java.nio.file.Path;
import io.spring.initializr.generator.buildsystem.gradle.GradleBuildSystem;
import io.spring.initializr.generator.language.java.JavaLanguage;
import io.spring.initializr.generator.language.kotlin.KotlinLanguage;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
@ -27,6 +34,28 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*/
class BuildSystemTests {
@Test
void gradleBuildSystem() {
BuildSystem gradle = BuildSystem.forId("gradle");
assertThat(gradle).isInstanceOf(GradleBuildSystem.class);
assertThat(gradle.id()).isEqualTo("gradle");
assertThat(gradle.toString()).isEqualTo("gradle");
}
@Test
void defaultMainDirectory(@TempDir Path directory) {
Path mainDirectory = BuildSystem.forId("gradle").getMainDirectory(directory,
new JavaLanguage());
assertThat(mainDirectory).isEqualTo(directory.resolve("src/main/java"));
}
@Test
void defaultTestDirectory(@TempDir Path directory) {
Path mainDirectory = BuildSystem.forId("gradle").getTestDirectory(directory,
new KotlinLanguage());
assertThat(mainDirectory).isEqualTo(directory.resolve("src/test/kotlin"));
}
@Test
void unknownBuildSystem() {
assertThatIllegalStateException().isThrownBy(() -> BuildSystem.forId("unknown"))

View File

@ -0,0 +1,403 @@
/*
* Copyright 2012-2019 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
*
* http://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 java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import io.spring.initializr.generator.buildsystem.DependencyScope;
import io.spring.initializr.generator.io.IndentingWriter;
import io.spring.initializr.generator.version.VersionProperty;
import io.spring.initializr.generator.version.VersionReference;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link GradleBuildWriter}
*
* @author Andy Wilkinson
*/
class GradleBuildWriterTests {
@Test
void gradleBuildWithCoordinates() throws IOException {
GradleBuild build = new GradleBuild();
build.setGroup("com.example");
build.setVersion("0.0.1-SNAPSHOT");
List<String> lines = generateBuild(build);
assertThat(lines).contains("group = 'com.example'", "version = '0.0.1-SNAPSHOT'");
}
@Test
void gradleBuildWithSourceCompatibility() throws IOException {
GradleBuild build = new GradleBuild();
build.setSourceCompatibility("11");
List<String> lines = generateBuild(build);
assertThat(lines).contains("sourceCompatibility = '11'");
}
@Test
void gradleBuildWithBuildscriptDependency() throws IOException {
GradleBuild build = new GradleBuild();
build.repositories().add("maven-central");
build.buildscript((buildscript) -> buildscript.dependency(
"org.springframework.boot:spring-boot-gradle-plugin:2.1.0.RELEASE"));
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("buildscript {", " repositories {",
" mavenCentral()", " }", " dependencies {",
" classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.1.0.RELEASE'",
" }", "}");
}
@Test
void gradleBuildWithBuildscriptExtProperty() throws IOException {
GradleBuild build = new GradleBuild();
build.repositories().add("maven-central");
build.buildscript((buildscript) -> buildscript.ext("kotlinVersion", "'1.2.51'"));
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("buildscript {", " ext {",
" kotlinVersion = '1.2.51'", " }");
}
@Test
void gradleBuildWithPlugin() throws IOException {
GradleBuild build = new GradleBuild();
build.addPlugin("java");
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("plugins {", " id 'java'", "}");
}
@Test
void gradleBuildWithPluginAndVersion() throws IOException {
GradleBuild build = new GradleBuild();
build.addPlugin("org.springframework.boot", "2.1.0.RELEASE");
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("plugins {",
" id 'org.springframework.boot' version '2.1.0.RELEASE'", "}");
}
@Test
void gradleBuildWithApplyPlugin() throws IOException {
GradleBuild build = new GradleBuild();
build.applyPlugin("io.spring.dependency-management");
List<String> lines = generateBuild(build);
assertThat(lines)
.containsSequence("apply plugin: 'io.spring.dependency-management'");
}
@Test
void gradleBuildWithMavenCentralRepository() throws IOException {
GradleBuild build = new GradleBuild();
build.repositories().add("maven-central");
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("repositories {", " mavenCentral()", "}");
}
@Test
void gradleBuildWithRepository() throws IOException {
GradleBuild build = new GradleBuild();
build.repositories().add("spring-milestones", "Spring Milestones",
"https://repo.spring.io/milestone");
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("repositories {",
" maven { url 'https://repo.spring.io/milestone' }", "}");
}
@Test
void gradleBuildWithSnapshotRepository() throws IOException {
GradleBuild build = new GradleBuild();
build.repositories().add("spring-snapshots", "Spring Snapshots",
"https://repo.spring.io/snapshot", true);
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("repositories {",
" maven { url 'https://repo.spring.io/snapshot' }", "}");
}
@Test
void gradleBuildWithPluginRepository() throws IOException {
GradleBuild build = new GradleBuild();
build.pluginRepositories().add("spring-milestones", "Spring Milestones",
"https://repo.spring.io/milestone");
List<String> lines = generateBuild(build);
assertThat(lines).doesNotContain("repositories {");
}
@Test
void gradleBuildWithTaskCustomizedWithInvocations() throws IOException {
GradleBuild build = new GradleBuild();
build.customizeTask("asciidoctor", (task) -> {
task.invoke("inputs.dir", "snippetsDir");
task.invoke("dependsOn", "test");
});
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("asciidoctor {", " inputs.dir snippetsDir",
" dependsOn test", "}");
}
@Test
void gradleBuildWithTaskCustomizedWithAssignments() throws IOException {
GradleBuild build = new GradleBuild();
build.customizeTask("compileKotlin", (task) -> {
task.set("kotlinOptions.freeCompilerArgs", "['-Xjsr305=strict']");
task.set("kotlinOptions.jvmTarget", "'1.8'");
});
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("compileKotlin {",
" kotlinOptions.freeCompilerArgs = ['-Xjsr305=strict']",
" kotlinOptions.jvmTarget = '1.8'", "}");
}
@Test
void gradleBuildWithTaskCustomizedWithNestedCustomization() throws IOException {
GradleBuild build = new GradleBuild();
build.customizeTask("compileKotlin", (compileKotlin) -> compileKotlin
.nested("kotlinOptions", (kotlinOptions) -> {
kotlinOptions.set("freeCompilerArgs", "['-Xjsr305=strict']");
kotlinOptions.set("jvmTarget", "'1.8'");
}));
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("compileKotlin {", " kotlinOptions {",
" freeCompilerArgs = ['-Xjsr305=strict']",
" jvmTarget = '1.8'", " }", "}");
}
@Test
void gradleBuildWithExt() throws Exception {
GradleBuild build = new GradleBuild();
build.setGroup("com.example.demo");
build.setArtifact("demo");
build.ext("java.version", "1.8").ext("alpha", "a");
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence(" set('alpha', 'a')",
" set('java.version', '1.8')");
}
@Test
void gradleBuildWithVersionProperties() throws IOException {
GradleBuild build = new GradleBuild();
build.addVersionProperty(VersionProperty.of("version.property"), "1.2.3");
build.addInternalVersionProperty("internal.property", "4.5.6");
build.addExternalVersionProperty("external.property", "7.8.9");
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("ext {",
" set('external.property', '7.8.9')",
" set('internalProperty', '4.5.6')",
" set('versionProperty', '1.2.3')", "}");
}
@Test
void gradleBuildWithVersionedDependency() throws IOException {
GradleBuild build = new GradleBuild();
build.dependencies().add("kotlin-stdlib", "org.jetbrains.kotlin",
"kotlin-stdlib-jdk8", VersionReference.ofProperty("kotlin.version"),
DependencyScope.COMPILE);
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("dependencies {",
" implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}\"",
"}");
}
@Test
void gradleBuildWithExternalVersionedDependency() throws IOException {
GradleBuild build = new GradleBuild();
build.dependencies().add("acme", "com.example", "acme",
VersionReference.ofProperty(VersionProperty.of("acme.version", false)),
DependencyScope.COMPILE);
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("dependencies {",
" implementation \"com.example:acme:${property('acme.version')}\"",
"}");
}
@Test
void gradleBuildWithExtAndVersionProperties() throws Exception {
GradleBuild build = new GradleBuild();
build.setGroup("com.example.demo");
build.setArtifact("demo");
build.addInternalVersionProperty("test-version", "1.0");
build.addExternalVersionProperty("alpha-version", "0.1");
build.ext("myProperty", "42");
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence(" set('myProperty', '42')",
" set('alpha-version', '0.1')", " set('testVersion', '1.0')");
}
@Test
void gradleBuildWithConfiguration() throws Exception {
GradleBuild build = new GradleBuild();
build.addConfiguration("developmentOnly");
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("configurations {", " developmentOnly",
"}");
}
@Test
void gradleBuildWithConfigurationCustomization() throws Exception {
GradleBuild build = new GradleBuild();
build.customizeConfiguration("developmentOnly",
(configuration) -> configuration.extendsFrom("compile"));
build.customizeConfiguration("developmentOnly",
(configuration) -> configuration.extendsFrom("testCompile"));
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("configurations {", " developmentOnly {",
" extendsFrom compile,testCompile", " }", "}");
}
@Test
void gradleBuildWithConfigurationCustomizations() throws Exception {
GradleBuild build = new GradleBuild();
build.customizeConfiguration("developmentOnly",
(configuration) -> configuration.extendsFrom("compile"));
build.customizeConfiguration("testOnly",
(configuration) -> configuration.extendsFrom("testCompile"));
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("configurations {", " developmentOnly {",
" extendsFrom compile", " }", " testOnly {",
" extendsFrom testCompile", " }", "}");
}
@Test
void gradleBuildWithAnnotationProcessorDependency() throws IOException {
GradleBuild build = new GradleBuild();
build.dependencies().add("annotation-processor", "org.springframework.boot",
"spring-boot-configuration-processor",
DependencyScope.ANNOTATION_PROCESSOR);
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("dependencies {",
" annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'",
"}");
}
@Test
void gradleBuildWithCompileDependency() throws IOException {
GradleBuild build = new GradleBuild();
build.dependencies().add("root", "org.springframework.boot",
"spring-boot-starter", DependencyScope.COMPILE);
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("dependencies {",
" implementation 'org.springframework.boot:spring-boot-starter'", "}");
}
@Test
void gradleBuildWithRuntimeDependency() throws IOException {
GradleBuild build = new GradleBuild();
build.dependencies().add("driver", "com.example", "jdbc-driver",
VersionReference.ofValue("1.0.0"), DependencyScope.RUNTIME);
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("dependencies {",
" runtimeOnly 'com.example:jdbc-driver:1.0.0'", "}");
}
@Test
void gradleBuildWithProvidedRuntimeDependency() throws IOException {
GradleBuild build = new GradleBuild();
build.dependencies().add("tomcat", "org.springframework.boot",
"spring-boot-starter-tomcat", DependencyScope.PROVIDED_RUNTIME);
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("dependencies {",
" providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'",
"}");
}
@Test
void gradleBuildWithTestCompileDependency() throws IOException {
GradleBuild build = new GradleBuild();
build.dependencies().add("test", "org.springframework.boot",
"spring-boot-starter-test", DependencyScope.TEST_COMPILE);
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("dependencies {",
" testImplementation 'org.springframework.boot:spring-boot-starter-test'",
"}");
}
@Test
void gradleBuildWithCompileOnlyDependency() throws IOException {
GradleBuild build = new GradleBuild();
build.dependencies().add("test", "org.springframework.boot",
"spring-boot-starter-foobar", DependencyScope.COMPILE_ONLY);
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("dependencies {",
" compileOnly 'org.springframework.boot:spring-boot-starter-foobar'",
"}");
}
@Test
void gradleBuildWithTestRuntimeDependency() throws IOException {
GradleBuild build = new GradleBuild();
build.dependencies().add("embed-mongo", "de.flapdoodle.embed",
"de.flapdoodle.embed.mongo", DependencyScope.TEST_RUNTIME);
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("dependencies {",
" testRuntimeOnly 'de.flapdoodle.embed:de.flapdoodle.embed.mongo'",
"}");
}
@Test
void gradleBuildWithNonNullArtifactTypeDependency() throws IOException {
GradleBuild build = new GradleBuild();
build.dependencies().add("root", "org.springframework.boot",
"spring-boot-starter", null, DependencyScope.COMPILE, "tar.gz");
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("dependencies {",
" implementation 'org.springframework.boot:spring-boot-starter@tar.gz'",
"}");
}
@Test
void gradleBuildWithBom() throws IOException {
GradleBuild build = new GradleBuild();
build.boms().add("test", "com.example", "my-project-dependencies",
VersionReference.ofValue("1.0.0.RELEASE"));
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("dependencyManagement {", " imports {",
" mavenBom 'com.example:my-project-dependencies:1.0.0.RELEASE'",
" }", "}");
}
@Test
void gradleBuildWithOrderedBoms() throws IOException {
GradleBuild build = new GradleBuild();
build.boms().add("bom1", "com.example", "my-project-dependencies",
VersionReference.ofValue("1.0.0.RELEASE"), 5);
build.boms().add("bom2", "com.example", "root-dependencies",
VersionReference.ofProperty("root.version"), 2);
List<String> lines = generateBuild(build);
assertThat(lines).containsSequence("dependencyManagement {", " imports {",
" mavenBom 'com.example:my-project-dependencies:1.0.0.RELEASE'",
" mavenBom \"com.example:root-dependencies:${rootVersion}\"",
" }", "}");
}
@Test
void gradleBuildWithCustomVersion() throws IOException {
GradleBuild build = new GradleBuild();
build.setVersion("1.2.4.RELEASE");
List<String> lines = generateBuild(build);
assertThat(lines).contains("version = '1.2.4.RELEASE'");
}
private List<String> generateBuild(GradleBuild build) throws IOException {
GradleBuildWriter writer = new GradleBuildWriter();
StringWriter out = new StringWriter();
writer.writeTo(new IndentingWriter(out), build);
return Arrays.asList(out.toString().split("\\r?\\n"));
}
}

View File

@ -0,0 +1,90 @@
/*
* Copyright 2012-2019 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
*
* http://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 java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import io.spring.initializr.generator.io.IndentingWriter;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link GradleSettingsWriter}.
*
* @author Andy Wilkinson
*/
class GradleSettingsWriterTests {
@Test
void gradleBuildWithMavenCentralPluginRepository() throws IOException {
GradleBuild build = new GradleBuild();
build.pluginRepositories().add("maven-central");
List<String> lines = generateSettings(build);
assertThat(lines).containsSequence("pluginManagement {", " repositories {",
" mavenCentral()", " gradlePluginPortal()", " }", "}");
}
@Test
void gradleBuildWithPluginRepository() throws IOException {
GradleBuild build = new GradleBuild();
build.pluginRepositories().add("spring-milestones", "Spring Milestones",
"https://repo.spring.io/milestone");
List<String> lines = generateSettings(build);
assertThat(lines).containsSequence("pluginManagement {", " repositories {",
" maven { url 'https://repo.spring.io/milestone' }",
" gradlePluginPortal()", " }", " resolutionStrategy {",
" eachPlugin {",
" if(requested.id.id == 'org.springframework.boot') {",
" useModule(\"org.springframework.boot:spring-boot-gradle-plugin:${requested.version}\")",
" }", " }", " }", "}");
}
@Test
void gradleBuildWithSnapshotPluginRepository() throws IOException {
GradleBuild build = new GradleBuild();
build.pluginRepositories().add("spring-snapshots", "Spring Snapshots",
"https://repo.spring.io/snapshot", true);
List<String> lines = generateSettings(build);
assertThat(lines).containsSequence("pluginManagement {", " repositories {",
" maven { url 'https://repo.spring.io/snapshot' }",
" gradlePluginPortal()", " }", " resolutionStrategy {",
" eachPlugin {",
" if(requested.id.id == 'org.springframework.boot') {",
" useModule(\"org.springframework.boot:spring-boot-gradle-plugin:${requested.version}\")",
" }", " }", " }", "}");
}
@Test
void artifactIdShouldBeUsedAsTheRootProjectName() throws Exception {
GradleBuild build = new GradleBuild();
build.setArtifact("my-application");
List<String> lines = generateSettings(build);
assertThat(lines).containsSequence("rootProject.name = 'my-application'");
}
private List<String> generateSettings(GradleBuild build) throws IOException {
GradleSettingsWriter writer = new GradleSettingsWriter();
StringWriter out = new StringWriter();
writer.writeTo(new IndentingWriter(out), build);
return Arrays.asList(out.toString().split("\\r?\\n"));
}
}