Skip to content

Commit 9f464e9

Browse files
author
Phillip Webb
committed
Merge branch 'gh-1070'
2 parents c713c80 + f30b962 commit 9f464e9

File tree

29 files changed

+650
-114
lines changed

29 files changed

+650
-114
lines changed

spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarCommand.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
import org.springframework.boot.loader.tools.JarWriter;
5757
import org.springframework.boot.loader.tools.Layout;
5858
import org.springframework.boot.loader.tools.Layouts;
59+
import org.springframework.boot.loader.tools.Library;
60+
import org.springframework.boot.loader.tools.LibraryScope;
5961
import org.springframework.core.io.Resource;
6062
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
6163
import org.springframework.util.Assert;
@@ -248,7 +250,8 @@ private void addDependencies(JarWriter writer, List<URL> urls)
248250
private void addDependency(JarWriter writer, File dependency)
249251
throws FileNotFoundException, IOException {
250252
if (dependency.isFile()) {
251-
writer.writeNestedLibrary("lib/", dependency);
253+
writer.writeNestedLibrary("lib/", new Library(dependency,
254+
LibraryScope.COMPILE));
252255
}
253256
}
254257

spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,11 @@ The following configuration options are available:
511511
|`layout`
512512
|The type of archive, corresponding to how the dependencies are laid out inside
513513
(defaults to a guess based on the archive type).
514+
515+
|`requiresUnpack`
516+
|A list of dependencies (in the form ``groupId:artifactId'' that must be unpacked from
517+
fat jars in order to run. Items are still packaged into the fat jar, but they will be
518+
automatically unpacked when it runs.
514519
|===
515520

516521

@@ -619,7 +624,7 @@ Here is a typical example repackage:
619624
@Override
620625
public void doWithLibraries(LibraryCallback callback) throws IOException {
621626
// Build system specific implementation, callback for each dependency
622-
// callback.library(nestedFile, LibraryScope.COMPILE);
627+
// callback.library(new Library(nestedFile, LibraryScope.COMPILE));
623628
}
624629
});
625630
----

spring-boot-docs/src/main/asciidoc/howto.adoc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,6 +1618,50 @@ For Gradle users the steps are similar. Example:
16181618

16191619

16201620

1621+
[[howto-extract-specific-libraries-when-an-executable-jar-runs]]
1622+
=== Extract specific libraries when an executable jar runs
1623+
Most nested libraries in an executable jar do not need to be unpacked in order to run,
1624+
however, certain libraries can have problems. For example, JRuby includes its own nested
1625+
jar support which assumes that the `jruby-complete.jar` is always directly available as a
1626+
file in its own right.
1627+
1628+
To deal with any problematic libraries, you can flag that specific nested jars should be
1629+
automatically unpacked to the ``temp folder'' when the executable jar first runs.
1630+
1631+
For example, to indicate that JRuby should be flagged for unpack using the Maven Plugin
1632+
you would add the following configuration:
1633+
1634+
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
1635+
----
1636+
<build>
1637+
<plugins>
1638+
<plugin>
1639+
<groupId>org.springframework.boot</groupId>
1640+
<artifactId>spring-boot-maven-plugin</artifactId>
1641+
<configuration>
1642+
<requiresUnpack>
1643+
<dependency>
1644+
<groupId>org.jruby</groupId>
1645+
<artifactId>jruby-complete</artifactId>
1646+
</dependency>
1647+
</requiresUnpack>
1648+
</configuration>
1649+
</plugin>
1650+
</plugins>
1651+
</build>
1652+
----
1653+
1654+
And to do that same with Gradle:
1655+
1656+
[source,groovy,indent=0,subs="verbatim,attributes"]
1657+
----
1658+
springBoot {
1659+
requiresUnpack = ['org.jruby:jruby-complete']
1660+
}
1661+
----
1662+
1663+
1664+
16211665
[[howto-create-a-nonexecutable-jar]]
16221666
=== Create a non-executable JAR with exclusions
16231667
Often if you have an executable and a non-executable jar as build products, the executable

spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/SpringBootPluginExtension.groovy

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ public class SpringBootPluginExtension {
107107
(layout == null ? null : layout.layout)
108108
}
109109

110+
/**
111+
* Libraries that must be unpacked from fat jars in order to run. Use Strings in the
112+
* form {@literal groupId:artifactId}.
113+
*/
114+
Set<String> requiresUnpack;
115+
110116
/**
111117
* Location of an agent jar to attach to the VM when running the application with runJar task.
112118
*/
@@ -121,4 +127,5 @@ public class SpringBootPluginExtension {
121127
* If exclude rules should be applied to dependencies based on the spring-dependencies-bom
122128
*/
123129
boolean applyExcludeRules = true;
130+
124131
}

spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/repackage/ProjectLibraries.java

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.util.HashSet;
22+
import java.util.LinkedHashSet;
23+
import java.util.Set;
2124

2225
import org.gradle.api.Project;
2326
import org.gradle.api.artifacts.Configuration;
24-
import org.gradle.api.file.FileCollection;
27+
import org.gradle.api.artifacts.ModuleVersionIdentifier;
28+
import org.gradle.api.artifacts.ResolvedArtifact;
29+
import org.springframework.boot.gradle.SpringBootPluginExtension;
2530
import org.springframework.boot.loader.tools.Libraries;
31+
import org.springframework.boot.loader.tools.Library;
2632
import org.springframework.boot.loader.tools.LibraryCallback;
2733
import org.springframework.boot.loader.tools.LibraryScope;
2834

@@ -36,22 +42,24 @@ class ProjectLibraries implements Libraries {
3642

3743
private final Project project;
3844

45+
private final SpringBootPluginExtension extension;
46+
3947
private String providedConfigurationName = "providedRuntime";
4048

4149
private String customConfigurationName = null;
4250

4351
/**
4452
* Create a new {@link ProjectLibraries} instance of the specified {@link Project}.
45-
*
4653
* @param project the gradle project
54+
* @param extension the extension
4755
*/
48-
public ProjectLibraries(Project project) {
56+
public ProjectLibraries(Project project, SpringBootPluginExtension extension) {
4957
this.project = project;
58+
this.extension = extension;
5059
}
5160

5261
/**
5362
* Set the name of the provided configuration. Defaults to 'providedRuntime'.
54-
*
5563
* @param providedConfigurationName the providedConfigurationName to set
5664
*/
5765
public void setProvidedConfigurationName(String providedConfigurationName) {
@@ -64,27 +72,20 @@ public void setCustomConfigurationName(String customConfigurationName) {
6472

6573
@Override
6674
public void doWithLibraries(LibraryCallback callback) throws IOException {
67-
68-
FileCollection custom = this.customConfigurationName != null ? this.project
69-
.getConfigurations().findByName(this.customConfigurationName) : null;
70-
75+
Set<ResolvedArtifact> custom = getArtifacts(this.customConfigurationName);
7176
if (custom != null) {
7277
libraries(LibraryScope.CUSTOM, custom, callback);
7378
}
7479
else {
75-
FileCollection compile = this.project.getConfigurations()
76-
.getByName("compile");
77-
78-
FileCollection runtime = this.project.getConfigurations()
79-
.getByName("runtime");
80-
runtime = runtime.minus(compile);
80+
Set<ResolvedArtifact> compile = getArtifacts("compile");
8181

82-
FileCollection provided = this.project.getConfigurations()
83-
.findByName(this.providedConfigurationName);
82+
Set<ResolvedArtifact> runtime = getArtifacts("runtime");
83+
runtime = minus(runtime, compile);
8484

85+
Set<ResolvedArtifact> provided = getArtifacts(this.providedConfigurationName);
8586
if (provided != null) {
86-
compile = compile.minus(provided);
87-
runtime = runtime.minus(provided);
87+
compile = minus(compile, provided);
88+
runtime = minus(runtime, provided);
8889
}
8990

9091
libraries(LibraryScope.COMPILE, compile, callback);
@@ -93,12 +94,47 @@ public void doWithLibraries(LibraryCallback callback) throws IOException {
9394
}
9495
}
9596

96-
private void libraries(LibraryScope scope, FileCollection files,
97+
private Set<ResolvedArtifact> getArtifacts(String configurationName) {
98+
Configuration configuration = (configurationName == null ? null : this.project
99+
.getConfigurations().findByName(configurationName));
100+
return (configuration == null ? null : configuration.getResolvedConfiguration()
101+
.getResolvedArtifacts());
102+
}
103+
104+
private Set<ResolvedArtifact> minus(Set<ResolvedArtifact> source,
105+
Set<ResolvedArtifact> toRemove) {
106+
if (source == null || toRemove == null) {
107+
return source;
108+
}
109+
Set<File> filesToRemove = new HashSet<File>();
110+
for (ResolvedArtifact artifact : toRemove) {
111+
filesToRemove.add(artifact.getFile());
112+
}
113+
Set<ResolvedArtifact> result = new LinkedHashSet<ResolvedArtifact>();
114+
for (ResolvedArtifact artifact : source) {
115+
if (!toRemove.contains(artifact.getFile())) {
116+
result.add(artifact);
117+
}
118+
}
119+
return result;
120+
}
121+
122+
private void libraries(LibraryScope scope, Set<ResolvedArtifact> artifacts,
97123
LibraryCallback callback) throws IOException {
98-
if (files != null) {
99-
for (File file: files) {
100-
callback.library(file, scope);
124+
if (artifacts != null) {
125+
for (ResolvedArtifact artifact : artifacts) {
126+
callback.library(new Library(artifact.getFile(), scope, isUnpackRequired(artifact)));
101127
}
102128
}
103129
}
130+
131+
private boolean isUnpackRequired(ResolvedArtifact artifact) {
132+
if (this.extension.getRequiresUnpack() != null) {
133+
ModuleVersionIdentifier id = artifact.getModuleVersion().getId();
134+
return this.extension.getRequiresUnpack().contains(
135+
id.getGroup() + ":" + id.getName());
136+
}
137+
return false;
138+
}
139+
104140
}

spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/repackage/RepackagePluginFeatures.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.gradle.repackage;
1818

1919
import java.io.File;
20+
import java.io.IOException;
2021

2122
import org.gradle.api.Action;
2223
import org.gradle.api.Project;
@@ -27,6 +28,8 @@
2728
import org.gradle.api.tasks.bundling.Jar;
2829
import org.springframework.boot.gradle.PluginFeatures;
2930
import org.springframework.boot.gradle.SpringBootPluginExtension;
31+
import org.springframework.boot.loader.tools.Library;
32+
import org.springframework.boot.loader.tools.LibraryCallback;
3033
import org.springframework.util.StringUtils;
3134

3235
/**
@@ -124,11 +127,24 @@ private void setupInputOutputs(Jar jarTask, String classifier) {
124127
+ classifier + "." + StringUtils.getFilenameExtension(outputName);
125128
File outputFile = new File(inputFile.getParentFile(), outputName);
126129
this.task.getInputs().file(jarTask);
127-
this.task.getInputs().file(this.task.getDependencies());
130+
addLibraryDependencies(this.task);
128131
this.task.getOutputs().file(outputFile);
129132
this.task.setOutputFile(outputFile);
130133
}
131134

135+
private void addLibraryDependencies(final RepackageTask task) {
136+
try {
137+
task.getLibraries().doWithLibraries(new LibraryCallback() {
138+
public void library(Library library) throws IOException {
139+
task.getInputs().file(library.getFile());
140+
}
141+
});
142+
}
143+
catch (IOException ex) {
144+
throw new IllegalStateException(ex);
145+
}
146+
}
147+
132148
}
133149

134150
}

spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/repackage/RepackageTask.java

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21-
import java.util.ArrayList;
22-
import java.util.List;
2321
import java.util.concurrent.TimeUnit;
2422

2523
import org.gradle.api.Action;
@@ -29,8 +27,6 @@
2927
import org.gradle.api.tasks.TaskContainer;
3028
import org.gradle.api.tasks.bundling.Jar;
3129
import org.springframework.boot.gradle.SpringBootPluginExtension;
32-
import org.springframework.boot.loader.tools.LibraryCallback;
33-
import org.springframework.boot.loader.tools.LibraryScope;
3430
import org.springframework.boot.loader.tools.Repackager;
3531
import org.springframework.util.FileCopyUtils;
3632

@@ -101,27 +97,11 @@ public void repackage() {
10197
project.getTasks().withType(Jar.class, new RepackageAction(extension, libraries));
10298
}
10399

104-
public File[] getDependencies() {
105-
ProjectLibraries libraries = getLibraries();
106-
final List<File> files = new ArrayList<File>();
107-
try {
108-
libraries.doWithLibraries(new LibraryCallback() {
109-
@Override
110-
public void library(File file, LibraryScope scope) throws IOException {
111-
files.add(file);
112-
}
113-
});
114-
} catch (IOException ex) {
115-
throw new IllegalStateException("Cannot retrieve dependencies", ex);
116-
}
117-
return files.toArray(new File[files.size()]);
118-
}
119-
120-
private ProjectLibraries getLibraries() {
100+
public ProjectLibraries getLibraries() {
121101
Project project = getProject();
122102
SpringBootPluginExtension extension = project.getExtensions().getByType(
123103
SpringBootPluginExtension.class);
124-
ProjectLibraries libraries = new ProjectLibraries(project);
104+
ProjectLibraries libraries = new ProjectLibraries(project, extension);
125105
if (extension.getProvidedConfiguration() != null) {
126106
libraries.setProvidedConfigurationName(extension.getProvidedConfiguration());
127107
}

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,19 @@
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.security.DigestInputStream;
23+
import java.security.MessageDigest;
24+
import java.security.NoSuchAlgorithmException;
2025

2126
/**
2227
* Utilities for manipulating files and directories in Spring Boot tooling.
2328
*
2429
* @author Dave Syer
30+
* @author Phillip Webb
2531
*/
26-
public class FileUtils {
32+
public abstract class FileUtils {
2733

2834
/**
2935
* Utility to remove duplicate files from an "output" directory if they already exist
@@ -50,4 +56,37 @@ public static void removeDuplicatesFromOutputDirectory(File outputDirectory,
5056
}
5157
}
5258

59+
/**
60+
* Generate a SHA.1 Hash for a given file.
61+
* @param file the file to hash
62+
* @return the hash value as a String
63+
* @throws IOException
64+
*/
65+
public static String sha1Hash(File file) throws IOException {
66+
try {
67+
DigestInputStream inputStream = new DigestInputStream(new FileInputStream(
68+
file), MessageDigest.getInstance("SHA-1"));
69+
try {
70+
byte[] buffer = new byte[4098];
71+
while (inputStream.read(buffer) != -1) {
72+
// Read the entire stream
73+
}
74+
return bytesToHex(inputStream.getMessageDigest().digest());
75+
}
76+
finally {
77+
inputStream.close();
78+
}
79+
}
80+
catch (NoSuchAlgorithmException ex) {
81+
throw new IllegalStateException(ex);
82+
}
83+
}
84+
85+
private static String bytesToHex(byte[] bytes) {
86+
StringBuilder hex = new StringBuilder();
87+
for (byte b : bytes) {
88+
hex.append(String.format("%02x", b));
89+
}
90+
return hex.toString();
91+
}
5392
}

0 commit comments

Comments
 (0)