From 58ca9a1c5df4d58492cf6f33ed60997afd1aa32e Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 23 Mar 2016 17:58:54 +0000 Subject: [PATCH] Add a BuildInfo task for generating build.properties with Gradle The commit adds a new BuildInfo task that can be used to generate a build.properties file, intended for inclusion in the Actuator's info endpoint. A default instance of the task can be configure using the plugin's DSL: springBoot { buildInfo() } Additional properties can also be configured using the DSL: springBoot { buildInfo { additionalProperties = [ 'foo': 'bar' ] } } When configured via the DSL, the Java plugin's classes task is configured to depend on the build info task. Alternatively, if more control is required, the task can be declared and configured manually: task buildInfo(type: org.springframework.boot.gradle.buildinfo.BuildInfo) { additionalProperties = [ 'foo': 'bar' ] } classes { dependsOn buildInfo } See gh-2559 --- .../spring-boot-sample-actuator/build.gradle | 8 +- .../boot/gradle/SpringBootPlugin.java | 3 +- .../gradle/SpringBootPluginExtension.java | 29 +++- .../boot/gradle/buildinfo/BuildInfo.java | 137 ++++++++++++++++ .../loader/tools/BuildPropertiesWriter.java | 146 ++++++++++++++++++ .../boot/maven/BuildInfoMojo.java | 73 +-------- 6 files changed, 328 insertions(+), 68 deletions(-) create mode 100644 spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/buildinfo/BuildInfo.java create mode 100644 spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/BuildPropertiesWriter.java diff --git a/spring-boot-samples/spring-boot-sample-actuator/build.gradle b/spring-boot-samples/spring-boot-sample-actuator/build.gradle index 6498541b1c..6413d1c946 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/build.gradle +++ b/spring-boot-samples/spring-boot-sample-actuator/build.gradle @@ -22,9 +22,11 @@ apply plugin: 'spring-boot' jar { baseName = 'spring-boot-sample-actuator' - version = '0.0.0' } +group = 'org.springframework.boot' +version = springBootVersion + repositories { // NOTE: You should declare only repositories that you need here mavenLocal() @@ -59,3 +61,7 @@ springBoot { task wrapper(type: Wrapper) { gradleVersion = '1.6' } + +springBoot { + buildInfo() +} \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPlugin.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPlugin.java index 945dfd218d..c1d5911ee6 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPlugin.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPlugin.java @@ -39,7 +39,8 @@ public class SpringBootPlugin implements Plugin { @Override public void apply(Project project) { - project.getExtensions().create("springBoot", SpringBootPluginExtension.class); + project.getExtensions().create("springBoot", SpringBootPluginExtension.class, + project); project.getPlugins().apply(JavaPlugin.class); new AgentPluginFeatures().apply(project); new RepackagePluginFeatures().apply(project); diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java index 1f4ff7ecdc..ddd648ebd0 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * 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. @@ -20,6 +20,11 @@ import java.io.File; import java.util.Map; import java.util.Set; +import groovy.lang.Closure; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; + +import org.springframework.boot.gradle.buildinfo.BuildInfo; import org.springframework.boot.loader.tools.Layout; import org.springframework.boot.loader.tools.Layouts; @@ -39,9 +44,12 @@ import org.springframework.boot.loader.tools.Layouts; * @author Phillip Webb * @author Dave Syer * @author Stephane Nicoll + * @author Andy Wilkinson */ public class SpringBootPluginExtension { + private final Project project; + /** * The main class that should be run. Instead of setting this explicitly you can use * the 'mainClassName' of the project or the 'main' of the 'run' task. If not @@ -128,6 +136,10 @@ public class SpringBootPluginExtension { */ Map embeddedLaunchScriptProperties; + public SpringBootPluginExtension(Project project) { + this.project = project; + } + /** * Convenience method for use in a custom task. * @return the Layout to use or null if not explicitly set @@ -249,6 +261,21 @@ public class SpringBootPluginExtension { this.embeddedLaunchScriptProperties = embeddedLaunchScriptProperties; } + public void buildInfo() { + this.buildInfo(null); + } + + public void buildInfo(Closure taskConfigurer) { + BuildInfo buildInfo = this.project.getTasks().create("buildInfo", + BuildInfo.class); + this.project.getTasks().getByName(JavaPlugin.CLASSES_TASK_NAME) + .dependsOn(buildInfo); + if (taskConfigurer != null) { + taskConfigurer.setDelegate(buildInfo); + taskConfigurer.call(); + } + } + /** * Layout Types. */ diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/buildinfo/BuildInfo.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/buildinfo/BuildInfo.java new file mode 100644 index 0000000000..bfd510cf58 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/buildinfo/BuildInfo.java @@ -0,0 +1,137 @@ +/* + * 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 org.springframework.boot.gradle.buildinfo; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.gradle.api.DefaultTask; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.TaskExecutionException; +import org.gradle.api.tasks.bundling.Jar; + +import org.springframework.boot.loader.tools.BuildPropertiesWriter; +import org.springframework.boot.loader.tools.BuildPropertiesWriter.ProjectDetails; + +/** + * {@link DefaultTask} for generating a {@code build.properties} file from a + * {@code Project}. + *

+ * By default, the {@code build.properties} file is generated in + * project.buildDir/resources/main/META-INF/boot. + *

+ * + * @author Andy Wilkinson + */ +public class BuildInfo extends DefaultTask { + + @OutputFile + private File outputFile = getProject().file(new File(getProject().getBuildDir(), + "resources/main/META-INF/boot/build.properties")); + + @Input + private String projectGroup = getProject().getGroup().toString(); + + @Input + private String projectArtifact = ((Jar) getProject().getTasks() + .getByName(JavaPlugin.JAR_TASK_NAME)).getBaseName(); + + @Input + private String projectVersion = getProject().getVersion().toString(); + + @Input + private String projectName = getProject().getName(); + + @Input + private Map additionalProperties = new HashMap(); + + @TaskAction + public void generateBuildProperties() { + try { + new BuildPropertiesWriter(this.outputFile) + .writeBuildProperties(new ProjectDetails(this.projectGroup, + this.projectArtifact, this.projectVersion, this.projectName, + coerceToStringValues(this.additionalProperties))); + } + catch (IOException ex) { + throw new TaskExecutionException(this, ex); + } + } + + public String getProjectGroup() { + return this.projectGroup; + } + + public void setProjectGroup(String projectGroup) { + this.projectGroup = projectGroup; + } + + public String getProjectArtifact() { + return this.projectArtifact; + } + + public void setProjectArtifact(String projectArtifact) { + this.projectArtifact = projectArtifact; + } + + public String getProjectVersion() { + return this.projectVersion; + } + + public void setProjectVersion(String projectVersion) { + this.projectVersion = projectVersion; + } + + public String getProjectName() { + return this.projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public File getOutputFile() { + return this.outputFile; + } + + public void setOutputFile(File outputFile) { + this.outputFile = outputFile; + } + + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + public void setAdditionalProperties(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + private Map coerceToStringValues(Map input) { + Map output = new HashMap(); + for (Entry entry : this.additionalProperties.entrySet()) { + output.put(entry.getKey(), entry.getValue().toString()); + } + return output; + } + +} diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/BuildPropertiesWriter.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/BuildPropertiesWriter.java new file mode 100644 index 0000000000..8a0ec51eaf --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/BuildPropertiesWriter.java @@ -0,0 +1,146 @@ +/* + * 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 org.springframework.boot.loader.tools; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.Properties; + +/** + * A {@code BuildPropertiesWriter} writes the {@code build.properties} for consumption by + * the Actuator. + * + * @author Andy Wilkinson + * @author Stephane Nicoll + */ +public final class BuildPropertiesWriter { + + private final File outputFile; + + /** + * Creates a new {@code BuildPropertiesWriter} that will write to the given + * {@code outputFile}. + * + * @param outputFile the output file + */ + public BuildPropertiesWriter(File outputFile) { + this.outputFile = outputFile; + } + + public void writeBuildProperties(ProjectDetails projectDetails) throws IOException { + Properties properties = createBuildInfo(projectDetails); + createFileIfNecessary(this.outputFile); + FileOutputStream outputStream = new FileOutputStream(this.outputFile); + try { + properties.store(outputStream, "Properties"); + } + finally { + try { + outputStream.close(); + } + catch (IOException ex) { + // Continue + } + } + } + + private void createFileIfNecessary(File file) throws IOException { + if (file.exists()) { + return; + } + File parent = file.getParentFile(); + if (!parent.isDirectory() && !parent.mkdirs()) { + throw new IllegalStateException("Cannot create parent directory for '" + + this.outputFile.getAbsolutePath() + "'"); + } + if (!file.createNewFile()) { + throw new IllegalStateException("Cannot create target file '" + + this.outputFile.getAbsolutePath() + "'"); + } + } + + protected Properties createBuildInfo(ProjectDetails project) { + Properties properties = new Properties(); + properties.put("build.group", project.getGroup()); + properties.put("build.artifact", project.getArtifact()); + properties.put("build.name", project.getName()); + properties.put("build.version", project.getVersion()); + properties.put("build.time", formatDate(new Date())); + if (project.getAdditionalProperties() != null) { + for (Map.Entry entry : project.getAdditionalProperties() + .entrySet()) { + properties.put("build." + entry.getKey(), entry.getValue()); + } + } + return properties; + } + + private String formatDate(Date date) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + return sdf.format(date); + } + + /** + * Build-system agnostic details of a project. + */ + public static final class ProjectDetails { + + private final String group; + + private final String artifact; + + private final String name; + + private final String version; + + private final Map additionalProperties; + + public ProjectDetails(String group, String artifact, String version, String name, + Map additionalProperties) { + this.group = group; + this.artifact = artifact; + this.name = name; + this.version = version; + this.additionalProperties = additionalProperties; + } + + public String getGroup() { + return this.group; + } + + public String getArtifact() { + return this.artifact; + } + + public String getName() { + return this.name; + } + + public String getVersion() { + return this.version; + } + + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + } +} diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildInfoMojo.java b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildInfoMojo.java index 0df02016c9..04f2ee2184 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildInfoMojo.java +++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildInfoMojo.java @@ -17,14 +17,7 @@ package org.springframework.boot.maven; import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.text.SimpleDateFormat; -import java.util.Date; import java.util.Map; -import java.util.Properties; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -34,6 +27,9 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; +import org.springframework.boot.loader.tools.BuildPropertiesWriter; +import org.springframework.boot.loader.tools.BuildPropertiesWriter.ProjectDetails; + /** * Generate a {@code build.properties} file based the content of the current * {@link MavenProject}. @@ -65,68 +61,15 @@ public class BuildInfoMojo extends AbstractMojo { @Override public void execute() throws MojoExecutionException, MojoFailureException { - Properties properties = createBuildInfo(); try { - createFileIfNecessary(this.outputFile); - FileOutputStream outputStream = new FileOutputStream(this.outputFile); - try { - properties.store(outputStream, "Properties"); - } - finally { - closeQuietly(outputStream); - } + new BuildPropertiesWriter(this.outputFile) + .writeBuildProperties(new ProjectDetails(this.project.getGroupId(), + this.project.getArtifactId(), this.project.getVersion(), + this.project.getName(), this.additionalProperties)); } - catch (FileNotFoundException ex) { + catch (Exception ex) { throw new MojoExecutionException(ex.getMessage(), ex); } - catch (IOException e) { - throw new MojoExecutionException(e.getMessage(), e); - } - } - - private void closeQuietly(OutputStream outputStream) { - try { - outputStream.close(); - } - catch (IOException ex) { - getLog().error("Error closing FileOutputStream: " + outputStream); - } - } - - protected Properties createBuildInfo() { - Properties properties = new Properties(); - properties.put("build.group", this.project.getGroupId()); - properties.put("build.artifact", this.project.getArtifactId()); - properties.put("build.name", this.project.getName()); - properties.put("build.version", this.project.getVersion()); - properties.put("build.time", formatDate(new Date())); - if (this.additionalProperties != null) { - for (Map.Entry entry : this.additionalProperties.entrySet()) { - properties.put("build." + entry.getKey(), entry.getValue()); - } - } - return properties; - } - - private String formatDate(Date date) { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); - return sdf.format(date); - } - - private void createFileIfNecessary(File file) - throws MojoExecutionException, IOException { - if (file.exists()) { - return; - } - File parent = file.getParentFile(); - if (!parent.exists() && !parent.mkdirs()) { - throw new MojoExecutionException("Cannot create parent directory for '" - + this.outputFile.getAbsolutePath() + "'"); - } - if (!file.createNewFile()) { - throw new MojoExecutionException("Cannot create target file '" - + this.outputFile.getAbsolutePath() + "'"); - } } }