Compare commits

..

1 Commits

Author SHA1 Message Date
Andy Wilkinson 104fe6e638 Release v4.0.0-M3 2025-09-18 15:42:10 +01:00
2669 changed files with 28078 additions and 26697 deletions

View File

@ -23,7 +23,7 @@ inputs:
java-version: java-version:
description: 'Java version to use for the build' description: 'Java version to use for the build'
required: false required: false
default: '25' default: '24'
runs: runs:
using: composite using: composite
steps: steps:
@ -42,12 +42,12 @@ runs:
${{ inputs.java-toolchain == 'true' && '24' || '' }} ${{ inputs.java-toolchain == 'true' && '24' || '' }}
- name: Set Up Gradle With Read/Write Cache - name: Set Up Gradle With Read/Write Cache
if: ${{ inputs.cache-read-only == 'false' }} if: ${{ inputs.cache-read-only == 'false' }}
uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
with: with:
cache-read-only: false cache-read-only: false
develocity-access-key: ${{ inputs.develocity-access-key }} develocity-access-key: ${{ inputs.develocity-access-key }}
- name: Set Up Gradle - name: Set Up Gradle
uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
with: with:
develocity-access-key: ${{ inputs.develocity-access-key }} develocity-access-key: ${{ inputs.develocity-access-key }}
develocity-token-expiry: 4 develocity-token-expiry: 4

View File

@ -21,7 +21,7 @@ runs:
using: composite using: composite
steps: steps:
- name: Set Up JFrog CLI - name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@c32bf10843e4071112c4ea3abf622d3b27cd8c17 # v4.7.0 uses: jfrog/setup-jfrog-cli@88e9eba31c07e31beefa4cef5c0e93d1af9535d7 # v4.6.1
env: env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }} JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Artifacts - name: Download Artifacts

View File

@ -17,7 +17,7 @@ runs:
using: composite using: composite
steps: steps:
- name: Set Up JFrog CLI - name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@c32bf10843e4071112c4ea3abf622d3b27cd8c17 # v4.7.0 uses: jfrog/setup-jfrog-cli@88e9eba31c07e31beefa4cef5c0e93d1af9535d7 # v4.6.1
env: env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }} JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Release Artifacts - name: Download Release Artifacts

View File

@ -23,13 +23,16 @@ jobs:
toolchain: true toolchain: true
- version: 21 - version: 21
toolchain: true toolchain: true
- version: 25 - version: 24
toolchain: false toolchain: false
- version: 25
early-access: true
toolchain: true
exclude: exclude:
- os: - os:
name: Linux name: Linux
java: java:
version: 25 version: 24
- os: - os:
name: ${{ github.repository == 'spring-projects/spring-boot-commercial' && 'Windows' }} name: ${{ github.repository == 'spring-projects/spring-boot-commercial' && 'Windows' }}
steps: steps:

View File

@ -75,7 +75,7 @@ jobs:
runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }}
steps: steps:
- name: Set up JFrog CLI - name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@c32bf10843e4071112c4ea3abf622d3b27cd8c17 # v4.7.0 uses: jfrog/setup-jfrog-cli@88e9eba31c07e31beefa4cef5c0e93d1af9535d7 # v4.6.1
env: env:
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote build - name: Promote build

View File

@ -86,7 +86,7 @@ jobs:
runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }}
steps: steps:
- name: Set up JFrog CLI - name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@c32bf10843e4071112c4ea3abf622d3b27cd8c17 # v4.7.0 uses: jfrog/setup-jfrog-cli@88e9eba31c07e31beefa4cef5c0e93d1af9535d7 # v4.6.1
env: env:
JF_ENV_SPRING: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_JF_ARTIFACTORY_SPRING || secrets.JF_ARTIFACTORY_SPRING }} JF_ENV_SPRING: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_JF_ARTIFACTORY_SPRING || secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote open source build - name: Promote open source build

View File

@ -59,7 +59,7 @@ jobs:
with: with:
stable: true stable: true
- name: Set Up Gradle - name: Set Up Gradle
uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
with: with:
cache-read-only: false cache-read-only: false
- name: Configure Gradle Properties - name: Configure Gradle Properties

View File

@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config # Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below # Add key=value pairs of SDKs to use below
java=25-librca java=24.0.2-librca

View File

@ -14,7 +14,7 @@
"@springio/antora-xref-extension": "1.0.0-alpha.4", "@springio/antora-xref-extension": "1.0.0-alpha.4",
"@springio/antora-zip-contents-collector-extension": "1.0.0-alpha.8", "@springio/antora-zip-contents-collector-extension": "1.0.0-alpha.8",
"@springio/asciidoctor-extensions": "1.0.0-alpha.17", "@springio/asciidoctor-extensions": "1.0.0-alpha.17",
"patch-package": "^8.0.1" "patch-package": "^8.0.0"
} }
}, },
"node_modules/@antora/asciidoc-loader": { "node_modules/@antora/asciidoc-loader": {
@ -575,6 +575,14 @@
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz",
"integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==" "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ=="
}, },
"node_modules/at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/atomic-sleep": { "node_modules/atomic-sleep": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
@ -1398,17 +1406,17 @@
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
}, },
"node_modules/fs-extra": { "node_modules/fs-extra": {
"version": "10.1.0", "version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1", "jsonfile": "^6.0.1",
"universalify": "^2.0.0" "universalify": "^2.0.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=10"
} }
}, },
"node_modules/fs-mkdirp-stream": { "node_modules/fs-mkdirp-stream": {
@ -1985,10 +1993,9 @@
} }
}, },
"node_modules/jsonfile": { "node_modules/jsonfile": {
"version": "6.2.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"universalify": "^2.0.0" "universalify": "^2.0.0"
}, },
@ -2324,30 +2331,38 @@
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
}, },
"node_modules/os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/pako": { "node_modules/pako": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
}, },
"node_modules/patch-package": { "node_modules/patch-package": {
"version": "8.0.1", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
"integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
"license": "MIT",
"dependencies": { "dependencies": {
"@yarnpkg/lockfile": "^1.1.0", "@yarnpkg/lockfile": "^1.1.0",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"ci-info": "^3.7.0", "ci-info": "^3.7.0",
"cross-spawn": "^7.0.3", "cross-spawn": "^7.0.3",
"find-yarn-workspace-root": "^2.0.0", "find-yarn-workspace-root": "^2.0.0",
"fs-extra": "^10.0.0", "fs-extra": "^9.0.0",
"json-stable-stringify": "^1.0.2", "json-stable-stringify": "^1.0.2",
"klaw-sync": "^6.0.0", "klaw-sync": "^6.0.0",
"minimist": "^1.2.6", "minimist": "^1.2.6",
"open": "^7.4.2", "open": "^7.4.2",
"rimraf": "^2.6.3",
"semver": "^7.5.3", "semver": "^7.5.3",
"slash": "^2.0.0", "slash": "^2.0.0",
"tmp": "^0.2.4", "tmp": "^0.0.33",
"yaml": "^2.2.2" "yaml": "^2.2.2"
}, },
"bin": { "bin": {
@ -2735,6 +2750,18 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -3091,12 +3118,14 @@
} }
}, },
"node_modules/tmp": { "node_modules/tmp": {
"version": "0.2.5", "version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"license": "MIT", "dependencies": {
"os-tmpdir": "~1.0.2"
},
"engines": { "engines": {
"node": ">=14.14" "node": ">=0.6.0"
} }
}, },
"node_modules/to-absolute-glob": { "node_modules/to-absolute-glob": {
@ -3209,7 +3238,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }

View File

@ -12,7 +12,7 @@
"@springio/antora-zip-contents-collector-extension": "1.0.0-alpha.8", "@springio/antora-zip-contents-collector-extension": "1.0.0-alpha.8",
"@asciidoctor/tabs": "1.0.0-beta.6", "@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/asciidoctor-extensions": "1.0.0-alpha.17", "@springio/asciidoctor-extensions": "1.0.0-alpha.17",
"patch-package": "^8.0.1" "patch-package": "^8.0.0"
}, },
"config": { "config": {
"ui-bundle-url": "https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip" "ui-bundle-url": "https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip"

View File

@ -80,6 +80,7 @@ tasks.register("integrationTest") {
ant.propertyref(name: "ivy.class.path") ant.propertyref(name: "ivy.class.path")
} }
plainlistener() plainlistener()
file(layout.buildDirectory.dir("test-results/integrationTest")).mkdirs()
xmllistener(toDir: resultsDir) xmllistener(toDir: resultsDir)
fileset(dir: layout.buildDirectory.dir("it").get().asFile.toString(), includes: "**/build.xml") fileset(dir: layout.buildDirectory.dir("it").get().asFile.toString(), includes: "**/build.xml")
} }

View File

@ -141,13 +141,9 @@ final class ApplicationPluginAction implements PluginApplicationAction {
} }
} }
@SuppressWarnings("deprecation")
private void configureFileMode(CopySpec copySpec, int mode) { private void configureFileMode(CopySpec copySpec, int mode) {
try { copySpec.setFileMode(mode);
copySpec.getClass().getMethod("setFileMode", Integer.class).invoke(copySpec, Integer.valueOf(mode));
}
catch (Exception ex) {
throw new RuntimeException("Failed to set file mode on CopySpec", ex);
}
} }
} }

View File

@ -17,6 +17,7 @@
package org.springframework.boot.gradle.plugin; package org.springframework.boot.gradle.plugin;
import java.io.File; import java.io.File;
import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.jar.JarFile; import java.util.jar.JarFile;
@ -32,7 +33,7 @@ import org.gradle.api.specs.Spec;
*/ */
class JarTypeFileSpec implements Spec<File> { class JarTypeFileSpec implements Spec<File> {
private static final Set<String> EXCLUDED_JAR_TYPES = Set.of("dependencies-starter", "development-tool"); private static final Set<String> EXCLUDED_JAR_TYPES = Collections.singleton("dependencies-starter");
@Override @Override
public boolean isSatisfiedBy(File file) { public boolean isSatisfiedBy(File file) {

View File

@ -285,6 +285,7 @@ final class JavaPluginAction implements PluginApplicationAction {
private void configureProductionRuntimeClasspathConfiguration(Project project) { private void configureProductionRuntimeClasspathConfiguration(Project project) {
Configuration productionRuntimeClasspath = project.getConfigurations() Configuration productionRuntimeClasspath = project.getConfigurations()
.create(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME); .create(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME);
productionRuntimeClasspath.setVisible(false);
Configuration runtimeClasspath = project.getConfigurations() Configuration runtimeClasspath = project.getConfigurations()
.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); .getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
productionRuntimeClasspath.attributes((attributes) -> { productionRuntimeClasspath.attributes((attributes) -> {

View File

@ -104,6 +104,7 @@ class WarPluginAction implements PluginApplicationAction {
.set(project.provider(() -> javaPluginExtension(project).getTargetCompatibility())); .set(project.provider(() -> javaPluginExtension(project).getTargetCompatibility()));
bootWar.resolvedArtifacts(runtimeClasspath.getIncoming().getArtifacts().getResolvedArtifacts()); bootWar.resolvedArtifacts(runtimeClasspath.getIncoming().getArtifacts().getResolvedArtifacts());
}); });
bootWarProvider.map(War::getClasspath);
return bootWarProvider; return bootWarProvider;
} }

View File

@ -43,7 +43,7 @@ import org.springframework.util.Assert;
* @since 3.0.0 * @since 3.0.0
*/ */
@CacheableTask @CacheableTask
public abstract class ProcessTestAot extends AbstractAot { public class ProcessTestAot extends AbstractAot {
private @Nullable FileCollection classpathRoots; private @Nullable FileCollection classpathRoots;

View File

@ -34,6 +34,8 @@ import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.Optional;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.loader.tools.LoaderImplementation;
/** /**
* A Spring Boot "fat" archive task. * A Spring Boot "fat" archive task.
* *
@ -135,6 +137,15 @@ public interface BootArchive extends Task {
*/ */
void resolvedArtifacts(Provider<Set<ResolvedArtifactResult>> resolvedArtifacts); void resolvedArtifacts(Provider<Set<ResolvedArtifactResult>> resolvedArtifacts);
/**
* The loader implementation that should be used with the archive.
* @return the loader implementation
* @since 3.2.0
*/
@Input
@Optional
Property<LoaderImplementation> getLoaderImplementation();
/** /**
* Returns whether the JAR tools should be included as a dependency in the layered * Returns whether the JAR tools should be included as a dependency in the layered
* archive. * archive.

View File

@ -22,24 +22,32 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function; import java.util.function.Function;
import org.gradle.api.file.ConfigurableFilePermissions; import org.gradle.api.file.ConfigurableFilePermissions;
import org.gradle.api.file.CopySpec; import org.gradle.api.file.CopySpec;
import org.gradle.api.file.FileCopyDetails; import org.gradle.api.file.FileCopyDetails;
import org.gradle.api.file.FileTreeElement; import org.gradle.api.file.FileTreeElement;
import org.gradle.api.file.RelativePath;
import org.gradle.api.internal.file.copy.CopyAction; import org.gradle.api.internal.file.copy.CopyAction;
import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
import org.gradle.api.internal.file.copy.FileCopyDetailsInternal;
import org.gradle.api.java.archives.Attributes; import org.gradle.api.java.archives.Attributes;
import org.gradle.api.java.archives.Manifest; import org.gradle.api.java.archives.Manifest;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.specs.Spec; import org.gradle.api.specs.Spec;
import org.gradle.api.specs.Specs; import org.gradle.api.specs.Specs;
import org.gradle.api.tasks.WorkResult;
import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.util.PatternSet; import org.gradle.api.tasks.util.PatternSet;
import org.gradle.util.GradleVersion; import org.gradle.util.GradleVersion;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.loader.tools.LoaderImplementation;
/** /**
* Support class for implementations of {@link BootArchive}. * Support class for implementations of {@link BootArchive}.
* *
@ -115,11 +123,13 @@ class BootArchiveSupport {
return (version != null) ? version : "unknown"; return (version != null) ? version : "unknown";
} }
CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, boolean supportsSignatureFile) { CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies,
return createCopyAction(jar, resolvedDependencies, supportsSignatureFile, null, null); LoaderImplementation loaderImplementation, boolean supportsSignatureFile) {
return createCopyAction(jar, resolvedDependencies, loaderImplementation, supportsSignatureFile, null, null);
} }
CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, boolean supportsSignatureFile, CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies,
LoaderImplementation loaderImplementation, boolean supportsSignatureFile,
@Nullable LayerResolver layerResolver, @Nullable String jarmodeToolsLocation) { @Nullable LayerResolver layerResolver, @Nullable String jarmodeToolsLocation) {
File output = jar.getArchiveFile().get().getAsFile(); File output = jar.getArchiveFile().get().getAsFile();
Manifest manifest = jar.getManifest(); Manifest manifest = jar.getManifest();
@ -135,8 +145,9 @@ class BootArchiveSupport {
String encoding = jar.getMetadataCharset(); String encoding = jar.getMetadataCharset();
CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirPermissions, CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirPermissions,
filePermissions, includeDefaultLoader, jarmodeToolsLocation, requiresUnpack, exclusions, launchScript, filePermissions, includeDefaultLoader, jarmodeToolsLocation, requiresUnpack, exclusions, launchScript,
librarySpec, compressionResolver, encoding, resolvedDependencies, supportsSignatureFile, layerResolver); librarySpec, compressionResolver, encoding, resolvedDependencies, supportsSignatureFile, layerResolver,
return action; loaderImplementation);
return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action;
} }
private @Nullable Integer getUnixNumericDirPermissions(CopySpec copySpec) { private @Nullable Integer getUnixNumericDirPermissions(CopySpec copySpec) {
@ -153,22 +164,14 @@ class BootArchiveSupport {
return permissions.isPresent() ? permissions.get().toUnixNumeric() : null; return permissions.isPresent() ? permissions.get().toUnixNumeric() : null;
} }
@SuppressWarnings("deprecation")
private @Nullable Integer getDirMode(CopySpec copySpec) { private @Nullable Integer getDirMode(CopySpec copySpec) {
try { return copySpec.getDirMode();
return (Integer) copySpec.getClass().getMethod("getDirMode").invoke(copySpec);
}
catch (Exception ex) {
throw new RuntimeException("Failed to get dir mode from CopySpec", ex);
}
} }
@SuppressWarnings("deprecation")
private @Nullable Integer getFileMode(CopySpec copySpec) { private @Nullable Integer getFileMode(CopySpec copySpec) {
try { return copySpec.getFileMode();
return (Integer) copySpec.getClass().getMethod("getFileMode").invoke(copySpec);
}
catch (Exception ex) {
throw new RuntimeException("Failed to get file mode from CopySpec", ex);
}
} }
private boolean isUsingDefaultLoader(Jar jar) { private boolean isUsingDefaultLoader(Jar jar) {
@ -231,4 +234,26 @@ class BootArchiveSupport {
details.setRelativePath(details.getRelativeSourcePath()); details.setRelativePath(details.getRelativeSourcePath());
} }
/**
* {@link CopyAction} variant that sorts entries to ensure reproducible ordering.
*/
private static final class ReproducibleOrderingCopyAction implements CopyAction {
private final CopyAction delegate;
private ReproducibleOrderingCopyAction(CopyAction delegate) {
this.delegate = delegate;
}
@Override
public WorkResult execute(CopyActionProcessingStream stream) {
return this.delegate.execute((action) -> {
Map<RelativePath, FileCopyDetailsInternal> detailsByPath = new TreeMap<>();
stream.process((details) -> detailsByPath.put(details.getRelativePath(), details));
detailsByPath.values().forEach(action::processFile);
});
}
}
} }

View File

@ -38,6 +38,8 @@ import org.gradle.api.tasks.bundling.Jar;
import org.gradle.work.DisableCachingByDefault; import org.gradle.work.DisableCachingByDefault;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.loader.tools.LoaderImplementation;
/** /**
* A custom {@link Jar} task that produces a Spring Boot executable jar. * A custom {@link Jar} task that produces a Spring Boot executable jar.
* *
@ -143,12 +145,13 @@ public abstract class BootJar extends Jar implements BootArchive {
@Override @Override
protected CopyAction createCopyAction() { protected CopyAction createCopyAction() {
LoaderImplementation loaderImplementation = getLoaderImplementation().getOrElse(LoaderImplementation.DEFAULT);
LayerResolver layerResolver = null; LayerResolver layerResolver = null;
if (!isLayeredDisabled()) { if (!isLayeredDisabled()) {
layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary);
} }
String jarmodeToolsLocation = isIncludeJarmodeTools() ? LIB_DIRECTORY : null; String jarmodeToolsLocation = isIncludeJarmodeTools() ? LIB_DIRECTORY : null;
return this.support.createCopyAction(this, this.resolvedDependencies, true, layerResolver, return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, true, layerResolver,
jarmodeToolsLocation); jarmodeToolsLocation);
} }

View File

@ -38,6 +38,8 @@ import org.gradle.api.tasks.bundling.War;
import org.gradle.work.DisableCachingByDefault; import org.gradle.work.DisableCachingByDefault;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.loader.tools.LoaderImplementation;
/** /**
* A custom {@link War} task that produces a Spring Boot executable war. * A custom {@link War} task that produces a Spring Boot executable war.
* *
@ -117,13 +119,14 @@ public abstract class BootWar extends War implements BootArchive {
@Override @Override
protected CopyAction createCopyAction() { protected CopyAction createCopyAction() {
LoaderImplementation loaderImplementation = getLoaderImplementation().getOrElse(LoaderImplementation.DEFAULT);
LayerResolver layerResolver = null; LayerResolver layerResolver = null;
if (!isLayeredDisabled()) { if (!isLayeredDisabled()) {
layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary);
} }
String jarmodeToolsLocation = isIncludeJarmodeTools() ? LIB_DIRECTORY : null; String jarmodeToolsLocation = isIncludeJarmodeTools() ? LIB_DIRECTORY : null;
return this.support.createCopyAction(this, this.resolvedDependencies, false, layerResolver, return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, false,
jarmodeToolsLocation); layerResolver, jarmodeToolsLocation);
} }
private boolean isIncludeJarmodeTools() { private boolean isIncludeJarmodeTools() {

View File

@ -23,10 +23,13 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HexFormat;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
@ -60,6 +63,7 @@ import org.springframework.boot.loader.tools.JarModeLibrary;
import org.springframework.boot.loader.tools.Layer; import org.springframework.boot.loader.tools.Layer;
import org.springframework.boot.loader.tools.LayersIndex; import org.springframework.boot.loader.tools.LayersIndex;
import org.springframework.boot.loader.tools.LibraryCoordinates; import org.springframework.boot.loader.tools.LibraryCoordinates;
import org.springframework.boot.loader.tools.LoaderImplementation;
import org.springframework.boot.loader.tools.NativeImageArgFile; import org.springframework.boot.loader.tools.NativeImageArgFile;
import org.springframework.boot.loader.tools.ReachabilityMetadataProperties; import org.springframework.boot.loader.tools.ReachabilityMetadataProperties;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -116,13 +120,15 @@ class BootZipCopyAction implements CopyAction {
private final @Nullable LayerResolver layerResolver; private final @Nullable LayerResolver layerResolver;
private final LoaderImplementation loaderImplementation;
BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, @Nullable Integer dirMode, BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, @Nullable Integer dirMode,
@Nullable Integer fileMode, boolean includeDefaultLoader, @Nullable String jarmodeToolsLocation, @Nullable Integer fileMode, boolean includeDefaultLoader, @Nullable String jarmodeToolsLocation,
Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions, Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions,
@Nullable LaunchScriptConfiguration launchScript, Spec<FileCopyDetails> librarySpec, @Nullable LaunchScriptConfiguration launchScript, Spec<FileCopyDetails> librarySpec,
Function<FileCopyDetails, ZipCompression> compressionResolver, @Nullable String encoding, Function<FileCopyDetails, ZipCompression> compressionResolver, @Nullable String encoding,
ResolvedDependencies resolvedDependencies, boolean supportsSignatureFile, ResolvedDependencies resolvedDependencies, boolean supportsSignatureFile,
@Nullable LayerResolver layerResolver) { @Nullable LayerResolver layerResolver, LoaderImplementation loaderImplementation) {
this.output = output; this.output = output;
this.manifest = manifest; this.manifest = manifest;
this.preserveFileTimestamps = preserveFileTimestamps; this.preserveFileTimestamps = preserveFileTimestamps;
@ -139,6 +145,7 @@ class BootZipCopyAction implements CopyAction {
this.resolvedDependencies = resolvedDependencies; this.resolvedDependencies = resolvedDependencies;
this.supportsSignatureFile = supportsSignatureFile; this.supportsSignatureFile = supportsSignatureFile;
this.layerResolver = layerResolver; this.layerResolver = layerResolver;
this.loaderImplementation = loaderImplementation;
} }
@Override @Override
@ -322,7 +329,8 @@ class BootZipCopyAction implements CopyAction {
// Always write loader entries after META-INF directory (see gh-16698) // Always write loader entries after META-INF directory (see gh-16698)
return; return;
} }
LoaderZipEntries loaderEntries = new LoaderZipEntries(getTime(), getDirMode(), getFileMode()); LoaderZipEntries loaderEntries = new LoaderZipEntries(getTime(), getDirMode(), getFileMode(),
BootZipCopyAction.this.loaderImplementation);
this.writtenLoaderEntries = loaderEntries.writeTo(this.out); this.writtenLoaderEntries = loaderEntries.writeTo(this.out);
if (BootZipCopyAction.this.layerResolver != null) { if (BootZipCopyAction.this.layerResolver != null) {
for (String name : this.writtenLoaderEntries.getFiles()) { for (String name : this.writtenLoaderEntries.getFiles()) {
@ -504,13 +512,9 @@ class BootZipCopyAction implements CopyAction {
? details.getPermissions().toUnixNumeric() : getMode(details); ? details.getPermissions().toUnixNumeric() : getMode(details);
} }
@SuppressWarnings("deprecation")
private int getMode(FileCopyDetails details) { private int getMode(FileCopyDetails details) {
try { return details.getMode();
return (int) details.getClass().getMethod("getMode").invoke(details);
}
catch (Exception ex) {
throw new RuntimeException("Failed to get mode from FileCopyDetails", ex);
}
} }
} }
@ -586,24 +590,36 @@ class BootZipCopyAction implements CopyAction {
private static final int BUFFER_SIZE = 32 * 1024; private static final int BUFFER_SIZE = 32 * 1024;
private final boolean unpack; private final @Nullable MessageDigest messageDigest;
private final CRC32 crc = new CRC32(); private final CRC32 crc = new CRC32();
private long size; private long size;
StoredEntryPreparator(InputStream inputStream, boolean unpack) throws IOException { StoredEntryPreparator(InputStream inputStream, boolean unpack) throws IOException {
this.unpack = unpack; this.messageDigest = (unpack) ? sha1Digest() : null;
try (inputStream) { try (inputStream) {
load(inputStream); load(inputStream);
} }
} }
private static MessageDigest sha1Digest() {
try {
return MessageDigest.getInstance("SHA-1");
}
catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException(ex);
}
}
private void load(InputStream inputStream) throws IOException { private void load(InputStream inputStream) throws IOException {
byte[] buffer = new byte[BUFFER_SIZE]; byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead; int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) { while ((bytesRead = inputStream.read(buffer)) != -1) {
this.crc.update(buffer, 0, bytesRead); this.crc.update(buffer, 0, bytesRead);
if (this.messageDigest != null) {
this.messageDigest.update(buffer, 0, bytesRead);
}
this.size += bytesRead; this.size += bytesRead;
} }
} }
@ -613,8 +629,8 @@ class BootZipCopyAction implements CopyAction {
entry.setCompressedSize(this.size); entry.setCompressedSize(this.size);
entry.setCrc(this.crc.getValue()); entry.setCrc(this.crc.getValue());
entry.setMethod(ZipEntry.STORED); entry.setMethod(ZipEntry.STORED);
if (this.unpack) { if (this.messageDigest != null) {
entry.setComment("UNPACK"); entry.setComment("UNPACK:" + HexFormat.of().formatHex(this.messageDigest.digest()));
} }
} }

View File

@ -29,6 +29,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.gradle.api.file.FileTreeElement; import org.gradle.api.file.FileTreeElement;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.loader.tools.LoaderImplementation;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
@ -41,22 +42,27 @@ import org.springframework.util.StreamUtils;
*/ */
class LoaderZipEntries { class LoaderZipEntries {
private final LoaderImplementation loaderImplementation;
private final @Nullable Long entryTime; private final @Nullable Long entryTime;
private final int dirMode; private final int dirMode;
private final int fileMode; private final int fileMode;
LoaderZipEntries(@Nullable Long entryTime, int dirMode, int fileMode) { LoaderZipEntries(@Nullable Long entryTime, int dirMode, int fileMode,
@Nullable LoaderImplementation loaderImplementation) {
this.entryTime = entryTime; this.entryTime = entryTime;
this.dirMode = dirMode; this.dirMode = dirMode;
this.fileMode = fileMode; this.fileMode = fileMode;
this.loaderImplementation = (loaderImplementation != null) ? loaderImplementation
: LoaderImplementation.DEFAULT;
} }
WrittenEntries writeTo(ZipArchiveOutputStream out) throws IOException { WrittenEntries writeTo(ZipArchiveOutputStream out) throws IOException {
WrittenEntries written = new WrittenEntries(); WrittenEntries written = new WrittenEntries();
try (ZipInputStream loaderJar = new ZipInputStream( try (ZipInputStream loaderJar = new ZipInputStream(
getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) { getResourceAsStream("/" + this.loaderImplementation.getJarResourceName()))) {
java.util.zip.ZipEntry entry = loaderJar.getNextEntry(); java.util.zip.ZipEntry entry = loaderJar.getNextEntry();
while (entry != null) { while (entry != null) {
if (entry.isDirectory() && !entry.getName().equals("META-INF/")) { if (entry.isDirectory() && !entry.getName().equals("META-INF/")) {

View File

@ -124,7 +124,7 @@ class PackagingDocumentationTests {
try (JarFile jar = new JarFile(file)) { try (JarFile jar = new JarFile(file)) {
JarEntry entry = jar.getJarEntry("BOOT-INF/lib/jruby-complete-1.7.25.jar"); JarEntry entry = jar.getJarEntry("BOOT-INF/lib/jruby-complete-1.7.25.jar");
assertThat(entry).isNotNull(); assertThat(entry).isNotNull();
assertThat(entry.getComment()).isEqualTo("UNPACK"); assertThat(entry.getComment()).startsWith("UNPACK:");
} }
} }

View File

@ -24,6 +24,8 @@ import java.time.format.DateTimeFormatter;
import java.util.Properties; import java.util.Properties;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.initialization.GradlePropertiesController;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
@ -171,7 +173,11 @@ class BuildInfoTests {
private Project createProject(String projectName) { private Project createProject(String projectName) {
File projectDir = new File(this.temp, projectName); File projectDir = new File(this.temp, projectName);
return GradleProjectBuilder.builder().withProjectDir(projectDir).withName(projectName).build(); Project project = GradleProjectBuilder.builder().withProjectDir(projectDir).withName(projectName).build();
((ProjectInternal) project).getServices()
.get(GradlePropertiesController.class)
.loadGradlePropertiesFrom(projectDir, false);
return project;
} }
private BuildInfo createTask(Project project) { private BuildInfo createTask(Project project) {

View File

@ -108,6 +108,16 @@ abstract class AbstractBootArchiveIntegrationTests {
assertThat(firstHash).isEqualTo(secondHash); assertThat(firstHash).isEqualTo(secondHash);
} }
@TestTemplate
void classicLoader() throws IOException {
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
File jar = new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0];
try (JarFile jarFile = new JarFile(jar)) {
assertThat(jarFile.getEntry("org/springframework/boot/loader/LaunchedURLClassLoader.class")).isNotNull();
}
}
@TestTemplate @TestTemplate
void upToDateWhenBuiltTwice() { void upToDateWhenBuiltTwice() {
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
@ -233,8 +243,14 @@ abstract class AbstractBootArchiveIntegrationTests {
.filter((entry) -> !entry.isDirectory()) .filter((entry) -> !entry.isDirectory())
.map(JarEntry::getName) .map(JarEntry::getName)
.filter((name) -> name.startsWith(this.libPath)); .filter((name) -> name.startsWith(this.libPath));
assertThat(libEntryNames).containsExactly(this.libPath + "two-1.0.jar", if (this.gradleBuild.gradleVersionIsLessThan("9.0.0-rc-1")) {
this.libPath + "commons-io-2.19.0.jar"); assertThat(libEntryNames).containsExactly(this.libPath + "two-1.0.jar",
this.libPath + "commons-io-2.19.0.jar");
}
else {
assertThat(libEntryNames).containsExactly(this.libPath + "commons-io-2.19.0.jar",
this.libPath + "two-1.0.jar");
}
} }
} }
@ -277,7 +293,6 @@ abstract class AbstractBootArchiveIntegrationTests {
void jarTypeFilteringIsApplied() throws IOException { void jarTypeFilteringIsApplied() throws IOException {
File flatDirRepository = new File(this.gradleBuild.getProjectDir(), "repository"); File flatDirRepository = new File(this.gradleBuild.getProjectDir(), "repository");
createDependenciesStarterJar(new File(flatDirRepository, "starter.jar")); createDependenciesStarterJar(new File(flatDirRepository, "starter.jar"));
createDependenciesDeveloperToolsJar(new File(flatDirRepository, "devonly.jar"));
createStandardJar(new File(flatDirRepository, "standard.jar")); createStandardJar(new File(flatDirRepository, "standard.jar"));
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
.isEqualTo(TaskOutcome.SUCCESS); .isEqualTo(TaskOutcome.SUCCESS);
@ -654,10 +669,6 @@ abstract class AbstractBootArchiveIntegrationTests {
createJar(location, (attributes) -> attributes.putValue("Spring-Boot-Jar-Type", "dependencies-starter")); createJar(location, (attributes) -> attributes.putValue("Spring-Boot-Jar-Type", "dependencies-starter"));
} }
private void createDependenciesDeveloperToolsJar(File location) throws IOException {
createJar(location, (attributes) -> attributes.putValue("Spring-Boot-Jar-Type", "development-tool"));
}
private void createJar(File location, Consumer<Attributes> attributesConfigurer) throws IOException { private void createJar(File location, Consumer<Attributes> attributesConfigurer) throws IOException {
location.getParentFile().mkdirs(); location.getParentFile().mkdirs();
Manifest manifest = new Manifest(); Manifest manifest = new Manifest();

View File

@ -27,8 +27,6 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermission;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
@ -37,7 +35,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.UUID;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
@ -68,6 +65,7 @@ import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.gradle.junit.GradleProjectBuilder; import org.springframework.boot.gradle.junit.GradleProjectBuilder;
import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.DefaultLaunchScript;
import org.springframework.boot.loader.tools.JarModeLibrary; import org.springframework.boot.loader.tools.JarModeLibrary;
import org.springframework.boot.loader.tools.LoaderImplementation;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -285,6 +283,17 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
} }
} }
@Test
void loaderIsWrittenToTheRootOfTheJarWhenUsingClassicLoader() throws IOException {
this.task.getMainClass().set("com.example.Main");
this.task.getLoaderImplementation().set(LoaderImplementation.CLASSIC);
executeTask();
try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) {
assertThat(jarFile.getEntry("org/springframework/boot/loader/LaunchedURLClassLoader.class")).isNotNull();
assertThat(jarFile.getEntry("org/springframework/boot/loader/")).isNotNull();
}
}
@Test @Test
void unpackCommentIsAddedToEntryIdentifiedByAPattern() throws IOException { void unpackCommentIsAddedToEntryIdentifiedByAPattern() throws IOException {
this.task.getMainClass().set("com.example.Main"); this.task.getMainClass().set("com.example.Main");
@ -292,7 +301,7 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
this.task.requiresUnpack("**/one.jar"); this.task.requiresUnpack("**/one.jar");
executeTask(); executeTask();
try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) {
assertThat(jarFile.getEntry(this.libPath + "one.jar").getComment()).isEqualTo("UNPACK"); assertThat(jarFile.getEntry(this.libPath + "one.jar").getComment()).startsWith("UNPACK:");
assertThat(jarFile.getEntry(this.libPath + "two.jar").getComment()).isNull(); assertThat(jarFile.getEntry(this.libPath + "two.jar").getComment()).isNull();
} }
} }
@ -304,7 +313,7 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
this.task.requiresUnpack((element) -> element.getName().endsWith("two.jar")); this.task.requiresUnpack((element) -> element.getName().endsWith("two.jar"));
executeTask(); executeTask();
try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) {
assertThat(jarFile.getEntry(this.libPath + "two.jar").getComment()).isEqualTo("UNPACK"); assertThat(jarFile.getEntry(this.libPath + "two.jar").getComment()).startsWith("UNPACK:");
assertThat(jarFile.getEntry(this.libPath + "one.jar").getComment()).isNull(); assertThat(jarFile.getEntry(this.libPath + "one.jar").getComment()).isNull();
} }
} }
@ -410,46 +419,23 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
} }
@Test @Test
void archiveIsReproducibleByDefault() throws IOException { void reproducibleOrderingCanBeEnabled() throws IOException {
this.task.getMainClass().set("com.example.Main"); this.task.getMainClass().set("com.example.Main");
this.task.from(newFiles("files/b/bravo.txt", "files/a/alpha.txt", "files/c/charlie.txt")); this.task.from(newFile("bravo.txt"), newFile("alpha.txt"), newFile("charlie.txt"));
this.task.setReproducibleFileOrder(true);
executeTask(); executeTask();
assertThat(this.task.getArchiveFile().get().getAsFile()).exists(); assertThat(this.task.getArchiveFile().get().getAsFile()).exists();
List<String> files = new ArrayList<>(); List<String> textFiles = new ArrayList<>();
try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) {
Enumeration<JarEntry> entries = jarFile.entries(); Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) { while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement(); JarEntry entry = entries.nextElement();
assertThat(entry.getLastModifiedTime().toMillis()) if (entry.getName().endsWith(".txt")) {
.isEqualTo(ZipEntryConstants.CONSTANT_TIME_FOR_ZIP_ENTRIES); textFiles.add(entry.getName());
if (entry.getName().startsWith("files/")) {
files.add(entry.getName());
}
}
}
assertThat(files).containsExactly("files/", "files/a/", "files/a/alpha.txt", "files/b/", "files/b/bravo.txt",
"files/c/", "files/c/charlie.txt");
}
@Test
void archiveReproducibilityCanBeDisabled() throws IOException {
this.task.getMainClass().set("com.example.Main");
this.task.from(newFiles("files/b/bravo.txt", "files/a/alpha.txt", "files/c/charlie.txt"));
this.task.setPreserveFileTimestamps(true);
this.task.setReproducibleFileOrder(false);
executeTask();
assertThat(this.task.getArchiveFile().get().getAsFile()).exists();
try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".txt") || entry.getName().startsWith("BOOT-INF/lib/")) {
OffsetDateTime lastModifiedTime = entry.getLastModifiedTime().toInstant().atOffset(ZoneOffset.UTC);
assertThat(lastModifiedTime)
.isNotEqualTo(OffsetDateTime.of(1980, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC));
} }
} }
} }
assertThat(textFiles).containsExactly("alpha.txt", "bravo.txt", "charlie.txt");
} }
@Test @Test
@ -689,19 +675,6 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
return entryNames; return entryNames;
} }
protected File newFiles(String... names) throws IOException {
File dir = new File(this.temp, UUID.randomUUID().toString());
dir.mkdir();
List<File> files = new ArrayList<>();
for (String name : names) {
File file = new File(dir, name);
file.getParentFile().mkdirs();
file.createNewFile();
files.add(file);
}
return dir;
}
protected File newFile(String name) throws IOException { protected File newFile(String name) throws IOException {
File file = new File(this.temp, name); File file = new File(this.temp, name);
file.createNewFile(); file.createNewFile();

View File

@ -30,8 +30,8 @@ import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.buildpack.platform.build.BuildRequest; import org.springframework.boot.buildpack.platform.build.BuildRequest;
import org.springframework.boot.buildpack.platform.build.BuildpackReference; import org.springframework.boot.buildpack.platform.build.BuildpackReference;
import org.springframework.boot.buildpack.platform.build.PullPolicy; import org.springframework.boot.buildpack.platform.build.PullPolicy;
import org.springframework.boot.buildpack.platform.docker.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.gradle.junit.GradleProjectBuilder; import org.springframework.boot.gradle.junit.GradleProjectBuilder;

View File

@ -66,10 +66,18 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
copyClasspathApplication(); copyClasspathApplication();
BuildResult result = this.gradleBuild.build("launch"); BuildResult result = this.gradleBuild.build("launch");
String output = result.getOutput(); String output = result.getOutput();
assertThat(output).containsPattern("1\\. .*classes"); if (this.gradleBuild.gradleVersionIsLessThan("9.0.0-rc-1")) {
assertThat(output).containsPattern("2\\. .*library-1.0-SNAPSHOT.jar"); assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("3\\. .*commons-lang3-3.9.jar"); assertThat(output).containsPattern("2\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("4\\. .*spring-boot-jarmode-tools.*.jar"); assertThat(output).containsPattern("3\\. .*commons-lang3-3.9.jar");
assertThat(output).containsPattern("4\\. .*spring-boot-jarmode-tools.*.jar");
}
else {
assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("2\\. .*commons-lang3-3.9.jar");
assertThat(output).containsPattern("3\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("4\\. .*spring-boot-jarmode-tools.*.jar");
}
assertThat(output).doesNotContain("5. "); assertThat(output).doesNotContain("5. ");
} }
@ -78,10 +86,18 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
copyClasspathApplication(); copyClasspathApplication();
BuildResult result = this.gradleBuild.build("launch"); BuildResult result = this.gradleBuild.build("launch");
String output = result.getOutput(); String output = result.getOutput();
assertThat(output).containsPattern("1\\. .*classes"); if (this.gradleBuild.gradleVersionIsLessThan("9.0.0-rc-1")) {
assertThat(output).containsPattern("2\\. .*spring-boot-jarmode-tools.*.jar"); assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("3\\. .*library-1.0-SNAPSHOT.jar"); assertThat(output).containsPattern("2\\. .*spring-boot-jarmode-tools.*.jar");
assertThat(output).containsPattern("4\\. .*commons-lang3-3.9.jar"); assertThat(output).containsPattern("3\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("4\\. .*commons-lang3-3.9.jar");
}
else {
assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("2\\. .*spring-boot-jarmode-tools.*.jar");
assertThat(output).containsPattern("3\\. .*commons-lang3-3.9.jar");
assertThat(output).containsPattern("4\\. .*library-1.0-SNAPSHOT.jar");
}
assertThat(output).doesNotContain("5. "); assertThat(output).doesNotContain("5. ");
} }

View File

@ -15,11 +15,11 @@
*/ */
plugins { plugins {
id "org.springframework.boot.starter" id 'java'
id 'org.springframework.boot' version '{version}'
} }
description = "Starter testing using Spring Batch with JDBC" bootJar {
mainClass = 'com.example.Application'
dependencies { loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC
api(project(":starter:spring-boot-starter-jdbc-test"))
} }

View File

@ -21,8 +21,6 @@ plugins {
bootJar { bootJar {
mainClass = 'com.example.Application' mainClass = 'com.example.Application'
if (GradleVersion.current().compareTo(GradleVersion.version("9.0.0-rc-1")) < 0) { preserveFileTimestamps = false
preserveFileTimestamps = false reproducibleFileOrder = true
reproducibleFileOrder = true
}
} }

View File

@ -15,12 +15,11 @@
*/ */
plugins { plugins {
id "org.springframework.boot.starter" id 'war'
id 'org.springframework.boot' version '{version}'
} }
description = "" bootWar {
mainClass = 'com.example.Application'
dependencies { loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC
api(project(":starter:spring-boot-starter-activemq"))
api(project(":starter:spring-boot-starter-test"))
} }

View File

@ -21,8 +21,6 @@ plugins {
bootWar { bootWar {
mainClass = 'com.example.Application' mainClass = 'com.example.Application'
if (GradleVersion.current().compareTo(GradleVersion.version("9.0.0-rc-1")) < 0) { preserveFileTimestamps = false
preserveFileTimestamps = false reproducibleFileOrder = true
reproducibleFileOrder = true
}
} }

View File

@ -6,7 +6,7 @@
<!-- tag::different-versions[] --> <!-- tag::different-versions[] -->
<properties> <properties>
<slf4j.version>1.7.30</slf4j.version> <slf4j.version>1.7.30</slf4j.version>
<spring-data-bom.version>2024.1.10</spring-data-bom.version> <spring-data-releasetrain.version>Moore-SR6</spring-data-releasetrain.version>
</properties> </properties>
<!-- end::different-versions[] --> <!-- end::different-versions[] -->

View File

@ -24,8 +24,8 @@
<!-- Override Spring Data release train provided by Spring Boot --> <!-- Override Spring Data release train provided by Spring Boot -->
<dependency> <dependency>
<groupId>org.springframework.data</groupId> <groupId>org.springframework.data</groupId>
<artifactId>spring-data-bom</artifactId> <artifactId>spring-data-releasetrain</artifactId>
<version>2024.1.10</version> <version>2020.0.0-SR1</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>

View File

@ -67,9 +67,6 @@ include::example$using/different-versions-pom.xml[tags=different-versions]
Browse the xref:appendix:dependency-versions/properties.adoc[Dependency Versions Properties] section in the Spring Boot reference for a complete list of dependency version properties. Browse the xref:appendix:dependency-versions/properties.adoc[Dependency Versions Properties] section in the Spring Boot reference for a complete list of dependency version properties.
WARNING: Each Spring Boot release is designed and tested against a specific set of third-party dependencies.
Overriding versions may cause compatibility issues and should be done with care.
[[using.import]] [[using.import]]

View File

@ -159,7 +159,7 @@ abstract class AbstractArchiveIntegrationTests {
Optional<JarEntry> match = entries.filter((entry) -> entry.getName().startsWith(prefix)) Optional<JarEntry> match = entries.filter((entry) -> entry.getName().startsWith(prefix))
.findFirst(); .findFirst();
assertThat(match).as("Name starting with %s", prefix) assertThat(match).as("Name starting with %s", prefix)
.hasValueSatisfying((entry) -> assertThat(entry.getComment()).isEqualTo("UNPACK")); .hasValueSatisfying((entry) -> assertThat(entry.getComment()).startsWith("UNPACK:"));
}); });
}); });
return this; return this;

View File

@ -76,6 +76,26 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests {
}); });
} }
@TestTemplate
void whenJarWithClassicLoaderIsRepackagedInPlaceOnlyRepackagedJarIsInstalled(MavenBuild mavenBuild) {
mavenBuild.project("jar-with-classic-loader").goals("install").execute((project) -> {
File original = new File(project, "target/jar-with-classic-loader-0.0.1.BUILD-SNAPSHOT.jar.original");
assertThat(original).isFile();
File repackaged = new File(project, "target/jar-with-classic-loader-0.0.1.BUILD-SNAPSHOT.jar");
assertThat(launchScript(repackaged)).isEmpty();
assertThat(jar(repackaged)).manifest((manifest) -> {
manifest.hasMainClass("org.springframework.boot.loader.launch.JarLauncher");
manifest.hasStartClass("some.random.Main");
manifest.hasAttribute("Not-Used", "Foo");
}).hasEntryWithName("org/springframework/boot/loader/launch/JarLauncher.class");
assertThat(buildLog(project))
.contains("Replacing main artifact " + repackaged + " with repackaged archive,")
.contains("The original artifact has been renamed to " + original)
.contains("Installing " + repackaged + " to")
.doesNotContain("Installing " + original + " to");
});
}
@TestTemplate @TestTemplate
void whenAttachIsDisabledOnlyTheOriginalJarIsInstalled(MavenBuild mavenBuild) { void whenAttachIsDisabledOnlyTheOriginalJarIsInstalled(MavenBuild mavenBuild) {
mavenBuild.project("jar-attach-disabled").goals("install").execute((project) -> { mavenBuild.project("jar-attach-disabled").goals("install").execute((project) -> {
@ -176,40 +196,7 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests {
.hasEntryWithNameStartingWith("BOOT-INF/lib/spring-context") .hasEntryWithNameStartingWith("BOOT-INF/lib/spring-context")
.hasEntryWithNameStartingWith("BOOT-INF/lib/spring-core") .hasEntryWithNameStartingWith("BOOT-INF/lib/spring-core")
.hasEntryWithNameStartingWith("BOOT-INF/lib/commons-logging") .hasEntryWithNameStartingWith("BOOT-INF/lib/commons-logging")
.doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/servlet-api-"); .doesNotHaveEntryWithName("BOOT-INF/lib/servlet-api-2.5.jar");
});
}
@TestTemplate
void whenAnEntryIsOptionalByDefaultDoesNotAppearInTheRepackagedJar(MavenBuild mavenBuild) {
mavenBuild.project("jar-optional-default").goals("install").execute((project) -> {
File repackaged = new File(project, "target/jar-optional-default-0.0.1.BUILD-SNAPSHOT.jar");
assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/")
.hasEntryWithNameStartingWith("BOOT-INF/lib/spring-context")
.hasEntryWithNameStartingWith("BOOT-INF/lib/spring-core")
.doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/log4j-api-");
});
}
@TestTemplate
void whenAnEntryIsOptionalAndOptionalsIncludedAppearsInTheRepackagedJar(MavenBuild mavenBuild) {
mavenBuild.project("jar-optional-include").goals("install").execute((project) -> {
File repackaged = new File(project, "target/jar-optional-include-0.0.1.BUILD-SNAPSHOT.jar");
assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/")
.hasEntryWithNameStartingWith("BOOT-INF/lib/spring-context")
.hasEntryWithNameStartingWith("BOOT-INF/lib/spring-core")
.hasEntryWithNameStartingWith("BOOT-INF/lib/log4j-api-");
});
}
@TestTemplate
void whenAnEntryIsOptionalAndOptionalsExcludedDoesNotAppearInTheRepackagedJar(MavenBuild mavenBuild) {
mavenBuild.project("jar-optional-exclude").goals("install").execute((project) -> {
File repackaged = new File(project, "target/jar-optional-exclude-0.0.1.BUILD-SNAPSHOT.jar");
assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/")
.hasEntryWithNameStartingWith("BOOT-INF/lib/spring-context")
.hasEntryWithNameStartingWith("BOOT-INF/lib/spring-core")
.doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/log4j-api-");
}); });
} }
@ -261,8 +248,9 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests {
File repackaged = new File(project, "target/jar-exclude-group-0.0.1.BUILD-SNAPSHOT.jar"); File repackaged = new File(project, "target/jar-exclude-group-0.0.1.BUILD-SNAPSHOT.jar");
assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/") assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/")
.hasEntryWithNameStartingWith("BOOT-INF/lib/spring-context") .hasEntryWithNameStartingWith("BOOT-INF/lib/spring-context")
.hasEntryWithNameStartingWith("BOOT-INF/lib/spring-core")
.hasEntryWithNameStartingWith("BOOT-INF/lib/commons-logging") .hasEntryWithNameStartingWith("BOOT-INF/lib/commons-logging")
.doesNotHaveEntryWithName("BOOT-INF/lib/log4j-api-"); .doesNotHaveEntryWithName("BOOT-INF/lib/log4j-api-2.4.1.jar");
}); });
} }
@ -400,7 +388,7 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests {
assertThat(layerIndex.get("application")).contains("BOOT-INF/lib/jar-release-0.0.1.RELEASE.jar", assertThat(layerIndex.get("application")).contains("BOOT-INF/lib/jar-release-0.0.1.RELEASE.jar",
"BOOT-INF/lib/jar-snapshot-0.0.1.BUILD-SNAPSHOT.jar"); "BOOT-INF/lib/jar-snapshot-0.0.1.BUILD-SNAPSHOT.jar");
assertThat(layerIndex.get("dependencies")) assertThat(layerIndex.get("dependencies"))
.anyMatch((dependency) -> dependency.startsWith("BOOT-INF/lib/log4j-api-")); .anyMatch((dependency) -> dependency.startsWith("BOOT-INF/lib/log4j-api-2"));
} }
catch (IOException ex) { catch (IOException ex) {
// Ignore // Ignore

View File

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-optional-default</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>@java.version@</maven.compiler.source>
<maven.compiler.target>@java.version@</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>@maven-jar-plugin.version@</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>@spring-framework.version@</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>@log4j2.version@</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-optional-include</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>@java.version@</maven.compiler.source>
<maven.compiler.target>@java.version@</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<includeOptional>true</includeOptional>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>@maven-jar-plugin.version@</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>@spring-framework.version@</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>@log4j2.version@</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -3,7 +3,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId> <groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-optional-exclude</artifactId> <artifactId>jar-with-classic-loader</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version> <version>0.0.1.BUILD-SNAPSHOT</version>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -22,7 +22,7 @@
<goal>repackage</goal> <goal>repackage</goal>
</goals> </goals>
<configuration> <configuration>
<includeOptional>false</includeOptional> <loaderImplementation>CLASSIC</loaderImplementation>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
@ -31,6 +31,16 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<version>@maven-jar-plugin.version@</version> <version>@maven-jar-plugin.version@</version>
<configuration>
<archive>
<manifest>
<mainClass>some.random.Main</mainClass>
</manifest>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
@ -41,10 +51,10 @@
<version>@spring-framework.version@</version> <version>@spring-framework.version@</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>jakarta.servlet</groupId>
<artifactId>log4j-api</artifactId> <artifactId>jakarta.servlet-api</artifactId>
<version>@log4j2.version@</version> <version>@jakarta-servlet.version@</version>
<optional>true</optional> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -47,6 +47,7 @@ import org.springframework.boot.loader.tools.Layouts.Jar;
import org.springframework.boot.loader.tools.Layouts.None; import org.springframework.boot.loader.tools.Layouts.None;
import org.springframework.boot.loader.tools.Layouts.War; import org.springframework.boot.loader.tools.Layouts.War;
import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.LoaderImplementation;
import org.springframework.boot.loader.tools.Packager; import org.springframework.boot.loader.tools.Packager;
import org.springframework.boot.loader.tools.layer.CustomLayers; import org.springframework.boot.loader.tools.layer.CustomLayers;
@ -113,13 +114,6 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo
@Parameter(defaultValue = "false") @Parameter(defaultValue = "false")
public boolean includeSystemScope; public boolean includeSystemScope;
/**
* Include optional dependencies.
* @since 3.5.7
*/
@Parameter(defaultValue = "false")
public boolean includeOptional;
/** /**
* Include JAR tools. * Include JAR tools.
* @since 3.3.0 * @since 3.3.0
@ -148,6 +142,15 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo
return null; return null;
} }
/**
* Return the loader implementation that should be used.
* @return the loader implementation or {@code null}
* @since 3.2.0
*/
protected @Nullable LoaderImplementation getLoaderImplementation() {
return null;
}
/** /**
* Return the layout factory that will be used to determine the {@link LayoutType} if * Return the layout factory that will be used to determine the {@link LayoutType} if
* no explicit layout is set. * no explicit layout is set.
@ -165,6 +168,7 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo
*/ */
protected <P extends Packager> P getConfiguredPackager(Supplier<P> supplier) { protected <P extends Packager> P getConfiguredPackager(Supplier<P> supplier) {
P packager = supplier.get(); P packager = supplier.get();
packager.setLoaderImplementation(getLoaderImplementation());
packager.setLayoutFactory(getLayoutFactory()); packager.setLayoutFactory(getLayoutFactory());
packager.addMainClassTimeoutWarningListener(new LoggingMainClassTimeoutWarningListener(this::getLog)); packager.addMainClassTimeoutWarningListener(new LoggingMainClassTimeoutWarningListener(this::getLog));
packager.setMainClass(this.mainClass); packager.setMainClass(this.mainClass);
@ -227,9 +231,6 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo
if (!this.includeSystemScope) { if (!this.includeSystemScope) {
filters.add(new ScopeFilter(null, Artifact.SCOPE_SYSTEM)); filters.add(new ScopeFilter(null, Artifact.SCOPE_SYSTEM));
} }
if (!this.includeOptional) {
filters.add(DependencyFilter.exclude(Artifact::isOptional));
}
return filters.toArray(new ArtifactsFilter[0]); return filters.toArray(new ArtifactsFilter[0]);
} }

View File

@ -50,6 +50,7 @@ import org.springframework.boot.loader.tools.EntryWriter;
import org.springframework.boot.loader.tools.ImagePackager; import org.springframework.boot.loader.tools.ImagePackager;
import org.springframework.boot.loader.tools.LayoutFactory; import org.springframework.boot.loader.tools.LayoutFactory;
import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.LoaderImplementation;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -207,6 +208,13 @@ public abstract class BuildImageMojo extends AbstractPackagerMojo {
@Parameter @Parameter
private @Nullable LayoutType layout; private @Nullable LayoutType layout;
/**
* The loader implementation that should be used.
* @since 3.2.0
*/
@Parameter
private @Nullable LoaderImplementation loaderImplementation;
/** /**
* The layout factory that will be used to create the executable archive if no * The layout factory that will be used to create the executable archive if no
* explicit layout is set. Alternative layouts implementations can be provided by 3rd * explicit layout is set. Alternative layouts implementations can be provided by 3rd
@ -230,6 +238,11 @@ public abstract class BuildImageMojo extends AbstractPackagerMojo {
return this.layout; return this.layout;
} }
@Override
protected @Nullable LoaderImplementation getLoaderImplementation() {
return this.loaderImplementation;
}
/** /**
* Return the layout factory that will be used to determine the * Return the layout factory that will be used to determine the
* {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set. * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set.

View File

@ -16,11 +16,9 @@
package org.springframework.boot.maven; package org.springframework.boot.maven;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.Artifact;
import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactsFilter; import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactsFilter;
@ -83,22 +81,4 @@ public abstract class DependencyFilter extends AbstractArtifactsFilter {
return this.filters; return this.filters;
} }
/**
* Return a new {@link DependencyFilter} the excludes artifacts based on the given
* predicate.
* @param filter the predicate used to filter the artifacts.
* @return a new {@link DependencyFilter} instance
* @since 3.5.7
*/
public static DependencyFilter exclude(Predicate<Artifact> filter) {
return new DependencyFilter(Collections.emptyList()) {
@Override
protected boolean filter(Artifact artifact) {
return filter.test(artifact);
}
};
}
} }

View File

@ -34,8 +34,8 @@ import org.apache.maven.artifact.Artifact;
*/ */
class JarTypeFilter extends DependencyFilter { class JarTypeFilter extends DependencyFilter {
private static final Set<String> EXCLUDED_JAR_TYPES = Collections.unmodifiableSet( private static final Set<String> EXCLUDED_JAR_TYPES = Collections
new HashSet<>(Arrays.asList("annotation-processor", "dependencies-starter", "development-tool"))); .unmodifiableSet(new HashSet<>(Arrays.asList("annotation-processor", "dependencies-starter")));
JarTypeFilter() { JarTypeFilter() {
super(Collections.emptyList()); super(Collections.emptyList());

View File

@ -40,6 +40,7 @@ import org.springframework.boot.loader.tools.DefaultLaunchScript;
import org.springframework.boot.loader.tools.LaunchScript; import org.springframework.boot.loader.tools.LaunchScript;
import org.springframework.boot.loader.tools.LayoutFactory; import org.springframework.boot.loader.tools.LayoutFactory;
import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.LoaderImplementation;
import org.springframework.boot.loader.tools.Repackager; import org.springframework.boot.loader.tools.Repackager;
import org.springframework.lang.Contract; import org.springframework.lang.Contract;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -169,6 +170,13 @@ public class RepackageMojo extends AbstractPackagerMojo {
@Parameter(property = "spring-boot.repackage.layout") @Parameter(property = "spring-boot.repackage.layout")
private @Nullable LayoutType layout; private @Nullable LayoutType layout;
/**
* The loader implementation that should be used.
* @since 3.2.0
*/
@Parameter
private @Nullable LoaderImplementation loaderImplementation;
/** /**
* The layout factory that will be used to create the executable archive if no * The layout factory that will be used to create the executable archive if no
* explicit layout is set. Alternative layouts implementations can be provided by 3rd * explicit layout is set. Alternative layouts implementations can be provided by 3rd
@ -193,6 +201,11 @@ public class RepackageMojo extends AbstractPackagerMojo {
return this.layout; return this.layout;
} }
@Override
protected @Nullable LoaderImplementation getLoaderImplementation() {
return this.loaderImplementation;
}
/** /**
* Return the layout factory that will be used to determine the * Return the layout factory that will be used to determine the
* {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set. * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set.

View File

@ -1,50 +0,0 @@
/*
* Copyright 2012-present 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 org.springframework.boot.maven;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.handler.DefaultArtifactHandler;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DependencyFilter}.
*
* @author Phillip Webb
*/
class DependencyFilterTests {
@Test
void excludeFiltersBasedOnPredicate() throws ArtifactFilterException {
DependencyFilter filter = DependencyFilter.exclude(Artifact::isOptional);
ArtifactHandler ah = new DefaultArtifactHandler();
VersionRange v = VersionRange.createFromVersion("1.0.0");
DefaultArtifact a1 = new DefaultArtifact("com.example", "a1", v, "compile", "jar", null, ah, false);
DefaultArtifact a2 = new DefaultArtifact("com.example", "a2", v, "compile", "jar", null, ah, true);
DefaultArtifact a3 = new DefaultArtifact("com.example", "a3", v, "compile", "jar", null, ah, false);
Set<Artifact> filtered = filter.filter(Set.of(a1, a2, a3));
assertThat(filtered).containsExactlyInAnyOrder(a1, a3);
}
}

View File

@ -31,8 +31,8 @@ import org.springframework.boot.buildpack.platform.build.BuildRequest;
import org.springframework.boot.buildpack.platform.build.BuildpackReference; import org.springframework.boot.buildpack.platform.build.BuildpackReference;
import org.springframework.boot.buildpack.platform.build.Cache; import org.springframework.boot.buildpack.platform.build.Cache;
import org.springframework.boot.buildpack.platform.build.PullPolicy; import org.springframework.boot.buildpack.platform.build.PullPolicy;
import org.springframework.boot.buildpack.platform.docker.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.Owner;
import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.io.TarArchive;

View File

@ -60,11 +60,6 @@ class JarTypeFilterTests {
assertThat(new JarTypeFilter().filter(createArtifact("annotation-processor"))).isTrue(); assertThat(new JarTypeFilter().filter(createArtifact("annotation-processor"))).isTrue();
} }
@Test
void whenArtifactHasDevelopmentToolJarTypeThenItIsExcluded() {
assertThat(new JarTypeFilter().filter(createArtifact("development-tool"))).isTrue();
}
@Test @Test
void whenArtifactHasNoManifestFileThenItIsIncluded() { void whenArtifactHasNoManifestFileThenItIsIncluded() {
assertThat(new JarTypeFilter().filter(createArtifactWithNoManifest())).isFalse(); assertThat(new JarTypeFilter().filter(createArtifactWithNoManifest())).isFalse();

View File

@ -52,7 +52,7 @@ dependencies {
implementation("commons-codec:commons-codec:${commonsCodecVersion}") implementation("commons-codec:commons-codec:${commonsCodecVersion}")
implementation("de.undercouch.download:de.undercouch.download.gradle.plugin:5.5.0") implementation("de.undercouch.download:de.undercouch.download.gradle.plugin:5.5.0")
implementation("dev.adamko.dokkatoo:dokkatoo-plugin:2.3.1") implementation("dev.adamko.dokkatoo:dokkatoo-plugin:2.3.1")
implementation("dev.detekt:detekt-gradle-plugin:2.0.0-alpha.0") implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.8")
implementation("io.spring.gradle.antora:spring-antora-plugin:0.0.1") implementation("io.spring.gradle.antora:spring-antora-plugin:0.0.1")
implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}") implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}")
implementation("io.spring.nohttp:nohttp-gradle:0.0.11") implementation("io.spring.nohttp:nohttp-gradle:0.0.11")
@ -133,6 +133,10 @@ gradlePlugin {
id = "org.springframework.boot.integration-test" id = "org.springframework.boot.integration-test"
implementationClass = "org.springframework.boot.build.test.IntegrationTestPlugin" implementationClass = "org.springframework.boot.build.test.IntegrationTestPlugin"
} }
systemTestPlugin {
id = "org.springframework.boot.system-test"
implementationClass = "org.springframework.boot.build.test.SystemTestPlugin"
}
mavenPluginPlugin { mavenPluginPlugin {
id = "org.springframework.boot.maven-plugin" id = "org.springframework.boot.maven-plugin"
implementationClass = "org.springframework.boot.build.mavenplugin.MavenPluginPlugin" implementationClass = "org.springframework.boot.build.mavenplugin.MavenPluginPlugin"
@ -149,22 +153,10 @@ gradlePlugin {
id = "org.springframework.boot.starter" id = "org.springframework.boot.starter"
implementationClass = "org.springframework.boot.build.starters.StarterPlugin" implementationClass = "org.springframework.boot.build.starters.StarterPlugin"
} }
systemTestPlugin {
id = "org.springframework.boot.system-test"
implementationClass = "org.springframework.boot.build.test.SystemTestPlugin"
}
testAutoConfigurationPlugin {
id = "org.springframework.boot.test-auto-configuration"
implementationClass = "org.springframework.boot.build.test.autoconfigure.TestAutoConfigurationPlugin"
}
testFailuresPlugin { testFailuresPlugin {
id = "org.springframework.boot.test-failures" id = "org.springframework.boot.test-failures"
implementationClass = "org.springframework.boot.build.testing.TestFailuresPlugin" implementationClass = "org.springframework.boot.build.testing.TestFailuresPlugin"
} }
testSlicePlugin {
id = "org.springframework.boot.test-slice"
implementationClass = "org.springframework.boot.build.test.autoconfigure.TestSlicePlugin"
}
} }
} }

View File

@ -20,9 +20,9 @@ import java.net.URI;
import dev.adamko.dokkatoo.DokkatooExtension; import dev.adamko.dokkatoo.DokkatooExtension;
import dev.adamko.dokkatoo.formats.DokkatooHtmlPlugin; import dev.adamko.dokkatoo.formats.DokkatooHtmlPlugin;
import dev.detekt.gradle.Detekt; import io.gitlab.arturbosch.detekt.Detekt;
import dev.detekt.gradle.extensions.DetektExtension; import io.gitlab.arturbosch.detekt.DetektPlugin;
import dev.detekt.gradle.plugin.DetektPlugin; import io.gitlab.arturbosch.detekt.extensions.DetektExtension;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.SourceSetContainer;
@ -76,7 +76,6 @@ class KotlinConventions {
private void configureDokkatoo(Project project) { private void configureDokkatoo(Project project) {
DokkatooExtension dokkatoo = project.getExtensions().getByType(DokkatooExtension.class); DokkatooExtension dokkatoo = project.getExtensions().getByType(DokkatooExtension.class);
dokkatoo.getVersions().getJetbrainsDokka().set("2.1.0-Beta");
dokkatoo.getDokkatooSourceSets().configureEach((sourceSet) -> { dokkatoo.getDokkatooSourceSets().configureEach((sourceSet) -> {
if (SourceSet.MAIN_SOURCE_SET_NAME.equals(sourceSet.getName())) { if (SourceSet.MAIN_SOURCE_SET_NAME.equals(sourceSet.getName())) {
sourceSet.getSourceRoots().setFrom(project.file("src/main/kotlin")); sourceSet.getSourceRoots().setFrom(project.file("src/main/kotlin"));
@ -104,9 +103,7 @@ class KotlinConventions {
project.getPlugins().apply(DetektPlugin.class); project.getPlugins().apply(DetektPlugin.class);
DetektExtension detekt = project.getExtensions().getByType(DetektExtension.class); DetektExtension detekt = project.getExtensions().getByType(DetektExtension.class);
detekt.getConfig().setFrom(project.getRootProject().file("config/detekt/config.yml")); detekt.getConfig().setFrom(project.getRootProject().file("config/detekt/config.yml"));
project.getTasks() project.getTasks().withType(Detekt.class).configureEach((task) -> task.setJvmTarget(JVM_TARGET.getTarget()));
.withType(Detekt.class)
.configureEach((task) -> task.getJvmTarget().set(JVM_TARGET.getTarget()));
} }
} }

View File

@ -80,13 +80,6 @@ final class ArchitectureRules {
private static final String AUTOCONFIGURATION_ANNOTATION = "org.springframework.boot.autoconfigure.AutoConfiguration"; private static final String AUTOCONFIGURATION_ANNOTATION = "org.springframework.boot.autoconfigure.AutoConfiguration";
private static final String TEST_AUTOCONFIGURATION_ANNOTATION = "org.springframework.boot.test.autoconfigure.TestAutoConfiguration";
private static final Predicate<JavaPackage> NULL_MARKED_PACKAGE_FILTER = (candidate) -> !List
.of("org.springframework.boot.cli.json", "org.springframework.boot.configurationmetadata.json",
"org.springframework.boot.configurationprocessor.json")
.contains(candidate.getName());
private ArchitectureRules() { private ArchitectureRules() {
} }
@ -112,14 +105,13 @@ final class ArchitectureRules {
rules.add(noClassesShouldCallStringToUpperCaseWithoutLocale()); rules.add(noClassesShouldCallStringToUpperCaseWithoutLocale());
rules.add(noClassesShouldCallStringToLowerCaseWithoutLocale()); rules.add(noClassesShouldCallStringToLowerCaseWithoutLocale());
rules.add(conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType()); rules.add(conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType());
rules.add(enumSourceShouldNotHaveValueThatIsTheSameAsTypeOfMethodsFirstParameter()); rules.add(enumSourceShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodParameterType());
rules.add(classLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute()); rules.add(classLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute());
rules.add(methodLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute()); rules.add(methodLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute());
rules.add(conditionsShouldNotBePublic()); rules.add(conditionsShouldNotBePublic());
rules.add(allConfigurationPropertiesBindingBeanMethodsShouldBeStatic()); rules.add(allConfigurationPropertiesBindingBeanMethodsShouldBeStatic());
rules.add(autoConfigurationClassesShouldBePublicAndFinal()); rules.add(autoConfigurationClassesShouldBePublicAndFinal());
rules.add(autoConfigurationClassesShouldHaveNoPublicMembers()); rules.add(autoConfigurationClassesShouldHaveNoPublicMembers());
rules.add(testAutoConfigurationClassesShouldBePackagePrivateAndFinal());
return List.copyOf(rules); return List.copyOf(rules);
} }
@ -138,12 +130,7 @@ final class ArchitectureRules {
} }
private static ArchRule allPackagesShouldBeFreeOfTangles() { private static ArchRule allPackagesShouldBeFreeOfTangles() {
return SlicesRuleDefinition.slices() return SlicesRuleDefinition.slices().matching("(**)").should().beFreeOfCycles();
.matching("(**)")
.should()
.beFreeOfCycles()
.ignoreDependency("org.springframework.boot.env.EnvironmentPostProcessor",
"org.springframework.boot.SpringApplication");
} }
private static ArchRule allBeanPostProcessorBeanMethodsShouldBeStaticAndNotCausePrematureInitialization() { private static ArchRule allBeanPostProcessorBeanMethodsShouldBeStaticAndNotCausePrematureInitialization() {
@ -263,9 +250,7 @@ final class ArchitectureRules {
} }
static ArchRule packagesShouldBeAnnotatedWithNullMarked() { static ArchRule packagesShouldBeAnnotatedWithNullMarked() {
return ArchRuleDefinition.all(packages(NULL_MARKED_PACKAGE_FILTER)) return ArchRuleDefinition.all(packages()).should(beAnnotatedWithNullMarked()).allowEmptyShould(true);
.should(beAnnotatedWithNullMarked())
.allowEmptyShould(true);
} }
private static ArchCondition<? super JavaMethod> notSpecifyOnlyATypeThatIsTheSameAsTheMethodReturnType() { private static ArchCondition<? super JavaMethod> notSpecifyOnlyATypeThatIsTheSameAsTheMethodReturnType() {
@ -273,7 +258,7 @@ final class ArchitectureRules {
JavaAnnotation<JavaMethod> conditionalAnnotation = item JavaAnnotation<JavaMethod> conditionalAnnotation = item
.getAnnotationOfType("org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean"); .getAnnotationOfType("org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean");
Map<String, Object> properties = conditionalAnnotation.getProperties(); Map<String, Object> properties = conditionalAnnotation.getProperties();
if (!hasProperty("type", properties) && !hasProperty("name", properties)) { if (!properties.containsKey("type") && !properties.containsKey("name")) {
conditionalAnnotation.get("value").ifPresent((value) -> { conditionalAnnotation.get("value").ifPresent((value) -> {
if (containsOnlySingleType((JavaType[]) value, item.getReturnType())) { if (containsOnlySingleType((JavaType[]) value, item.getReturnType())) {
addViolation(events, item, conditionalAnnotation.getDescription() addViolation(events, item, conditionalAnnotation.getDescription()
@ -284,24 +269,16 @@ final class ArchitectureRules {
}); });
} }
private static boolean hasProperty(String name, Map<String, Object> properties) { private static ArchRule enumSourceShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodParameterType() {
Object property = properties.get(name);
if (property == null) {
return false;
}
return !property.getClass().isArray() || ((Object[]) property).length > 0;
}
private static ArchRule enumSourceShouldNotHaveValueThatIsTheSameAsTypeOfMethodsFirstParameter() {
return ArchRuleDefinition.methods() return ArchRuleDefinition.methods()
.that() .that()
.areAnnotatedWith("org.junit.jupiter.params.provider.EnumSource") .areAnnotatedWith("org.junit.jupiter.params.provider.EnumSource")
.should(notHaveValueThatIsTheSameAsTheTypeOfTheMethodsFirstParameter()) .should(notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType())
.allowEmptyShould(true); .allowEmptyShould(true);
} }
private static ArchCondition<? super JavaMethod> notHaveValueThatIsTheSameAsTheTypeOfTheMethodsFirstParameter() { private static ArchCondition<? super JavaMethod> notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType() {
return check("not have a value that is the same as the type of the method's first parameter", return check("not specify only a type that is the same as the method's parameter type",
ArchitectureRules::notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType); ArchitectureRules::notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType);
} }
@ -309,13 +286,15 @@ final class ArchitectureRules {
ConditionEvents events) { ConditionEvents events) {
JavaAnnotation<JavaMethod> enumSourceAnnotation = item JavaAnnotation<JavaMethod> enumSourceAnnotation = item
.getAnnotationOfType("org.junit.jupiter.params.provider.EnumSource"); .getAnnotationOfType("org.junit.jupiter.params.provider.EnumSource");
enumSourceAnnotation.get("value").ifPresent((value) -> { Map<String, Object> properties = enumSourceAnnotation.getProperties();
JavaType parameterType = item.getParameterTypes().get(0); if (properties.size() == 1 && item.getParameterTypes().size() == 1) {
if (value.equals(parameterType)) { enumSourceAnnotation.get("value").ifPresent((value) -> {
addViolation(events, item, enumSourceAnnotation.getDescription() if (value.equals(item.getParameterTypes().get(0))) {
+ " should not specify a value that is the same as the type of the method's first parameter"); addViolation(events, item, enumSourceAnnotation.getDescription()
} + " should not specify only a value that is the same as the method's parameter type");
}); }
});
}
} }
private static ArchRule classLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute() { private static ArchRule classLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute() {
@ -374,7 +353,8 @@ final class ArchitectureRules {
private static ArchRule autoConfigurationClassesShouldBePublicAndFinal() { private static ArchRule autoConfigurationClassesShouldBePublicAndFinal() {
return ArchRuleDefinition.classes() return ArchRuleDefinition.classes()
.that(areRegularAutoConfiguration()) .that()
.areAnnotatedWith(AUTOCONFIGURATION_ANNOTATION)
.should() .should()
.bePublic() .bePublic()
.andShould() .andShould()
@ -385,7 +365,8 @@ final class ArchitectureRules {
private static ArchRule autoConfigurationClassesShouldHaveNoPublicMembers() { private static ArchRule autoConfigurationClassesShouldHaveNoPublicMembers() {
return ArchRuleDefinition.members() return ArchRuleDefinition.members()
.that() .that()
.areDeclaredInClassesThat(areRegularAutoConfiguration()) .areDeclaredInClassesThat()
.areAnnotatedWith(AUTOCONFIGURATION_ANNOTATION)
.and(areNotDefaultConstructors()) .and(areNotDefaultConstructors())
.and(areNotConstants()) .and(areNotConstants())
.and(dontOverridePublicMethods()) .and(dontOverridePublicMethods())
@ -394,24 +375,6 @@ final class ArchitectureRules {
.allowEmptyShould(true); .allowEmptyShould(true);
} }
private static ArchRule testAutoConfigurationClassesShouldBePackagePrivateAndFinal() {
return ArchRuleDefinition.classes()
.that()
.areAnnotatedWith(TEST_AUTOCONFIGURATION_ANNOTATION)
.should()
.bePackagePrivate()
.andShould()
.haveModifier(JavaModifier.FINAL)
.allowEmptyShould(true);
}
private static DescribedPredicate<JavaClass> areRegularAutoConfiguration() {
return DescribedPredicate.describe("Regular @AutoConfiguration",
(javaClass) -> javaClass.isMetaAnnotatedWith(AUTOCONFIGURATION_ANNOTATION)
&& !javaClass.isMetaAnnotatedWith(TEST_AUTOCONFIGURATION_ANNOTATION)
&& !javaClass.isAnnotation());
}
private static DescribedPredicate<? super JavaMember> dontOverridePublicMethods() { private static DescribedPredicate<? super JavaMember> dontOverridePublicMethods() {
OverridesPublicMethod<JavaMember> predicate = new OverridesPublicMethod<>(); OverridesPublicMethod<JavaMember> predicate = new OverridesPublicMethod<>();
return DescribedPredicate.describe("don't override public methods", (member) -> !predicate.test(member)); return DescribedPredicate.describe("don't override public methods", (member) -> !predicate.test(member));
@ -517,11 +480,11 @@ final class ArchitectureRules {
return string + " should be used instead"; return string + " should be used instead";
} }
static ClassesTransformer<JavaPackage> packages(Predicate<JavaPackage> filter) { static ClassesTransformer<JavaPackage> packages() {
return new AbstractClassesTransformer<>("packages") { return new AbstractClassesTransformer<>("packages") {
@Override @Override
public Iterable<JavaPackage> doTransform(JavaClasses collection) { public Iterable<JavaPackage> doTransform(JavaClasses collection) {
return collection.stream().map(JavaClass::getPackage).filter(filter).collect(Collectors.toSet()); return collection.stream().map(JavaClass::getPackage).collect(Collectors.toSet());
} }
}; };
} }

View File

@ -19,7 +19,6 @@ package org.springframework.boot.build.autoconfigure;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -55,8 +54,8 @@ public record AutoConfigurationClass(String name, List<String> before, List<Stri
attributes.getOrDefault("afterName", Collections.emptyList())); attributes.getOrDefault("afterName", Collections.emptyList()));
} }
public static AutoConfigurationClass of(InputStream input) { static AutoConfigurationClass of(File classFile) {
try { try (FileInputStream input = new FileInputStream(classFile)) {
ClassReader classReader = new ClassReader(input); ClassReader classReader = new ClassReader(input);
AutoConfigurationClassVisitor visitor = new AutoConfigurationClassVisitor(); AutoConfigurationClassVisitor visitor = new AutoConfigurationClassVisitor();
classReader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); classReader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
@ -67,15 +66,6 @@ public record AutoConfigurationClass(String name, List<String> before, List<Stri
} }
} }
static AutoConfigurationClass of(File classFile) {
try (InputStream input = new FileInputStream(classFile)) {
return of(input);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
private static final class AutoConfigurationClassVisitor extends ClassVisitor { private static final class AutoConfigurationClassVisitor extends ClassVisitor {
private AutoConfigurationClass autoConfigurationClass; private AutoConfigurationClass autoConfigurationClass;

View File

@ -38,10 +38,7 @@ import org.gradle.api.tasks.SkipWhenEmpty;
*/ */
public abstract class AutoConfigurationImportsTask extends DefaultTask { public abstract class AutoConfigurationImportsTask extends DefaultTask {
/** static final String IMPORTS_FILE = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports";
* The path of the {@code AutoConfiguration.imports} file.
*/
public static final String IMPORTS_FILE = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports";
private FileCollection sourceFiles = getProject().getObjects().fileCollection(); private FileCollection sourceFiles = getProject().getObjects().fileCollection();

View File

@ -65,7 +65,7 @@ public class ConfigurationPropertiesPlugin implements Plugin<Project> {
public static final String CHECK_ADDITIONAL_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkAdditionalSpringConfigurationMetadata"; public static final String CHECK_ADDITIONAL_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkAdditionalSpringConfigurationMetadata";
/** /**
* Name of the {@link CheckSpringConfigurationMetadata} task. * Name of the {@link CheckAdditionalSpringConfigurationMetadata} task.
*/ */
public static final String CHECK_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkSpringConfigurationMetadata"; public static final String CHECK_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkSpringConfigurationMetadata";

View File

@ -220,7 +220,6 @@ public abstract class DocumentConfigurationProperties extends DefaultTask {
private void testingPrefixes(Config prefix) { private void testingPrefixes(Config prefix) {
prefix.accept("spring.test."); prefix.accept("spring.test.");
prefix.accept("spring.restdocs.");
} }
private void testcontainersPrefixes(Config prefix) { private void testcontainersPrefixes(Config prefix) {

View File

@ -41,7 +41,7 @@ public class OptionalDependenciesPlugin implements Plugin<Project> {
@Override @Override
public void apply(Project project) { public void apply(Project project) {
Configuration optional = project.getConfigurations().create("optional"); Configuration optional = project.getConfigurations().create("optional");
optional.setCanBeConsumed(true); optional.setCanBeConsumed(false);
optional.setCanBeResolved(false); optional.setCanBeResolved(false);
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
SourceSetContainer sourceSets = project.getExtensions() SourceSetContainer sourceSets = project.getExtensions()

View File

@ -1,233 +0,0 @@
/*
* Copyright 2025-present 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 org.springframework.boot.build.test.autoconfigure;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileTree;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SkipWhenEmpty;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.VerificationException;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.springframework.boot.build.autoconfigure.AutoConfigurationClass;
/**
* Task to check the contents of a project's
* {@code META-INF/spring/*.AutoConfigure*.imports} files.
*
* @author Andy Wilkinson
*/
public abstract class CheckAutoConfigureImports extends DefaultTask {
private FileCollection sourceFiles = getProject().getObjects().fileCollection();
private FileCollection classpath = getProject().getObjects().fileCollection();
public CheckAutoConfigureImports() {
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
}
@InputFiles
@SkipWhenEmpty
@PathSensitive(PathSensitivity.RELATIVE)
public FileTree getSource() {
return this.sourceFiles.getAsFileTree()
.matching((filter) -> filter.include("META-INF/spring/*.AutoConfigure*.imports"));
}
public void setSource(Object source) {
this.sourceFiles = getProject().getObjects().fileCollection().from(source);
}
@Classpath
public FileCollection getClasspath() {
return this.classpath;
}
public void setClasspath(Object classpath) {
this.classpath = getProject().getObjects().fileCollection().from(classpath);
}
@OutputDirectory
public abstract DirectoryProperty getOutputDirectory();
@TaskAction
void execute() {
Map<String, List<String>> allProblems = new TreeMap<>();
for (AutoConfigureImports autoConfigureImports : loadImports()) {
List<String> problems = new ArrayList<>();
if (!find(autoConfigureImports.annotationName)) {
problems.add("Annotation '%s' was not found".formatted(autoConfigureImports.annotationName));
}
for (String imported : autoConfigureImports.imports) {
String importedClassName = imported;
if (importedClassName.startsWith("optional:")) {
importedClassName = importedClassName.substring("optional:".length());
}
boolean found = find(importedClassName, (input) -> {
if (!correctlyAnnotated(input)) {
problems.add("Imported auto-configuration '%s' is not annotated with @AutoConfiguration"
.formatted(imported));
}
});
if (!found) {
problems.add("Imported auto-configuration '%s' was not found".formatted(importedClassName));
}
}
List<String> sortedValues = new ArrayList<>(autoConfigureImports.imports);
Collections.sort(sortedValues, (i1, i2) -> {
boolean imported1 = i1.startsWith("optional:");
boolean imported2 = i2.startsWith("optional:");
int comparison = Boolean.compare(imported1, imported2);
if (comparison != 0) {
return comparison;
}
return i1.compareTo(i2);
});
if (!sortedValues.equals(autoConfigureImports.imports)) {
File sortedOutputFile = getOutputDirectory().file("sorted-" + autoConfigureImports.fileName)
.get()
.getAsFile();
writeString(sortedOutputFile, sortedValues.stream().collect(Collectors.joining(System.lineSeparator()))
+ System.lineSeparator());
problems.add(
"Entries should be required then optional, each sorted alphabetically (expected content written to '%s')"
.formatted(sortedOutputFile.getAbsolutePath()));
}
if (!problems.isEmpty()) {
allProblems.computeIfAbsent(autoConfigureImports.fileName, (unused) -> new ArrayList<>())
.addAll(problems);
}
}
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
writeReport(allProblems, outputFile);
if (!allProblems.isEmpty()) {
throw new VerificationException(
"AutoConfigure….imports checks failed. See '%s' for details".formatted(outputFile));
}
}
private List<AutoConfigureImports> loadImports() {
return getSource().getFiles().stream().map((file) -> {
String fileName = file.getName();
String annotationName = fileName.substring(0, fileName.length() - ".imports".length());
return new AutoConfigureImports(annotationName, loadImports(file), fileName);
}).toList();
}
private List<String> loadImports(File importsFile) {
try {
return Files.readAllLines(importsFile.toPath());
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
private boolean find(String className) {
return find(className, (input) -> {
});
}
private boolean find(String className, Consumer<InputStream> handler) {
for (File root : this.classpath.getFiles()) {
String classFilePath = className.replace(".", "/") + ".class";
if (root.isDirectory()) {
File classFile = new File(root, classFilePath);
if (classFile.isFile()) {
try (InputStream input = new FileInputStream(classFile)) {
handler.accept(input);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
return true;
}
}
else {
try (JarFile jar = new JarFile(root)) {
ZipEntry entry = jar.getEntry(classFilePath);
if (entry != null) {
try (InputStream input = jar.getInputStream(entry)) {
handler.accept(input);
}
return true;
}
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}
return false;
}
private boolean correctlyAnnotated(InputStream classFile) {
return AutoConfigurationClass.of(classFile) != null;
}
private void writeReport(Map<String, List<String>> allProblems, File outputFile) {
outputFile.getParentFile().mkdirs();
StringBuilder report = new StringBuilder();
if (!allProblems.isEmpty()) {
allProblems.forEach((fileName, problems) -> {
report.append("Found problems in '%s':%n".formatted(fileName));
problems.forEach((problem) -> report.append(" - %s%n".formatted(problem)));
});
}
writeString(outputFile, report.toString());
}
private void writeString(File file, String content) {
try {
Files.writeString(file.toPath(), content);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
record AutoConfigureImports(String annotationName, List<String> imports, String fileName) {
}
}

View File

@ -17,14 +17,17 @@
package org.springframework.boot.build.test.autoconfigure; package org.springframework.boot.build.test.autoconfigure;
import java.io.File; import java.io.File;
import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.io.Reader;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.Enumeration;
import java.util.Map; import java.util.Properties;
import java.util.TreeMap; import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.gradle.api.DefaultTask; import org.gradle.api.DefaultTask;
import org.gradle.api.Task; import org.gradle.api.Task;
@ -35,9 +38,9 @@ import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskAction;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.build.test.autoconfigure.TestSliceMetadata.TestSlice; import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/** /**
* {@link Task} used to document test slices. * {@link Task} used to document test slices.
@ -46,16 +49,16 @@ import org.springframework.boot.build.test.autoconfigure.TestSliceMetadata.TestS
*/ */
public abstract class DocumentTestSlices extends DefaultTask { public abstract class DocumentTestSlices extends DefaultTask {
private FileCollection testSliceMetadata; private FileCollection testSlices;
@InputFiles @InputFiles
@PathSensitive(PathSensitivity.RELATIVE) @PathSensitive(PathSensitivity.RELATIVE)
public FileCollection getTestSlices() { public FileCollection getTestSlices() {
return this.testSliceMetadata; return this.testSlices;
} }
public void setTestSlices(FileCollection testSlices) { public void setTestSlices(FileCollection testSlices) {
this.testSliceMetadata = testSlices; this.testSlices = testSlices;
} }
@OutputFile @OutputFile
@ -63,42 +66,61 @@ public abstract class DocumentTestSlices extends DefaultTask {
@TaskAction @TaskAction
void documentTestSlices() throws IOException { void documentTestSlices() throws IOException {
Map<String, List<TestSlice>> testSlices = readTestSlices(); Set<TestSlice> testSlices = readTestSlices();
writeTable(testSlices); writeTable(testSlices);
} }
private Map<String, List<TestSlice>> readTestSlices() { @SuppressWarnings("unchecked")
Map<String, List<TestSlice>> testSlices = new TreeMap<>(); private Set<TestSlice> readTestSlices() throws IOException {
for (File metadataFile : this.testSliceMetadata) { Set<TestSlice> testSlices = new TreeSet<>();
JsonMapper mapper = JsonMapper.builder().build(); for (File metadataFile : this.testSlices) {
TestSliceMetadata metadata = mapper.readValue(metadataFile, TestSliceMetadata.class); Properties metadata = new Properties();
List<TestSlice> slices = new ArrayList<>(metadata.testSlices()); try (Reader reader = new FileReader(metadataFile)) {
Collections.sort(slices, (s1, s2) -> s1.annotation().compareTo(s2.annotation())); metadata.load(reader);
testSlices.put(metadata.module(), slices); }
for (String name : Collections.list((Enumeration<String>) metadata.propertyNames())) {
testSlices.add(new TestSlice(name,
new TreeSet<>(StringUtils.commaDelimitedListToSet(metadata.getProperty(name)))));
}
} }
return testSlices; return testSlices;
} }
private void writeTable(Map<String, List<TestSlice>> testSlicesByModule) throws IOException { private void writeTable(Set<TestSlice> testSlices) throws IOException {
File outputFile = getOutputFile().getAsFile().get(); File outputFile = getOutputFile().getAsFile().get();
outputFile.getParentFile().mkdirs(); outputFile.getParentFile().mkdirs();
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) { try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {
writer.println("[cols=\"d,d,a\"]"); writer.println("[cols=\"d,a\"]");
writer.println("|==="); writer.println("|===");
writer.println("|Module | Test slice | Imported auto-configuration"); writer.println("| Test slice | Imported auto-configuration");
testSlicesByModule.forEach((module, testSlices) -> { for (TestSlice testSlice : testSlices) {
testSlices.forEach((testSlice) -> { writer.println();
writer.println(); writer.printf("| `@%s`%n", testSlice.className);
writer.printf("| `%s`%n", module); writer.println("| ");
writer.printf("| javadoc:%s[format=annotation]%n", testSlice.annotation()); for (String importedAutoConfiguration : testSlice.importedAutoConfigurations) {
writer.println("| "); writer.printf("`%s`%n", importedAutoConfiguration);
for (String importedAutoConfiguration : testSlice.importedAutoConfigurations()) { }
writer.printf("`%s`%n", importedAutoConfiguration); }
}
});
});
writer.println("|==="); writer.println("|===");
} }
} }
private static final class TestSlice implements Comparable<TestSlice> {
private final String className;
private final SortedSet<String> importedAutoConfigurations;
private TestSlice(String className, SortedSet<String> importedAutoConfigurations) {
this.className = ClassUtils.getShortName(className);
this.importedAutoConfigurations = importedAutoConfigurations;
}
@Override
public int compareTo(TestSlice other) {
return this.className.compareTo(other.className);
}
}
} }

View File

@ -1,238 +0,0 @@
/*
* Copyright 2012-present 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 org.springframework.boot.build.test.autoconfigure;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.Task;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskAction;
import org.springframework.boot.build.test.autoconfigure.TestSliceMetadata.TestSlice;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.util.StringUtils;
/**
* A {@link Task} for generating metadata describing a project's test slices.
*
* @author Andy Wilkinson
*/
public abstract class GenerateTestSliceMetadata extends DefaultTask {
private final ObjectFactory objectFactory;
private FileCollection classpath;
private FileCollection importsFiles;
private FileCollection classesDirs;
@Inject
public GenerateTestSliceMetadata(ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
}
public void setSourceSet(SourceSet sourceSet) {
this.classpath = sourceSet.getRuntimeClasspath();
this.importsFiles = this.objectFactory.fileTree()
.from(new File(sourceSet.getOutput().getResourcesDir(), "META-INF/spring"));
this.importsFiles.filter((file) -> file.getName().endsWith(".imports"));
getSpringFactories().set(new File(sourceSet.getOutput().getResourcesDir(), "META-INF/spring.factories"));
this.classesDirs = sourceSet.getOutput().getClassesDirs();
}
@OutputFile
public abstract RegularFileProperty getOutputFile();
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
abstract RegularFileProperty getSpringFactories();
@Classpath
FileCollection getClasspath() {
return this.classpath;
}
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
FileCollection getImportFiles() {
return this.importsFiles;
}
@Classpath
FileCollection getClassesDirs() {
return this.classesDirs;
}
@TaskAction
void generateTestSliceMetadata() throws IOException {
TestSliceMetadata metadata = readTestSlices();
File outputFile = getOutputFile().getAsFile().get();
outputFile.getParentFile().mkdirs();
metadata.writeTo(outputFile);
}
private TestSliceMetadata readTestSlices() throws IOException {
List<TestSlice> testSlices = new ArrayList<>();
try (URLClassLoader classLoader = new URLClassLoader(
StreamSupport.stream(this.classpath.spliterator(), false).map(this::toURL).toArray(URL[]::new))) {
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(classLoader);
Properties springFactories = readSpringFactories(getSpringFactories().getAsFile().getOrNull());
readImportsFiles(springFactories, this.importsFiles);
for (File classesDir : this.classesDirs) {
testSlices.addAll(readTestSlices(classesDir, metadataReaderFactory, springFactories));
}
}
return new TestSliceMetadata(getProject().getName(), testSlices);
}
/**
* Reads the given imports files and puts them in springFactories. The key is the file
* name, the value is the file contents, split by line, delimited with a comma. This
* is done to mimic the spring.factories structure.
* @param springFactories spring.factories parsed as properties
* @param importsFiles the imports files to read
*/
private void readImportsFiles(Properties springFactories, FileCollection importsFiles) {
for (File file : importsFiles.getFiles()) {
try {
List<String> lines = removeComments(Files.readAllLines(file.toPath()));
String fileNameWithoutExtension = file.getName()
.substring(0, file.getName().length() - ".imports".length());
springFactories.setProperty(fileNameWithoutExtension,
StringUtils.collectionToCommaDelimitedString(lines));
}
catch (IOException ex) {
throw new UncheckedIOException("Failed to read file " + file, ex);
}
}
}
private List<String> removeComments(List<String> lines) {
List<String> result = new ArrayList<>();
for (String line : lines) {
int commentIndex = line.indexOf('#');
if (commentIndex > -1) {
line = line.substring(0, commentIndex);
}
line = line.trim();
if (!line.isEmpty()) {
result.add(line);
}
}
return result;
}
private URL toURL(File file) {
try {
return file.toURI().toURL();
}
catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
private Properties readSpringFactories(File file) throws IOException {
Properties springFactories = new Properties();
if (file.isFile()) {
try (Reader in = new FileReader(file)) {
springFactories.load(in);
}
}
return springFactories;
}
private List<TestSlice> readTestSlices(File classesDir, MetadataReaderFactory metadataReaderFactory,
Properties springFactories) throws IOException {
try (Stream<Path> classes = Files.walk(classesDir.toPath())) {
return classes.filter((path) -> path.toString().endsWith("Test.class"))
.map((path) -> getMetadataReader(path, metadataReaderFactory))
.filter((metadataReader) -> metadataReader.getClassMetadata().isAnnotation())
.map((metadataReader) -> readTestSlice(metadataReader, springFactories))
.toList();
}
}
private MetadataReader getMetadataReader(Path path, MetadataReaderFactory metadataReaderFactory) {
try {
return metadataReaderFactory.getMetadataReader(new FileSystemResource(path));
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private TestSlice readTestSlice(MetadataReader metadataReader, Properties springFactories) {
String annotationName = metadataReader.getClassMetadata().getClassName();
List<String> importedAutoConfiguration = getImportedAutoConfiguration(springFactories,
metadataReader.getAnnotationMetadata());
return new TestSlice(annotationName, importedAutoConfiguration);
}
private List<String> getImportedAutoConfiguration(Properties springFactories,
AnnotationMetadata annotationMetadata) {
Stream<String> importers = findMetaImporters(annotationMetadata);
if (annotationMetadata.isAnnotated("org.springframework.boot.autoconfigure.ImportAutoConfiguration")) {
importers = Stream.concat(importers, Stream.of(annotationMetadata.getClassName()));
}
return importers
.flatMap((importer) -> StringUtils.commaDelimitedListToSet(springFactories.getProperty(importer)).stream())
.toList();
}
private Stream<String> findMetaImporters(AnnotationMetadata annotationMetadata) {
return annotationMetadata.getAnnotationTypes()
.stream()
.filter((annotationType) -> isAutoConfigurationImporter(annotationType, annotationMetadata));
}
private boolean isAutoConfigurationImporter(String annotationType, AnnotationMetadata metadata) {
return metadata.getMetaAnnotationTypes(annotationType)
.contains("org.springframework.boot.autoconfigure.ImportAutoConfiguration");
}
}

View File

@ -1,61 +0,0 @@
/*
* Copyright 2012-present 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 org.springframework.boot.build.test.autoconfigure;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
/**
* {@link Plugin} for projects that define test auto-configuration. When the
* {@link JavaPlugin} is applied it:
*
* <ul>
* <li>Add checks to ensure AutoConfigure*.import files and related annotations are
* correct</li>
* </ul>
*
* @author Andy Wilkinson
*/
public class TestAutoConfigurationPlugin implements Plugin<Project> {
@Override
public void apply(Project target) {
target.getPlugins().withType(JavaPlugin.class, (plugin) -> {
TaskProvider<CheckAutoConfigureImports> checkAutoConfigureImports = target.getTasks()
.register("checkAutoConfigureImports", CheckAutoConfigureImports.class, (task) -> {
SourceSet mainSourceSet = target.getExtensions()
.getByType(JavaPluginExtension.class)
.getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
task.setSource(mainSourceSet.getResources());
ConfigurableFileCollection classpath = target.files(mainSourceSet.getRuntimeClasspath(),
target.getConfigurations().getByName(mainSourceSet.getRuntimeClasspathConfigurationName()));
task.setClasspath(classpath);
});
target.getTasks()
.named(LifecycleBasePlugin.CHECK_TASK_NAME)
.configure((check) -> check.dependsOn(checkAutoConfigureImports));
});
}
}

View File

@ -17,30 +17,229 @@
package org.springframework.boot.build.test.autoconfigure; package org.springframework.boot.build.test.autoconfigure;
import java.io.File; import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Properties;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import tools.jackson.databind.SerializationFeature; import javax.inject.Inject;
import tools.jackson.databind.json.JsonMapper;
import org.gradle.api.DefaultTask;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskAction;
import org.springframework.core.CollectionFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.util.StringUtils;
/** /**
* Metadata describing a module's test slices. * A {@link Task} for generating metadata describing a project's test slices.
* *
* @param module the module's name
* @param testSlices the module's test slices
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
record TestSliceMetadata(String module, List<TestSlice> testSlices) { public abstract class TestSliceMetadata extends DefaultTask {
static TestSliceMetadata readFrom(File file) { private final ObjectFactory objectFactory;
return JsonMapper.builder().build().readValue(file, TestSliceMetadata.class);
private FileCollection classpath;
private FileCollection importsFiles;
private FileCollection classesDirs;
@Inject
public TestSliceMetadata(ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
Configuration testSliceMetadata = getProject().getConfigurations().maybeCreate("testSliceMetadata");
getProject().afterEvaluate((evaluated) -> evaluated.getArtifacts()
.add(testSliceMetadata.getName(), getOutputFile(), (artifact) -> artifact.builtBy(this)));
} }
void writeTo(File file) { public void setSourceSet(SourceSet sourceSet) {
JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build().writeValue(file, this); this.classpath = sourceSet.getRuntimeClasspath();
this.importsFiles = this.objectFactory.fileTree()
.from(new File(sourceSet.getOutput().getResourcesDir(), "META-INF/spring"));
this.importsFiles.filter((file) -> file.getName().endsWith(".imports"));
getSpringFactories().set(new File(sourceSet.getOutput().getResourcesDir(), "META-INF/spring.factories"));
this.classesDirs = sourceSet.getOutput().getClassesDirs();
} }
record TestSlice(String annotation, List<String> importedAutoConfigurations) { @OutputFile
public abstract RegularFileProperty getOutputFile();
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
abstract RegularFileProperty getSpringFactories();
@Classpath
FileCollection getClasspath() {
return this.classpath;
}
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
FileCollection getImportFiles() {
return this.importsFiles;
}
@Classpath
FileCollection getClassesDirs() {
return this.classesDirs;
}
@TaskAction
void documentTestSlices() throws IOException {
Properties testSlices = readTestSlices();
File outputFile = getOutputFile().getAsFile().get();
outputFile.getParentFile().mkdirs();
try (FileWriter writer = new FileWriter(outputFile)) {
testSlices.store(writer, null);
}
}
private Properties readTestSlices() throws IOException {
Properties testSlices = CollectionFactory.createSortedProperties(true);
try (URLClassLoader classLoader = new URLClassLoader(
StreamSupport.stream(this.classpath.spliterator(), false).map(this::toURL).toArray(URL[]::new))) {
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(classLoader);
Properties springFactories = readSpringFactories(getSpringFactories().getAsFile().get());
readImportsFiles(springFactories, this.importsFiles);
for (File classesDir : this.classesDirs) {
addTestSlices(testSlices, classesDir, metadataReaderFactory, springFactories);
}
}
return testSlices;
}
/**
* Reads the given imports files and puts them in springFactories. The key is the file
* name, the value is the file contents, split by line, delimited with a comma. This
* is done to mimic the spring.factories structure.
* @param springFactories spring.factories parsed as properties
* @param importsFiles the imports files to read
*/
private void readImportsFiles(Properties springFactories, FileCollection importsFiles) {
for (File file : importsFiles.getFiles()) {
try {
List<String> lines = removeComments(Files.readAllLines(file.toPath()));
String fileNameWithoutExtension = file.getName()
.substring(0, file.getName().length() - ".imports".length());
springFactories.setProperty(fileNameWithoutExtension,
StringUtils.collectionToCommaDelimitedString(lines));
}
catch (IOException ex) {
throw new UncheckedIOException("Failed to read file " + file, ex);
}
}
}
private List<String> removeComments(List<String> lines) {
List<String> result = new ArrayList<>();
for (String line : lines) {
int commentIndex = line.indexOf('#');
if (commentIndex > -1) {
line = line.substring(0, commentIndex);
}
line = line.trim();
if (!line.isEmpty()) {
result.add(line);
}
}
return result;
}
private URL toURL(File file) {
try {
return file.toURI().toURL();
}
catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
private Properties readSpringFactories(File file) throws IOException {
Properties springFactories = new Properties();
try (Reader in = new FileReader(file)) {
springFactories.load(in);
}
return springFactories;
}
private void addTestSlices(Properties testSlices, File classesDir, MetadataReaderFactory metadataReaderFactory,
Properties springFactories) throws IOException {
try (Stream<Path> classes = Files.walk(classesDir.toPath())) {
classes.filter((path) -> path.toString().endsWith("Test.class"))
.map((path) -> getMetadataReader(path, metadataReaderFactory))
.filter((metadataReader) -> metadataReader.getClassMetadata().isAnnotation())
.forEach((metadataReader) -> addTestSlice(testSlices, springFactories, metadataReader));
}
}
private MetadataReader getMetadataReader(Path path, MetadataReaderFactory metadataReaderFactory) {
try {
return metadataReaderFactory.getMetadataReader(new FileSystemResource(path));
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private void addTestSlice(Properties testSlices, Properties springFactories, MetadataReader metadataReader) {
testSlices.setProperty(metadataReader.getClassMetadata().getClassName(),
StringUtils.collectionToCommaDelimitedString(
getImportedAutoConfiguration(springFactories, metadataReader.getAnnotationMetadata())));
}
private SortedSet<String> getImportedAutoConfiguration(Properties springFactories,
AnnotationMetadata annotationMetadata) {
Stream<String> importers = findMetaImporters(annotationMetadata);
if (annotationMetadata.isAnnotated("org.springframework.boot.autoconfigure.ImportAutoConfiguration")) {
importers = Stream.concat(importers, Stream.of(annotationMetadata.getClassName()));
}
return importers
.flatMap((importer) -> StringUtils.commaDelimitedListToSet(springFactories.getProperty(importer)).stream())
.collect(Collectors.toCollection(TreeSet::new));
}
private Stream<String> findMetaImporters(AnnotationMetadata annotationMetadata) {
return annotationMetadata.getAnnotationTypes()
.stream()
.filter((annotationType) -> isAutoConfigurationImporter(annotationType, annotationMetadata));
}
private boolean isAutoConfigurationImporter(String annotationType, AnnotationMetadata metadata) {
return metadata.getMetaAnnotationTypes(annotationType)
.contains("org.springframework.boot.autoconfigure.ImportAutoConfiguration");
} }
} }

View File

@ -1,78 +0,0 @@
/*
* Copyright 2012-present 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 org.springframework.boot.build.test.autoconfigure;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.attributes.Category;
import org.gradle.api.attributes.Usage;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.PluginContainer;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskProvider;
/**
* {@link Plugin} for projects that define one or more test slices. When applied, it:
*
* <ul>
* <li>Applies the {@link TestAutoConfigurationPlugin}
* </ul>
* Additionally, when the {@link JavaPlugin} is applied it:
*
* <ul>
* <li>Defines a task that produces metadata describing the test slices. The metadata is
* made available as an artifact in the {@code testSliceMetadata} configuration
* </ul>
*
* @author Andy Wilkinson
*/
public class TestSlicePlugin implements Plugin<Project> {
private static final String TEST_SLICE_METADATA_CONFIGURATION_NAME = "testSliceMetadata";
@Override
public void apply(Project target) {
PluginContainer plugins = target.getPlugins();
plugins.apply(TestAutoConfigurationPlugin.class);
plugins.withType(JavaPlugin.class, (plugin) -> {
TaskProvider<GenerateTestSliceMetadata> generateTestSliceMetadata = target.getTasks()
.register("generateTestSliceMetadata", GenerateTestSliceMetadata.class, (task) -> {
SourceSet mainSourceSet = target.getExtensions()
.getByType(JavaPluginExtension.class)
.getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
task.setSourceSet(mainSourceSet);
task.getOutputFile().set(target.getLayout().getBuildDirectory().file("test-slice-metadata.json"));
});
addMetadataArtifact(target, generateTestSliceMetadata);
});
}
private void addMetadataArtifact(Project project, TaskProvider<GenerateTestSliceMetadata> task) {
project.getConfigurations().consumable(TEST_SLICE_METADATA_CONFIGURATION_NAME, (configuration) -> {
configuration.attributes((attributes) -> {
attributes.attribute(Category.CATEGORY_ATTRIBUTE,
project.getObjects().named(Category.class, Category.DOCUMENTATION));
attributes.attribute(Usage.USAGE_ATTRIBUTE,
project.getObjects().named(Usage.class, "test-slice-metadata"));
});
});
project.getArtifacts().add(TEST_SLICE_METADATA_CONFIGURATION_NAME, task);
}
}

View File

@ -116,18 +116,6 @@ apiref-openjdk=https://docs.oracle.com/en/java/javase/17/docs/api
# === Code Links === # === Code Links ===
code-spring-boot=https://github.com/{github-repo}/tree/{github-ref} code-spring-boot=https://github.com/{github-repo}/tree/{github-ref}
code-spring-boot-autoconfigure-src={code-spring-boot}/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure code-spring-boot-autoconfigure-src={code-spring-boot}/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure
code-spring-boot-batch-jdbc-src={code-spring-boot}/module/spring-boot-batch-jdbc/src/main/java/org/springframework/boot/batch/jdbc
code-spring-boot-batch-src={code-spring-boot}/module/spring-boot-batch/src/main/java/org/springframework/boot/batch
code-spring-boot-freemarker-src={code-spring-boot}/module/spring-boot-freemarker/src/main/java/org/springframework/boot/freemarker
code-spring-boot-groovy-templates-src={code-spring-boot}/module/spring-boot-groovy-templates/src/main/java/org/springframework/boot/groovy/template
code-spring-boot-hibernate-src={code-spring-boot}/module/spring-boot-hibernate/src/main/java/org/springframework/boot/hibernate
code-spring-boot-integration-src={code-spring-boot}/module/spring-boot-integration/src/main/java/org/springframework/boot/integration
code-spring-boot-jdbc-src={code-spring-boot}/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc
code-spring-boot-jooq-src={code-spring-boot}/module/spring-boot-jooq/src/main/java/org/springframework/boot/jooq
code-spring-boot-jpa-src={code-spring-boot}/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa
code-spring-boot-latest=https://github.com/{github-repo}/tree/main code-spring-boot-latest=https://github.com/{github-repo}/tree/main
code-spring-boot-servlet-src={code-spring-boot}/module/spring-boot-servlet/src/main/java/org/springframework/boot/servlet
code-spring-boot-thymeleaf-src={code-spring-boot}/module/spring-boot-thymeleaf/src/main/java/org/springframework/boot/thymeleaf
code-spring-boot-webmvc-src={code-spring-boot}/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc

View File

@ -77,9 +77,7 @@ class ConventionsPluginTests {
out.println(" id 'org.springframework.boot.conventions'"); out.println(" id 'org.springframework.boot.conventions'");
out.println("}"); out.println("}");
out.println("version = '1.2.3'"); out.println("version = '1.2.3'");
out.println("java {"); out.println("sourceCompatibility = '17'");
out.println(" sourceCompatibility = '17'");
out.println("}");
out.println("description 'Test project for manifest customization'"); out.println("description 'Test project for manifest customization'");
out.println("jar.archiveFileName = 'test.jar'"); out.println("jar.archiveFileName = 'test.jar'");
} }
@ -109,9 +107,7 @@ class ConventionsPluginTests {
out.println(" id 'org.springframework.boot.conventions'"); out.println(" id 'org.springframework.boot.conventions'");
out.println("}"); out.println("}");
out.println("version = '1.2.3'"); out.println("version = '1.2.3'");
out.println("java {"); out.println("sourceCompatibility = '17'");
out.println(" sourceCompatibility = '17'");
out.println("}");
out.println("description 'Test'"); out.println("description 'Test'");
} }
runGradle("assemble"); runGradle("assemble");
@ -140,9 +136,7 @@ class ConventionsPluginTests {
out.println(" id 'org.springframework.boot.conventions'"); out.println(" id 'org.springframework.boot.conventions'");
out.println("}"); out.println("}");
out.println("version = '1.2.3'"); out.println("version = '1.2.3'");
out.println("java {"); out.println("sourceCompatibility = '17'");
out.println(" sourceCompatibility = '17'");
out.println("}");
out.println("description 'Test'"); out.println("description 'Test'");
} }
runGradle("assemble"); runGradle("assemble");

View File

@ -57,8 +57,6 @@ class ArchitectureCheckTests {
private static final String SPRING_CONTEXT = "org.springframework:spring-context:6.2.9"; private static final String SPRING_CONTEXT = "org.springframework:spring-context:6.2.9";
private static final String JUNIT_JUPITER = "org.junit.jupiter:junit-jupiter:5.12.0";
private static final String SPRING_INTEGRATION_JMX = "org.springframework.integration:spring-integration-jmx:6.5.1"; private static final String SPRING_INTEGRATION_JMX = "org.springframework.integration:spring-integration-jmx:6.5.1";
private GradleBuild gradleBuild; private GradleBuild gradleBuild;
@ -285,29 +283,6 @@ class ArchitectureCheckTests {
build(this.gradleBuild.withNullMarked(true), Task.CHECK_ARCHITECTURE_TEST); build(this.gradleBuild.withNullMarked(true), Task.CHECK_ARCHITECTURE_TEST);
} }
@Test
void whenEnumSourceValueIsInferredShouldSucceedAndWriteEmptyReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_TEST, "junit/enumsource/inferredfromparametertype");
build(this.gradleBuild.withDependencies(JUNIT_JUPITER), Task.CHECK_ARCHITECTURE_TEST);
}
@Test
void whenEnumSourceValueIsNotTheSameAsTypeOfMethodsFirstParameterShouldSucceedAndWriteEmptyReport()
throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_TEST, "junit/enumsource/valuenecessary");
build(this.gradleBuild.withDependencies(JUNIT_JUPITER), Task.CHECK_ARCHITECTURE_TEST);
}
@Test
void whenEnumSourceValueIsSameAsTypeOfMethodsFirstParameterShouldFailAndWriteReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_TEST, "junit/enumsource/sameasparametertype");
buildAndFail(this.gradleBuild.withDependencies(JUNIT_JUPITER), Task.CHECK_ARCHITECTURE_TEST,
"method <org.springframework.boot.build.architecture.junit.enumsource.sameasparametertype"
+ ".EnumSourceSameAsParameterType.exampleMethod(org.springframework.boot.build."
+ "architecture.junit.enumsource.sameasparametertype.EnumSourceSameAsParameterType$Example)>",
"should not have a value that is the same as the type of the method's first parameter");
}
private void prepareTask(Task task, String... sourceDirectories) throws IOException { private void prepareTask(Task task, String... sourceDirectories) throws IOException {
for (String sourceDirectory : sourceDirectories) { for (String sourceDirectory : sourceDirectories) {
FileSystemUtils.copyRecursively( FileSystemUtils.copyRecursively(

View File

@ -1,34 +0,0 @@
/*
* Copyright 2012-present 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 org.springframework.boot.build.architecture.junit.enumsource.inferredfromparametertype;
import org.junit.jupiter.params.provider.EnumSource;
class EnumSourceInferredFromParameterType {
@EnumSource
void exampleMethod(Example example) {
}
enum Example {
ONE, TWO, THREE
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2012-present 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 org.springframework.boot.build.architecture.junit.enumsource.sameasparametertype;
import org.junit.jupiter.params.provider.EnumSource;
class EnumSourceSameAsParameterType {
@EnumSource(Example.class)
void exampleMethod(Example example) {
}
enum Example {
ONE, TWO, THREE
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2012-present 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 org.springframework.boot.build.architecture.junit.enumsource.valuenecessary;
import org.junit.jupiter.params.provider.EnumSource;
class EnumSourceValueNecessary {
@EnumSource(Example.class)
void exampleMethod(String thing, Example example) {
}
enum Example {
ONE, TWO, THREE
}
}

View File

@ -31,7 +31,6 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.jupiter.params.support.ParameterDeclarations;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -245,8 +244,7 @@ class DependencyVersionUpgradeTests {
static class InputProvider implements ArgumentsProvider { static class InputProvider implements ArgumentsProvider {
@Override @Override
public Stream<? extends Arguments> provideArguments(ParameterDeclarations parameterDeclarations, public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
ExtensionContext context) {
Method testMethod = context.getRequiredTestMethod(); Method testMethod = context.getRequiredTestMethod();
Stream<Arguments> artifactVersions = artifactVersions(testMethod) Stream<Arguments> artifactVersions = artifactVersions(testMethod)
.map((artifactVersion) -> Arguments.of(VersionType.ARTIFACT_VERSION.parse(artifactVersion.current()), .map((artifactVersion) -> Arguments.of(VersionType.ARTIFACT_VERSION.parse(artifactVersion.current()),

View File

@ -1,50 +0,0 @@
/*
* Copyright 2012-present 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 org.springframework.boot.build.test.autoconfigure;
import java.io.File;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.build.test.autoconfigure.TestSliceMetadata.TestSlice;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link TestSliceMetadata}.
*
* @author Andy Wilkinson
*/
public class TestSliceMetadataTests {
@TempDir
private File temp;
@Test
void roundtripJson() {
TestSliceMetadata source = new TestSliceMetadata("example",
List.of(new TestSlice("ExampleOneTest", List.of("com.example.OneAutoConfiguration")),
new TestSlice("ExampleTwoTest", List.of("com.example.TwoAutoConfiguration"))));
File metadataFile = new File(this.temp, "metadata.json");
source.writeTo(metadataFile);
TestSliceMetadata readBack = TestSliceMetadata.readFrom(metadataFile);
assertThat(source).isEqualTo(readBack);
}
}

View File

@ -166,7 +166,6 @@ class TestFailuresPluginIntegrationTests {
writer.println("dependencies {"); writer.println("dependencies {");
writer.println(" testImplementation 'org.junit.jupiter:junit-jupiter:5.6.0'"); writer.println(" testImplementation 'org.junit.jupiter:junit-jupiter:5.6.0'");
writer.println(" testImplementation 'org.assertj:assertj-core:3.11.1'"); writer.println(" testImplementation 'org.assertj:assertj-core:3.11.1'");
writer.println(" testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.6.0'");
writer.println("}"); writer.println("}");
writer.println(); writer.println();
writer.println("test {"); writer.println("test {");

View File

@ -21,11 +21,11 @@ import java.util.function.Consumer;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent; import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent;
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.docker.type.VolumeName;

View File

@ -21,7 +21,7 @@ import java.util.stream.IntStream;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.ApiVersion; import org.springframework.boot.buildpack.platform.docker.type.ApiVersion;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**

View File

@ -21,11 +21,11 @@ import java.util.function.Consumer;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent; import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent;
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.docker.type.VolumeName;

View File

@ -28,8 +28,8 @@ import java.util.function.Function;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.Owner;
import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.io.TarArchive;

View File

@ -24,7 +24,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.DockerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi;
import org.springframework.boot.buildpack.platform.docker.DockerLog; import org.springframework.boot.buildpack.platform.docker.DockerLog;
import org.springframework.boot.buildpack.platform.docker.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener; import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener;
import org.springframework.boot.buildpack.platform.docker.TotalProgressPushListener; import org.springframework.boot.buildpack.platform.docker.TotalProgressPushListener;
@ -35,6 +34,7 @@ import org.springframework.boot.buildpack.platform.docker.transport.DockerEngine
import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive; import org.springframework.boot.buildpack.platform.docker.type.ImageArchive;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.io.IOBiConsumer; import org.springframework.boot.buildpack.platform.io.IOBiConsumer;
import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.io.TarArchive;

View File

@ -31,7 +31,7 @@ import tools.jackson.databind.node.ObjectNode;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -149,7 +149,7 @@ class BuilderMetadata extends MappedObject {
*/ */
void attachTo(ImageConfig.Update update) { void attachTo(ImageConfig.Update update) {
try { try {
String json = SharedJsonMapper.get().writeValueAsString(getNode()); String json = SharedObjectMapper.get().writeValueAsString(getNode());
update.withLabel(LABEL_NAME, json); update.withLabel(LABEL_NAME, json);
} }
catch (JacksonException ex) { catch (JacksonException ex) {
@ -189,7 +189,7 @@ class BuilderMetadata extends MappedObject {
* @throws IOException on IO error * @throws IOException on IO error
*/ */
static BuilderMetadata fromJson(String json) throws IOException { static BuilderMetadata fromJson(String json) throws IOException {
return new BuilderMetadata(SharedJsonMapper.get().readTree(json)); return new BuilderMetadata(SharedObjectMapper.get().readTree(json));
} }
/** /**

View File

@ -27,7 +27,7 @@ import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -90,7 +90,7 @@ final class BuildpackLayersMetadata extends MappedObject {
* @throws IOException on IO error * @throws IOException on IO error
*/ */
static BuildpackLayersMetadata fromJson(String json) throws IOException { static BuildpackLayersMetadata fromJson(String json) throws IOException {
return fromJson(SharedJsonMapper.get().readTree(json)); return fromJson(SharedObjectMapper.get().readTree(json));
} }
/** /**

View File

@ -25,7 +25,7 @@ import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -113,7 +113,7 @@ final class BuildpackMetadata extends MappedObject {
* @throws IOException on IO error * @throws IOException on IO error
*/ */
static BuildpackMetadata fromJson(String json) throws IOException { static BuildpackMetadata fromJson(String json) throws IOException {
return fromJson(SharedJsonMapper.get().readTree(json)); return fromJson(SharedObjectMapper.get().readTree(json));
} }
/** /**

View File

@ -28,10 +28,10 @@ import com.sun.jna.Platform;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.build.Cache.Bind; import org.springframework.boot.buildpack.platform.build.Cache.Bind;
import org.springframework.boot.buildpack.platform.docker.ApiVersion;
import org.springframework.boot.buildpack.platform.docker.DockerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi;
import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent; import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent;
import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost;
import org.springframework.boot.buildpack.platform.docker.type.ApiVersion;
import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig;
import org.springframework.boot.buildpack.platform.docker.type.ContainerContent; import org.springframework.boot.buildpack.platform.docker.type.ContainerContent;

View File

@ -34,18 +34,20 @@ import org.springframework.boot.buildpack.platform.docker.PushImageUpdateEvent.E
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport;
import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response;
import org.springframework.boot.buildpack.platform.docker.type.ApiVersion;
import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig;
import org.springframework.boot.buildpack.platform.docker.type.ContainerContent; import org.springframework.boot.buildpack.platform.docker.type.ContainerContent;
import org.springframework.boot.buildpack.platform.docker.type.ContainerReference; import org.springframework.boot.buildpack.platform.docker.type.ContainerReference;
import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus; import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive; import org.springframework.boot.buildpack.platform.docker.type.ImageArchive;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
import org.springframework.boot.buildpack.platform.io.IOBiConsumer; import org.springframework.boot.buildpack.platform.io.IOBiConsumer;
import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.io.TarArchive;
import org.springframework.boot.buildpack.platform.json.JsonStream; import org.springframework.boot.buildpack.platform.json.JsonStream;
import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -111,7 +113,7 @@ public class DockerApi {
Assert.notNull(http, "'http' must not be null"); Assert.notNull(http, "'http' must not be null");
Assert.notNull(log, "'log' must not be null"); Assert.notNull(log, "'log' must not be null");
this.http = http; this.http = http;
this.jsonStream = new JsonStream(SharedJsonMapper.get()); this.jsonStream = new JsonStream(SharedObjectMapper.get());
this.image = new ImageApi(); this.image = new ImageApi();
this.container = new ContainerApi(); this.container = new ContainerApi();
this.volume = new VolumeApi(); this.volume = new VolumeApi();
@ -397,7 +399,7 @@ public class DockerApi {
: buildUrl("/containers/create"); : buildUrl("/containers/create");
try (Response response = http().post(createUri, "application/json", config::writeTo)) { try (Response response = http().post(createUri, "application/json", config::writeTo)) {
return ContainerReference return ContainerReference
.of(SharedJsonMapper.get().readTree(response.getContent()).at("/Id").asString()); .of(SharedObjectMapper.get().readTree(response.getContent()).at("/Id").asString());
} }
} }

View File

@ -27,7 +27,7 @@ import java.util.Set;
import com.sun.jna.Platform; import com.sun.jna.Platform;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
/** /**
* Invokes a Docker credential helper executable that can be used to get {@link Credential * Invokes a Docker credential helper executable that can be used to get {@link Credential
@ -59,7 +59,7 @@ class CredentialHelper {
int exitCode = process.waitFor(); int exitCode = process.waitFor();
try (InputStream response = process.getInputStream()) { try (InputStream response = process.getInputStream()) {
if (exitCode == 0) { if (exitCode == 0) {
return new Credential(SharedJsonMapper.get().readTree(response)); return new Credential(SharedObjectMapper.get().readTree(response));
} }
String errorMessage = new String(response.readAllBytes(), StandardCharsets.UTF_8); String errorMessage = new String(response.readAllBytes(), StandardCharsets.UTF_8);
if (!isCredentialsNotFoundError(errorMessage)) { if (!isCredentialsNotFoundError(errorMessage)) {

View File

@ -34,7 +34,7 @@ import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.NullNode; import tools.jackson.databind.node.NullNode;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.boot.buildpack.platform.system.Environment; import org.springframework.boot.buildpack.platform.system.Environment;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -202,7 +202,7 @@ final class DockerConfigurationMetadata {
} }
static DockerConfig fromJson(String json) { static DockerConfig fromJson(String json) {
return new DockerConfig(SharedJsonMapper.get().readTree(json)); return new DockerConfig(SharedObjectMapper.get().readTree(json));
} }
static DockerConfig empty() { static DockerConfig empty() {
@ -286,7 +286,7 @@ final class DockerConfigurationMetadata {
} }
static DockerContext fromJson(String json) { static DockerContext fromJson(String json) {
return new DockerContext(SharedJsonMapper.get().readTree(json), null); return new DockerContext(SharedObjectMapper.get().readTree(json), null);
} }
static DockerContext empty() { static DockerContext empty() {

View File

@ -22,7 +22,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException; import tools.jackson.core.JacksonException;
import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
/** /**
* {@link DockerRegistryAuthentication} that uses a Base64 encoded auth header value based * {@link DockerRegistryAuthentication} that uses a Base64 encoded auth header value based
@ -42,7 +42,7 @@ class JsonEncodedDockerRegistryAuthentication implements DockerRegistryAuthentic
protected void createAuthHeader() { protected void createAuthHeader() {
try { try {
this.authHeader = Base64.getUrlEncoder().encodeToString(SharedJsonMapper.get().writeValueAsBytes(this)); this.authHeader = Base64.getUrlEncoder().encodeToString(SharedObjectMapper.get().writeValueAsBytes(this));
} }
catch (JacksonException ex) { catch (JacksonException ex) {
throw new IllegalStateException("Error creating Docker registry authentication header", ex); throw new IllegalStateException("Error creating Docker registry authentication header", ex);

View File

@ -42,7 +42,7 @@ import tools.jackson.core.JacksonException;
import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.Content;
import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.IOConsumer;
import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -195,7 +195,7 @@ abstract class HttpClientTransport implements HttpTransport {
return null; return null;
} }
try { try {
return SharedJsonMapper.get().readValue(content, Errors.class); return SharedObjectMapper.get().readValue(content, Errors.class);
} }
catch (JacksonException ex) { catch (JacksonException ex) {
return null; return null;
@ -207,7 +207,7 @@ abstract class HttpClientTransport implements HttpTransport {
return null; return null;
} }
try { try {
Message message = SharedJsonMapper.get().readValue(content, Message.class); Message message = SharedObjectMapper.get().readValue(content, Message.class);
return (message.getMessage() != null) ? message : null; return (message.getMessage() != null) ? message : null;
} }
catch (JacksonException ex) { catch (JacksonException ex) {

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.buildpack.platform.docker; package org.springframework.boot.buildpack.platform.docker.type;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -28,7 +28,7 @@ import org.springframework.util.Assert;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick * @author Scott Frederick
* @since 4.0.0 * @since 3.4.0
*/ */
public final class ApiVersion { public final class ApiVersion {

View File

@ -27,11 +27,11 @@ import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.ArrayNode;
import tools.jackson.databind.node.ObjectNode; import tools.jackson.databind.node.ObjectNode;
import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
@ -54,8 +54,8 @@ public class ContainerConfig {
List<String> securityOptions) { List<String> securityOptions) {
Assert.notNull(image, "'image' must not be null"); Assert.notNull(image, "'image' must not be null");
Assert.hasText(command, "'command' must not be empty"); Assert.hasText(command, "'command' must not be empty");
JsonMapper jsonMapper = SharedJsonMapper.get(); ObjectMapper objectMapper = SharedObjectMapper.get();
ObjectNode node = jsonMapper.createObjectNode(); ObjectNode node = objectMapper.createObjectNode();
if (StringUtils.hasText(user)) { if (StringUtils.hasText(user)) {
node.put("User", user); node.put("User", user);
} }
@ -77,7 +77,7 @@ public class ContainerConfig {
ArrayNode securityOptsNode = hostConfigNode.putArray("SecurityOpt"); ArrayNode securityOptsNode = hostConfigNode.putArray("SecurityOpt");
securityOptions.forEach(securityOptsNode::add); securityOptions.forEach(securityOptsNode::add);
} }
this.json = jsonMapper.writeValueAsString(node); this.json = objectMapper.writeValueAsString(node);
} }
/** /**

View File

@ -41,7 +41,7 @@ import org.springframework.boot.buildpack.platform.io.InspectedContent;
import org.springframework.boot.buildpack.platform.io.Layout; import org.springframework.boot.buildpack.platform.io.Layout;
import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.Owner;
import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.io.TarArchive;
import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -273,7 +273,7 @@ public class ImageArchive implements TarArchive {
private ImageArchive applyTo(IOConsumer<Update> update) throws IOException { private ImageArchive applyTo(IOConsumer<Update> update) throws IOException {
update.accept(this); update.accept(this);
Instant createDate = (this.createDate != null) ? this.createDate : WINDOWS_EPOCH_PLUS_SECOND; Instant createDate = (this.createDate != null) ? this.createDate : WINDOWS_EPOCH_PLUS_SECOND;
return new ImageArchive(SharedJsonMapper.get(), this.config, createDate, this.tag, this.image.getOs(), return new ImageArchive(SharedObjectMapper.get(), this.config, createDate, this.tag, this.image.getOs(),
this.image.getArchitecture(), this.image.getVariant(), this.image.getLayers(), this.image.getArchitecture(), this.image.getVariant(), this.image.getLayers(),
Collections.unmodifiableList(this.newLayers)); Collections.unmodifiableList(this.newLayers));
} }

View File

@ -14,20 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.buildpack.platform.docker; package org.springframework.boot.buildpack.platform.docker.type;
import java.util.Objects; import java.util.Objects;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* A platform specification for a Docker image. * A platform specification for a Docker image.
* *
* @author Scott Frederick * @author Scott Frederick
* @since 4.0.0 * @since 3.4.0
*/ */
public class ImagePlatform { public class ImagePlatform {

View File

@ -23,7 +23,7 @@ import java.util.function.Consumer;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.JsonParser; import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken; import tools.jackson.core.JsonToken;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.node.ObjectNode; import tools.jackson.databind.node.ObjectNode;
/** /**
@ -34,15 +34,14 @@ import tools.jackson.databind.node.ObjectNode;
*/ */
public class JsonStream { public class JsonStream {
private final JsonMapper jsonMapper; private final ObjectMapper objectMapper;
/** /**
* Create a new {@link JsonStream} backed by the given JSON mapper. * Create a new {@link JsonStream} backed by the given object mapper.
* @param jsonMapper the object mapper to use * @param objectMapper the object mapper to use
* @since 4.0.0
*/ */
public JsonStream(JsonMapper jsonMapper) { public JsonStream(ObjectMapper objectMapper) {
this.jsonMapper = jsonMapper; this.objectMapper = objectMapper;
} }
/** /**
@ -64,7 +63,7 @@ public class JsonStream {
* @throws IOException on IO error * @throws IOException on IO error
*/ */
public <T> void get(InputStream content, Class<T> type, Consumer<T> consumer) throws IOException { public <T> void get(InputStream content, Class<T> type, Consumer<T> consumer) throws IOException {
try (JsonParser parser = this.jsonMapper.createParser(content)) { try (JsonParser parser = this.objectMapper.createParser(content)) {
while (!parser.isClosed()) { while (!parser.isClosed()) {
JsonToken token = parser.nextToken(); JsonToken token = parser.nextToken();
if (token != null && token != JsonToken.END_OBJECT) { if (token != null && token != JsonToken.END_OBJECT) {
@ -80,13 +79,13 @@ public class JsonStream {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> @Nullable T read(JsonParser parser, Class<T> type) { private <T> @Nullable T read(JsonParser parser, Class<T> type) {
if (ObjectNode.class.isAssignableFrom(type)) { if (ObjectNode.class.isAssignableFrom(type)) {
ObjectNode node = (ObjectNode) this.jsonMapper.readTree(parser); ObjectNode node = (ObjectNode) this.objectMapper.readTree(parser);
if (node == null || node.isMissingNode() || node.isEmpty()) { if (node == null || node.isMissingNode() || node.isEmpty()) {
return null; return null;
} }
return (T) node; return (T) node;
} }
return this.jsonMapper.readValue(parser, type); return this.objectMapper.readValue(parser, type);
} }
} }

View File

@ -34,7 +34,6 @@ import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException; import tools.jackson.core.JacksonException;
import tools.jackson.databind.JsonNode; import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
@ -146,7 +145,7 @@ public class MappedObject {
return null; return null;
} }
try { try {
return SharedJsonMapper.get().treeToValue(result, type); return SharedObjectMapper.get().treeToValue(result, type);
} }
catch (JacksonException ex) { catch (JacksonException ex) {
throw new IllegalStateException(ex); throw new IllegalStateException(ex);
@ -190,8 +189,8 @@ public class MappedObject {
*/ */
protected static <T extends MappedObject, C> T of(C content, ContentReader<C> reader, Function<JsonNode, T> factory) protected static <T extends MappedObject, C> T of(C content, ContentReader<C> reader, Function<JsonNode, T> factory)
throws IOException { throws IOException {
JsonMapper jsonMapper = SharedJsonMapper.get(); ObjectMapper objectMapper = SharedObjectMapper.get();
JsonNode node = reader.read(jsonMapper, content); JsonNode node = reader.read(objectMapper, content);
return factory.apply(node); return factory.apply(node);
} }

View File

@ -18,19 +18,20 @@ package org.springframework.boot.buildpack.platform.json;
import tools.jackson.core.json.JsonWriteFeature; import tools.jackson.core.json.JsonWriteFeature;
import tools.jackson.databind.DeserializationFeature; import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.PropertyNamingStrategies; import tools.jackson.databind.PropertyNamingStrategies;
import tools.jackson.databind.SerializationFeature; import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.json.JsonMapper;
/** /**
* Provides access to a shared pre-configured {@link JsonMapper}. * Provides access to a shared pre-configured {@link ObjectMapper}.
* *
* @author Phillip Webb * @author Phillip Webb
* @since 4.0.0 * @since 2.3.0
*/ */
public final class SharedJsonMapper { public final class SharedObjectMapper {
private static final JsonMapper INSTANCE; private static final ObjectMapper INSTANCE;
static { static {
INSTANCE = JsonMapper.builder() INSTANCE = JsonMapper.builder()
@ -41,10 +42,10 @@ public final class SharedJsonMapper {
.build(); .build();
} }
private SharedJsonMapper() { private SharedObjectMapper() {
} }
public static JsonMapper get() { public static ObjectMapper get() {
return INSTANCE; return INSTANCE;
} }

View File

@ -20,7 +20,7 @@ import java.util.stream.IntStream;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.docker.ApiVersion; import org.springframework.boot.buildpack.platform.docker.type.ApiVersion;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException;

View File

@ -37,9 +37,9 @@ import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.buildpack.platform.docker.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ImageName; import org.springframework.boot.buildpack.platform.docker.type.ImageName;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.Owner;
import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.io.TarArchive;

View File

@ -31,7 +31,6 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi
import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi;
import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi;
import org.springframework.boot.buildpack.platform.docker.DockerLog; import org.springframework.boot.buildpack.platform.docker.DockerLog;
import org.springframework.boot.buildpack.platform.docker.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener; import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication; import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication;
import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException; import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException;
@ -40,6 +39,7 @@ import org.springframework.boot.buildpack.platform.docker.type.ContainerReferenc
import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus; import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive; import org.springframework.boot.buildpack.platform.docker.type.ImageArchive;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.io.TarArchive;

View File

@ -41,7 +41,6 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi;
import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi;
import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi;
import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi;
import org.springframework.boot.buildpack.platform.docker.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost;
import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Binding;
@ -49,11 +48,12 @@ import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig;
import org.springframework.boot.buildpack.platform.docker.type.ContainerContent; import org.springframework.boot.buildpack.platform.docker.type.ContainerContent;
import org.springframework.boot.buildpack.platform.docker.type.ContainerReference; import org.springframework.boot.buildpack.platform.docker.type.ContainerReference;
import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus; import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.IOConsumer;
import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.io.TarArchive;
import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.boot.testsupport.junit.BooleanValueSource; import org.springframework.boot.testsupport.junit.BooleanValueSource;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
@ -491,7 +491,7 @@ class LifecycleTests {
} }
private ArrayNode getCommand(ContainerConfig config) { private ArrayNode getCommand(ContainerConfig config) {
JsonNode node = SharedJsonMapper.get().readTree(config.toString()); JsonNode node = SharedObjectMapper.get().readTree(config.toString());
return (ArrayNode) node.at("/Cmd"); return (ArrayNode) node.at("/Cmd");
} }

View File

@ -25,10 +25,10 @@ import java.util.function.Consumer;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.docker.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent; import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent;
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;

View File

@ -45,12 +45,14 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi.SystemApi;
import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi;
import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport;
import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response;
import org.springframework.boot.buildpack.platform.docker.type.ApiVersion;
import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig;
import org.springframework.boot.buildpack.platform.docker.type.ContainerContent; import org.springframework.boot.buildpack.platform.docker.type.ContainerContent;
import org.springframework.boot.buildpack.platform.docker.type.ContainerReference; import org.springframework.boot.buildpack.platform.docker.type.ContainerReference;
import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus; import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive; import org.springframework.boot.buildpack.platform.docker.type.ImageArchive;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.Content;

View File

@ -32,7 +32,7 @@ class PullUpdateEventTests extends AbstractJsonTests {
@Test @Test
@SuppressWarnings("removal") @SuppressWarnings("removal")
void readValueWhenFullDeserializesJson() throws Exception { void readValueWhenFullDeserializesJson() throws Exception {
PullImageUpdateEvent event = getJsonMapper().readValue(getContent("pull-update-full.json"), PullImageUpdateEvent event = getObjectMapper().readValue(getContent("pull-update-full.json"),
PullImageUpdateEvent.class); PullImageUpdateEvent.class);
assertThat(event.getId()).isEqualTo("4f4fb700ef54"); assertThat(event.getId()).isEqualTo("4f4fb700ef54");
assertThat(event.getStatus()).isEqualTo("Extracting"); assertThat(event.getStatus()).isEqualTo("Extracting");
@ -42,7 +42,7 @@ class PullUpdateEventTests extends AbstractJsonTests {
@Test @Test
void readValueWhenMinimalDeserializesJson() throws Exception { void readValueWhenMinimalDeserializesJson() throws Exception {
PullImageUpdateEvent event = getJsonMapper().readValue(getContent("pull-update-minimal.json"), PullImageUpdateEvent event = getObjectMapper().readValue(getContent("pull-update-minimal.json"),
PullImageUpdateEvent.class); PullImageUpdateEvent.class);
assertThat(event.getId()).isNull(); assertThat(event.getId()).isNull();
assertThat(event.getStatus()).isEqualTo("Status: Downloaded newer image for paketo-buildpacks/cnb:base"); assertThat(event.getStatus()).isEqualTo("Status: Downloaded newer image for paketo-buildpacks/cnb:base");
@ -52,7 +52,7 @@ class PullUpdateEventTests extends AbstractJsonTests {
@Test @Test
void readValueWhenEmptyDetailsDeserializesJson() throws Exception { void readValueWhenEmptyDetailsDeserializesJson() throws Exception {
PullImageUpdateEvent event = getJsonMapper().readValue(getContent("pull-with-empty-details.json"), PullImageUpdateEvent event = getObjectMapper().readValue(getContent("pull-with-empty-details.json"),
PullImageUpdateEvent.class); PullImageUpdateEvent.class);
assertThat(event.getId()).isEqualTo("d837a2a1365e"); assertThat(event.getId()).isEqualTo("d837a2a1365e");
assertThat(event.getStatus()).isEqualTo("Pulling fs layer"); assertThat(event.getStatus()).isEqualTo("Pulling fs layer");

Some files were not shown because too many files have changed in this diff Show More