Skip to content

Commit b5b4a02

Browse files
committed
Automatically add jarmode jars when packaging
Update the `Packager` to automatically add the layertools jarmode jar when producing a layered jar. Closes gh-19865
1 parent 2b83ede commit b5b4a02

File tree

9 files changed

+272
-60
lines changed

9 files changed

+272
-60
lines changed

spring-boot-project/spring-boot-tools/spring-boot-loader-tools/build.gradle

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def generatedResources = "${buildDir}/generated-resources/main"
1111

1212
configurations {
1313
loader
14+
jarmode
1415
}
1516

1617
dependencies {
@@ -22,6 +23,8 @@ dependencies {
2223

2324
loader(project(":spring-boot-project:spring-boot-tools:spring-boot-loader"))
2425

26+
jarmode(project(":spring-boot-project:spring-boot-tools:spring-boot-jarmode-layertools"))
27+
2528
testImplementation("org.assertj:assertj-core")
2629
testImplementation("org.junit.jupiter:junit-jupiter")
2730
testImplementation("org.mockito:mockito-core")
@@ -45,6 +48,18 @@ task reproducibleLoaderJar(type: Jar) {
4548
destinationDirectory = file("${generatedResources}/META-INF/loader")
4649
}
4750

51+
task reproducibleJarModeLayerToolsJar(type: Jar) {
52+
dependsOn configurations.jarmode
53+
from {
54+
zipTree(configurations.jarmode.incoming.files.filter {it.name.startsWith "spring-boot-jarmode-layertools" }.singleFile)
55+
}
56+
reproducibleFileOrder = true
57+
preserveFileTimestamps = false
58+
archiveFileName = "spring-boot-jarmode-layertools.jar"
59+
destinationDirectory = file("${generatedResources}/META-INF/jarmode")
60+
}
61+
4862
processResources {
4963
dependsOn reproducibleLoaderJar
64+
dependsOn reproducibleJarModeLayerToolsJar
5065
}

spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,15 @@
2020
import java.io.BufferedWriter;
2121
import java.io.ByteArrayInputStream;
2222
import java.io.ByteArrayOutputStream;
23-
import java.io.File;
24-
import java.io.FileInputStream;
2523
import java.io.IOException;
2624
import java.io.InputStream;
2725
import java.io.OutputStream;
2826
import java.io.OutputStreamWriter;
2927
import java.net.URL;
3028
import java.nio.charset.StandardCharsets;
29+
import java.util.Collection;
3130
import java.util.Enumeration;
3231
import java.util.HashSet;
33-
import java.util.List;
3432
import java.util.Set;
3533
import java.util.jar.JarEntry;
3634
import java.util.jar.JarFile;
@@ -127,17 +125,16 @@ public void writeEntry(String entryName, InputStream inputStream) throws IOExcep
127125

128126
/**
129127
* Write a nested library.
130-
* @param destination the destination of the library
128+
* @param location the destination of the library
131129
* @param library the library
132130
* @throws IOException if the write fails
133131
*/
134-
public void writeNestedLibrary(String destination, Library library) throws IOException {
135-
File file = library.getFile();
136-
JarArchiveEntry entry = new JarArchiveEntry(destination + library.getName());
137-
entry.setTime(getNestedLibraryTime(file));
138-
new CrcAndSize(file).setupStoredEntry(entry);
139-
try (FileInputStream input = new FileInputStream(file)) {
140-
writeEntry(entry, new InputStreamEntryWriter(input), new LibraryUnpackHandler(library));
132+
public void writeNestedLibrary(String location, Library library) throws IOException {
133+
JarArchiveEntry entry = new JarArchiveEntry(location + library.getName());
134+
entry.setTime(getNestedLibraryTime(library));
135+
new CrcAndSize(library::openStream).setupStoredEntry(entry);
136+
try (InputStream inputStream = library.openStream()) {
137+
writeEntry(entry, new InputStreamEntryWriter(inputStream), new LibraryUnpackHandler(library));
141138
}
142139
}
143140

@@ -148,7 +145,7 @@ public void writeNestedLibrary(String destination, Library library) throws IOExc
148145
* @throws IOException if the write fails
149146
* @since 2.3.0
150147
*/
151-
public void writeIndexFile(String location, List<String> lines) throws IOException {
148+
public void writeIndexFile(String location, Collection<String> lines) throws IOException {
152149
if (location != null) {
153150
JarArchiveEntry entry = new JarArchiveEntry(location);
154151
writeEntry(entry, (outputStream) -> {
@@ -163,22 +160,22 @@ public void writeIndexFile(String location, List<String> lines) throws IOExcepti
163160
}
164161
}
165162

166-
private long getNestedLibraryTime(File file) {
163+
private long getNestedLibraryTime(Library library) {
167164
try {
168-
try (JarFile jarFile = new JarFile(file)) {
169-
Enumeration<JarEntry> entries = jarFile.entries();
170-
while (entries.hasMoreElements()) {
171-
JarEntry entry = entries.nextElement();
165+
try (JarInputStream jarStream = new JarInputStream(library.openStream())) {
166+
JarEntry entry = jarStream.getNextJarEntry();
167+
while (entry != null) {
172168
if (!entry.isDirectory()) {
173169
return entry.getTime();
174170
}
171+
entry = jarStream.getNextJarEntry();
175172
}
176173
}
177174
}
178175
catch (Exception ex) {
179-
// Ignore and just use the source file timestamp
176+
// Ignore and just use the library timestamp
180177
}
181-
return file.lastModified();
178+
return library.getLastModified();
182179
}
183180

184181
/**
@@ -292,8 +289,8 @@ private static class CrcAndSize {
292289

293290
private long size;
294291

295-
CrcAndSize(File file) throws IOException {
296-
try (FileInputStream inputStream = new FileInputStream(file)) {
292+
CrcAndSize(InputStreamSupplier supplier) throws IOException {
293+
try (InputStream inputStream = supplier.openStream()) {
297294
load(inputStream);
298295
}
299296
}
@@ -380,7 +377,7 @@ public boolean requiresUnpack(String name) {
380377

381378
@Override
382379
public String sha1Hash(String name) throws IOException {
383-
return FileUtils.sha1Hash(this.library.getFile());
380+
return Digest.sha1(this.library::openStream);
384381
}
385382

386383
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.loader.tools;
18+
19+
import java.io.IOException;
20+
import java.security.DigestInputStream;
21+
import java.security.MessageDigest;
22+
import java.security.NoSuchAlgorithmException;
23+
24+
/**
25+
* Utility class used to calculate digests.
26+
*
27+
* @author Phillip Webb
28+
*/
29+
final class Digest {
30+
31+
private Digest() {
32+
}
33+
34+
/**
35+
* Return the SHA1 digest from the supplied stream.
36+
* @param supplier the stream supplier
37+
* @return the SHA1 digest
38+
* @throws IOException on IO error
39+
*/
40+
static String sha1(InputStreamSupplier supplier) throws IOException {
41+
try {
42+
try (DigestInputStream inputStream = new DigestInputStream(supplier.openStream(),
43+
MessageDigest.getInstance("SHA-1"))) {
44+
byte[] buffer = new byte[4098];
45+
while (inputStream.read(buffer) != -1) {
46+
// Read the entire stream
47+
}
48+
return bytesToHex(inputStream.getMessageDigest().digest());
49+
}
50+
}
51+
catch (NoSuchAlgorithmException ex) {
52+
throw new IllegalStateException(ex);
53+
}
54+
}
55+
56+
private static String bytesToHex(byte[] bytes) {
57+
StringBuilder hex = new StringBuilder();
58+
for (byte b : bytes) {
59+
hex.append(String.format("%02x", b));
60+
}
61+
return hex.toString();
62+
}
63+
64+
}

spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/FileUtils.java

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@
1717
package org.springframework.boot.loader.tools;
1818

1919
import java.io.File;
20-
import java.io.FileInputStream;
2120
import java.io.IOException;
22-
import java.security.DigestInputStream;
23-
import java.security.MessageDigest;
24-
import java.security.NoSuchAlgorithmException;
2521

2622
/**
2723
* Utilities for manipulating files and directories in Spring Boot tooling.
@@ -62,27 +58,7 @@ public static void removeDuplicatesFromOutputDirectory(File outputDirectory, Fil
6258
* @throws IOException if the file cannot be read
6359
*/
6460
public static String sha1Hash(File file) throws IOException {
65-
try {
66-
try (DigestInputStream inputStream = new DigestInputStream(new FileInputStream(file),
67-
MessageDigest.getInstance("SHA-1"))) {
68-
byte[] buffer = new byte[4098];
69-
while (inputStream.read(buffer) != -1) {
70-
// Read the entire stream
71-
}
72-
return bytesToHex(inputStream.getMessageDigest().digest());
73-
}
74-
}
75-
catch (NoSuchAlgorithmException ex) {
76-
throw new IllegalStateException(ex);
77-
}
78-
}
79-
80-
private static String bytesToHex(byte[] bytes) {
81-
StringBuilder hex = new StringBuilder();
82-
for (byte b : bytes) {
83-
hex.append(String.format("%02x", b));
84-
}
85-
return hex.toString();
61+
return Digest.sha1(InputStreamSupplier.forFile(file));
8662
}
8763

8864
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.loader.tools;
18+
19+
import java.io.File;
20+
import java.io.FileInputStream;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
24+
/**
25+
* Supplier to provide an {@link InputStream}.
26+
*
27+
* @author Phillip Webb
28+
*/
29+
@FunctionalInterface
30+
interface InputStreamSupplier {
31+
32+
/**
33+
* Returns a new open {@link InputStream} at the begining of the content.
34+
* @return a new {@link InputStream}
35+
* @throws IOException on IO error
36+
*/
37+
InputStream openStream() throws IOException;
38+
39+
/**
40+
* Factory method to create an {@link InputStreamSupplier} for the given {@link File}.
41+
* @param file the source file
42+
* @return a new {@link InputStreamSupplier} instance
43+
*/
44+
static InputStreamSupplier forFile(File file) {
45+
return () -> new FileInputStream(file);
46+
}
47+
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.loader.tools;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
import java.net.URL;
23+
24+
import org.springframework.util.Assert;
25+
26+
/**
27+
* {@link Library} implementation for internal jarmode jars.
28+
*
29+
* @author Phillip Webb
30+
*/
31+
class JarModeLibrary extends Library {
32+
33+
static final JarModeLibrary LAYER_TOOLS = new JarModeLibrary("spring-boot-jarmode-layertools.jar");
34+
35+
JarModeLibrary(String name) {
36+
super(name, null, LibraryScope.RUNTIME, false);
37+
}
38+
39+
@Override
40+
InputStream openStream() throws IOException {
41+
String path = "META-INF/jarmode/" + getName();
42+
URL resource = getClass().getClassLoader().getResource(path);
43+
Assert.state(resource != null, "Unable to find resource " + path);
44+
return resource.openStream();
45+
}
46+
47+
@Override
48+
long getLastModified() {
49+
return 0L;
50+
}
51+
52+
@Override
53+
public File getFile() {
54+
throw new UnsupportedOperationException("Unable to access jar mode library file");
55+
}
56+
57+
}

spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Library.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
package org.springframework.boot.loader.tools;
1818

1919
import java.io.File;
20+
import java.io.FileInputStream;
21+
import java.io.IOException;
22+
import java.io.InputStream;
2023

2124
/**
2225
* Encapsulates information about a single library that may be packed into the archive.
@@ -85,6 +88,15 @@ public File getFile() {
8588
return this.file;
8689
}
8790

91+
/**
92+
* Open a stream that provides the content of the source file.
93+
* @return the file content
94+
* @throws IOException on error
95+
*/
96+
InputStream openStream() throws IOException {
97+
return new FileInputStream(this.file);
98+
}
99+
88100
/**
89101
* Return the scope of the library.
90102
* @return the scope
@@ -102,4 +114,8 @@ public boolean isUnpackRequired() {
102114
return this.unpackRequired;
103115
}
104116

117+
long getLastModified() {
118+
return this.file.lastModified();
119+
}
120+
105121
}

0 commit comments

Comments
 (0)