Commit 5ad4d627 authored by Andy Wilkinson's avatar Andy Wilkinson

Fix classpath index so entries match those expected by the launcher

This reverts commit ad164269 and adds
some additional tests.

Fixes gh-24192
parent 826d79be
/* /*
* 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.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;
......
...@@ -44,6 +44,7 @@ import org.gradle.testkit.runner.UnexpectedBuildFailure; ...@@ -44,6 +44,7 @@ import org.gradle.testkit.runner.UnexpectedBuildFailure;
import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.TestTemplate;
import org.springframework.boot.loader.tools.JarModeLibrary; import org.springframework.boot.loader.tools.JarModeLibrary;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -240,6 +241,28 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests { ...@@ -240,6 +241,28 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
assertExtractedLayers(layerNames, indexedLayers); assertExtractedLayers(layerNames, indexedLayers);
} }
@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).doesNotContain("4. ");
}
@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\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("3\\. .*commons-lang3-3.9.jar");
assertThat(output).doesNotContain("4. ");
}
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);
...@@ -339,4 +362,14 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests { ...@@ -339,4 +362,14 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
return extractedLayers; return extractedLayers;
} }
private void copyClasspathApplication() throws IOException {
copyApplication("classpath");
}
private void copyApplication(String name) throws IOException {
File output = new File(this.gradleBuild.getProjectDir(), "src/main/java/com/example/bootjar/" + name);
output.mkdirs();
FileSystemUtils.copyRecursively(new File("src/test/java/com/example/bootjar/" + name), output);
}
} }
/* /*
* 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.
...@@ -200,9 +200,10 @@ class BootJarTests extends AbstractBootArchiveTests<TestBootJar> { ...@@ -200,9 +200,10 @@ class BootJarTests extends AbstractBootArchiveTests<TestBootJar> {
@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\"");
} }
} }
...@@ -224,9 +225,10 @@ class BootJarTests extends AbstractBootArchiveTests<TestBootJar> { ...@@ -224,9 +225,10 @@ class BootJarTests extends AbstractBootArchiveTests<TestBootJar> {
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\"");
} }
} }
......
...@@ -81,7 +81,8 @@ class BootRunIntegrationTests { ...@@ -81,7 +81,8 @@ class BootRunIntegrationTests {
copyClasspathApplication(); copyClasspathApplication();
BuildResult result = this.gradleBuild.build("echoMainClassName"); BuildResult result = this.gradleBuild.build("echoMainClassName");
assertThat(result.task(":echoMainClassName").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":echoMainClassName").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
...@@ -129,9 +130,9 @@ class BootRunIntegrationTests { ...@@ -129,9 +130,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")
}
/* /*
* 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.
...@@ -473,15 +473,11 @@ public abstract class Packager { ...@@ -473,15 +473,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