Skip to content

Commit f80490b

Browse files
Precompute Spring Boot version at build time
Closes gh-29670
1 parent 76510fa commit f80490b

File tree

4 files changed

+141
-64
lines changed

4 files changed

+141
-64
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.build;
18+
19+
import java.io.File;
20+
import java.io.FileWriter;
21+
import java.io.IOException;
22+
import java.io.PrintWriter;
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
26+
import org.gradle.api.DefaultTask;
27+
import org.gradle.api.Task;
28+
import org.gradle.api.file.DirectoryProperty;
29+
import org.gradle.api.model.ObjectFactory;
30+
import org.gradle.api.provider.Property;
31+
import org.gradle.api.tasks.Input;
32+
import org.gradle.api.tasks.OutputDirectory;
33+
import org.gradle.api.tasks.TaskAction;
34+
35+
/**
36+
* {@link Task} to generate properties and write them to disk as a properties file.
37+
*
38+
* @author Scott Frederick
39+
*/
40+
public class GeneratePropertiesResource extends DefaultTask {
41+
42+
private final Property<String> propertiesFileName;
43+
44+
private final DirectoryProperty destinationDirectory;
45+
46+
private final Map<String, String> properties = new HashMap<>();
47+
48+
public GeneratePropertiesResource() {
49+
ObjectFactory objects = getProject().getObjects();
50+
this.propertiesFileName = objects.property(String.class);
51+
this.destinationDirectory = objects.directoryProperty();
52+
}
53+
54+
@OutputDirectory
55+
public DirectoryProperty getDestinationDirectory() {
56+
return this.destinationDirectory;
57+
}
58+
59+
@Input
60+
public Property<String> getPropertiesFileName() {
61+
return this.propertiesFileName;
62+
}
63+
64+
public void property(String name, String value) {
65+
this.properties.put(name, value);
66+
}
67+
68+
@Input
69+
public Map<String, String> getProperties() {
70+
return this.properties;
71+
}
72+
73+
@TaskAction
74+
void generatePropertiesFile() throws IOException {
75+
File outputFile = this.destinationDirectory.file(this.propertiesFileName).get().getAsFile();
76+
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {
77+
this.properties.forEach((key, value) -> writer.printf("%s=%s\n", key, value));
78+
}
79+
}
80+
81+
}

buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -104,6 +104,8 @@ class JavaConventions {
104104

105105
private static final String SOURCE_AND_TARGET_COMPATIBILITY = "1.8";
106106

107+
private static final String SPRING_BOOT_PROPERTIES_FILE = "spring-boot.properties";
108+
107109
void apply(Project project) {
108110
project.getPlugins().withType(JavaBasePlugin.class, (java) -> {
109111
project.getPlugins().apply(TestFailuresPlugin.class);
@@ -112,36 +114,45 @@ void apply(Project project) {
112114
configureJavadocConventions(project);
113115
configureTestConventions(project);
114116
configureJarManifestConventions(project);
117+
configureMetaInfResourcesConventions(project);
115118
configureDependencyManagement(project);
116119
configureToolchain(project);
117120
configureProhibitedDependencyChecks(project);
118121
});
119122
}
120123

121124
private void configureJarManifestConventions(Project project) {
122-
ExtractResources extractLegalResources = project.getTasks().create("extractLegalResources",
123-
ExtractResources.class);
124-
extractLegalResources.getDestinationDirectory().set(project.getLayout().getBuildDirectory().dir("legal"));
125-
extractLegalResources.setResourcesNames(Arrays.asList("LICENSE.txt", "NOTICE.txt"));
126-
extractLegalResources.property("version", project.getVersion().toString());
127125
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
128126
Set<String> sourceJarTaskNames = sourceSets.stream().map(SourceSet::getSourcesJarTaskName)
129127
.collect(Collectors.toSet());
130128
Set<String> javadocJarTaskNames = sourceSets.stream().map(SourceSet::getJavadocJarTaskName)
131129
.collect(Collectors.toSet());
132-
project.getTasks().withType(Jar.class, (jar) -> project.afterEvaluate((evaluated) -> {
133-
jar.metaInf((metaInf) -> metaInf.from(extractLegalResources));
134-
jar.manifest((manifest) -> {
135-
Map<String, Object> attributes = new TreeMap<>();
136-
attributes.put("Automatic-Module-Name", project.getName().replace("-", "."));
137-
attributes.put("Build-Jdk-Spec", SOURCE_AND_TARGET_COMPATIBILITY);
138-
attributes.put("Built-By", "Spring");
139-
attributes.put("Implementation-Title",
140-
determineImplementationTitle(project, sourceJarTaskNames, javadocJarTaskNames, jar));
141-
attributes.put("Implementation-Version", project.getVersion());
142-
manifest.attributes(attributes);
143-
});
144-
}));
130+
project.getTasks().withType(Jar.class,
131+
(jar) -> project.afterEvaluate((evaluated) -> jar.manifest((manifest) -> {
132+
Map<String, Object> attributes = new TreeMap<>();
133+
attributes.put("Automatic-Module-Name", project.getName().replace("-", "."));
134+
attributes.put("Build-Jdk-Spec", SOURCE_AND_TARGET_COMPATIBILITY);
135+
attributes.put("Built-By", "Spring");
136+
attributes.put("Implementation-Title",
137+
determineImplementationTitle(project, sourceJarTaskNames, javadocJarTaskNames, jar));
138+
attributes.put("Implementation-Version", project.getVersion());
139+
manifest.attributes(attributes);
140+
})));
141+
}
142+
143+
private void configureMetaInfResourcesConventions(Project project) {
144+
ExtractResources extractLegalResources = project.getTasks().create("extractLegalResources",
145+
ExtractResources.class);
146+
extractLegalResources.getDestinationDirectory().set(project.getLayout().getBuildDirectory().dir("legal"));
147+
extractLegalResources.setResourcesNames(Arrays.asList("LICENSE.txt", "NOTICE.txt"));
148+
extractLegalResources.property("version", project.getVersion().toString());
149+
GeneratePropertiesResource generateInfo = project.getTasks().create("generateSpringBootInfo",
150+
GeneratePropertiesResource.class);
151+
generateInfo.getDestinationDirectory().set(project.getLayout().getBuildDirectory().dir("info"));
152+
generateInfo.getPropertiesFileName().set(SPRING_BOOT_PROPERTIES_FILE);
153+
generateInfo.property("version", project.getVersion().toString());
154+
project.getTasks().withType(Jar.class, (jar) -> project.afterEvaluate(
155+
(evaluated) -> jar.metaInf((metaInf) -> metaInf.from(extractLegalResources, generateInfo))));
145156
}
146157

147158
private String determineImplementationTitle(Project project, Set<String> sourceJarTaskNames,

buildSrc/src/test/java/org/springframework/boot/build/ConventionsPluginTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ void jarIncludesLegalFiles() throws IOException {
8383
try (JarFile jar = new JarFile(file)) {
8484
assertThatLicenseIsPresent(jar);
8585
assertThatNoticeIsPresent(jar);
86+
assertThatSpringBootPropertiesIsPresent(jar);
8687
Attributes mainAttributes = jar.getManifest().getMainAttributes();
8788
assertThat(mainAttributes.getValue("Implementation-Title"))
8889
.isEqualTo("Test project for manifest customization");
@@ -112,6 +113,7 @@ void sourceJarIsBuilt() throws IOException {
112113
try (JarFile jar = new JarFile(file)) {
113114
assertThatLicenseIsPresent(jar);
114115
assertThatNoticeIsPresent(jar);
116+
assertThatSpringBootPropertiesIsPresent(jar);
115117
Attributes mainAttributes = jar.getManifest().getMainAttributes();
116118
assertThat(mainAttributes.getValue("Implementation-Title"))
117119
.isEqualTo("Source for " + this.projectDir.getName());
@@ -141,6 +143,7 @@ void javadocJarIsBuilt() throws IOException {
141143
try (JarFile jar = new JarFile(file)) {
142144
assertThatLicenseIsPresent(jar);
143145
assertThatNoticeIsPresent(jar);
146+
assertThatSpringBootPropertiesIsPresent(jar);
144147
Attributes mainAttributes = jar.getManifest().getMainAttributes();
145148
assertThat(mainAttributes.getValue("Implementation-Title"))
146149
.isEqualTo("Javadoc for " + this.projectDir.getName());
@@ -165,6 +168,13 @@ private void assertThatNoticeIsPresent(JarFile jar) throws IOException {
165168
assertThat(noticeContent).doesNotContain("${");
166169
}
167170

171+
private void assertThatSpringBootPropertiesIsPresent(JarFile jar) throws IOException {
172+
JarEntry properties = jar.getJarEntry("META-INF/spring-boot.properties");
173+
assertThat(properties).isNotNull();
174+
String content = FileCopyUtils.copyToString(new InputStreamReader(jar.getInputStream(properties)));
175+
assertThat(content).contains("version=");
176+
}
177+
168178
@Test
169179
void testRetryIsConfiguredWithThreeRetriesOnCI() throws IOException {
170180
try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
Lines changed: 20 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,30 +16,22 @@
1616

1717
package org.springframework.boot;
1818

19-
import java.io.File;
2019
import java.io.IOException;
21-
import java.net.JarURLConnection;
22-
import java.net.URL;
23-
import java.net.URLConnection;
24-
import java.security.CodeSource;
25-
import java.util.jar.Attributes;
26-
import java.util.jar.Attributes.Name;
27-
import java.util.jar.JarFile;
20+
import java.io.InputStream;
21+
import java.util.Properties;
2822

2923
/**
30-
* Class that exposes the Spring Boot version. Fetches the
31-
* {@link Name#IMPLEMENTATION_VERSION Implementation-Version} manifest attribute from the
32-
* jar file via {@link Package#getImplementationVersion()}, falling back to locating the
33-
* jar file that contains this class and reading the {@code Implementation-Version}
34-
* attribute from its manifest.
35-
* <p>
36-
* This class might not be able to determine the Spring Boot version in all environments.
37-
* Consider using a reflection-based check instead: For example, checking for the presence
38-
* of a specific Spring Boot method that you intend to call.
24+
* Exposes the Spring Boot version.
25+
*
26+
* The version information is read from a file that is stored in the Spring Boot library.
27+
* If the version information cannot be read from the file, consider using a
28+
* reflection-based check instead (for example, checking for the presence of a specific
29+
* Spring Boot method that you intend to call).
3930
*
4031
* @author Drummond Dawson
4132
* @author Hendrig Sellik
4233
* @author Andy Wilkinson
34+
* @author Scott Frederick
4335
* @since 1.3.0
4436
*/
4537
public final class SpringBootVersion {
@@ -51,38 +43,21 @@ private SpringBootVersion() {
5143
* Return the full version string of the present Spring Boot codebase, or {@code null}
5244
* if it cannot be determined.
5345
* @return the version of Spring Boot or {@code null}
54-
* @see Package#getImplementationVersion()
5546
*/
5647
public static String getVersion() {
57-
return determineSpringBootVersion();
58-
}
59-
60-
private static String determineSpringBootVersion() {
61-
String implementationVersion = SpringBootVersion.class.getPackage().getImplementationVersion();
62-
if (implementationVersion != null) {
63-
return implementationVersion;
64-
}
65-
CodeSource codeSource = SpringBootVersion.class.getProtectionDomain().getCodeSource();
66-
if (codeSource == null) {
67-
return null;
68-
}
69-
URL codeSourceLocation = codeSource.getLocation();
70-
try {
71-
URLConnection connection = codeSourceLocation.openConnection();
72-
if (connection instanceof JarURLConnection) {
73-
return getImplementationVersion(((JarURLConnection) connection).getJarFile());
48+
InputStream input = SpringBootVersion.class.getClassLoader()
49+
.getResourceAsStream("META-INF/spring-boot.properties");
50+
if (input != null) {
51+
try {
52+
Properties properties = new Properties();
53+
properties.load(input);
54+
return properties.getProperty("version");
7455
}
75-
try (JarFile jarFile = new JarFile(new File(codeSourceLocation.toURI()))) {
76-
return getImplementationVersion(jarFile);
56+
catch (IOException ex) {
57+
// fall through
7758
}
7859
}
79-
catch (Exception ex) {
80-
return null;
81-
}
82-
}
83-
84-
private static String getImplementationVersion(JarFile jarFile) throws IOException {
85-
return jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);
60+
return null;
8661
}
8762

8863
}

0 commit comments

Comments
 (0)