Add support for configurable properties

This commit introduces a `buildProperties` property on the request that
can be used to specify Gradle/Maven build-specific properties as well as
an arbitrary number of version overrides.

Instead of hard-coding some properties in the templates, these defaults
are now inherited from the request itself.

Closes gh-259
This commit is contained in:
Stephane Nicoll
2016-07-12 13:39:11 +02:00
parent 2632d69036
commit d9c65a5a07
13 changed files with 293 additions and 29 deletions

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2016 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
/**
* Build properties associated to a project request.
*
* @author Stephane Nicoll
*/
class BuildProperties {
/**
* Maven-specific build properties, added to the regular {@code properties} element.
*/
final TreeMap<String, Closure<String>> maven = new TreeMap<>()
/**
* Gradle-specific build properties, added to the {@code buildscript} section
* of the gradle build.
*/
final TreeMap<String, Closure<String>> gradle = new TreeMap<>()
/**
* Version properties. Shared between the two build systems.
*/
final TreeMap<String, Closure<String>> versions = new TreeMap<>()
}

View File

@@ -68,7 +68,11 @@ class ProjectGenerator {
*/ */
byte[] generateMavenPom(ProjectRequest request) { byte[] generateMavenPom(ProjectRequest request) {
try { try {
def model = initializeModel(request) def model = resolveModel(request)
if (!isMavenBuild(request)) {
throw new InvalidProjectRequestException("Could not generate Maven pom, " +
"invalid project type $request.type")
}
def content = doGenerateMavenPom(model) def content = doGenerateMavenPom(model)
publishProjectGeneratedEvent(request) publishProjectGeneratedEvent(request)
content content
@@ -83,7 +87,11 @@ class ProjectGenerator {
*/ */
byte[] generateGradleBuild(ProjectRequest request) { byte[] generateGradleBuild(ProjectRequest request) {
try { try {
def model = initializeModel(request) def model = resolveModel(request)
if (!isGradleBuild(request)) {
throw new InvalidProjectRequestException("Could not generate Gradle build, " +
"invalid project type $request.type")
}
def content = doGenerateGradleBuild(model) def content = doGenerateGradleBuild(model)
publishProjectGeneratedEvent(request) publishProjectGeneratedEvent(request)
content content
@@ -107,7 +115,7 @@ class ProjectGenerator {
} }
protected File doGenerateProjectStructure(ProjectRequest request) { protected File doGenerateProjectStructure(ProjectRequest request) {
def model = initializeModel(request) def model = resolveModel(request)
def rootDir = File.createTempFile('tmp', '', new File(tmpdir)) def rootDir = File.createTempFile('tmp', '', new File(tmpdir))
addTempFile(rootDir.name, rootDir) addTempFile(rootDir.name, rootDir)
@@ -196,7 +204,13 @@ class ProjectGenerator {
eventPublisher.publishEvent(event) eventPublisher.publishEvent(event)
} }
protected Map initializeModel(ProjectRequest request) { /**
* Resolve the specified {@link ProjectRequest} and return the model to use
* to generate the project
* @param request the request to handle
* @return a model for that request
*/
protected Map resolveModel(ProjectRequest request) {
Assert.notNull request.bootVersion, 'boot version must not be null' Assert.notNull request.bootVersion, 'boot version must not be null'
def model = [:] def model = [:]
def metadata = metadataProvider.get() def metadata = metadataProvider.get()
@@ -213,6 +227,7 @@ class ProjectGenerator {
if (isMavenBuild(request)) { if (isMavenBuild(request)) {
ParentPom parentPom = metadata.configuration.env.maven.resolveParentPom(request.bootVersion) ParentPom parentPom = metadata.configuration.env.maven.resolveParentPom(request.bootVersion)
if (parentPom.includeSpringBootBom && !request.boms['spring-boot']) { if (parentPom.includeSpringBootBom && !request.boms['spring-boot']) {
request.buildProperties.versions['spring-boot.version'] = { request.bootVersion }
request.boms['spring-boot'] = metadata.createSpringBootBom('${spring-boot.version}') request.boms['spring-boot'] = metadata.createSpringBootBom('${spring-boot.version}')
} }

View File

@@ -54,6 +54,11 @@ class ProjectRequest extends BasicProjectRequest {
final Map<String, Repository> repositories = [:] final Map<String, Repository> repositories = [:]
/**
* Build properties.
*/
final BuildProperties buildProperties = new BuildProperties()
def facets = [] def facets = []
def build def build
@@ -136,6 +141,8 @@ class ProjectRequest extends BasicProjectRequest {
initializeRepositories(metadata, requestedVersion) initializeRepositories(metadata, requestedVersion)
initializeProperties(metadata)
afterResolution(metadata) afterResolution(metadata)
} }
@@ -157,6 +164,22 @@ class ProjectRequest extends BasicProjectRequest {
} }
} }
protected void initializeProperties(InitializrMetadata metadata) {
if ('gradle'.equals(build)) {
buildProperties.gradle['springBootVersion'] = { getBootVersion() }
if ('kotlin'.equals(language)) {
buildProperties.gradle['kotlinVersion'] = { metadata.configuration.env.kotlin.version }
}
} else {
buildProperties.maven['project.build.sourceEncoding'] = { 'UTF-8' }
buildProperties.maven['project.reporting.outputEncoding'] = { 'UTF-8' }
buildProperties.versions['java.version'] = { getJavaVersion() }
if ('kotlin'.equals(language)) {
buildProperties.versions['kotlin.version'] = { metadata.configuration.env.kotlin.version }
}
}
}
private void resolveBom(InitializrMetadata metadata, String bomId, Version requestedVersion) { private void resolveBom(InitializrMetadata metadata, String bomId, Version requestedVersion) {
if (!boms[bomId]) { if (!boms[bomId]) {
def bom = metadata.configuration.env.boms[bomId].resolve(requestedVersion) def bom = metadata.configuration.env.boms[bomId].resolve(requestedVersion)

View File

@@ -1,7 +1,6 @@
buildscript { buildscript {
ext { ext {<% buildProperties.gradle.each { %>
springBootVersion = '${bootVersion}'<% if (language=='kotlin') { %> ${it.key} = '${it.value.call()}'<% } %>
kotlinVersion = '${kotlinVersion}'<% } %>
} }
repositories { repositories {
mavenCentral()<% if (!bootVersion.contains("RELEASE")) { %> mavenCentral()<% if (!bootVersion.contains("RELEASE")) { %>
@@ -38,6 +37,8 @@ repositories {
<% if (providedDependencies) { %>configurations { <% if (providedDependencies) { %>configurations {
providedRuntime providedRuntime
} }
<% } %><% if (buildProperties.versions) { %><%buildProperties.versions.each { %>
ext['${it.key}'] = '${it.value.call()}'<% } %>
<% } %> <% } %>
dependencies {<% compileDependencies.each { %> dependencies {<% compileDependencies.each { %>
compile('${it.groupId}:${it.artifactId}${it.version ? ":$it.version" : ""}${it.type ? "@$it.type" : ""}')<% } %><% if (language=='groovy') { %> compile('${it.groupId}:${it.artifactId}${it.version ? ":$it.version" : ""}${it.type ? "@$it.type" : ""}')<% } %><% if (language=='groovy') { %>

View File

@@ -18,12 +18,9 @@
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
<properties> <properties><% buildProperties.maven.each { %>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <${it.key}>${it.value()}</${it.key}><% } %><%buildProperties.versions.each { %>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <${it.key}>${it.value()}</${it.key}><%}%>
<java.version>${javaVersion}</java.version><% if (language=='kotlin') { %>
<kotlin.version>${kotlinVersion}</kotlin.version><% } %><% if (includeSpringBootBom) { %>
<spring-boot.version>${bootVersion}</spring-boot.version><%}%>
</properties> </properties>
<dependencies><% compileDependencies.each { %> <dependencies><% compileDependencies.each { %>

View File

@@ -33,32 +33,40 @@ import static io.spring.initializr.test.generator.ProjectAssert.DEFAULT_PACKAGE_
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
class ProjectGeneratorBuildTests extends AbstractProjectGeneratorTests { class ProjectGeneratorBuildTests extends AbstractProjectGeneratorTests {
@Parameterized.Parameters(name = "{0} with {1}") @Parameterized.Parameters(name = "{0}")
public static Object[] parameters() { public static Object[] parameters() {
Object[] javaMaven = ["java", "maven", "pom.xml"] Object[] maven = ["maven", "pom.xml"]
Object[] javaGradle = ["java", "gradle", "build.gradle"] Object[] gradle = ["gradle", "build.gradle"]
Object[] groovyMaven = ["groovy", "maven", "pom.xml"] Object[] parameters = [maven, gradle]
Object[] groovyGradle = ["groovy", "gradle", "build.gradle"]
Object[] kotlinMaven = ["kotlin", "maven", "pom.xml"]
Object[] kotlinGradle = ["kotlin", "gradle", "build.gradle"]
Object[] parameters = [javaMaven, javaGradle, groovyMaven, groovyGradle, kotlinMaven, kotlinGradle]
parameters parameters
} }
private final String language
private final String build private final String build
private final String fileName private final String fileName
private final String assertFileName private final String assertFileName
ProjectGeneratorBuildTests(String language, String build, String fileName) { ProjectGeneratorBuildTests(String build, String fileName) {
this.language = language
this.build = build this.build = build
this.fileName = fileName this.fileName = fileName
this.assertFileName = fileName + ".gen" this.assertFileName = fileName + ".gen"
} }
@Test @Test
public void standardJar() { public void standardJarJava() {
testStandardJar('java')
}
@Test
public void standardJarGroovy() {
testStandardJar('groovy')
}
@Test
public void standardJarKotlin() {
testStandardJar('kotlin')
}
private void testStandardJar(def language) {
def request = createProjectRequest() def request = createProjectRequest()
request.language = language request.language = language
request.type = "$build-project" request.type = "$build-project"
@@ -68,7 +76,21 @@ class ProjectGeneratorBuildTests extends AbstractProjectGeneratorTests {
} }
@Test @Test
public void standardWar() { public void standardWarJava() {
testStandardWar('java')
}
@Test
public void standardWarGroovy() {
testStandardWar('java')
}
@Test
public void standardWarKotlin() {
testStandardWar('kotlin')
}
private void testStandardWar(def language) {
def request = createProjectRequest('web') def request = createProjectRequest('web')
request.packaging = 'war' request.packaging = 'war'
request.language = language request.language = language
@@ -78,4 +100,15 @@ class ProjectGeneratorBuildTests extends AbstractProjectGeneratorTests {
.equalsTo(new ClassPathResource("project/$language/war/$assertFileName")) .equalsTo(new ClassPathResource("project/$language/war/$assertFileName"))
} }
@Test
public void versionOverride() {
def request = createProjectRequest('web')
request.type = "$build-project"
request.buildProperties.versions['spring-foo.version'] = {'0.1.0.RELEASE'}
request.buildProperties.versions['spring-bar.version'] = {'0.2.0.RELEASE'}
def project = generateProject(request)
project.sourceCodeAssert("$fileName")
.equalsTo(new ClassPathResource("project/$build/version-override-$assertFileName"))
}
} }

View File

@@ -19,7 +19,9 @@ package io.spring.initializr.generator
import io.spring.initializr.metadata.BillOfMaterials import io.spring.initializr.metadata.BillOfMaterials
import io.spring.initializr.metadata.Dependency import io.spring.initializr.metadata.Dependency
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.ExpectedException
import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
@@ -37,6 +39,9 @@ import static org.junit.Assert.fail
*/ */
class ProjectGeneratorTests extends AbstractProjectGeneratorTests { class ProjectGeneratorTests extends AbstractProjectGeneratorTests {
@Rule
public final ExpectedException thrown = ExpectedException.none()
@Test @Test
void defaultMavenPom() { void defaultMavenPom() {
def request = createProjectRequest('web') def request = createProjectRequest('web')
@@ -611,6 +616,50 @@ class ProjectGeneratorTests extends AbstractProjectGeneratorTests {
.hasDependenciesCount(3) .hasDependenciesCount(3)
} }
@Test
void buildPropertiesMaven() {
def request = createProjectRequest('web')
request.buildProperties.maven['name'] = { 'test' }
request.buildProperties.versions['foo.version'] = { '1.2.3' }
request.buildProperties.gradle['ignore.property'] = { 'yes' }
generateMavenPom(request)
.hasProperty('name', 'test')
.hasProperty('foo.version', '1.2.3')
.hasNoProperty('ignore.property')
}
@Test
void buildPropertiesGradle() {
def request = createProjectRequest('web')
request.buildProperties.gradle['name'] = { 'test' }
request.buildProperties.versions['foo.version'] = { '1.2.3' }
request.buildProperties.maven['ignore.property'] = { 'yes' }
generateGradleBuild(request)
.contains("name = 'test'")
.contains("ext['foo.version'] = '1.2.3'")
.doesNotContain('ignore.property')
}
@Test
void invalidProjectTypeMavenPom() {
def request = createProjectRequest('web')
request.type = 'gradle-build'
this.thrown.expect(InvalidProjectRequestException)
this.thrown.expectMessage('gradle-build')
projectGenerator.generateMavenPom(request)
}
@Test
void invalidProjectTypeGradleBuild() {
def request = createProjectRequest('web')
request.type = 'maven-build'
this.thrown.expect(InvalidProjectRequestException)
this.thrown.expectMessage('maven-build')
projectGenerator.generateGradleBuild(request)
}
@Test @Test
void invalidDependency() { void invalidDependency() {
def request = createProjectRequest('foo-bar') def request = createProjectRequest('foo-bar')

View File

@@ -113,6 +113,11 @@ class PomAssert {
this this
} }
PomAssert hasNoProperty(String name) {
assertFalse "No property $name should have been found", properties.containsKey(name)
this
}
PomAssert hasDependenciesCount(int count) { PomAssert hasDependenciesCount(int count) {
assertEquals "Wrong number of declared dependencies -->'${dependencies.keySet()}", assertEquals "Wrong number of declared dependencies -->'${dependencies.keySet()}",
count, dependencies.size() count, dependencies.size()

View File

@@ -0,0 +1,45 @@
buildscript {
ext {
springBootVersion = '1.2.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath('io.spring.gradle:dependency-management-plugin:0.5.1.RELEASE')
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'spring-boot'
apply plugin: 'io.spring.dependency-management'
jar {
baseName = 'demo'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
ext['spring-bar.version'] = '0.2.0.RELEASE'
ext['spring-foo.version'] = '0.1.0.RELEASE'
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
eclipse {
classpath {
containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
}
}

View File

@@ -1,7 +1,7 @@
buildscript { buildscript {
ext { ext {
springBootVersion = '1.2.3.RELEASE'
kotlinVersion = '1.0.1' kotlinVersion = '1.0.1'
springBootVersion = '1.2.3.RELEASE'
} }
repositories { repositories {
mavenCentral() mavenCentral()

View File

@@ -1,7 +1,7 @@
buildscript { buildscript {
ext { ext {
springBootVersion = '1.2.3.RELEASE'
kotlinVersion = '1.0.1' kotlinVersion = '1.0.1'
springBootVersion = '1.2.3.RELEASE'
} }
repositories { repositories {
mavenCentral() mavenCentral()

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-bar.version>0.2.0.RELEASE</spring-bar.version>
<spring-foo.version>0.1.0.RELEASE</spring-foo.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -191,6 +191,7 @@ class MainController extends AbstractInitializrController {
@RequestMapping('/pom') @RequestMapping('/pom')
@ResponseBody @ResponseBody
ResponseEntity<byte[]> pom(BasicProjectRequest request) { ResponseEntity<byte[]> pom(BasicProjectRequest request) {
request.type = 'maven-build'
def mavenPom = projectGenerator.generateMavenPom((ProjectRequest) request) def mavenPom = projectGenerator.generateMavenPom((ProjectRequest) request)
createResponseEntity(mavenPom, 'application/octet-stream', 'pom.xml') createResponseEntity(mavenPom, 'application/octet-stream', 'pom.xml')
} }
@@ -198,6 +199,7 @@ class MainController extends AbstractInitializrController {
@RequestMapping('/build') @RequestMapping('/build')
@ResponseBody @ResponseBody
ResponseEntity<byte[]> gradle(BasicProjectRequest request) { ResponseEntity<byte[]> gradle(BasicProjectRequest request) {
request.type = 'gradle-build'
def gradleBuild = projectGenerator.generateGradleBuild((ProjectRequest) request) def gradleBuild = projectGenerator.generateGradleBuild((ProjectRequest) request)
createResponseEntity(gradleBuild, 'application/octet-stream', 'build.gradle') createResponseEntity(gradleBuild, 'application/octet-stream', 'build.gradle')
} }