diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml
index b54e7da4cf..8e8440307c 100644
--- a/spring-boot-samples/pom.xml
+++ b/spring-boot-samples/pom.xml
@@ -32,6 +32,7 @@
spring-boot-sample-atmosphere
spring-boot-sample-batch
spring-boot-sample-cache
+ spring-boot-sample-custom-layout
spring-boot-sample-data-cassandra
spring-boot-sample-data-couchbase
spring-boot-sample-data-elasticsearch
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/pom.xml b/spring-boot-samples/spring-boot-sample-custom-layout/pom.xml
new file mode 100644
index 0000000000..2bf7677aa6
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/pom.xml
@@ -0,0 +1,107 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-samples
+ 1.5.0.BUILD-SNAPSHOT
+
+ spring-boot-sample-custom-layout
+ Spring Boot Custom Layout Sample
+ Spring Boot Custom Layout Sample
+ http://projects.spring.io/spring-boot/
+
+ Pivotal Software, Inc.
+ http://www.spring.io
+
+
+ ${basedir}/../..
+
+
+
+ org.springframework.boot
+ spring-boot-loader-tools
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.gradle
+ gradle-tooling-api
+ ${gradle.version}
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-invoker-plugin
+
+ ${project.build.directory}/it
+ src/it/settings.xml
+ ${project.build.directory}/local-repo
+ verify
+ true
+ ${skipTests}
+ true
+
+
+
+ integration-test
+
+ install
+ run
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-effective-pom
+ generate-test-resources
+
+ copy
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${project.version}
+ effective-pom
+ true
+ ${project.build.directory}
+ dependencies-pom.xml
+
+
+
+
+
+
+
+
+
+
+ gradle
+ http://repo.gradle.org/gradle/libs-releases-local
+
+ true
+
+
+ false
+
+
+
+
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/build.gradle b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/build.gradle
new file mode 100644
index 0000000000..4bad1c3da1
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/build.gradle
@@ -0,0 +1,28 @@
+buildscript {
+ repositories {
+ flatDir {
+ dirs '../..'
+ }
+ mavenLocal()
+ }
+ dependencies {
+ classpath "org.springframework.boot:spring-boot-gradle-plugin:${project.bootVersion}"
+ classpath "org.springframework.boot:spring-boot-sample-custom-layout:${project.bootVersion}"
+ }
+}
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+apply plugin: 'java'
+apply plugin: 'org.springframework.boot'
+
+springBoot {
+ layoutFactory = new sample.layout.SampleLayoutFactory('custom')
+}
+
+dependencies {
+ compile 'org.springframework.boot:spring-boot-starter'
+}
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/pom.xml b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/pom.xml
new file mode 100644
index 0000000000..c8e41f877d
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/pom.xml
@@ -0,0 +1,41 @@
+
+
+ 4.0.0
+ org.springframework.boot.maven.it
+ custom
+ 0.0.1.BUILD-SNAPSHOT
+
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ @project.version@
+
+
+
+ repackage
+
+
+
+ custom
+
+
+
+
+
+
+ @project.groupId@
+ @project.artifactId@
+ @project.version@
+
+
+
+
+
+
+
+
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/src/main/java/org/test/SampleApplication.java b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/src/main/java/org/test/SampleApplication.java
new file mode 100644
index 0000000000..e8784d4593
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/src/main/java/org/test/SampleApplication.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012-2014 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.test;
+
+public class SampleApplication {
+
+ public static void main(String[] args) {
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/verify.groovy b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/verify.groovy
new file mode 100644
index 0000000000..bcb4277c0e
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/verify.groovy
@@ -0,0 +1,7 @@
+import java.io.*;
+import sample.layout.*;
+
+Verify.verify(
+ new File( basedir, "target/custom-0.0.1.BUILD-SNAPSHOT.jar" ), "custom"
+);
+
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/build.gradle b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/build.gradle
new file mode 100644
index 0000000000..7c10b209ff
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/build.gradle
@@ -0,0 +1,24 @@
+buildscript {
+ repositories {
+ flatDir {
+ dirs '../..'
+ }
+ mavenLocal()
+ }
+ dependencies {
+ classpath "org.springframework.boot:spring-boot-gradle-plugin:${project.bootVersion}"
+ classpath "org.springframework.boot:spring-boot-sample-custom-layout:${project.bootVersion}"
+ }
+}
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+apply plugin: 'java'
+apply plugin: 'org.springframework.boot'
+
+dependencies {
+ compile 'org.springframework.boot:spring-boot-starter'
+}
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/pom.xml b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/pom.xml
new file mode 100644
index 0000000000..8f5ef946cb
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/pom.xml
@@ -0,0 +1,36 @@
+
+
+ 4.0.0
+ org.springframework.boot.maven.it
+ default
+ 0.0.1.BUILD-SNAPSHOT
+
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ @project.version@
+
+
+
+ repackage
+
+
+
+
+
+ @project.groupId@
+ @project.artifactId@
+ @project.version@
+
+
+
+
+
+
+
+
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/src/main/java/org/test/SampleApplication.java b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/src/main/java/org/test/SampleApplication.java
new file mode 100644
index 0000000000..e8784d4593
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/src/main/java/org/test/SampleApplication.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012-2014 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.test;
+
+public class SampleApplication {
+
+ public static void main(String[] args) {
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/verify.groovy b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/verify.groovy
new file mode 100644
index 0000000000..a9cd8afb07
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/verify.groovy
@@ -0,0 +1,7 @@
+import java.io.*;
+import sample.layout.*;
+
+Verify.verify(
+ new File( basedir, "target/default-0.0.1.BUILD-SNAPSHOT.jar" ), "sample"
+);
+
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/main/java/sample/layout/SampleLayout.java b/spring-boot-samples/spring-boot-sample-custom-layout/src/main/java/sample/layout/SampleLayout.java
new file mode 100644
index 0000000000..ce993bf9b4
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/main/java/sample/layout/SampleLayout.java
@@ -0,0 +1,42 @@
+/*
+ * 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 sample.layout;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.springframework.boot.loader.tools.CustomLoaderLayout;
+import org.springframework.boot.loader.tools.Layouts;
+import org.springframework.boot.loader.tools.LoaderClassesWriter;
+
+/**
+ * @author pwebb
+ */
+public class SampleLayout extends Layouts.Jar implements CustomLoaderLayout {
+
+ private String name;
+
+ public SampleLayout(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException {
+ writer.writeEntry(this.name, new ByteArrayInputStream("test".getBytes()));
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/main/java/sample/layout/SampleLayoutFactory.java b/spring-boot-samples/spring-boot-sample-custom-layout/src/main/java/sample/layout/SampleLayoutFactory.java
new file mode 100644
index 0000000000..50a7ff1f4d
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/main/java/sample/layout/SampleLayoutFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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 sample.layout;
+
+import java.io.File;
+
+import org.springframework.boot.loader.tools.Layout;
+import org.springframework.boot.loader.tools.LayoutFactory;
+
+public class SampleLayoutFactory implements LayoutFactory {
+
+ private String name = "sample";
+
+ public SampleLayoutFactory() {
+ }
+
+ public SampleLayoutFactory(String name) {
+ this.name = name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public Layout getLayout(File source) {
+ return new SampleLayout(this.name);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/main/resources/META-INF/spring.factories b/spring-boot-samples/spring-boot-sample-custom-layout/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000000..3f2ec6985e
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.loader.tools.LayoutFactory=\
+sample.layout.SampleLayoutFactory
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/test/java/sample/layout/GradeIT.java b/spring-boot-samples/spring-boot-sample-custom-layout/src/test/java/sample/layout/GradeIT.java
new file mode 100644
index 0000000000..5a132fa00e
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/test/java/sample/layout/GradeIT.java
@@ -0,0 +1,89 @@
+/*
+ * 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 sample.layout;
+
+import java.io.File;
+import java.io.FileReader;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathFactory;
+
+import org.gradle.tooling.GradleConnector;
+import org.gradle.tooling.ProjectConnection;
+import org.gradle.tooling.internal.consumer.DefaultGradleConnector;
+import org.junit.Test;
+import org.xml.sax.InputSource;
+
+import org.springframework.util.FileCopyUtils;
+
+public class GradeIT {
+
+ @Test
+ public void sampleDefault() throws Exception {
+ test("default", "sample");
+ }
+
+ @Test
+ public void sampleCustom() throws Exception {
+ test("custom", "custom");
+ }
+
+ private void test(String name, String expected) throws Exception {
+ File projectDirectory = new File("target/gradleit/" + name);
+ File javaDirectory = new File(
+ "target/gradleit/" + name + "/src/main/java/org/test/");
+ projectDirectory.mkdirs();
+ javaDirectory.mkdirs();
+ File script = new File(projectDirectory, "build.gradle");
+ FileCopyUtils.copy(new File("src/it/" + name + "/build.gradle"), script);
+ FileCopyUtils.copy(
+ new File("src/it/" + name
+ + "/src/main/java/org/test/SampleApplication.java"),
+ new File(javaDirectory, "SampleApplication.java"));
+ GradleConnector gradleConnector = GradleConnector.newConnector();
+ gradleConnector.useGradleVersion("2.9");
+ ((DefaultGradleConnector) gradleConnector).embedded(true);
+ ProjectConnection project = gradleConnector.forProjectDirectory(projectDirectory)
+ .connect();
+ project.newBuild().forTasks("clean", "build")
+ .withArguments("-PbootVersion=" + getBootVersion()).run();
+ Verify.verify(
+ new File("target/gradleit/" + name + "/build/libs/" + name + ".jar"),
+ expected);
+ }
+
+ public static String getBootVersion() {
+ return evaluateExpression(
+ "/*[local-name()='project']/*[local-name()='version']" + "/text()");
+ }
+
+ private static String evaluateExpression(String expression) {
+ try {
+ XPathFactory xPathFactory = XPathFactory.newInstance();
+ XPath xpath = xPathFactory.newXPath();
+ XPathExpression expr = xpath.compile(expression);
+ String version = expr.evaluate(
+ new InputSource(new FileReader("target/dependencies-pom.xml")));
+ return version;
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException("Failed to evaluate expression", ex);
+ }
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/test/java/sample/layout/Verify.java b/spring-boot-samples/spring-boot-sample-custom-layout/src/test/java/sample/layout/Verify.java
new file mode 100644
index 0000000000..496eea6728
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/test/java/sample/layout/Verify.java
@@ -0,0 +1,45 @@
+/*
+ * 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 sample.layout;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public final class Verify {
+
+ private Verify() {
+ }
+
+ public static void verify(File file, String entry) throws Exception {
+ ZipFile zipFile = new ZipFile(file);
+ try {
+ Enumeration extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ if (entries.nextElement().getName().equals(entry)) {
+ return;
+ }
+ }
+ throw new AssertionError("No entry " + entry);
+ }
+ finally {
+ zipFile.close();
+ }
+ }
+
+}