Skip to content

Commit 84f9603

Browse files
committed
Put project deps in app layer and make customization easier
Previously, when building a layered jar with Gradle, project dependencies were treated the same as any other dependency, being included in the dependencies or snapshot dependencies layer based on their version. This commit updates the default layering when using Gradle to include project dependencies in the application layer by default. The DSL has also been updated to allow their layer to be customized using new includeProjectDependencies() and excludeProjectDependencies() methods rather than relying on including and excluding them via a group:artifact:version pattern. Closes gh-23431
1 parent ad6ea94 commit 84f9603

File tree

11 files changed

+292
-35
lines changed

11 files changed

+292
-35
lines changed

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging.adoc

+7-5
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,10 @@ Layered jars use the same layout as regular boot packaged jars, but include an a
282282

283283
By default, the following layers are defined:
284284

285-
* `dependencies` for any dependency whose version does not contain `SNAPSHOT`.
285+
* `dependencies` for any non-project dependency whose version does not contain `SNAPSHOT`.
286286
* `spring-boot-loader` for the jar loader classes.
287-
* `snapshot-dependencies` for any dependency whose version contains `SNAPSHOT`.
288-
* `application` for application classes and resources.
287+
* `snapshot-dependencies` for any non-project dependency whose version contains `SNAPSHOT`.
288+
* `application` for project dependencies, application classes, and resources.
289289

290290
The layers order is important as it determines how likely previous layers can be cached when part of the application changes.
291291
The default order is `dependencies`, `spring-boot-loader`, `snapshot-dependencies`, `application`.
@@ -355,13 +355,15 @@ Any content not claimed by an earlier `intoLayer` closure remains available for
355355
The `intoLayer` closure claims content using nested `include` and `exclude` calls.
356356
The `application` closure uses Ant-style patch matching for include/exclude parameters.
357357
The `dependencies` section uses `group:artifact[:version]` patterns.
358+
It also provides `includeProjectDependencies()` and `excludeProjectDependencies()` methods that can be used to include or exclude project dependencies.
358359

359360
If no `include` call is made, then all content (not claimed by an earlier closure) is considered.
360361

361362
If no `exclude` call is made, then no exclusions are applied.
362363

363-
Looking at the `dependencies` closure in the example above, we can see that the first `intoLayer` will claim all SNAPSHOT dependencies for the `snapshot-dependencies` layer.
364-
The subsequent `intoLayer` will claim anything left (in this case, any dependency that is not a SNAPSHOT) for the `dependencies` layer.
364+
Looking at the `dependencies` closure in the example above, we can see that the first `intoLayer` will claim all project dependencies for the `application` layer.
365+
The next `intoLayer` will claim all SNAPSHOT dependencies for the `snapshot-dependencies` layer.
366+
The third and final `intoLayer` will claim anything left (in this case, any dependency that is not a project dependency or a SNAPSHOT) for the `dependencies` layer.
365367

366368
The `application` closure has similar rules.
367369
First claiming `org/springframework/boot/loader/**` content for the `spring-boot-loader` layer.

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ bootJar {
1717
intoLayer("application")
1818
}
1919
dependencies {
20+
intoLayer("application") {
21+
includeProjectDependencies()
22+
}
2023
intoLayer("snapshot-dependencies") {
2124
include "*:*:*SNAPSHOT"
2225
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
import org.gradle.api.file.FileCopyDetails;
2222
import org.gradle.api.specs.Spec;
2323

24+
import org.springframework.boot.gradle.tasks.bundling.ResolvedDependencies.DependencyDescriptor;
2425
import org.springframework.boot.loader.tools.Layer;
2526
import org.springframework.boot.loader.tools.Library;
26-
import org.springframework.boot.loader.tools.LibraryCoordinates;
2727

2828
/**
2929
* Resolver backed by a {@link LayeredSpec} that provides the destination {@link Layer}
@@ -76,8 +76,10 @@ Iterable<Layer> getLayers() {
7676

7777
private Library asLibrary(FileCopyDetails details) {
7878
File file = details.getFile();
79-
LibraryCoordinates coordinates = this.resolvedDependencies.find(file);
80-
return new Library(null, file, null, coordinates, false);
79+
DependencyDescriptor dependency = this.resolvedDependencies.find(file);
80+
return (dependency != null)
81+
? new Library(null, file, null, dependency.getCoordinates(), false, dependency.isProjectDependency())
82+
: new Library(file, null);
8183
}
8284

8385
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayeredSpec.java

+106-15
Original file line numberDiff line numberDiff line change
@@ -227,31 +227,33 @@ public abstract static class IntoLayersSpec implements Serializable {
227227

228228
private final List<IntoLayerSpec> intoLayers;
229229

230+
private final Function<String, IntoLayerSpec> specFactory;
231+
230232
boolean isEmpty() {
231233
return this.intoLayers.isEmpty();
232234
}
233235

234-
IntoLayersSpec(IntoLayerSpec... spec) {
236+
IntoLayersSpec(Function<String, IntoLayerSpec> specFactory, IntoLayerSpec... spec) {
235237
this.intoLayers = new ArrayList<>(Arrays.asList(spec));
238+
this.specFactory = specFactory;
236239
}
237240

238241
public void intoLayer(String layer) {
239-
this.intoLayers.add(new IntoLayerSpec(layer));
242+
this.intoLayers.add(this.specFactory.apply(layer));
240243
}
241244

242245
public void intoLayer(String layer, Closure<?> closure) {
243246
intoLayer(layer, ConfigureUtil.configureUsing(closure));
244247
}
245248

246249
public void intoLayer(String layer, Action<IntoLayerSpec> action) {
247-
IntoLayerSpec spec = new IntoLayerSpec(layer);
250+
IntoLayerSpec spec = this.specFactory.apply(layer);
248251
action.execute(spec);
249252
this.intoLayers.add(spec);
250253
}
251254

252-
<T> List<ContentSelector<T>> asSelectors(Function<String, ContentFilter<T>> filterFactory) {
253-
return this.intoLayers.stream().map((content) -> content.asSelector(filterFactory))
254-
.collect(Collectors.toList());
255+
<T> List<ContentSelector<T>> asSelectors(Function<IntoLayerSpec, ContentSelector<T>> selectorFactory) {
256+
return this.intoLayers.stream().map(selectorFactory).collect(Collectors.toList());
255257
}
256258

257259
}
@@ -279,8 +281,8 @@ public IntoLayerSpec(String intoLayer) {
279281
/**
280282
* Adds patterns that control the content that is included in the layer. If no
281283
* includes are specified then all content is included. If includes are specified
282-
* then content must match an inclusion pattern and not match any exclusion
283-
* patterns to be included.
284+
* then content must match an inclusion and not match any exclusions to be
285+
* included.
284286
* @param patterns the patterns to be included
285287
*/
286288
public void include(String... patterns) {
@@ -291,7 +293,7 @@ public void include(String... patterns) {
291293
* Adds patterns that control the content that is excluded from the layer. If no
292294
* excludes a specified no content is excluded. If exclusions are specified then
293295
* any content that matches an exclusion will be excluded irrespective of whether
294-
* it matches an include pattern.
296+
* it matches an include.
295297
* @param patterns the patterns to be excluded
296298
*/
297299
public void exclude(String... patterns) {
@@ -303,6 +305,76 @@ <T> ContentSelector<T> asSelector(Function<String, ContentFilter<T>> filterFacto
303305
return new IncludeExcludeContentSelector<>(layer, this.includes, this.excludes, filterFactory);
304306
}
305307

308+
String getIntoLayer() {
309+
return this.intoLayer;
310+
}
311+
312+
List<String> getIncludes() {
313+
return this.includes;
314+
}
315+
316+
List<String> getExcludes() {
317+
return this.excludes;
318+
}
319+
320+
}
321+
322+
/**
323+
* Spec that controls the dependencies that should be part of a particular layer.
324+
*
325+
* @since 2.4.0
326+
*/
327+
public static class DependenciesIntoLayerSpec extends IntoLayerSpec {
328+
329+
private boolean includeProjectDependencies;
330+
331+
private boolean excludeProjectDependencies;
332+
333+
/**
334+
* Creates a new {@code IntoLayerSpec} that will control the content of the given
335+
* layer.
336+
* @param intoLayer the layer
337+
*/
338+
public DependenciesIntoLayerSpec(String intoLayer) {
339+
super(intoLayer);
340+
}
341+
342+
/**
343+
* Configures the layer to include project dependencies. If no includes are
344+
* specified then all content is included. If includes are specified then content
345+
* must match an inclusion and not match any exclusions to be included.
346+
*/
347+
public void includeProjectDependencies() {
348+
this.includeProjectDependencies = true;
349+
}
350+
351+
/**
352+
* Configures the layer to exclude project dependencies. If no excludes a
353+
* specified no content is excluded. If exclusions are specified then any content
354+
* that matches an exclusion will be excluded irrespective of whether it matches
355+
* an include.
356+
*/
357+
public void excludeProjectDependencies() {
358+
this.excludeProjectDependencies = true;
359+
}
360+
361+
ContentSelector<Library> asLibrarySelector(Function<String, ContentFilter<Library>> filterFactory) {
362+
Layer layer = new Layer(getIntoLayer());
363+
List<ContentFilter<Library>> includeFilters = getIncludes().stream().map(filterFactory)
364+
.collect(Collectors.toList());
365+
if (this.includeProjectDependencies) {
366+
includeFilters = new ArrayList<>(includeFilters);
367+
includeFilters.add(Library::isLocal);
368+
}
369+
List<ContentFilter<Library>> excludeFilters = getExcludes().stream().map(filterFactory)
370+
.collect(Collectors.toList());
371+
if (this.excludeProjectDependencies) {
372+
excludeFilters = new ArrayList<>(includeFilters);
373+
excludeFilters.add(Library::isLocal);
374+
}
375+
return new IncludeExcludeContentSelector<>(layer, includeFilters, excludeFilters);
376+
}
377+
306378
}
307379

308380
/**
@@ -317,30 +389,49 @@ public static class ApplicationSpec extends IntoLayersSpec {
317389
* included
318390
*/
319391
public ApplicationSpec(IntoLayerSpec... contents) {
320-
super(contents);
392+
super(new IntoLayerSpecFactory(), contents);
321393
}
322394

323395
List<ContentSelector<String>> asSelectors() {
324-
return asSelectors(ApplicationContentFilter::new);
396+
return asSelectors((spec) -> spec.asSelector(ApplicationContentFilter::new));
397+
}
398+
399+
private static final class IntoLayerSpecFactory implements Function<String, IntoLayerSpec>, Serializable {
400+
401+
@Override
402+
public IntoLayerSpec apply(String layer) {
403+
return new IntoLayerSpec(layer);
404+
}
405+
325406
}
326407

327408
}
328409

329410
/**
330411
* An {@link IntoLayersSpec} that controls the layers to which dependencies belong.
331412
*/
332-
public static class DependenciesSpec extends IntoLayersSpec {
413+
public static class DependenciesSpec extends IntoLayersSpec implements Serializable {
333414

334415
/**
335416
* Creates a new {@code DependenciesSpec} with the given {@code contents}.
336417
* @param contents specs for the layers in which dependencies should be included
337418
*/
338-
public DependenciesSpec(IntoLayerSpec... contents) {
339-
super(contents);
419+
public DependenciesSpec(DependenciesIntoLayerSpec... contents) {
420+
super(new IntoLayerSpecFactory(), contents);
340421
}
341422

342423
List<ContentSelector<Library>> asSelectors() {
343-
return asSelectors(LibraryContentFilter::new);
424+
return asSelectors(
425+
(spec) -> ((DependenciesIntoLayerSpec) spec).asLibrarySelector(LibraryContentFilter::new));
426+
}
427+
428+
private static final class IntoLayerSpecFactory implements Function<String, IntoLayerSpec>, Serializable {
429+
430+
@Override
431+
public IntoLayerSpec apply(String layer) {
432+
return new DependenciesIntoLayerSpec(layer);
433+
}
434+
344435
}
345436

346437
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/ResolvedDependencies.java

+46-11
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
import java.io.File;
2020
import java.util.LinkedHashMap;
2121
import java.util.Map;
22+
import java.util.Set;
23+
import java.util.stream.Collectors;
2224

2325
import org.gradle.api.artifacts.Configuration;
2426
import org.gradle.api.artifacts.ModuleVersionIdentifier;
27+
import org.gradle.api.artifacts.ProjectDependency;
2528
import org.gradle.api.artifacts.ResolvedArtifact;
2629
import org.gradle.api.artifacts.ResolvedConfiguration;
2730

@@ -42,15 +45,19 @@ class ResolvedDependencies {
4245
private final Map<Configuration, ResolvedConfigurationDependencies> configurationDependencies = new LinkedHashMap<>();
4346

4447
void processConfiguration(Configuration configuration) {
48+
Set<String> projectDependencyIds = configuration.getAllDependencies().withType(ProjectDependency.class).stream()
49+
.map((projectDependency) -> projectDependency.getGroup() + ":" + projectDependency.getName() + ":"
50+
+ projectDependency.getVersion())
51+
.collect(Collectors.toSet());
4552
this.configurationDependencies.put(configuration,
46-
new ResolvedConfigurationDependencies(configuration.getResolvedConfiguration()));
53+
new ResolvedConfigurationDependencies(projectDependencyIds, configuration.getResolvedConfiguration()));
4754
}
4855

49-
LibraryCoordinates find(File file) {
56+
DependencyDescriptor find(File file) {
5057
for (ResolvedConfigurationDependencies dependencies : this.configurationDependencies.values()) {
51-
LibraryCoordinates coordinates = dependencies.find(file);
52-
if (coordinates != null) {
53-
return coordinates;
58+
DependencyDescriptor dependency = dependencies.find(file);
59+
if (dependency != null) {
60+
return dependency;
5461
}
5562
}
5663
return null;
@@ -61,19 +68,23 @@ LibraryCoordinates find(File file) {
6168
*/
6269
private static class ResolvedConfigurationDependencies {
6370

64-
private final Map<File, LibraryCoordinates> artifactCoordinates = new LinkedHashMap<>();
71+
private final Map<File, DependencyDescriptor> dependencies = new LinkedHashMap<>();
6572

66-
ResolvedConfigurationDependencies(ResolvedConfiguration resolvedConfiguration) {
73+
ResolvedConfigurationDependencies(Set<String> projectDependencyIds,
74+
ResolvedConfiguration resolvedConfiguration) {
6775
if (!resolvedConfiguration.hasError()) {
6876
for (ResolvedArtifact resolvedArtifact : resolvedConfiguration.getResolvedArtifacts()) {
69-
this.artifactCoordinates.put(resolvedArtifact.getFile(),
70-
new ModuleVersionIdentifierLibraryCoordinates(resolvedArtifact.getModuleVersion().getId()));
77+
ModuleVersionIdentifier id = resolvedArtifact.getModuleVersion().getId();
78+
boolean projectDependency = projectDependencyIds
79+
.contains(id.getGroup() + ":" + id.getName() + ":" + id.getVersion());
80+
this.dependencies.put(resolvedArtifact.getFile(), new DependencyDescriptor(
81+
new ModuleVersionIdentifierLibraryCoordinates(id), projectDependency));
7182
}
7283
}
7384
}
7485

75-
LibraryCoordinates find(File file) {
76-
return this.artifactCoordinates.get(file);
86+
DependencyDescriptor find(File file) {
87+
return this.dependencies.get(file);
7788
}
7889

7990
}
@@ -111,4 +122,28 @@ public String toString() {
111122

112123
}
113124

125+
/**
126+
* Describes a dependency in a {@link ResolvedConfiguration}.
127+
*/
128+
static final class DependencyDescriptor {
129+
130+
private final LibraryCoordinates coordinates;
131+
132+
private final boolean projectDependency;
133+
134+
private DependencyDescriptor(LibraryCoordinates coordinates, boolean projectDependency) {
135+
this.coordinates = coordinates;
136+
this.projectDependency = projectDependency;
137+
}
138+
139+
LibraryCoordinates getCoordinates() {
140+
return this.coordinates;
141+
}
142+
143+
boolean isProjectDependency() {
144+
return this.projectDependency;
145+
}
146+
147+
}
148+
114149
}

0 commit comments

Comments
 (0)