Commit 45f298bc authored by Andy Wilkinson's avatar Andy Wilkinson

Merge branch '2.4.x'

Closes gh-24711
parents 1d30a471 9e389c83
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -243,7 +243,7 @@ class BootZipCopyAction implements CopyAction { ...@@ -243,7 +243,7 @@ class BootZipCopyAction implements CopyAction {
details.copyTo(this.out); details.copyTo(this.out);
this.out.closeArchiveEntry(); this.out.closeArchiveEntry();
if (BootZipCopyAction.this.librarySpec.isSatisfiedBy(details)) { if (BootZipCopyAction.this.librarySpec.isSatisfiedBy(details)) {
this.writtenLibraries.add(name.substring(name.lastIndexOf('/') + 1)); this.writtenLibraries.add(name);
} }
if (BootZipCopyAction.this.layerResolver != null) { if (BootZipCopyAction.this.layerResolver != null) {
Layer layer = BootZipCopyAction.this.layerResolver.getLayer(details); Layer layer = BootZipCopyAction.this.layerResolver.getLayer(details);
......
/*
* Copyright 2012-2019 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
*
* https://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 com.example.bootjar.classpath;
import java.net.URL;
import java.net.URLClassLoader;
/**
* Application used for testing classpath handling with BootJar.
*
* @author Andy Wilkinson
*/
public class BootJarClasspathApplication {
protected BootJarClasspathApplication() {
}
public static void main(String[] args) {
int i = 1;
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
System.out.println(i++ + ". " + url.getFile());
}
}
}
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.example.main; package com.example.bootjar.main;
/** /**
* Application used for testing {@code BootRun}'s main class configuration. * Application used for testing {@code BootRun}'s main class configuration.
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.example.classpath; package com.example.bootrun.classpath;
import java.io.File; import java.io.File;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.example.jvmargs; package com.example.bootrun.jvmargs;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
......
/*
* Copyright 2012-2020 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
*
* https://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 com.example.bootrun.main;
/**
* Application used for testing {@code BootRun}'s main class configuration.
*
* @author Andy Wilkinson
*/
public class CustomMainClass {
protected CustomMainClass() {
}
public static void main(String[] args) {
System.out.println(CustomMainClass.class.getName());
}
}
/*
* Copyright 2012-2020 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
*
* https://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 com.example.bootwar.main;
/**
* Application used for testing {@code BootRun}'s main class configuration.
*
* @author Andy Wilkinson
*/
public class CustomMainClass {
protected CustomMainClass() {
}
public static void main(String[] args) {
System.out.println(CustomMainClass.class.getName());
}
}
...@@ -19,6 +19,7 @@ package org.springframework.boot.gradle.tasks.bundling; ...@@ -19,6 +19,7 @@ package org.springframework.boot.gradle.tasks.bundling;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Locale;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.jar.Attributes; import java.util.jar.Attributes;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
...@@ -196,7 +197,8 @@ abstract class AbstractBootArchiveIntegrationTests { ...@@ -196,7 +197,8 @@ abstract class AbstractBootArchiveIntegrationTests {
.isEqualTo(TaskOutcome.SUCCESS); .isEqualTo(TaskOutcome.SUCCESS);
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
Attributes mainAttributes = jarFile.getManifest().getMainAttributes(); Attributes mainAttributes = jarFile.getManifest().getMainAttributes();
assertThat(mainAttributes.getValue("Start-Class")).isEqualTo("com.example.main.CustomMainClass"); assertThat(mainAttributes.getValue("Start-Class"))
.isEqualTo("com.example." + this.taskName.toLowerCase(Locale.ENGLISH) + ".main.CustomMainClass");
} }
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
.isEqualTo(TaskOutcome.UP_TO_DATE); .isEqualTo(TaskOutcome.UP_TO_DATE);
...@@ -206,10 +208,13 @@ abstract class AbstractBootArchiveIntegrationTests { ...@@ -206,10 +208,13 @@ abstract class AbstractBootArchiveIntegrationTests {
copyApplication("main"); copyApplication("main");
} }
private void copyApplication(String name) throws IOException { protected void copyApplication(String name) throws IOException {
File output = new File(this.gradleBuild.getProjectDir(), "src/main/java/com/example/" + name); File output = new File(this.gradleBuild.getProjectDir(),
"src/main/java/com/example/" + this.taskName.toLowerCase() + "/" + name);
output.mkdirs(); output.mkdirs();
FileSystemUtils.copyRecursively(new File("src/test/java/com/example/" + name), output); FileSystemUtils.copyRecursively(
new File("src/test/java/com/example/" + this.taskName.toLowerCase(Locale.ENGLISH) + "/" + name),
output);
} }
private void createStandardJar(File location) throws IOException { private void createStandardJar(File location) throws IOException {
......
...@@ -296,6 +296,30 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests { ...@@ -296,6 +296,30 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
.isEqualTo(TaskOutcome.SUCCESS); .isEqualTo(TaskOutcome.SUCCESS);
} }
@TestTemplate
void packagedApplicationClasspath() throws IOException {
copyClasspathApplication();
BuildResult result = this.gradleBuild.build("launch");
String output = result.getOutput();
assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("2\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("3\\. .*commons-lang3-3.9.jar");
assertThat(output).containsPattern("4\\. .*spring-boot-jarmode-layertools-.*.jar");
assertThat(output).doesNotContain("5. ");
}
@TestTemplate
void explodedApplicationClasspath() throws IOException {
copyClasspathApplication();
BuildResult result = this.gradleBuild.build("launch");
String output = result.getOutput();
assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("2\\. .*spring-boot-jarmode-layertools-.*.jar");
assertThat(output).containsPattern("3\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("4\\. .*commons-lang3-3.9.jar");
assertThat(output).doesNotContain("5. ");
}
private void assertExtractedLayers(List<String> layerNames, Map<String, List<String>> indexedLayers) private void assertExtractedLayers(List<String> layerNames, Map<String, List<String>> indexedLayers)
throws IOException { throws IOException {
Map<String, List<String>> extractedLayers = readExtractedLayers(this.gradleBuild.getProjectDir(), layerNames); Map<String, List<String>> extractedLayers = readExtractedLayers(this.gradleBuild.getProjectDir(), layerNames);
...@@ -395,4 +419,8 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests { ...@@ -395,4 +419,8 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
return extractedLayers; return extractedLayers;
} }
private void copyClasspathApplication() throws IOException {
copyApplication("classpath");
}
} }
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -230,9 +230,10 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> { ...@@ -230,9 +230,10 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
@Test @Test
void whenJarIsLayeredClasspathIndexPointsToLayeredLibs() throws IOException { void whenJarIsLayeredClasspathIndexPointsToLayeredLibs() throws IOException {
try (JarFile jarFile = new JarFile(createLayeredJar())) { try (JarFile jarFile = new JarFile(createLayeredJar())) {
assertThat(entryLines(jarFile, "BOOT-INF/classpath.idx")).containsExactly("- \"first-library.jar\"", assertThat(entryLines(jarFile, "BOOT-INF/classpath.idx")).containsExactly(
"- \"second-library.jar\"", "- \"third-library-SNAPSHOT.jar\"", "- \"first-project-library.jar\"", "- \"BOOT-INF/lib/first-library.jar\"", "- \"BOOT-INF/lib/second-library.jar\"",
"- \"second-project-library-SNAPSHOT.jar\""); "- \"BOOT-INF/lib/third-library-SNAPSHOT.jar\"", "- \"BOOT-INF/lib/first-project-library.jar\"",
"- \"BOOT-INF/lib/second-project-library-SNAPSHOT.jar\"");
} }
} }
...@@ -254,9 +255,10 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> { ...@@ -254,9 +255,10 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
try (JarFile jarFile = new JarFile(createPopulatedJar())) { try (JarFile jarFile = new JarFile(createPopulatedJar())) {
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classpath-Index")) assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classpath-Index"))
.isEqualTo("BOOT-INF/classpath.idx"); .isEqualTo("BOOT-INF/classpath.idx");
assertThat(entryLines(jarFile, "BOOT-INF/classpath.idx")).containsExactly("- \"first-library.jar\"", assertThat(entryLines(jarFile, "BOOT-INF/classpath.idx")).containsExactly(
"- \"second-library.jar\"", "- \"third-library-SNAPSHOT.jar\"", "- \"first-project-library.jar\"", "- \"BOOT-INF/lib/first-library.jar\"", "- \"BOOT-INF/lib/second-library.jar\"",
"- \"second-project-library-SNAPSHOT.jar\""); "- \"BOOT-INF/lib/third-library-SNAPSHOT.jar\"", "- \"BOOT-INF/lib/first-project-library.jar\"",
"- \"BOOT-INF/lib/second-project-library-SNAPSHOT.jar\"");
} }
} }
......
...@@ -71,7 +71,7 @@ class BootRunIntegrationTests { ...@@ -71,7 +71,7 @@ class BootRunIntegrationTests {
copyMainClassApplication(); copyMainClassApplication();
BuildResult result = this.gradleBuild.build("bootRun"); BuildResult result = this.gradleBuild.build("bootRun");
assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("com.example.main.CustomMainClass"); assertThat(result.getOutput()).contains("com.example.bootrun.main.CustomMainClass");
} }
@TestTemplate @TestTemplate
...@@ -79,7 +79,7 @@ class BootRunIntegrationTests { ...@@ -79,7 +79,7 @@ class BootRunIntegrationTests {
copyMainClassApplication(); copyMainClassApplication();
BuildResult result = this.gradleBuild.build("bootRun"); BuildResult result = this.gradleBuild.build("bootRun");
assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("com.example.main.CustomMainClass"); assertThat(result.getOutput()).contains("com.example.bootrun.main.CustomMainClass");
} }
@TestTemplate @TestTemplate
...@@ -87,7 +87,8 @@ class BootRunIntegrationTests { ...@@ -87,7 +87,8 @@ class BootRunIntegrationTests {
copyClasspathApplication(); copyClasspathApplication();
BuildResult result = this.gradleBuild.build("bootRun"); BuildResult result = this.gradleBuild.build("bootRun");
assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("Main class name = com.example.classpath.BootRunClasspathApplication"); assertThat(result.getOutput())
.contains("Main class name = com.example.bootrun.classpath.BootRunClasspathApplication");
} }
@TestTemplate @TestTemplate
...@@ -150,9 +151,9 @@ class BootRunIntegrationTests { ...@@ -150,9 +151,9 @@ class BootRunIntegrationTests {
} }
private void copyApplication(String name) throws IOException { private void copyApplication(String name) throws IOException {
File output = new File(this.gradleBuild.getProjectDir(), "src/main/java/com/example/" + name); File output = new File(this.gradleBuild.getProjectDir(), "src/main/java/com/example/bootrun/" + name);
output.mkdirs(); output.mkdirs();
FileSystemUtils.copyRecursively(new File("src/test/java/com/example/" + name), output); FileSystemUtils.copyRecursively(new File("src/test/java/com/example/bootrun/" + name), output);
} }
private String canonicalPathOf(String path) throws IOException { private String canonicalPathOf(String path) throws IOException {
......
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
repositories {
mavenCentral()
maven { url "file:repository" }
}
dependencies {
implementation("com.example:library:1.0-SNAPSHOT")
implementation("org.apache.commons:commons-lang3:3.9")
}
task explode(type: Sync) {
dependsOn(bootJar)
destinationDir = file("$buildDir/exploded")
from zipTree(files(bootJar).singleFile)
}
task launch(type: JavaExec) {
classpath = files(explode)
main = 'org.springframework.boot.loader.JarLauncher'
}
\ No newline at end of file
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
task launch(type: JavaExec) {
classpath = files(bootJar)
}
repositories {
mavenCentral()
maven { url "file:repository" }
}
dependencies {
implementation("com.example:library:1.0-SNAPSHOT")
implementation("org.apache.commons:commons-lang3:3.9")
}
...@@ -3,4 +3,4 @@ plugins { ...@@ -3,4 +3,4 @@ plugins {
id 'org.springframework.boot' version '{version}' id 'org.springframework.boot' version '{version}'
} }
mainClassName = 'com.example.main.CustomMainClass' mainClassName = 'com.example.bootrun.main.CustomMainClass'
...@@ -4,5 +4,5 @@ plugins { ...@@ -4,5 +4,5 @@ plugins {
} }
springBoot { springBoot {
mainClass = 'com.example.main.CustomMainClass' mainClass = 'com.example.bootrun.main.CustomMainClass'
} }
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -479,15 +479,11 @@ public abstract class Packager { ...@@ -479,15 +479,11 @@ public abstract class Packager {
} }
private void writeClasspathIndex(RepackagingLayout layout, AbstractJarWriter writer) throws IOException { private void writeClasspathIndex(RepackagingLayout layout, AbstractJarWriter writer) throws IOException {
List<String> names = this.libraries.keySet().stream().map(this::getJarName) List<String> names = this.libraries.keySet().stream().map((path) -> "- \"" + path + "\"")
.map((name) -> "- \"" + name + "\"").collect(Collectors.toList()); .collect(Collectors.toList());
writer.writeIndexFile(layout.getClasspathIndexFileLocation(), names); writer.writeIndexFile(layout.getClasspathIndexFileLocation(), names);
} }
private String getJarName(String path) {
return path.substring(path.lastIndexOf('/') + 1);
}
} }
} }
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -234,7 +234,7 @@ abstract class AbstractPackagerTests<P extends Packager> { ...@@ -234,7 +234,7 @@ abstract class AbstractPackagerTests<P extends Packager> {
String index = getPackagedEntryContent("BOOT-INF/classpath.idx"); String index = getPackagedEntryContent("BOOT-INF/classpath.idx");
String[] libraries = index.split("\\r?\\n"); String[] libraries = index.split("\\r?\\n");
List<String> expected = Stream.of(libJarFile1, libJarFile2, libJarFile3) List<String> expected = Stream.of(libJarFile1, libJarFile2, libJarFile3)
.map((jar) -> "- \"" + jar.getName() + "\"").collect(Collectors.toList()); .map((jar) -> "- \"BOOT-INF/lib/" + jar.getName() + "\"").collect(Collectors.toList());
assertThat(Arrays.asList(libraries)).containsExactlyElementsOf(expected); assertThat(Arrays.asList(libraries)).containsExactlyElementsOf(expected);
} }
...@@ -265,7 +265,7 @@ abstract class AbstractPackagerTests<P extends Packager> { ...@@ -265,7 +265,7 @@ abstract class AbstractPackagerTests<P extends Packager> {
assertThat(hasPackagedEntry("BOOT-INF/classpath.idx")).isTrue(); assertThat(hasPackagedEntry("BOOT-INF/classpath.idx")).isTrue();
String classpathIndex = getPackagedEntryContent("BOOT-INF/classpath.idx"); String classpathIndex = getPackagedEntryContent("BOOT-INF/classpath.idx");
List<String> expectedClasspathIndex = Stream.of(libJarFile1, libJarFile2, libJarFile3) List<String> expectedClasspathIndex = Stream.of(libJarFile1, libJarFile2, libJarFile3)
.map((file) -> "- \"" + file.getName() + "\"").collect(Collectors.toList()); .map((file) -> "- \"BOOT-INF/lib/" + file.getName() + "\"").collect(Collectors.toList());
assertThat(Arrays.asList(classpathIndex.split("\\n"))).containsExactlyElementsOf(expectedClasspathIndex); assertThat(Arrays.asList(classpathIndex.split("\\n"))).containsExactlyElementsOf(expectedClasspathIndex);
assertThat(hasPackagedEntry("BOOT-INF/layers.idx")).isTrue(); assertThat(hasPackagedEntry("BOOT-INF/layers.idx")).isTrue();
String layersIndex = getPackagedEntryContent("BOOT-INF/layers.idx"); String layersIndex = getPackagedEntryContent("BOOT-INF/layers.idx");
...@@ -296,7 +296,7 @@ abstract class AbstractPackagerTests<P extends Packager> { ...@@ -296,7 +296,7 @@ abstract class AbstractPackagerTests<P extends Packager> {
assertThat(hasPackagedEntry("BOOT-INF/classpath.idx")).isTrue(); assertThat(hasPackagedEntry("BOOT-INF/classpath.idx")).isTrue();
String classpathIndex = getPackagedEntryContent("BOOT-INF/classpath.idx"); String classpathIndex = getPackagedEntryContent("BOOT-INF/classpath.idx");
assertThat(Arrays.asList(classpathIndex.split("\\n"))) assertThat(Arrays.asList(classpathIndex.split("\\n")))
.containsExactly("- \"spring-boot-jarmode-layertools.jar\""); .containsExactly("- \"BOOT-INF/lib/spring-boot-jarmode-layertools.jar\"");
assertThat(hasPackagedEntry("BOOT-INF/layers.idx")).isTrue(); assertThat(hasPackagedEntry("BOOT-INF/layers.idx")).isTrue();
String layersIndex = getPackagedEntryContent("BOOT-INF/layers.idx"); String layersIndex = getPackagedEntryContent("BOOT-INF/layers.idx");
List<String> expectedLayers = new ArrayList<>(); List<String> expectedLayers = new ArrayList<>();
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -78,11 +78,11 @@ class ClassPathIndexFileTests { ...@@ -78,11 +78,11 @@ class ClassPathIndexFileTests {
ClassPathIndexFile indexFile = copyAndLoadTestIndexFile(); ClassPathIndexFile indexFile = copyAndLoadTestIndexFile();
List<URL> urls = indexFile.getUrls(); List<URL> urls = indexFile.getUrls();
List<File> expected = new ArrayList<>(); List<File> expected = new ArrayList<>();
expected.add(new File(this.temp, "a.jar")); expected.add(new File(this.temp, "BOOT-INF/layers/one/lib/a.jar"));
expected.add(new File(this.temp, "b.jar")); expected.add(new File(this.temp, "BOOT-INF/layers/one/lib/b.jar"));
expected.add(new File(this.temp, "c.jar")); expected.add(new File(this.temp, "BOOT-INF/layers/one/lib/c.jar"));
expected.add(new File(this.temp, "d.jar")); expected.add(new File(this.temp, "BOOT-INF/layers/two/lib/d.jar"));
expected.add(new File(this.temp, "e.jar")); expected.add(new File(this.temp, "BOOT-INF/layers/two/lib/e.jar"));
assertThat(urls).containsExactly(expected.stream().map(this::toUrl).toArray(URL[]::new)); assertThat(urls).containsExactly(expected.stream().map(this::toUrl).toArray(URL[]::new));
} }
......
- "a.jar" - "BOOT-INF/layers/one/lib/a.jar"
- "b.jar" - "BOOT-INF/layers/one/lib/b.jar"
- "c.jar" - "BOOT-INF/layers/one/lib/c.jar"
- "d.jar" - "BOOT-INF/layers/two/lib/d.jar"
- "e.jar" - "BOOT-INF/layers/two/lib/e.jar"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment