Improve support for version token

This commit fixes the use of version token with Gradle. The standard
format with maven is "foo-bar.version" while Gradle uses
"fooBarVersion". The former format does not work with Gradle as it
attempts to interpret "-" as an operation.

A `VersionProperty` now defines a standard structure for the property
and allow to derive a camel cased version. That way, Maven still uses
the standard format while Gradle generates a consistent "fooBarVersion"
property.

Closes gh-396
This commit is contained in:
Stephane Nicoll 2017-04-22 16:42:01 +02:00
parent aff85819a9
commit fae8769748
14 changed files with 257 additions and 43 deletions

View File

@ -20,6 +20,8 @@ import java.util.Map;
import java.util.TreeMap;
import java.util.function.Supplier;
import io.spring.initializr.util.VersionProperty;
/**
* Build properties associated to a project request.
*
@ -41,7 +43,7 @@ public class BuildProperties {
/**
* Version properties. Shared between the two build systems.
*/
private final TreeMap<String, Supplier<String>> versions = new TreeMap<>();
private final TreeMap<VersionProperty, Supplier<String>> versions = new TreeMap<>();
public Map<String, Supplier<String>> getMaven() {
return maven;
@ -51,7 +53,7 @@ public class BuildProperties {
return gradle;
}
public Map<String, Supplier<String>> getVersions() {
public Map<VersionProperty, Supplier<String>> getVersions() {
return versions;
}

View File

@ -25,6 +25,7 @@ import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -39,6 +40,7 @@ import io.spring.initializr.metadata.InitializrMetadataProvider;
import io.spring.initializr.metadata.MetadataElement;
import io.spring.initializr.util.TemplateRenderer;
import io.spring.initializr.util.Version;
import io.spring.initializr.util.VersionProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -361,11 +363,9 @@ public class ProjectGenerator {
model.put("hasRepositories", true);
}
List<BillOfMaterials> resolvedBoms = request.getBoms().values().stream()
.sorted(Comparator.comparing(BillOfMaterials::getOrder))
.collect(Collectors.toList());
List<Map<String,String>> resolvedBoms = buildResolvedBoms(request);
model.put("resolvedBoms", resolvedBoms);
ArrayList<BillOfMaterials> reversedBoms = new ArrayList<>(resolvedBoms);
ArrayList<Map<String,String>> reversedBoms = new ArrayList<>(resolvedBoms);
Collections.reverse(reversedBoms);
model.put("reversedBoms", reversedBoms);
@ -390,7 +390,7 @@ public class ProjectGenerator {
Map<String, String> versions = new LinkedHashMap<>();
model.put("buildPropertiesVersions", versions.entrySet());
request.getBuildProperties().getVersions().forEach((k, v) ->
versions.put(k, v.get()));
versions.put(computeVersionProperty(request,k), v.get()));
Map<String, String> gradle = new LinkedHashMap<>();
model.put("buildPropertiesGradle", gradle.entrySet());
request.getBuildProperties().getGradle().forEach((k, v) ->
@ -455,6 +455,31 @@ public class ProjectGenerator {
return model;
}
private List<Map<String,String>> buildResolvedBoms(ProjectRequest request) {
return request.getBoms().values().stream()
.sorted(Comparator.comparing(BillOfMaterials::getOrder))
.map(bom -> toBomModel(request, bom))
.collect(Collectors.toList());
}
private Map<String,String> toBomModel(ProjectRequest request, BillOfMaterials bom) {
Map<String, String> model = new HashMap<>();
model.put("groupId", bom.getGroupId());
model.put("artifactId", bom.getArtifactId());
model.put("versionToken", (bom.getVersionProperty() != null
? "${" + computeVersionProperty(request, bom.getVersionProperty()) + "}"
: bom.getVersion()));
return model;
}
private String computeVersionProperty(ProjectRequest request,
VersionProperty property) {
if (isGradleBuild(request)) {
return property.toCamelCaseFormat();
}
return property.toStandardFormat();
}
protected void setupTestModel(ProjectRequest request, Map<String, Object> model) {
String imports = "";
String testAnnotations = "";

View File

@ -29,6 +29,7 @@ import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.metadata.Repository;
import io.spring.initializr.metadata.Type;
import io.spring.initializr.util.Version;
import io.spring.initializr.util.VersionProperty;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.StringUtils;
@ -226,10 +227,11 @@ public class ProjectRequest extends BasicProjectRequest {
buildProperties.getMaven().put("project.build.sourceEncoding", () -> "UTF-8");
buildProperties.getMaven().put("project.reporting.outputEncoding",
() -> "UTF-8");
buildProperties.getVersions().put("java.version", this::getJavaVersion);
buildProperties.getVersions().put(new VersionProperty("java.version"),
this::getJavaVersion);
if ("kotlin".equals(getLanguage())) {
buildProperties.getVersions().put("kotlin.version", () -> metadata
.getConfiguration().getEnv().getKotlin().getVersion());
buildProperties.getVersions().put(new VersionProperty("kotlin.version"),
() -> metadata.getConfiguration().getEnv().getKotlin().getVersion());
}
}
}

View File

@ -25,6 +25,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import io.spring.initializr.util.InvalidVersionException;
import io.spring.initializr.util.Version;
import io.spring.initializr.util.VersionParser;
import io.spring.initializr.util.VersionProperty;
import io.spring.initializr.util.VersionRange;
/**
@ -39,7 +40,7 @@ public class BillOfMaterials {
private String groupId;
private String artifactId;
private String version;
private String versionProperty;
private VersionProperty versionProperty;
private Integer order = Integer.MAX_VALUE;
private List<String> additionalBoms = new ArrayList<>();
private List<String> repositories = new ArrayList<>();
@ -86,18 +87,22 @@ public class BillOfMaterials {
}
/**
* Return the property to use to externalize the version of the BOM. When this is set,
* a version property is automatically added rather than setting the version in the
* bom declaration itself.
* Return the {@link VersionProperty} to use to externalize the version of the BOM.
* When this is set, a version property is automatically added rather than setting
* the version in the bom declaration itself.
*/
public String getVersionProperty() {
public VersionProperty getVersionProperty() {
return versionProperty;
}
public void setVersionProperty(String versionProperty) {
public void setVersionProperty(VersionProperty versionProperty) {
this.versionProperty = versionProperty;
}
public void setVersionProperty(String versionPropertyName) {
setVersionProperty(new VersionProperty(versionPropertyName));
}
/**
* Return the relative order of this BOM where lower values have higher priority. The
* default value is {@code Integer.MAX_VALUE}, indicating lowest priority. The Spring
@ -139,16 +144,6 @@ public class BillOfMaterials {
return mappings;
}
/**
* Determine the version placeholder to use for this instance. If a version property
* is defined, this returns the reference for the property. Otherwise this returns the
* plain {@link #version}
*/
@JsonIgnore
public String getVersionToken() {
return (versionProperty != null ? "${" + versionProperty + "}" : version);
}
public void validate() {
if (version == null && mappings.isEmpty()) {
throw new InvalidInitializrMetadataException(

View File

@ -23,6 +23,7 @@ import java.util.stream.Collectors;
import io.spring.initializr.util.Version;
import io.spring.initializr.util.VersionParser;
import io.spring.initializr.util.VersionProperty;
/**
* Meta-data used to generate a project.
@ -236,7 +237,7 @@ public class InitializrMetadata {
public BillOfMaterials createSpringBootBom(String bootVersion, String versionProperty) {
BillOfMaterials bom = BillOfMaterials.create("org.springframework.boot",
"spring-boot-dependencies", bootVersion);
bom.setVersionProperty(versionProperty);
bom.setVersionProperty(new VersionProperty(versionProperty));
bom.setOrder(100);
return bom;
}

View File

@ -0,0 +1,102 @@
/*
* Copyright 2012-2017 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.util;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import org.springframework.util.StringUtils;
/**
* Represents a valid property for a version. A property must be lower case and
* can define a dot or an hyphen to separate words. For instance, "foo-acme.version",
* "foo.acme.version" or "foo-acme-version" are valid properties.
*
* @author Stephane Nicoll
*/
public class VersionProperty implements Serializable, Comparable<VersionProperty> {
private static final List<Character> SUPPORTED_CHARS = Arrays.asList('.', '-');
private final String property;
public VersionProperty(String property) {
this.property = validateFormat(property);
}
/**
* Return a camel cased representation of this instance.
*/
public String toCamelCaseFormat() {
String[] tokens = this.property.split("\\-|\\.");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < tokens.length; i++) {
String part = tokens[i];
if (i > 0) {
part = StringUtils.capitalize(part);
}
sb.append(part);
}
return sb.toString();
}
public String toStandardFormat() {
return this.property;
}
private static String validateFormat(String property) {
for (char c : property.toCharArray()) {
if (Character.isUpperCase(c)) {
throw new IllegalArgumentException(
"Invalid property '" + property + "', must not contain upper case");
}
if (!Character.isLetterOrDigit(c) && !SUPPORTED_CHARS.contains(c)) {
throw new IllegalArgumentException(
"Unsupported character '" + c + "' for '" + property + "'");
}
}
return property;
}
@Override
public int compareTo(VersionProperty o) {
return this.property.compareTo(o.property);
}
@Override
public String toString() {
return this.property;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VersionProperty that = (VersionProperty) o;
return property.equals(that.property);
}
@Override
public int hashCode() {
return property.hashCode();
}
}

View File

@ -67,9 +67,11 @@ configurations {
{{/providedDependencies}}
{{^buildPropertiesVersions.empty}}
{{#buildPropertiesVersions}}
ext['{{key}}'] = '{{value}}'
ext {
{{#buildPropertiesVersions}}
{{key}} = '{{value}}'
{{/buildPropertiesVersions}}
}
{{/buildPropertiesVersions.empty}}
dependencies {

View File

@ -21,6 +21,7 @@ import io.spring.initializr.metadata.Dependency;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.test.generator.ProjectAssert;
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder;
import io.spring.initializr.util.VersionProperty;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@ -102,10 +103,10 @@ public class ProjectGeneratorBuildTests extends AbstractProjectGeneratorTests {
@Test
public void versionOverride() {
ProjectRequest request = createProjectRequest("web");
request.getBuildProperties().getVersions().put("spring-foo.version",
() -> "0.1.0.RELEASE");
request.getBuildProperties().getVersions().put("spring-bar.version",
() -> "0.2.0.RELEASE");
request.getBuildProperties().getVersions().put(
new VersionProperty("spring-foo.version"), () -> "0.1.0.RELEASE");
request.getBuildProperties().getVersions().put(
new VersionProperty("spring-bar.version"), () -> "0.2.0.RELEASE");
ProjectAssert project = generateProject(request);
project.sourceCodeAssert(fileName).equalsTo(new ClassPathResource(
"project/" + build + "/version-override-" + assertFileName));

View File

@ -23,6 +23,7 @@ import io.spring.initializr.metadata.Dependency;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.test.generator.ProjectAssert;
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder;
import io.spring.initializr.util.VersionProperty;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@ -689,7 +690,8 @@ public class ProjectGeneratorTests extends AbstractProjectGeneratorTests {
public void buildPropertiesMaven() {
ProjectRequest request = createProjectRequest("web");
request.getBuildProperties().getMaven().put("name", () -> "test");
request.getBuildProperties().getVersions().put("foo.version", () -> "1.2.3");
request.getBuildProperties().getVersions().put(
new VersionProperty("foo.version"), () -> "1.2.3");
request.getBuildProperties().getGradle().put("ignore.property", () -> "yes");
generateMavenPom(request).hasProperty("name", "test")
@ -700,11 +702,13 @@ public class ProjectGeneratorTests extends AbstractProjectGeneratorTests {
public void buildPropertiesGradle() {
ProjectRequest request = createProjectRequest("web");
request.getBuildProperties().getGradle().put("name", () -> "test");
request.getBuildProperties().getVersions().put("foo.version", () -> "1.2.3");
request.getBuildProperties().getVersions().put(
new VersionProperty("foo.version"), () -> "1.2.3");
request.getBuildProperties().getMaven().put("ignore.property", () -> "yes");
generateGradleBuild(request).contains("name = 'test'")
.contains("ext['foo.version'] = '1.2.3'")
.contains("ext {")
.contains("fooVersion = '1.2.3'")
.doesNotContain("ignore.property");
}

View File

@ -24,6 +24,7 @@ import java.util.Map;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder;
import io.spring.initializr.util.VersionProperty;
import org.junit.Before;
import org.junit.Test;
@ -57,7 +58,7 @@ public class ProjectRequestResolverTests {
ProjectRequest request = resolve(createMavenProjectRequest(), postProcessors);
assertEquals("1.2", request.getJavaVersion());
assertEquals("1.2", request.getBuildProperties().getVersions()
.get("java.version").get());
.get(new VersionProperty("java.version")).get());
}
@Test

View File

@ -59,7 +59,8 @@ public class BillOfMaterialsTests {
assertThat(resolved.getGroupId(), equalTo("com.example"));
assertThat(resolved.getArtifactId(), equalTo("bom"));
assertThat(resolved.getVersion(), equalTo("1.1.0"));
assertThat(resolved.getVersionProperty(), equalTo("bom.version"));
assertThat(resolved.getVersionProperty().toStandardFormat(),
equalTo("bom.version"));
assertThat(resolved.getRepositories().size(), equalTo(1));
assertThat(resolved.getRepositories().get(0), equalTo("repo-main"));
assertThat(resolved.getAdditionalBoms().size(), equalTo(1));
@ -98,7 +99,8 @@ public class BillOfMaterialsTests {
assertThat(resolved.getGroupId(), equalTo("com.example"));
assertThat(resolved.getArtifactId(), equalTo("bom"));
assertThat(resolved.getVersion(), equalTo("1.1.0"));
assertThat(resolved.getVersionProperty(), equalTo("example.version"));
assertThat(resolved.getVersionProperty().toStandardFormat(),
equalTo("example.version"));
}
@Test

View File

@ -0,0 +1,73 @@
/*
* Copyright 2012-2017 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.util;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link VersionProperty}.
*
* @author Stephane Nicoll
*/
public class VersionPropertyTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testStandardProperty() {
assertThat(new VersionProperty("spring-boot.version")
.toStandardFormat()).isEqualTo("spring-boot.version");
}
@Test
public void testCamelCaseProperty() {
assertThat(new VersionProperty("spring-boot.version")
.toCamelCaseFormat()).isEqualTo("springBootVersion");
}
@Test
public void testStandardPropertyWithNoSeparator() {
assertThat(new VersionProperty("springbootversion")
.toStandardFormat()).isEqualTo("springbootversion");
}
@Test
public void testCamelCasePropertyWithNoSeparator() {
assertThat(new VersionProperty("springbootversion")
.toCamelCaseFormat()).isEqualTo("springbootversion");
}
@Test
public void testInvalidPropertyUpperCase() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("upper case");
new VersionProperty("Spring-boot.version");
}
@Test
public void testInvalidPropertyIllegalCharacter() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Unsupported character");
new VersionProperty("spring-boot_version");
}
}

View File

@ -24,7 +24,9 @@ repositories {
}
ext['foo.version'] = '1.3.3'
ext {
fooVersion = '1.3.3'
}
dependencies {
compile('org.acme:foo')
@ -33,6 +35,6 @@ dependencies {
dependencyManagement {
imports {
mavenBom "org.acme:foo-bom:${foo.version}"
mavenBom "org.acme:foo-bom:${fooVersion}"
}
}

View File

@ -24,8 +24,10 @@ repositories {
}
ext['spring-bar.version'] = '0.2.0.RELEASE'
ext['spring-foo.version'] = '0.1.0.RELEASE'
ext {
springBarVersion = '0.2.0.RELEASE'
springFooVersion = '0.1.0.RELEASE'
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')