diff --git a/spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc b/spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc index 707e50c3ab4..d1238397250 100644 --- a/spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc +++ b/spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc @@ -395,6 +395,10 @@ want the other Boot features but not this one) |`embeddedLaunchScriptProperties` |Additional properties that to be expanded in the launch script. The default script supports a `mode` property which can contain the values `auto`, `service` or `run`. + +|`excludeDevtools` +|Boolean flag to indicate if the devtools jar should be excluded from the repackaged +archives. Defaults to `false`. |=== diff --git a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index 11841aca5d3..ec91c5e414c 100644 --- a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -844,6 +844,10 @@ applied to other modules using your project. Gradle does not support `optional` dependencies out-of-the-box so you may want to have a look to the {propdeps-plugin}[`propdeps-plugin`] in the meantime. +TIP: If you want to ensure that devtools is never included in a production build, you can +use set the `excludeDevtools` build property to completely remove the JAR. The property is +supported with both the Maven and Gradle plugins. + [[using-boot-devtools-property-defaults]] diff --git a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/RepackagingTests.java b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/RepackagingTests.java index fc902e14c24..f2031968808 100644 --- a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/RepackagingTests.java +++ b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/RepackagingTests.java @@ -47,19 +47,24 @@ public class RepackagingTests { } @Test - public void repackagingEnabled() { + public void repackagingEnabled() throws IOException { project.newBuild().forTasks("clean", "build") - .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=true").run(); + .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=true", + "-PexcludeDevtools=false") + .run(); File buildLibs = new File("target/repackage/build/libs"); - assertTrue(new File(buildLibs, "repackage.jar").exists()); + File repackageFile = new File(buildLibs, "repackage.jar"); + assertTrue(repackageFile.exists()); assertTrue(new File(buildLibs, "repackage.jar.original").exists()); assertFalse(new File(buildLibs, "repackage-sources.jar.original").exists()); + assertTrue(isDevToolsJarIncluded(repackageFile)); } @Test public void repackagingDisabled() { project.newBuild().forTasks("clean", "build") - .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=false") + .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=false", + "-PexcludeDevtools=false") .run(); File buildLibs = new File("target/repackage/build/libs"); assertTrue(new File(buildLibs, "repackage.jar").exists()); @@ -70,7 +75,8 @@ public class RepackagingTests { @Test public void repackagingDisabledWithCustomRepackagedJar() { project.newBuild().forTasks("clean", "build", "customRepackagedJar") - .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=false") + .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=false", + "-PexcludeDevtools=false") .run(); File buildLibs = new File("target/repackage/build/libs"); assertTrue(new File(buildLibs, "repackage.jar").exists()); @@ -84,7 +90,8 @@ public class RepackagingTests { public void repackagingDisabledWithCustomRepackagedJarUsingStringJarTaskReference() { project.newBuild() .forTasks("clean", "build", "customRepackagedJarWithStringReference") - .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=false") + .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=false", + "-PexcludeDevtools=false") .run(); File buildLibs = new File("target/repackage/build/libs"); assertTrue(new File(buildLibs, "repackage.jar").exists()); @@ -97,7 +104,9 @@ public class RepackagingTests { @Test public void repackagingEnabledWithCustomRepackagedJar() { project.newBuild().forTasks("clean", "build", "customRepackagedJar") - .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=true").run(); + .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=true", + "-PexcludeDevtools=false") + .run(); File buildLibs = new File("target/repackage/build/libs"); assertTrue(new File(buildLibs, "repackage.jar").exists()); assertTrue(new File(buildLibs, "repackage.jar.original").exists()); @@ -110,7 +119,9 @@ public class RepackagingTests { public void repackagingEnableWithCustomRepackagedJarUsingStringJarTaskReference() { project.newBuild() .forTasks("clean", "build", "customRepackagedJarWithStringReference") - .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=true").run(); + .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=true", + "-PexcludeDevtools=false") + .run(); File buildLibs = new File("target/repackage/build/libs"); assertTrue(new File(buildLibs, "repackage.jar").exists()); assertTrue(new File(buildLibs, "repackage.jar.original").exists()); @@ -124,10 +135,38 @@ public class RepackagingTests { FileCopyUtils.copy(new File("src/test/resources/foo.jar"), new File("target/repackage/foo.jar")); project.newBuild().forTasks("clean", "build") - .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=true").run(); + .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=true", + "-PexcludeDevtools=false") + .run(); File buildLibs = new File("target/repackage/build/libs"); JarFile jarFile = new JarFile(new File(buildLibs, "repackage.jar")); assertThat(jarFile.getEntry("lib/foo.jar"), notNullValue()); jarFile.close(); } + + @Test + public void repackagingEnabledExcludeDevtools() throws IOException { + project.newBuild().forTasks("clean", "build") + .withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=true", + "-PexcludeDevtools=true") + .run(); + File buildLibs = new File("target/repackage/build/libs"); + File repackageFile = new File(buildLibs, "repackage.jar"); + assertTrue(repackageFile.exists()); + assertTrue(new File(buildLibs, "repackage.jar.original").exists()); + assertFalse(new File(buildLibs, "repackage-sources.jar.original").exists()); + assertFalse(isDevToolsJarIncluded(repackageFile)); + } + + private boolean isDevToolsJarIncluded(File repackageFile) throws IOException { + JarFile jarFile = new JarFile(repackageFile); + try { + String name = "lib/spring-boot-devtools-" + BOOT_VERSION + ".jar"; + return jarFile.getEntry(name) != null; + } + finally { + jarFile.close(); + } + } + } diff --git a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/repackage.gradle b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/repackage.gradle index 439ef9e485a..0e281f44620 100644 --- a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/repackage.gradle +++ b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/repackage.gradle @@ -18,11 +18,13 @@ apply plugin: 'java' dependencies { compile 'org.springframework.boot:spring-boot-starter-freemarker' compile 'org.springframework.boot:spring-boot-starter-web' + compile 'org.springframework.boot:spring-boot-devtools' compile files("foo.jar") } springBoot { mainClass = 'foo.bar.Baz' + excludeDevtools = Boolean.valueOf(project.excludeDevtools) } bootRepackage.enabled = Boolean.valueOf(project.repackage) 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 af6b1015fe7..1f4ff7ecdc2 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 @@ -38,6 +38,7 @@ import org.springframework.boot.loader.tools.Layouts; * * @author Phillip Webb * @author Dave Syer + * @author Stephane Nicoll */ public class SpringBootPluginExtension { @@ -87,6 +88,11 @@ public class SpringBootPluginExtension { */ Set requiresUnpack; + /** + * Whether Spring Boot Devtools should be excluded from the fat jar. + */ + boolean excludeDevtools = false; + /** * Location of an agent jar to attach to the VM when running the application with * runJar task. @@ -186,6 +192,14 @@ public class SpringBootPluginExtension { this.requiresUnpack = requiresUnpack; } + public boolean isExcludeDevtools() { + return this.excludeDevtools; + } + + public void setExcludeDevtools(boolean excludeDevtools) { + this.excludeDevtools = excludeDevtools; + } + public File getAgent() { return this.agent; } diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/ProjectLibraries.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/ProjectLibraries.java index bd5d39b7272..1b411540eb1 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/ProjectLibraries.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/ProjectLibraries.java @@ -155,12 +155,26 @@ class ProjectLibraries implements Libraries { if (libraries != null) { Set duplicates = getDuplicates(libraries); for (GradleLibrary library : libraries) { - library.setIncludeGroupName(duplicates.contains(library.getName())); - callback.library(library); + if (!isExcluded(library)) { + library.setIncludeGroupName(duplicates.contains(library.getName())); + callback.library(library); + } } } } + private boolean isExcluded(GradleLibrary library) { + if (this.extension.isExcludeDevtools() && isDevToolsJar(library)) { + return true; + } + return false; + } + + private boolean isDevToolsJar(GradleLibrary library) { + return "org.springframework.boot".equals(library.getGroup()) + && library.getName().startsWith("spring-boot-devtools"); + } + private Set getDuplicates(Set libraries) { Set duplicates = new HashSet(); Set seen = new HashSet(); @@ -187,6 +201,10 @@ class ProjectLibraries implements Libraries { this.includeGroupName = includeGroupName; } + public String getGroup() { + return this.group; + } + @Override public String getName() { String name = super.getName(); diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Exclude.java b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Exclude.java index c3c713e5dca..04fd0f1d1c9 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Exclude.java +++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Exclude.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ExcludeFilter.java b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ExcludeFilter.java index 3c86b46b8a2..0113deae73c 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ExcludeFilter.java +++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ExcludeFilter.java @@ -16,6 +16,7 @@ package org.springframework.boot.maven; +import java.util.Arrays; import java.util.List; import org.apache.maven.artifact.Artifact; @@ -29,6 +30,10 @@ import org.apache.maven.artifact.Artifact; */ public class ExcludeFilter extends DependencyFilter { + public ExcludeFilter(Exclude... excludes) { + this(Arrays.asList(excludes)); + } + public ExcludeFilter(List excludes) { super(excludes); } diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java index 133cc2a2ac4..219fb8a7fe3 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java +++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java @@ -35,6 +35,7 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; +import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter; import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.LaunchScript; import org.springframework.boot.loader.tools.Layout; @@ -153,6 +154,13 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { @Parameter private Properties embeddedLaunchScriptProperties; + /** + * Exclude Spring Boot devtools. + * @since 1.3 + */ + @Parameter(defaultValue = "false") + private boolean excludeDevtools; + @Override public void execute() throws MojoExecutionException, MojoFailureException { if (this.project.getPackaging().equals("pom")) { @@ -190,7 +198,7 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { } Set artifacts = filterDependencies(this.project.getArtifacts(), - getFilters()); + getFilters(getAdditionalFilters())); Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack, getLog()); @@ -213,6 +221,17 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { } } + private ArtifactsFilter[] getAdditionalFilters() { + if (this.excludeDevtools) { + Exclude exclude = new Exclude(); + exclude.setGroupId("org.springframework.boot"); + exclude.setArtifactId("spring-boot-devtools"); + ExcludeFilter filter = new ExcludeFilter(exclude); + return new ArtifactsFilter[] { filter }; + } + return new ArtifactsFilter[] {}; + } + private File getTargetFile() { String classifier = (this.classifier == null ? "" : this.classifier.trim()); if (classifier.length() > 0 && !classifier.startsWith("-")) {