diff --git a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/FullyExecutableJarTests.java b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/FullyExecutableJarTests.java new file mode 100644 index 00000000000..8bb9a0c93b2 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/FullyExecutableJarTests.java @@ -0,0 +1,182 @@ +/* + * 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. + * 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; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.gradle.tooling.ProjectConnection; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +/** + * Integration tests for creating a fully executable jar with Gradle. + * + * @author Andy Wilkinson + */ +public class FullyExecutableJarTests { + + private static final String BOOT_VERSION = Versions.getBootVersion(); + + private static ProjectConnection project; + + @BeforeClass + public static void createProject() throws IOException { + project = new ProjectCreator().createProject("executable-jar"); + } + + @Test + public void jarIsNotExecutableByDefault() throws IOException { + project.newBuild().forTasks("clean", "build") + .withArguments("-PbootVersion=" + BOOT_VERSION).run(); + File buildLibs = new File("target/executable-jar/build/libs"); + File executableJar = new File(buildLibs, "executable-jar.jar"); + assertThat(isFullyExecutable(executableJar), is(false)); + } + + @Test + public void madeExecutableViaExtension() throws IOException { + project.newBuild().forTasks("clean", "build").withArguments( + "-PbootVersion=" + BOOT_VERSION, "-PextensionExecutable=true").run(); + File buildLibs = new File("target/executable-jar/build/libs"); + File executableJar = new File(buildLibs, "executable-jar.jar"); + assertThat(isFullyExecutable(executableJar), is(true)); + } + + @Test + public void madeExecutableViaTask() throws IOException { + project.newBuild().forTasks("clean", "build") + .withArguments("-PbootVersion=" + BOOT_VERSION, "-PtaskExecutable=true") + .run(); + File buildLibs = new File("target/executable-jar/build/libs"); + File executableJar = new File(buildLibs, "executable-jar.jar"); + assertThat(isFullyExecutable(executableJar), is(true)); + } + + @Test + public void taskTakesPrecedenceForMakingJarExecutable() throws IOException { + project.newBuild().forTasks("clean", "build") + .withArguments("-PbootVersion=" + BOOT_VERSION, + "-PextensionExecutable=false", "-PtaskExecutable=true") + .run(); + File buildLibs = new File("target/executable-jar/build/libs"); + File executableJar = new File(buildLibs, "executable-jar.jar"); + assertThat(isFullyExecutable(executableJar), is(true)); + } + + @Test + public void scriptPropertiesFromTask() throws IOException { + project.newBuild().forTasks("clean", "build") + .withArguments("-PbootVersion=" + BOOT_VERSION, "-PtaskProperties=true") + .run(); + File buildLibs = new File("target/executable-jar/build/libs"); + File executableJar = new File(buildLibs, "executable-jar.jar"); + assertThat(isFullyExecutable(executableJar), is(true)); + assertThat(containsLine("# Provides:.*__task__", executableJar), is(true)); + } + + @Test + public void scriptPropertiesFromExtension() throws IOException { + project.newBuild().forTasks("clean", "build").withArguments( + "-PbootVersion=" + BOOT_VERSION, "-PextensionProperties=true").run(); + File buildLibs = new File("target/executable-jar/build/libs"); + File executableJar = new File(buildLibs, "executable-jar.jar"); + assertThat(isFullyExecutable(executableJar), is(true)); + assertThat(containsLine("# Provides:.*__extension__", executableJar), is(true)); + } + + @Test + public void taskTakesPrecedenceForScriptProperties() throws IOException { + project.newBuild().forTasks("clean", "build") + .withArguments("-PbootVersion=" + BOOT_VERSION, + "-PextensionProperties=true", "-PtaskProperties=true") + .run(); + File buildLibs = new File("target/executable-jar/build/libs"); + File executableJar = new File(buildLibs, "executable-jar.jar"); + assertThat(isFullyExecutable(executableJar), is(true)); + assertThat(containsLine("# Provides:.*__task__", executableJar), is(true)); + } + + @Test + public void customScriptFromTask() throws IOException { + project.newBuild().forTasks("clean", "build") + .withArguments("-PbootVersion=" + BOOT_VERSION, "-PtaskScript=true") + .run(); + File buildLibs = new File("target/executable-jar/build/libs"); + File executableJar = new File(buildLibs, "executable-jar.jar"); + assertThat(containsLine("Custom task script", executableJar), is(true)); + } + + @Test + public void customScriptFromExtension() throws IOException { + project.newBuild().forTasks("clean", "build") + .withArguments("-PbootVersion=" + BOOT_VERSION, "-PextensionScript=true") + .run(); + File buildLibs = new File("target/executable-jar/build/libs"); + File executableJar = new File(buildLibs, "executable-jar.jar"); + assertThat(containsLine("Custom extension script", executableJar), is(true)); + } + + @Test + public void taskTakesPrecedenceForCustomScript() throws IOException { + project.newBuild().forTasks("clean", "build") + .withArguments("-PbootVersion=" + BOOT_VERSION, "-PextensionScript=true", + "-PtaskScript=true") + .run(); + File buildLibs = new File("target/executable-jar/build/libs"); + File executableJar = new File(buildLibs, "executable-jar.jar"); + assertThat(containsLine("Custom task script", executableJar), is(true)); + } + + private boolean isFullyExecutable(File file) throws IOException { + return containsLine("#!/bin/bash", file); + } + + private boolean containsLine(String toMatch, File file) throws IOException { + Pattern pattern = Pattern.compile(toMatch); + for (String line : readLines(file)) { + if (pattern.matcher(line).matches()) { + return true; + } + } + return false; + } + + private List readLines(File file) throws IOException { + BufferedReader reader = new BufferedReader(new FileReader(file)); + String line; + List lines = new ArrayList(); + try { + while ((line = reader.readLine()) != null && lines.size() < 50) { + lines.add(line); + } + } + finally { + reader.close(); + } + return lines; + } + +} diff --git a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/executable-jar/build.gradle b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/executable-jar/build.gradle new file mode 100644 index 00000000000..14444a71e7c --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/executable-jar/build.gradle @@ -0,0 +1,53 @@ +buildscript { + repositories { + mavenLocal() + } + dependencies { + classpath "org.springframework.boot:spring-boot-gradle-plugin:${project.bootVersion}" + } +} + +repositories { + mavenLocal() + mavenCentral() +} + +apply plugin: 'spring-boot' +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' +} + +if (project.properties['taskExecutable']) { + bootRepackage.executable = Boolean.valueOf(project.taskExecutable) +} + +if (project.properties['extensionExecutable']) { + springBoot.executable = Boolean.valueOf(project.extensionExecutable) +} + +if (project.properties['taskProperties']) { + bootRepackage.executable = true + bootRepackage.embeddedLaunchScriptProperties = ['initInfoProvides': '__task__'] +} + +if (project.properties['extensionProperties']) { + bootRepackage.executable = true + springBoot.embeddedLaunchScriptProperties = ['initInfoProvides': '__extension__'] +} + +if (project.properties['taskScript']) { + bootRepackage.embeddedLaunchScript = file('task.script') +} + +if (project.properties['extensionScript']) { + springBoot.embeddedLaunchScript = file('extension.script') +} diff --git a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/executable-jar/extension.script b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/executable-jar/extension.script new file mode 100644 index 00000000000..9c5657221ef --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/executable-jar/extension.script @@ -0,0 +1 @@ +Custom extension script diff --git a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/executable-jar/task.script b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/executable-jar/task.script new file mode 100644 index 00000000000..17a55b540d5 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/resources/executable-jar/task.script @@ -0,0 +1 @@ +Custom task script 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 b81ebc1f6bc..2c82f06fd56 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 @@ -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. @@ -48,6 +48,8 @@ class ProjectLibraries implements Libraries { private final SpringBootPluginExtension extension; + private final boolean excludeDevtools; + private String providedConfigurationName = "providedRuntime"; private String customConfigurationName = null; @@ -56,10 +58,13 @@ class ProjectLibraries implements Libraries { * Create a new {@link ProjectLibraries} instance of the specified {@link Project}. * @param project the gradle project * @param extension the extension + * @param excludeDevTools whether Spring Boot Devtools should be excluded */ - ProjectLibraries(Project project, SpringBootPluginExtension extension) { + ProjectLibraries(Project project, SpringBootPluginExtension extension, + boolean excludeDevTools) { this.project = project; this.extension = extension; + this.excludeDevtools = excludeDevTools; } /** @@ -165,7 +170,7 @@ class ProjectLibraries implements Libraries { } private boolean isExcluded(GradleLibrary library) { - if (this.extension.isExcludeDevtools() && isDevToolsJar(library)) { + if (this.excludeDevtools && isDevToolsJar(library)) { return true; } return false; diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackageTask.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackageTask.java index 342207ce25a..da068e4ca0b 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackageTask.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackageTask.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. @@ -19,6 +19,7 @@ package org.springframework.boot.gradle.repackage; import java.io.File; import java.io.IOException; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -57,6 +58,14 @@ public class RepackageTask extends DefaultTask { private File outputFile; + private Boolean excludeDevtools; + + private Boolean executable; + + private File embeddedLaunchScript; + + private Map embeddedLaunchScriptProperties; + public void setCustomConfiguration(String customConfiguration) { this.customConfiguration = customConfiguration; } @@ -89,6 +98,39 @@ public class RepackageTask extends DefaultTask { this.outputFile = file; } + public Boolean getExcludeDevtools() { + return this.excludeDevtools; + } + + public void setExcludeDevtools(Boolean excludeDevtools) { + this.excludeDevtools = excludeDevtools; + } + + public Boolean getExecutable() { + return this.executable; + } + + public void setExecutable(Boolean executable) { + this.executable = executable; + } + + public File getEmbeddedLaunchScript() { + return this.embeddedLaunchScript; + } + + public void setEmbeddedLaunchScript(File embeddedLaunchScript) { + this.embeddedLaunchScript = embeddedLaunchScript; + } + + public Map getEmbeddedLaunchScriptProperties() { + return this.embeddedLaunchScriptProperties; + } + + public void setEmbeddedLaunchScriptProperties( + Map embeddedLaunchScriptProperties) { + this.embeddedLaunchScriptProperties = embeddedLaunchScriptProperties; + } + @TaskAction public void repackage() { Project project = getProject(); @@ -102,7 +144,9 @@ public class RepackageTask extends DefaultTask { Project project = getProject(); SpringBootPluginExtension extension = project.getExtensions() .getByType(SpringBootPluginExtension.class); - ProjectLibraries libraries = new ProjectLibraries(project, extension); + ProjectLibraries libraries = new ProjectLibraries(project, extension, + (this.excludeDevtools != null && this.excludeDevtools) + || extension.isExcludeDevtools()); if (extension.getProvidedConfiguration() != null) { libraries.setProvidedConfigurationName(extension.getProvidedConfiguration()); } @@ -223,14 +267,30 @@ public class RepackageTask extends DefaultTask { } private LaunchScript getLaunchScript() throws IOException { - if (this.extension.isExecutable() - || this.extension.getEmbeddedLaunchScript() != null) { - return new DefaultLaunchScript(this.extension.getEmbeddedLaunchScript(), - this.extension.getEmbeddedLaunchScriptProperties()); + if (isExecutable() || getEmbeddedLaunchScript() != null) { + return new DefaultLaunchScript(getEmbeddedLaunchScript(), + getEmbeddedLaunchScriptProperties()); } return null; } + private boolean isExecutable() { + return RepackageTask.this.executable != null ? RepackageTask.this.executable + : this.extension.isExecutable(); + } + + private File getEmbeddedLaunchScript() { + return RepackageTask.this.embeddedLaunchScript != null + ? RepackageTask.this.embeddedLaunchScript + : this.extension.getEmbeddedLaunchScript(); + } + + private Map getEmbeddedLaunchScriptProperties() { + return RepackageTask.this.embeddedLaunchScriptProperties != null + ? RepackageTask.this.embeddedLaunchScriptProperties + : this.extension.getEmbeddedLaunchScriptProperties(); + } + } /**