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 6b662acd1ab4..116dc7d4d9f3 100644 --- a/spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc +++ b/spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc @@ -480,9 +480,12 @@ The following configuration options are available: |The name of the custom configuration. |`layout` -|The type of archive, corresponding to how the dependencies are laid out inside - (defaults to a guess based on the archive type). See - <>. +|The `LayoutType` of archive, corresponding to how the dependencies are laid out inside + (defaults to a guess based on the archive type). See +<>. + +|`layoutFactory` +|A factory for the actual `Layout` derived from the `LayoutType`. If the factory is provided then the layout is ignored. |`requiresUnpack` |A list of dependencies (in the form "`groupId:artifactId`" that must be unpacked from @@ -493,7 +496,7 @@ The following configuration options are available: [[build-tool-plugins-gradle-configuration-layouts]] -==== Available layouts +==== Available built-in layouts The `layout` attribute configures the format of the archive and whether the bootstrap loader should be included or not. The following layouts are available: @@ -530,6 +533,40 @@ loader should be included or not. The following layouts are available: +[[build-tool-plugins-gradle-configuration-custom-layout]] +==== Using a custom layout +If you have custom requirements for how to arrange the dependencies and loader classes +inside the repackaged jar, you can use a custom layout in addition to the built-in values. +Any library which defines one or more `LayoutFactory` implementations can be added to the +build script dependencies and then the layout type becomes available in the `springBoot` +configuration. For example + +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +buildscript { + ext { + springBootVersion = '1.5.0.BUILD-SNAPSHOT' + customVersion = '0.0.1.BUILD-SNAPSHOT' + } + repositories { + mavenLocal() + mavenCentral() + } + dependencies { + classpath("com.example:custom-layout:${customVersion}") + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + } +} + +springBoot { + layoutFactory = new com.example.CustomLayoutFactory() +} +---- + +If there is only one custom `LayoutFactory` on the build classpath and +it is listed in `META-INF/spring.factories` then it is unnecessary to +explicitly set it in the `springBoot` configuration. + [[build-tool-plugins-understanding-the-gradle-plugin]] === Understanding how the Gradle plugin works When `spring-boot` is applied to your Gradle project a default task named `bootRepackage` 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 987336250ccb..fa32355ea2ed 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 @@ -21,11 +21,14 @@ 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.LayoutFactory; +import org.springframework.boot.loader.tools.LayoutType; import org.springframework.boot.loader.tools.Layouts; /** @@ -90,6 +93,11 @@ public class SpringBootPluginExtension { */ LayoutType layout; + /** + * The layout factory to use to convert a layout type into an actual layout. + */ + LayoutFactory layoutFactory = Layouts.getDefaultLayoutFactory(); + /** * Libraries that must be unpacked from fat jars in order to run. Use Strings in the * form {@literal groupId:artifactId}. @@ -142,10 +150,21 @@ public SpringBootPluginExtension(Project project) { /** * Convenience method for use in a custom task. + * @param file the file to use to guess the layout if necessary * @return the Layout to use or null if not explicitly set */ - public Layout convertLayout() { - return (this.layout == null ? null : this.layout.layout); + public Layout convertLayout(File file) { + Layout result; + if (this.layoutFactory != null) { + result = this.layoutFactory.getLayout(); + } + else if (this.layout == null) { + result = Layouts.forFile(file); + } + else { + result = Layouts.forType(this.layout); + } + return result; } public String getMainClass() { @@ -196,6 +215,14 @@ public void setLayout(LayoutType layout) { this.layout = layout; } + public LayoutFactory getLayoutFactory() { + return this.layoutFactory; + } + + public void setLayoutFactory(LayoutFactory layoutFactory) { + this.layoutFactory = layoutFactory; + } + public Set getRequiresUnpack() { return this.requiresUnpack; } @@ -276,29 +303,4 @@ public void buildInfo(Closure taskConfigurer) { } } - /** - * Layout Types. - */ - enum LayoutType { - - JAR(new Layouts.Jar()), - - WAR(new Layouts.War()), - - ZIP(new Layouts.Expanded()), - - DIR(new Layouts.Expanded()), - - MODULE(new Layouts.Module()), - - NONE(new Layouts.None()); - - Layout layout; - - LayoutType(Layout layout) { - this.layout = layout; - } - - } - } 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 92d99dbf3514..218cc999a745 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 @@ -217,9 +217,7 @@ private void repackage(File file) { } Repackager repackager = new LoggingRepackager(file); setMainClass(repackager); - if (this.extension.convertLayout() != null) { - repackager.setLayout(this.extension.convertLayout()); - } + repackager.setLayout(this.extension.convertLayout(file)); repackager.setBackupSource(this.extension.isBackupSource()); try { LaunchScript launchScript = getLaunchScript(); diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java index d5a05dd0946f..929ef645dcb3 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java @@ -53,8 +53,6 @@ */ public class JarWriter { - private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar"; - private static final int BUFFER_SIZE = 32 * 1024; private final JarOutputStream jarOutput; @@ -206,9 +204,20 @@ private long getNestedLibraryTime(File file) { /** * Write the required spring-boot-loader classes to the JAR. * @throws IOException if the classes cannot be written + * @deprecated us {@link #writeLoaderClasses(String)} instead */ + @Deprecated public void writeLoaderClasses() throws IOException { - URL loaderJar = getClass().getClassLoader().getResource(NESTED_LOADER_JAR); + writeLoaderClasses(Layouts.DEFAULT_LOADER_JAR); + } + + /** + * Write the required spring-boot-loader classes to the JAR. + * @param loaderJarPath the path to the loader jar (in the classpath) + * @throws IOException if the classes cannot be written + */ + public void writeLoaderClasses(String loaderJarPath) throws IOException { + URL loaderJar = getClass().getClassLoader().getResource(loaderJarPath); JarInputStream inputStream = new JarInputStream( new BufferedInputStream(loaderJar.openStream())); JarEntry entry; diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layout.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layout.java index 38681e107eeb..09f7ded6be26 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layout.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layout.java @@ -40,15 +40,25 @@ public interface Layout { String getLibraryDestination(String libraryName, LibraryScope scope); /** - * Returns the location of classes within the archive. + * Returns the location of classes within the archive. Empty if the location is the + * root path, otherwise ends with a slash ('/'). * @return the classes location */ String getClassesLocation(); /** - * Returns if loader classes should be included to make the archive executable. + * Returns if loader classes should be included to make the archive executable. If + * true, then {@link #getLoaderJarPath()} should point to a valid jar file that + * contains the loader classes. * @return if the layout is executable */ boolean isExecutable(); + /** + * Returns the path to a nested jar that contains the loader, and which will be + * unpacked into the root of the repackaged jar. + * @return the path to a nested jar that contains the loader + */ + String getLoaderJarPath(); + } diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutFactory.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutFactory.java new file mode 100644 index 000000000000..09b892ddefa7 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutFactory.java @@ -0,0 +1,29 @@ +/* + * 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.loader.tools; + +/** + * Strategy for creating instances of {@link Layout}. + * + * @author Dave Syer + * + */ +public interface LayoutFactory { + + Layout getLayout(); + +} diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutType.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutType.java new file mode 100644 index 000000000000..41131f188538 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutType.java @@ -0,0 +1,80 @@ +/* + * 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.loader.tools; + +import java.io.File; + +/** + * Archive layout types. + * + * @author Dave Syer + */ +public enum LayoutType { + + /** + * Jar Layout. + */ + JAR, + + /** + * War Layout. + */ + WAR, + + /** + * Zip Layout. + */ + ZIP, + + /** + * Dir Layout. + */ + DIR, + + /** + * Module Layout. + */ + MODULE, + + /** + * No Layout. + */ + NONE; + + + /** + * Return a layout type for the given source file. + * @param file the source file + * @return a {@link Layout} + */ + public static LayoutType forFile(File file) { + if (file == null) { + throw new IllegalArgumentException("File must not be null"); + } + if (file.getName().toLowerCase().endsWith(".jar")) { + return JAR; + } + if (file.getName().toLowerCase().endsWith(".war")) { + return WAR; + } + if (file.isDirectory() || file.getName().toLowerCase().endsWith(".zip")) { + return ZIP; + } + throw new IllegalArgumentException("Unable to deduce layout for '" + file + "'"); + } + +} diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java index 3cebdc7a05c5..b13e6c67d699 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java @@ -21,9 +21,12 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import org.springframework.core.io.support.SpringFactoriesLoader; + /** * Common {@link Layout}s. * @@ -33,6 +36,11 @@ */ public final class Layouts { + /** + * Default value for {@link Layout#getLoaderJarPath()}. + */ + public static final String DEFAULT_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar"; + private Layouts() { } @@ -42,19 +50,42 @@ private Layouts() { * @return a {@link Layout} */ public static Layout forFile(File file) { - if (file == null) { - throw new IllegalArgumentException("File must not be null"); - } - if (file.getName().toLowerCase().endsWith(".jar")) { - return new Jar(); - } - if (file.getName().toLowerCase().endsWith(".war")) { - return new War(); + return forType(LayoutType.forFile(file)); + } + + /** + * Return a layout for the given type. + * @param type the layout type + * @return a {@link Layout} + */ + public static Layout forType(LayoutType type) { + switch (type) { + case JAR: + return new Layouts.Jar(); + case WAR: + return new Layouts.War(); + case ZIP: + return new Layouts.Expanded(); + case MODULE: + return new Layouts.Module(); + default: + return new Layouts.None(); } - if (file.isDirectory() || file.getName().toLowerCase().endsWith(".zip")) { - return new Expanded(); + } + + /** + * Gets a default layout factory, trying first to find a unique one in spring + * factories, and then falling back to {@link DefaultLayoutFactory} if there isn't + * one. + * @return the default layout factory + */ + public static LayoutFactory getDefaultLayoutFactory() { + List factories = SpringFactoriesLoader + .loadFactories(LayoutFactory.class, null); + if (factories.size() == 1) { + return factories.get(0); } - throw new IllegalStateException("Unable to deduce layout for '" + file + "'"); + return null; } /** @@ -87,6 +118,11 @@ public boolean isExecutable() { return true; } + @Override + public String getLoaderJarPath() { + return DEFAULT_LOADER_JAR; + } + } /** @@ -116,6 +152,11 @@ public boolean isExecutable() { return false; } + @Override + public String getLoaderJarPath() { + return DEFAULT_LOADER_JAR; + } + } /** @@ -154,6 +195,11 @@ public boolean isExecutable() { return true; } + @Override + public String getLoaderJarPath() { + return DEFAULT_LOADER_JAR; + } + } /** @@ -188,6 +234,11 @@ public boolean isExecutable() { return false; } + @Override + public String getLoaderJarPath() { + return DEFAULT_LOADER_JAR; + } + } } diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java index c25129844bbb..ec651463692d 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java @@ -217,7 +217,7 @@ public void library(Library library) throws IOException { } writeNestedLibraries(standardLibraries, seen, writer); if (this.layout.isExecutable()) { - writer.writeLoaderClasses(); + writer.writeLoaderClasses(this.layout.getLoaderJarPath()); } } finally { @@ -301,8 +301,11 @@ else if (startClass != null) { (this.layout instanceof RepackagingLayout) ? ((RepackagingLayout) this.layout).getRepackagedClassesLocation() : this.layout.getClassesLocation()); - manifest.getMainAttributes().putValue(BOOT_LIB_ATTRIBUTE, - this.layout.getLibraryDestination("", LibraryScope.COMPILE)); + String libraryDestination = this.layout.getLibraryDestination("", + LibraryScope.COMPILE); + if (libraryDestination != null) { + manifest.getMainAttributes().putValue(BOOT_LIB_ATTRIBUTE, libraryDestination); + } return manifest; } diff --git a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java index 4c3ee88ea617..0f8225ccad00 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java @@ -40,8 +40,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; /** @@ -346,7 +346,8 @@ public void customLayout() throws Exception { Layout layout = mock(Layout.class); final LibraryScope scope = mock(LibraryScope.class); given(layout.getLauncherClassName()).willReturn("testLauncher"); - given(layout.getLibraryDestination(anyString(), eq(scope))).willReturn("test/"); + given(layout.getLibraryDestination(anyString(), any(LibraryScope.class))) + .willReturn("test/"); repackager.setLayout(layout); repackager.repackage(new Libraries() { @Override @@ -355,6 +356,32 @@ public void doWithLibraries(LibraryCallback callback) throws IOException { } }); assertThat(hasEntry(file, "test/" + libJarFile.getName())).isTrue(); + assertThat(getManifest(file).getMainAttributes().getValue("Spring-Boot-Lib")) + .isEqualTo("test/"); + assertThat(getManifest(file).getMainAttributes().getValue("Main-Class")) + .isEqualTo("testLauncher"); + } + + @Test + public void customLayoutNoBootLib() throws Exception { + TestJarFile libJar = new TestJarFile(this.temporaryFolder); + libJar.addClass("a/b/C.class", ClassWithoutMainMethod.class); + final File libJarFile = libJar.getFile(); + this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class); + File file = this.testJarFile.getFile(); + Repackager repackager = new Repackager(file); + Layout layout = mock(Layout.class); + final LibraryScope scope = mock(LibraryScope.class); + given(layout.getLauncherClassName()).willReturn("testLauncher"); + repackager.setLayout(layout); + repackager.repackage(new Libraries() { + @Override + public void doWithLibraries(LibraryCallback callback) throws IOException { + callback.library(new Library(libJarFile, scope)); + } + }); + assertThat(getManifest(file).getMainAttributes()) + .doesNotContainKey("Spring-Boot-Lib"); assertThat(getManifest(file).getMainAttributes().getValue("Main-Class")) .isEqualTo("testLauncher"); } 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 309eb72db37d..ba950166bb0d 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 @@ -43,6 +43,8 @@ import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.LaunchScript; import org.springframework.boot.loader.tools.Layout; +import org.springframework.boot.loader.tools.LayoutFactory; +import org.springframework.boot.loader.tools.LayoutType; import org.springframework.boot.loader.tools.Layouts; import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Repackager; @@ -133,6 +135,13 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { @Parameter private LayoutType layout; + /** + * The type of the layout factory that converts layout type to an actual layout. + * @since 1.0 + */ + @Parameter + private LayoutFactory layoutFactory = Layouts.getDefaultLayoutFactory(); + /** * A list of the libraries that must be unpacked from fat jars in order to run. * Specify each library as a <dependency> with a @@ -226,9 +235,22 @@ private File getTargetFile() { private Repackager getRepackager(File source) { Repackager repackager = new LoggingRepackager(source, getLog()); repackager.setMainClass(this.mainClass); - if (this.layout != null) { - getLog().info("Layout: " + this.layout); - repackager.setLayout(this.layout.layout()); + Layout forUse; + if (this.layoutFactory != null) { + forUse = this.layoutFactory.getLayout(); + } + else if (this.layout == null) { + forUse = Layouts.forFile(source); + } + else { + forUse = Layouts.forType(this.layout); + } + getLog().info("Layout: " + forUse.getClass()); + try { + repackager.setLayout(forUse); + } + catch (Exception e) { + throw new IllegalStateException("Cannot create layout", e); } return repackager; } @@ -309,53 +331,6 @@ else if (!source.equals(repackaged)) { } } - /** - * Archive layout types. - */ - public enum LayoutType { - - /** - * Jar Layout. - */ - JAR(new Layouts.Jar()), - - /** - * War Layout. - */ - WAR(new Layouts.War()), - - /** - * Zip Layout. - */ - ZIP(new Layouts.Expanded()), - - /** - * Dir Layout. - */ - DIR(new Layouts.Expanded()), - - /** - * Module Layout. - */ - MODULE(new Layouts.Module()), - - /** - * No Layout. - */ - NONE(new Layouts.None()); - - private final Layout layout; - - public Layout layout() { - return this.layout; - } - - LayoutType(Layout layout) { - this.layout = layout; - } - - } - private static class LoggingRepackager extends Repackager { private final Log log; diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/examples/custom-layout.apt.vm b/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/examples/custom-layout.apt.vm new file mode 100644 index 000000000000..01b0ea0b37d6 --- /dev/null +++ b/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/examples/custom-layout.apt.vm @@ -0,0 +1,58 @@ + ----- + Use a custom layout + ----- + Dave Syer + ----- + 2016-10-30 + ----- + + Spring Boot repackages the jar file for this project using a custom + layout factory defined in the additional jar file, provided as a dependency + to the build plugin: + +--- + + ... + + ... + + ... + + ${project.groupId} + ${project.artifactId} + ${project.version} + + + + repackage + + + + + + + + + com.example + custom-layout + 0.0.1.BUILD-SNAPSHOT + + + ... + + ... + + ... + + ... + +--- + + The layout is provided as an implementation of <> + (from spring-boot-loader-tools) explicitly specified in the pom. If + there is only one custom `LayoutFactory` on the plugin classpath and + it is listed in `META-INF/spring.factories` then it is unnecessary + to explicitly set it in the plugin configuration. + + + diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/index.apt b/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/index.apt index dcf50844057e..cb767f9e6415 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/index.apt +++ b/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/index.apt @@ -48,6 +48,8 @@ Spring Boot Maven Plugin * {{{./examples/repackage-disable-attach.html}Local repackaged artifact}} + * {{{./examples/custom-layout.html}Custom layout}} + * {{{./examples/exclude-dependency.html}Exclude a dependency}} * {{{./examples/run-debug.html}Debug the application}}