Repository: spring-projects/spring-boot Branch: main Commit: d04cb90d1983 Files: 11593 Total size: 35.9 MB Directory structure: gitextract_5h52i2gm/ ├── .editorconfig ├── .git-blame-ignore-revs ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── config.yml │ │ └── issue.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── actions/ │ │ ├── await-http-resource/ │ │ │ └── action.yml │ │ ├── create-github-release/ │ │ │ ├── action.yml │ │ │ ├── changelog-generator-commercial.yml │ │ │ └── changelog-generator-oss.yml │ │ ├── prepare-gradle-build/ │ │ │ └── action.yml │ │ ├── print-jvm-thread-dumps/ │ │ │ └── action.yml │ │ ├── publish-gradle-plugin/ │ │ │ ├── action.yml │ │ │ ├── artifacts.spec │ │ │ ├── build.gradle │ │ │ └── settings.gradle │ │ ├── publish-to-sdkman/ │ │ │ └── action.yml │ │ ├── send-notification/ │ │ │ └── action.yml │ │ ├── sync-to-maven-central/ │ │ │ ├── action.yml │ │ │ └── artifacts.spec │ │ └── update-homebrew-tap/ │ │ └── action.yml │ ├── dco.yml │ ├── dependabot.yml │ ├── scripts/ │ │ └── reclaim-docker-diskspace.sh │ └── workflows/ │ ├── build-and-deploy-snapshot.yml │ ├── build-pull-request.yml │ ├── ci.yml │ ├── distribute.yml │ ├── release-milestone.yml │ ├── release.yml │ ├── run-codeql-analysis.yml │ ├── run-system-tests.yml │ ├── trigger-docs-build.yml │ └── verify.yml ├── .gitignore ├── .idea/ │ └── .gitignore ├── .sdkmanrc ├── CONTRIBUTING.adoc ├── LICENSE.txt ├── README.adoc ├── SUPPORT.adoc ├── antora/ │ └── package.json ├── build-plugin/ │ ├── spring-boot-antlib/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── it/ │ │ │ └── sample/ │ │ │ ├── build.xml │ │ │ ├── ivysettings.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── resources/ │ │ │ └── foo │ │ └── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── ant/ │ │ │ ├── FindMainClass.java │ │ │ ├── ShareAntlibLoader.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── ant/ │ │ └── antlib.xml │ ├── spring-boot-gradle-plugin/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── gradle/ │ │ │ │ └── tasks/ │ │ │ │ └── bundling/ │ │ │ │ ├── BootBuildImageIntegrationTests.java │ │ │ │ └── BootBuildImageRegistryIntegrationTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── gradle/ │ │ │ └── tasks/ │ │ │ └── bundling/ │ │ │ ├── BootBuildImageIntegrationTests-buildsImageOnLinuxArmWithImagePlatformLinuxArm.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithApplicationDirectory.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithBindCaches.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithBinding.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithBuildpackFromBuilder.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithBuildpackFromDirectory.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithBuildpackFromTarGzip.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithBuildpacksFromImages.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithCommandLineOptions.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithCreatedDate.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithCurrentCreatedDate.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithCustomBuilderAndRunImage.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithCustomName.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithEmptySecurityOptions.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithNetworkModeNone.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithPullPolicy.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithTag.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithTrustBuilder.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithVolumeCaches.gradle │ │ │ ├── BootBuildImageIntegrationTests-buildsImageWithWarPackagingAndJarConfiguration.gradle │ │ │ ├── BootBuildImageIntegrationTests-failsWhenBuildingOnLinuxAmdWithImagePlatformLinuxArm.gradle │ │ │ ├── BootBuildImageIntegrationTests-failsWhenCachesAreConfiguredTwice.gradle │ │ │ ├── BootBuildImageIntegrationTests-failsWithBuilderError.gradle │ │ │ ├── BootBuildImageIntegrationTests-failsWithBuildpackNotInBuilder.gradle │ │ │ ├── BootBuildImageIntegrationTests-failsWithIncompatiblePlatform.gradle │ │ │ ├── BootBuildImageIntegrationTests-failsWithInvalidCreatedDate.gradle │ │ │ ├── BootBuildImageIntegrationTests-failsWithInvalidTag.gradle │ │ │ ├── BootBuildImageIntegrationTests.gradle │ │ │ └── BootBuildImageRegistryIntegrationTests.gradle │ │ ├── docs/ │ │ │ └── antora/ │ │ │ ├── antora.yml │ │ │ ├── local-nav.adoc │ │ │ └── modules/ │ │ │ └── gradle-plugin/ │ │ │ ├── examples/ │ │ │ │ ├── aot/ │ │ │ │ │ ├── apply-aot-plugin.gradle │ │ │ │ │ ├── apply-aot-plugin.gradle.kts │ │ │ │ │ ├── apply-native-image-plugin.gradle │ │ │ │ │ └── apply-native-image-plugin.gradle.kts │ │ │ │ ├── getting-started/ │ │ │ │ │ ├── apply-plugin-commercial.gradle │ │ │ │ │ ├── apply-plugin-commercial.gradle.kts │ │ │ │ │ ├── apply-plugin-release.gradle │ │ │ │ │ ├── apply-plugin-release.gradle.kts │ │ │ │ │ ├── apply-plugin-snapshot.gradle │ │ │ │ │ ├── milestone-settings.gradle │ │ │ │ │ ├── milestone-settings.gradle.kts │ │ │ │ │ ├── snapshot-settings.gradle │ │ │ │ │ ├── snapshot-settings.gradle.kts │ │ │ │ │ ├── typical-plugins.gradle │ │ │ │ │ └── typical-plugins.gradle.kts │ │ │ │ ├── integrating-with-actuator/ │ │ │ │ │ ├── build-info-additional.gradle │ │ │ │ │ ├── build-info-additional.gradle.kts │ │ │ │ │ ├── build-info-basic.gradle │ │ │ │ │ ├── build-info-basic.gradle.kts │ │ │ │ │ ├── build-info-custom-values.gradle │ │ │ │ │ ├── build-info-custom-values.gradle.kts │ │ │ │ │ ├── build-info-exclude-time.gradle │ │ │ │ │ └── build-info-exclude-time.gradle.kts │ │ │ │ ├── managing-dependencies/ │ │ │ │ │ ├── configure-bom-with-plugins.gradle.kts │ │ │ │ │ ├── configure-bom.gradle │ │ │ │ │ ├── configure-bom.gradle.kts │ │ │ │ │ ├── configure-platform.gradle │ │ │ │ │ ├── configure-platform.gradle.kts │ │ │ │ │ ├── custom-version-with-platform.gradle │ │ │ │ │ ├── custom-version-with-platform.gradle.kts │ │ │ │ │ ├── custom-version.gradle │ │ │ │ │ ├── custom-version.gradle.kts │ │ │ │ │ ├── depend-on-plugin-commercial.gradle │ │ │ │ │ ├── depend-on-plugin-commercial.gradle.kts │ │ │ │ │ ├── depend-on-plugin-milestone.gradle │ │ │ │ │ ├── depend-on-plugin-release.gradle │ │ │ │ │ ├── depend-on-plugin-release.gradle.kts │ │ │ │ │ ├── depend-on-plugin-snapshot.gradle │ │ │ │ │ ├── dependencies.gradle │ │ │ │ │ └── dependencies.gradle.kts │ │ │ │ ├── packaging/ │ │ │ │ │ ├── application-plugin-main-class.gradle │ │ │ │ │ ├── application-plugin-main-class.gradle.kts │ │ │ │ │ ├── boot-build-image-bind-caches.gradle │ │ │ │ │ ├── boot-build-image-bind-caches.gradle.kts │ │ │ │ │ ├── boot-build-image-builder.gradle │ │ │ │ │ ├── boot-build-image-builder.gradle.kts │ │ │ │ │ ├── boot-build-image-buildpacks.gradle │ │ │ │ │ ├── boot-build-image-buildpacks.gradle.kts │ │ │ │ │ ├── boot-build-image-caches.gradle │ │ │ │ │ ├── boot-build-image-caches.gradle.kts │ │ │ │ │ ├── boot-build-image-docker-auth-token.gradle │ │ │ │ │ ├── boot-build-image-docker-auth-token.gradle.kts │ │ │ │ │ ├── boot-build-image-docker-auth-user.gradle │ │ │ │ │ ├── boot-build-image-docker-auth-user.gradle.kts │ │ │ │ │ ├── boot-build-image-docker-host-colima.gradle │ │ │ │ │ ├── boot-build-image-docker-host-colima.gradle.kts │ │ │ │ │ ├── boot-build-image-docker-host-podman.gradle │ │ │ │ │ ├── boot-build-image-docker-host-podman.gradle.kts │ │ │ │ │ ├── boot-build-image-docker-host.gradle │ │ │ │ │ ├── boot-build-image-docker-host.gradle.kts │ │ │ │ │ ├── boot-build-image-env-proxy.gradle │ │ │ │ │ ├── boot-build-image-env-proxy.gradle.kts │ │ │ │ │ ├── boot-build-image-env-runtime.gradle │ │ │ │ │ ├── boot-build-image-env-runtime.gradle.kts │ │ │ │ │ ├── boot-build-image-env.gradle │ │ │ │ │ ├── boot-build-image-env.gradle.kts │ │ │ │ │ ├── boot-build-image-name.gradle │ │ │ │ │ ├── boot-build-image-name.gradle.kts │ │ │ │ │ ├── boot-build-image-publish.gradle │ │ │ │ │ ├── boot-build-image-publish.gradle.kts │ │ │ │ │ ├── boot-jar-and-jar-classifiers.gradle │ │ │ │ │ ├── boot-jar-and-jar-classifiers.gradle.kts │ │ │ │ │ ├── boot-jar-layered-custom.gradle │ │ │ │ │ ├── boot-jar-layered-custom.gradle.kts │ │ │ │ │ ├── boot-jar-layered-disabled.gradle │ │ │ │ │ ├── boot-jar-layered-disabled.gradle.kts │ │ │ │ │ ├── boot-jar-layered-exclude-tools.gradle │ │ │ │ │ ├── boot-jar-layered-exclude-tools.gradle.kts │ │ │ │ │ ├── boot-jar-main-class.gradle │ │ │ │ │ ├── boot-jar-main-class.gradle.kts │ │ │ │ │ ├── boot-jar-manifest-main-class.gradle │ │ │ │ │ ├── boot-jar-manifest-main-class.gradle.kts │ │ │ │ │ ├── boot-jar-requires-unpack.gradle │ │ │ │ │ ├── boot-jar-requires-unpack.gradle.kts │ │ │ │ │ ├── boot-war-include-devtools.gradle │ │ │ │ │ ├── boot-war-include-devtools.gradle.kts │ │ │ │ │ ├── boot-war-properties-launcher.gradle │ │ │ │ │ ├── boot-war-properties-launcher.gradle.kts │ │ │ │ │ ├── only-boot-jar.gradle │ │ │ │ │ ├── only-boot-jar.gradle.kts │ │ │ │ │ ├── spring-boot-dsl-main-class.gradle │ │ │ │ │ ├── spring-boot-dsl-main-class.gradle.kts │ │ │ │ │ ├── war-container-dependency.gradle │ │ │ │ │ └── war-container-dependency.gradle.kts │ │ │ │ ├── publishing/ │ │ │ │ │ ├── maven-publish.gradle │ │ │ │ │ └── maven-publish.gradle.kts │ │ │ │ └── running/ │ │ │ │ ├── application-plugin-main-class-name.gradle │ │ │ │ ├── application-plugin-main-class-name.gradle.kts │ │ │ │ ├── boot-run-disable-optimized-launch.gradle │ │ │ │ ├── boot-run-disable-optimized-launch.gradle.kts │ │ │ │ ├── boot-run-main.gradle │ │ │ │ ├── boot-run-main.gradle.kts │ │ │ │ ├── boot-run-source-resources.gradle │ │ │ │ ├── boot-run-source-resources.gradle.kts │ │ │ │ ├── boot-run-system-property.gradle │ │ │ │ ├── boot-run-system-property.gradle.kts │ │ │ │ ├── spring-boot-dsl-main-class-name.gradle │ │ │ │ └── spring-boot-dsl-main-class-name.gradle.kts │ │ │ ├── pages/ │ │ │ │ ├── aot.adoc │ │ │ │ ├── getting-started.adoc │ │ │ │ ├── index.adoc │ │ │ │ ├── integrating-with-actuator.adoc │ │ │ │ ├── introduction.adoc │ │ │ │ ├── managing-dependencies.adoc │ │ │ │ ├── packaging-oci-image.adoc │ │ │ │ ├── packaging.adoc │ │ │ │ ├── publishing.adoc │ │ │ │ ├── reacting.adoc │ │ │ │ └── running.adoc │ │ │ └── partials/ │ │ │ └── nav-gradle-plugin.adoc │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── gradle/ │ │ │ │ ├── dsl/ │ │ │ │ │ ├── SpringBootExtension.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── plugin/ │ │ │ │ │ ├── ApplicationPluginAction.java │ │ │ │ │ ├── CyclonedxPluginAction.java │ │ │ │ │ ├── DependencyManagementPluginAction.java │ │ │ │ │ ├── JarTypeFileSpec.java │ │ │ │ │ ├── JavaPluginAction.java │ │ │ │ │ ├── KotlinPluginAction.java │ │ │ │ │ ├── NativeImagePluginAction.java │ │ │ │ │ ├── PluginApplicationAction.java │ │ │ │ │ ├── ProtobufPluginAction.java │ │ │ │ │ ├── ResolveMainClassName.java │ │ │ │ │ ├── SinglePublishedArtifact.java │ │ │ │ │ ├── SpringBootAotPlugin.java │ │ │ │ │ ├── SpringBootPlugin.java │ │ │ │ │ ├── WarPluginAction.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── tasks/ │ │ │ │ │ ├── aot/ │ │ │ │ │ │ ├── AbstractAot.java │ │ │ │ │ │ ├── ProcessAot.java │ │ │ │ │ │ ├── ProcessTestAot.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── buildinfo/ │ │ │ │ │ │ ├── BuildInfo.java │ │ │ │ │ │ ├── BuildInfoProperties.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── bundling/ │ │ │ │ │ │ ├── BootArchive.java │ │ │ │ │ │ ├── BootArchiveSupport.java │ │ │ │ │ │ ├── BootBuildImage.java │ │ │ │ │ │ ├── BootJar.java │ │ │ │ │ │ ├── BootWar.java │ │ │ │ │ │ ├── BootZipCopyAction.java │ │ │ │ │ │ ├── CacheSpec.java │ │ │ │ │ │ ├── DefaultTimeZoneOffset.java │ │ │ │ │ │ ├── DockerSpec.java │ │ │ │ │ │ ├── LayerResolver.java │ │ │ │ │ │ ├── LayeredSpec.java │ │ │ │ │ │ ├── LoaderZipEntries.java │ │ │ │ │ │ ├── ResolvedDependencies.java │ │ │ │ │ │ ├── ZipCompression.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── run/ │ │ │ │ │ ├── BootRun.java │ │ │ │ │ └── package-info.java │ │ │ │ └── util/ │ │ │ │ ├── VersionExtractor.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── unixStartScript.txt │ │ │ └── windowsStartScript.txt │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── gradle/ │ │ │ ├── TaskConfigurationAvoidanceTests.java │ │ │ ├── docs/ │ │ │ │ ├── AotDocumentationTests.java │ │ │ │ ├── Examples.java │ │ │ │ ├── GettingStartedDocumentationTests.java │ │ │ │ ├── IntegratingWithActuatorDocumentationTests.java │ │ │ │ ├── ManagingDependenciesDocumentationTests.java │ │ │ │ ├── PackagingDocumentationTests.java │ │ │ │ ├── PublishingDocumentationTests.java │ │ │ │ ├── RunningDocumentationTests.java │ │ │ │ └── package-info.java │ │ │ ├── dsl/ │ │ │ │ └── BuildInfoDslIntegrationTests.java │ │ │ ├── junit/ │ │ │ │ ├── GradleBuildFieldSetter.java │ │ │ │ ├── GradleCompatibility.java │ │ │ │ ├── GradleCompatibilityExtension.java │ │ │ │ ├── GradleMultiDslExtension.java │ │ │ │ ├── GradleProjectBuilder.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── plugin/ │ │ │ │ ├── ApplicationPluginActionIntegrationTests.java │ │ │ │ ├── CyclonedxPluginActionIntegrationTests.java │ │ │ │ ├── DependencyManagementPluginActionIntegrationTests.java │ │ │ │ ├── JavaPluginActionIntegrationTests.java │ │ │ │ ├── KotlinPluginActionIntegrationTests.java │ │ │ │ ├── NativeImagePluginActionIntegrationTests.java │ │ │ │ ├── OnlyDependencyManagementIntegrationTests.java │ │ │ │ ├── ProtobufPluginActionIntegrationTests.java │ │ │ │ ├── SpringBootAotPluginIntegrationTests.java │ │ │ │ ├── SpringBootPluginIntegrationTests.java │ │ │ │ ├── SpringBootPluginTests.java │ │ │ │ └── WarPluginActionIntegrationTests.java │ │ │ ├── tasks/ │ │ │ │ ├── buildinfo/ │ │ │ │ │ ├── BuildInfoIntegrationTests.java │ │ │ │ │ └── BuildInfoTests.java │ │ │ │ ├── bundling/ │ │ │ │ │ ├── AbstractBootArchiveIntegrationTests.java │ │ │ │ │ ├── AbstractBootArchiveTests.java │ │ │ │ │ ├── BootBuildImageTests.java │ │ │ │ │ ├── BootJarIntegrationTests.java │ │ │ │ │ ├── BootJarTests.java │ │ │ │ │ ├── BootWarIntegrationTests.java │ │ │ │ │ ├── BootWarTests.java │ │ │ │ │ ├── DefaultTimeZoneOffsetTests.java │ │ │ │ │ ├── DockerSpecTests.java │ │ │ │ │ ├── MavenPublishingIntegrationTests.java │ │ │ │ │ └── PomCondition.java │ │ │ │ └── run/ │ │ │ │ ├── BootRunIntegrationTests.java │ │ │ │ └── BootTestRunIntegrationTests.java │ │ │ └── testkit/ │ │ │ ├── PluginClasspathGradleBuild.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── com/ │ │ │ └── example/ │ │ │ ├── bootjar/ │ │ │ │ ├── classpath/ │ │ │ │ │ └── BootJarClasspathApplication.java │ │ │ │ └── main/ │ │ │ │ └── CustomMainClass.java │ │ │ ├── bootrun/ │ │ │ │ ├── classpath/ │ │ │ │ │ └── BootRunClasspathApplication.java │ │ │ │ ├── jvmargs/ │ │ │ │ │ └── BootRunJvmArgsApplication.java │ │ │ │ └── main/ │ │ │ │ └── CustomMainClass.java │ │ │ ├── boottestrun/ │ │ │ │ ├── classpath/ │ │ │ │ │ └── BootTestRunClasspathApplication.java │ │ │ │ ├── jvmargs/ │ │ │ │ │ └── BootTestRunJvmArgsApplication.java │ │ │ │ └── nomain/ │ │ │ │ └── BootTestRunNoMain.java │ │ │ └── bootwar/ │ │ │ └── main/ │ │ │ └── CustomMainClass.java │ │ ├── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── gradle/ │ │ │ ├── dsl/ │ │ │ │ ├── BuildInfoDslIntegrationTests-additionalProperties.gradle │ │ │ │ ├── BuildInfoDslIntegrationTests-basicJar.gradle │ │ │ │ ├── BuildInfoDslIntegrationTests-basicWar.gradle │ │ │ │ ├── BuildInfoDslIntegrationTests-classesDependency.gradle │ │ │ │ ├── BuildInfoDslIntegrationTests-jarWithCustomName.gradle │ │ │ │ └── BuildInfoDslIntegrationTests-warWithCustomName.gradle │ │ │ ├── plugin/ │ │ │ │ ├── ApplicationPluginActionIntegrationTests-applicationNameCanBeUsedToCustomizeDistributionName.gradle │ │ │ │ ├── ApplicationPluginActionIntegrationTests-scriptsHaveCorrectPermissions.gradle │ │ │ │ ├── ApplicationPluginActionIntegrationTests-tarDistributionForJarCanBeBuilt.gradle │ │ │ │ ├── ApplicationPluginActionIntegrationTests-tarDistributionForWarCanBeBuilt.gradle │ │ │ │ ├── ApplicationPluginActionIntegrationTests-taskConfigurationIsAvoided.gradle │ │ │ │ ├── ApplicationPluginActionIntegrationTests-zipDistributionForJarCanBeBuilt.gradle │ │ │ │ ├── ApplicationPluginActionIntegrationTests-zipDistributionForWarCanBeBuilt.gradle │ │ │ │ ├── ApplicationPluginActionIntegrationTests.gradle │ │ │ │ ├── CyclonedxPluginActionIntegrationTests-sbomIsIncludedInUberJar.gradle │ │ │ │ ├── CyclonedxPluginActionIntegrationTests-sbomIsIncludedInUberWar.gradle │ │ │ │ ├── DependencyManagementPluginActionIntegrationTests-helpfulErrorWhenVersionlessDependencyFailsToResolve.gradle │ │ │ │ ├── DependencyManagementPluginActionIntegrationTests.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-additionalMetadataLocationsConfiguredWhenProcessorIsPresent.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-additionalMetadataLocationsNotConfiguredWhenProcessorIsAbsent.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-applyingJavaPluginCreatesBootJarTask.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-applyingJavaPluginCreatesBootRunTask.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-applyingJavaPluginCreatesBootTestRunTask.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-applyingJavaPluginCreatesDevelopmentOnlyConfiguration.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-applyingJavaPluginCreatesTestAndDevelopmentOnlyConfiguration.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-assembleRunsBootJarAndJar.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-compileClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-compileClasspathDoesNotIncludeTestAndDevelopmentOnlyDependencies.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-errorMessageIsHelpfulWhenMainClassCannotBeResolved.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-javaCompileTasksCanOverrideDefaultParametersCompilerFlag.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-javaCompileTasksUseParametersAndAdditionalCompilerFlags.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-javaCompileTasksUseParametersCompilerFlagByDefault.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-javaCompileTasksUseUtf8Encoding.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-noBootJarTaskWithoutJavaPluginApplied.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-noBootRunTaskWithoutJavaPluginApplied.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-noBootTestRunTaskWithoutJavaPluginApplied.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-productionRuntimeClasspathIsConfiguredWithAttributesThatMatchRuntimeClasspath.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-productionRuntimeClasspathIsConfiguredWithResolvabilityAndConsumabilityThatMatchesRuntimeClasspath.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-runtimeClasspathIncludesDevelopmentOnlyDependencies.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-runtimeClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-taskConfigurationIsAvoided.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-testCompileClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-testCompileClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-testRuntimeClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle │ │ │ │ ├── JavaPluginActionIntegrationTests-testRuntimeClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle │ │ │ │ ├── KotlinPluginActionIntegrationTests-compileAotJavaHasTransitiveRuntimeDependenciesOnItsClasspathWhenUsingKotlin.gradle │ │ │ │ ├── KotlinPluginActionIntegrationTests-compileAotTestJavaHasTransitiveRuntimeDependenciesOnItsClasspathWhenUsingKotlin.gradle │ │ │ │ ├── KotlinPluginActionIntegrationTests-kotlinCompileTasksCanOverrideDefaultJavaParametersFlag.gradle │ │ │ │ ├── KotlinPluginActionIntegrationTests-kotlinCompileTasksUseJavaParametersFlagByDefault.gradle │ │ │ │ ├── KotlinPluginActionIntegrationTests-kotlinVersionPropertyIsSet.gradle │ │ │ │ ├── KotlinPluginActionIntegrationTests-noKotlinVersionPropertyWithoutKotlinPlugin.gradle │ │ │ │ ├── KotlinPluginActionIntegrationTests-taskConfigurationIsAvoided.gradle │ │ │ │ ├── MavenPluginActionIntegrationTests.gradle │ │ │ │ ├── NativeImagePluginActionIntegrationTests-applyingNativeImagePluginAppliesAotPlugin.gradle │ │ │ │ ├── NativeImagePluginActionIntegrationTests-classesGeneratedDuringAotProcessingAreOnTheNativeImageClasspath.gradle │ │ │ │ ├── NativeImagePluginActionIntegrationTests-classesGeneratedDuringAotTestProcessingAreOnTheTestNativeImageClasspath.gradle │ │ │ │ ├── NativeImagePluginActionIntegrationTests-developmentOnlyDependenciesDoNotAppearInNativeImageClasspath.gradle │ │ │ │ ├── NativeImagePluginActionIntegrationTests-nativeEntryIsAddedToManifest.gradle │ │ │ │ ├── NativeImagePluginActionIntegrationTests-reachabilityMetadataConfigurationFilesAreCopiedToJar.gradle │ │ │ │ ├── NativeImagePluginActionIntegrationTests-reachabilityMetadataConfigurationFilesFromFileRepositoryAreCopiedToJar.gradle │ │ │ │ ├── NativeImagePluginActionIntegrationTests-testAndDevelopmentOnlyDependenciesDoNotAppearInNativeImageClasspath.gradle │ │ │ │ ├── OnlyDependencyManagementIntegrationTests.gradle │ │ │ │ ├── ProtobufPluginActionIntegrationTests-usesVersionOfGrpcPluginDependencyWhenSpecified.gradle │ │ │ │ ├── ProtobufPluginActionIntegrationTests-usesVersionOfProtocDependencyWhenSpecified.gradle │ │ │ │ ├── ProtobufPluginActionIntegrationTests.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-applyingAotPluginCreatesProcessAotTask.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-applyingAotPluginCreatesProcessTestAotTask.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-applyingAotPluginDoesNotPreventConfigurationOfJavaToolchainLanguageVersion.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-noProcessAotTaskWithoutAotPluginApplied.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-noProcessTestAotTaskWithoutAotPluginApplied.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-processAotDoesNotHaveDevelopmentOnlyDependenciesOnItsClasspath.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-processAotDoesNotHaveTestAndDevelopmentOnlyDependenciesOnItsClasspath.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-processAotHasLibraryResourcesOnItsClasspath.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-processAotHasTransitiveRuntimeDependenciesOnItsClasspath.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-processAotIsSkippedWhenProjectHasNoMainSource.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-processAotRunsWhenProjectHasMainSource.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-processTestAotDoesNotHaveDevelopmentOnlyDependenciesOnItsClasspath.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-processTestAotHasLibraryResourcesOnItsClasspath.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-processTestAotHasTestAndDevelopmentOnlyDependenciesOnItsClasspath.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-processTestAotHasTransitiveRuntimeDependenciesOnItsClasspath.gradle │ │ │ │ ├── SpringBootAotPluginIntegrationTests-processTestAotIsSkippedWhenProjectHasNoTestSource.gradle │ │ │ │ ├── SpringBootPluginIntegrationTests-unresolvedDependenciesAreAnalyzedWhenDependencyResolutionFails.gradle │ │ │ │ ├── SpringBootPluginIntegrationTests.gradle │ │ │ │ ├── WarPluginActionIntegrationTests-assembleRunsBootWarAndWar.gradle │ │ │ │ ├── WarPluginActionIntegrationTests-taskConfigurationIsAvoided.gradle │ │ │ │ └── WarPluginActionIntegrationTests.gradle │ │ │ └── tasks/ │ │ │ ├── buildinfo/ │ │ │ │ ├── BuildInfoIntegrationTests-basicExecution.gradle │ │ │ │ ├── BuildInfoIntegrationTests-defaultValues.gradle │ │ │ │ ├── BuildInfoIntegrationTests-excludeProperties.gradle │ │ │ │ ├── BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceAsTimeChanges.gradle │ │ │ │ ├── BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedGradlePropertiesProjectVersion.gradle │ │ │ │ ├── BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion.gradle │ │ │ │ ├── BuildInfoIntegrationTests-reproducibleOutputWithFixedTime.gradle │ │ │ │ └── BuildInfoIntegrationTests-upToDateWhenExecutedTwiceWithFixedTime.gradle │ │ │ ├── bundling/ │ │ │ │ ├── BootJarIntegrationTests-applicationPluginMainClassNameIsUsed.gradle │ │ │ │ ├── BootJarIntegrationTests-classesFromASecondarySourceSetCanBeIncludedInTheArchive.gradle │ │ │ │ ├── BootJarIntegrationTests-customLayers.gradle │ │ │ │ ├── BootJarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchive.gradle │ │ │ │ ├── BootJarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle │ │ │ │ ├── BootJarIntegrationTests-developmentOnlyDependenciesCanBeIncludedInTheArchive.gradle │ │ │ │ ├── BootJarIntegrationTests-dirModeAndFileModeAreApplied.gradle │ │ │ │ ├── BootJarIntegrationTests-duplicatesAreHandledGracefully.gradle │ │ │ │ ├── BootJarIntegrationTests-explodedApplicationClasspath.gradle │ │ │ │ ├── BootJarIntegrationTests-implicitLayers.gradle │ │ │ │ ├── BootJarIntegrationTests-jarTypeFilteringIsApplied.gradle │ │ │ │ ├── BootJarIntegrationTests-layersWithCustomSourceSet.gradle │ │ │ │ ├── BootJarIntegrationTests-multiModuleCustomLayers.gradle │ │ │ │ ├── BootJarIntegrationTests-multiModuleImplicitLayers.gradle │ │ │ │ ├── BootJarIntegrationTests-notUpToDateWhenBuiltWithToolsAndThenWithoutTools.gradle │ │ │ │ ├── BootJarIntegrationTests-notUpToDateWhenBuiltWithoutLayersAndThenWithLayers.gradle │ │ │ │ ├── BootJarIntegrationTests-packagedApplicationClasspath.gradle │ │ │ │ ├── BootJarIntegrationTests-reproducibleArchive.gradle │ │ │ │ ├── BootJarIntegrationTests-signed.gradle │ │ │ │ ├── BootJarIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle │ │ │ │ ├── BootJarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle │ │ │ │ ├── BootJarIntegrationTests-testAndDevelopmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle │ │ │ │ ├── BootJarIntegrationTests-testAndDevelopmentOnlyDependenciesCanBeIncludedInTheArchive.gradle │ │ │ │ ├── BootJarIntegrationTests-upToDateWhenBuiltTwice.gradle │ │ │ │ ├── BootJarIntegrationTests-upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered.gradle │ │ │ │ ├── BootJarIntegrationTests-versionMismatchBetweenTransitiveDevelopmentOnlyImplementationDependenciesDoesNotRemoveDependencyFromTheArchive.gradle │ │ │ │ ├── BootJarIntegrationTests-whenAResolvableCopyOfAnUnresolvableConfigurationIsResolvedThenResolutionSucceeds.gradle │ │ │ │ ├── BootJarIntegrationTests.gradle │ │ │ │ ├── BootWarIntegrationTests-applicationPluginMainClassNameIsUsed.gradle │ │ │ │ ├── BootWarIntegrationTests-classesFromASecondarySourceSetCanBeIncludedInTheArchive.gradle │ │ │ │ ├── BootWarIntegrationTests-customLayers.gradle │ │ │ │ ├── BootWarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle │ │ │ │ ├── BootWarIntegrationTests-developmentOnlyDependenciesCanBeIncludedInTheArchive.gradle │ │ │ │ ├── BootWarIntegrationTests-dirModeAndFileModeAreApplied.gradle │ │ │ │ ├── BootWarIntegrationTests-duplicatesAreHandledGracefully.gradle │ │ │ │ ├── BootWarIntegrationTests-implicitLayers.gradle │ │ │ │ ├── BootWarIntegrationTests-jarTypeFilteringIsApplied.gradle │ │ │ │ ├── BootWarIntegrationTests-layersWithCustomSourceSet.gradle │ │ │ │ ├── BootWarIntegrationTests-multiModuleCustomLayers.gradle │ │ │ │ ├── BootWarIntegrationTests-multiModuleImplicitLayers.gradle │ │ │ │ ├── BootWarIntegrationTests-notUpToDateWhenBuiltWithToolsAndThenWithoutTools.gradle │ │ │ │ ├── BootWarIntegrationTests-notUpToDateWhenBuiltWithoutLayersAndThenWithLayers.gradle │ │ │ │ ├── BootWarIntegrationTests-reproducibleArchive.gradle │ │ │ │ ├── BootWarIntegrationTests-signed.gradle │ │ │ │ ├── BootWarIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle │ │ │ │ ├── BootWarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle │ │ │ │ ├── BootWarIntegrationTests-testAndDevelopmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle │ │ │ │ ├── BootWarIntegrationTests-testAndDevelopmentOnlyDependenciesCanBeIncludedInTheArchive.gradle │ │ │ │ ├── BootWarIntegrationTests-upToDateWhenBuiltTwice.gradle │ │ │ │ ├── BootWarIntegrationTests-upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered.gradle │ │ │ │ ├── BootWarIntegrationTests-versionMismatchBetweenTransitiveDevelopmentOnlyImplementationDependenciesDoesNotRemoveDependencyFromTheArchive.gradle │ │ │ │ ├── BootWarIntegrationTests.gradle │ │ │ │ ├── MavenIntegrationTests-bootJarCanBeUploaded.gradle │ │ │ │ ├── MavenIntegrationTests-bootWarCanBeUploaded.gradle │ │ │ │ ├── MavenPublishingIntegrationTests-bootJarCanBePublished.gradle │ │ │ │ └── MavenPublishingIntegrationTests-bootWarCanBePublished.gradle │ │ │ └── run/ │ │ │ ├── BootRunIntegrationTests-applicationPluginJvmArgumentsAreUsed.gradle │ │ │ ├── BootRunIntegrationTests-applicationPluginMainClassNameIsNotUsedWhenItIsNull.gradle │ │ │ ├── BootRunIntegrationTests-applicationPluginMainClassNameIsUsed.gradle │ │ │ ├── BootRunIntegrationTests-basicExecution.gradle │ │ │ ├── BootRunIntegrationTests-classesFromASecondarySourceSetCanBeOnTheClasspath.gradle │ │ │ ├── BootRunIntegrationTests-defaultJvmArgs.gradle │ │ │ ├── BootRunIntegrationTests-developmentOnlyDependenciesAreOnTheClasspath.gradle │ │ │ ├── BootRunIntegrationTests-jarTypeFilteringIsAppliedToTheClasspath.gradle │ │ │ ├── BootRunIntegrationTests-optimizedLaunchDisabledJvmArgs.gradle │ │ │ ├── BootRunIntegrationTests-sourceResourcesCanBeUsed.gradle │ │ │ ├── BootRunIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle │ │ │ ├── BootRunIntegrationTests-testAndDevelopmentOnlyDependenciesAreOnTheClasspath.gradle │ │ │ ├── BootTestRunIntegrationTests-applicationPluginJvmArgumentsAreUsed.gradle │ │ │ ├── BootTestRunIntegrationTests-basicExecution.gradle │ │ │ ├── BootTestRunIntegrationTests-defaultJvmArgs.gradle │ │ │ ├── BootTestRunIntegrationTests-developmentOnlyDependenciesAreNotOnTheClasspath.gradle │ │ │ ├── BootTestRunIntegrationTests-failsGracefullyWhenNoTestMainMethodIsFound.gradle │ │ │ ├── BootTestRunIntegrationTests-jarTypeFilteringIsAppliedToTheClasspath.gradle │ │ │ ├── BootTestRunIntegrationTests-optimizedLaunchDisabledJvmArgs.gradle │ │ │ └── BootTestRunIntegrationTests-testAndDevelopmentOnlyDependenciesAreOnTheClasspath.gradle │ │ ├── reachability-metadata-repository/ │ │ │ ├── ch.qos.logback/ │ │ │ │ └── logback-classic/ │ │ │ │ ├── 1.2.11/ │ │ │ │ │ ├── index.json │ │ │ │ │ └── reflect-config.json │ │ │ │ └── index.json │ │ │ ├── index.json │ │ │ ├── org.jline/ │ │ │ │ └── jline/ │ │ │ │ ├── 3.21.0/ │ │ │ │ │ ├── index.json │ │ │ │ │ ├── jni-config.json │ │ │ │ │ ├── proxy-config.json │ │ │ │ │ ├── reflect-config.json │ │ │ │ │ └── resource-config.json │ │ │ │ └── index.json │ │ │ └── schemas/ │ │ │ ├── library-and-framework-list-schema-v1.0.0.json │ │ │ ├── metadata-library-index-schema-v1.0.0.json │ │ │ └── metadata-root-index-schema-v1.0.0.json │ │ └── repository/ │ │ ├── com/ │ │ │ └── example/ │ │ │ └── library/ │ │ │ └── 1.0-SNAPSHOT/ │ │ │ └── library-1.0-SNAPSHOT.pom │ │ ├── commons-io-consumer/ │ │ │ ├── one/ │ │ │ │ └── 1.0/ │ │ │ │ └── one-1.0.pom │ │ │ └── two/ │ │ │ └── 1.0/ │ │ │ └── two-1.0.pom │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── spring-boot-dependencies/ │ │ └── TEST-SNAPSHOT/ │ │ └── spring-boot-dependencies-TEST-SNAPSHOT.pom │ └── spring-boot-maven-plugin/ │ ├── build.gradle │ └── src/ │ ├── dockerTest/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── maven/ │ │ │ ├── BuildImageRegistryIntegrationTests.java │ │ │ └── BuildImageTests.java │ │ └── projects/ │ │ ├── build-image/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-app-dir/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-bad-buildpack/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-bind-caches/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-bindings/ │ │ │ ├── bindings/ │ │ │ │ └── ca-certificates/ │ │ │ │ ├── test.crt │ │ │ │ └── type │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-builder-error/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-caches-multiple/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-classifier/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-classifier-source/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-classifier-source-with-repackage/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-classifier-with-repackage/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-cmd-line/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-created-date/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-current-created-date/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-custom-builder/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-custom-buildpacks/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-custom-name/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-empty-env-entry/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-final-name/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-fork-classifier/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-multi-module/ │ │ │ ├── app/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ ├── library/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleLibrary.java │ │ │ └── pom.xml │ │ ├── build-image-network/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-platform-linux-arm/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-publish/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-security-opts/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-tags/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-trust-builder/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-volume-caches/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-war-packaging/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-image-with-repackage/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ └── build-image-zip-packaging/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── org/ │ │ └── test/ │ │ └── SampleApplication.java │ ├── docs/ │ │ └── antora/ │ │ ├── antora.yml │ │ ├── local-nav.adoc │ │ └── modules/ │ │ └── maven-plugin/ │ │ ├── examples/ │ │ │ ├── aot/ │ │ │ │ └── pom.xml │ │ │ ├── aot-native/ │ │ │ │ └── pom.xml │ │ │ ├── aot-native-profile-buildpacks/ │ │ │ │ └── pom.xml │ │ │ ├── aot-native-profile-nbt/ │ │ │ │ └── pom.xml │ │ │ ├── build-info/ │ │ │ │ └── pom.xml │ │ │ ├── getting-started/ │ │ │ │ ├── plugin-repositories-pom.xml │ │ │ │ └── pom.xml │ │ │ ├── integration-tests/ │ │ │ │ ├── customize-jmx-port-pom.xml │ │ │ │ ├── failsafe-pom.xml │ │ │ │ ├── pom.xml │ │ │ │ ├── random-port-pom.xml │ │ │ │ └── skip-integration-tests-pom.xml │ │ │ ├── packaging/ │ │ │ │ ├── classified-artifact-pom.xml │ │ │ │ ├── custom-layers-classpath-pom.xml │ │ │ │ ├── custom-layers-pom.xml │ │ │ │ ├── custom-layout-pom.xml │ │ │ │ ├── custom-name-pom.xml │ │ │ │ ├── different-classifier-pom.xml │ │ │ │ ├── disable-layers-pom.xml │ │ │ │ ├── exclude-artifact-group-pom.xml │ │ │ │ ├── exclude-artifact-pom.xml │ │ │ │ ├── exclude-dependency-pom.xml │ │ │ │ ├── jar-plugin-first-pom.xml │ │ │ │ ├── layers-configuration.xml │ │ │ │ ├── layers.xml │ │ │ │ ├── local-repackaged-artifact-pom.xml │ │ │ │ ├── non-default-pom.xml │ │ │ │ ├── repackage-configuration-pom.xml │ │ │ │ └── repackage-pom.xml │ │ │ ├── packaging-oci-image/ │ │ │ │ ├── bind-caches-pom.xml │ │ │ │ ├── build-image-example-builder-configuration-pom.xml │ │ │ │ ├── buildpacks-pom.xml │ │ │ │ ├── caches-pom.xml │ │ │ │ ├── custom-image-builder-pom.xml │ │ │ │ ├── custom-image-name-pom.xml │ │ │ │ ├── docker-colima-pom.xml │ │ │ │ ├── docker-minikube-pom.xml │ │ │ │ ├── docker-podman-pom.xml │ │ │ │ ├── docker-pom-authentication-command-line.xml │ │ │ │ ├── docker-pom.xml │ │ │ │ ├── docker-registry-authentication-pom.xml │ │ │ │ ├── docker-token-authentication-pom.xml │ │ │ │ ├── paketo-pom.xml │ │ │ │ ├── pom.xml │ │ │ │ └── runtime-jvm-configuration-pom.xml │ │ │ ├── running/ │ │ │ │ ├── active-profiles-pom.xml │ │ │ │ ├── application-arguments-pom.xml │ │ │ │ ├── debug-pom.xml │ │ │ │ ├── devtools-pom.xml │ │ │ │ ├── environment-variables-pom.xml │ │ │ │ ├── hot-refresh-pom.xml │ │ │ │ └── system-properties-pom.xml │ │ │ └── using/ │ │ │ ├── default-and-override-pom.xml │ │ │ ├── different-versions-pom.xml │ │ │ ├── no-starter-parent-override-dependencies-pom.xml │ │ │ └── no-starter-parent-pom.xml │ │ ├── pages/ │ │ │ ├── aot.adoc │ │ │ ├── build-image.adoc │ │ │ ├── build-info.adoc │ │ │ ├── getting-started.adoc │ │ │ ├── goals.adoc │ │ │ ├── help.adoc │ │ │ ├── index.adoc │ │ │ ├── integration-tests.adoc │ │ │ ├── packaging.adoc │ │ │ ├── run.adoc │ │ │ └── using.adoc │ │ └── partials/ │ │ └── nav-maven-plugin.adoc │ ├── intTest/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── maven/ │ │ │ ├── AbstractArchiveIntegrationTests.java │ │ │ ├── AotTests.java │ │ │ ├── BuildInfoIntegrationTests.java │ │ │ ├── EclipseM2eIntegrationTests.java │ │ │ ├── JarIntegrationTests.java │ │ │ ├── MavenBuild.java │ │ │ ├── MavenBuildExtension.java │ │ │ ├── RunIntegrationTests.java │ │ │ ├── StartStopIntegrationTests.java │ │ │ ├── TestRunIntegrationTests.java │ │ │ ├── Versions.java │ │ │ └── WarIntegrationTests.java │ │ └── projects/ │ │ ├── aot/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── aot-arguments/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ ├── SampleApplication.java │ │ │ └── TestProfileConfiguration.java │ │ ├── aot-class-proxy/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ ├── SampleApplication.java │ │ │ └── SampleRunner.java │ │ ├── aot-compiler-arguments/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── aot-exclude-devtools/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── aot-jdk-proxy/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── aot-jvm-arguments/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ ├── SampleApplication.java │ │ │ └── TestProfileConfiguration.java │ │ ├── aot-module-info/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ ├── module-info.java │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── aot-profile/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ ├── SampleApplication.java │ │ │ └── TestProfileConfiguration.java │ │ ├── aot-release/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── aot-resource-generation/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ ├── ResourceRegisteringAotProcessor.java │ │ │ │ └── SampleApplication.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── aot.factories │ │ ├── aot-system-properties/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ ├── SampleApplication.java │ │ │ └── TestProfileConfiguration.java │ │ ├── aot-test/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplicationTests.java │ │ ├── aot-test-exclude-devtools/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplicationTests.java │ │ ├── aot-test-skip/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplicationTests.java │ │ ├── build-info/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-info-additional-properties/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-info-custom-build-time/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-info-custom-file/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-info-disable-build-time/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-info-exclude-build-properties/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-info-exclude-build-time/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-info-reproducible/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── build-info-reproducible-epoch-seconds/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-attach-disabled/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-classifier-main/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-classifier-main-attach-disabled/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-classifier-source/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-classifier-source-attach-disabled/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-create-dir/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-custom-dir/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-custom-layout/ │ │ │ ├── custom/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ ├── default/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ ├── layout/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── smoketest/ │ │ │ │ │ └── layout/ │ │ │ │ │ ├── SampleLayout.java │ │ │ │ │ └── SampleLayoutFactory.java │ │ │ │ └── resources/ │ │ │ │ └── META-INF/ │ │ │ │ └── spring.factories │ │ │ └── pom.xml │ │ ├── jar-exclude-entry/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-exclude-group/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-include-entry/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-layered/ │ │ │ ├── jar/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ ├── jar-release/ │ │ │ │ └── pom.xml │ │ │ ├── jar-snapshot/ │ │ │ │ └── pom.xml │ │ │ └── pom.xml │ │ ├── jar-layered-custom/ │ │ │ ├── jar/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ ├── layers.xml │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── org/ │ │ │ │ │ └── test/ │ │ │ │ │ └── SampleApplication.java │ │ │ │ └── resources/ │ │ │ │ └── application.yml │ │ │ ├── jar-classifier/ │ │ │ │ └── pom.xml │ │ │ ├── jar-release/ │ │ │ │ └── pom.xml │ │ │ ├── jar-snapshot/ │ │ │ │ └── pom.xml │ │ │ └── pom.xml │ │ ├── jar-layered-custom-name/ │ │ │ ├── jar/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── org/ │ │ │ │ │ └── test/ │ │ │ │ │ └── SampleApplication.java │ │ │ │ └── resources/ │ │ │ │ └── application.yml │ │ │ ├── jar-classifier/ │ │ │ │ └── pom.xml │ │ │ ├── jar-layers-configuration/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── resources/ │ │ │ │ └── META-INF/ │ │ │ │ └── spring/ │ │ │ │ └── layers/ │ │ │ │ └── custom.xml │ │ │ ├── jar-release/ │ │ │ │ └── pom.xml │ │ │ ├── jar-snapshot/ │ │ │ │ └── pom.xml │ │ │ └── pom.xml │ │ ├── jar-layered-disabled/ │ │ │ ├── jar/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ ├── jar-release/ │ │ │ │ └── pom.xml │ │ │ ├── jar-snapshot/ │ │ │ │ └── pom.xml │ │ │ └── pom.xml │ │ ├── jar-lib-name-conflict/ │ │ │ ├── acme-lib/ │ │ │ │ └── pom.xml │ │ │ ├── another-acme-lib/ │ │ │ │ └── pom.xml │ │ │ ├── pom.xml │ │ │ └── test-project/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-no-tools/ │ │ │ ├── jar/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ ├── jar-release/ │ │ │ │ └── pom.xml │ │ │ ├── jar-snapshot/ │ │ │ │ └── pom.xml │ │ │ └── pom.xml │ │ ├── jar-optional-default/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-optional-exclude/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-optional-include/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-output-timestamp/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-pom/ │ │ │ └── pom.xml │ │ ├── jar-signed/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-skip/ │ │ │ └── pom.xml │ │ ├── jar-system-scope/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-system-scope-default/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-test-scope/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-with-kotlin-module/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── kotlin/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.kt │ │ ├── jar-with-layout-property/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-with-unpack/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── jar-with-zip-layout/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-additional-classpath-directory/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── additional-elements/ │ │ │ │ ├── another/ │ │ │ │ │ └── two.txt │ │ │ │ └── one.txt │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-additional-classpath-jar/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-arguments/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-arguments-commandline/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-envargs/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-exclude/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-fork/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-jvm-system-props/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-jvmargs/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-jvmargs-commandline/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-profiles/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-toolchains/ │ │ │ ├── jdkHome/ │ │ │ │ └── bin/ │ │ │ │ └── java │ │ │ ├── pom.xml │ │ │ ├── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── toolchains.xml │ │ ├── run-use-test-classpath/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── run-working-directory/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── settings.xml │ │ ├── start-stop/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── start-stop-skip/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── test-run/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── TestSampleApplication.java │ │ ├── war/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── webapp/ │ │ │ └── index.html │ │ ├── war-exclude-entry/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── webapp/ │ │ │ └── index.html │ │ ├── war-layered/ │ │ │ ├── jar-release/ │ │ │ │ └── pom.xml │ │ │ ├── jar-snapshot/ │ │ │ │ └── pom.xml │ │ │ ├── pom.xml │ │ │ └── war/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── webapp/ │ │ │ └── index.html │ │ ├── war-layered-custom/ │ │ │ ├── jar-classifier/ │ │ │ │ └── pom.xml │ │ │ ├── jar-release/ │ │ │ │ └── pom.xml │ │ │ ├── jar-snapshot/ │ │ │ │ └── pom.xml │ │ │ ├── pom.xml │ │ │ └── war/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── layers.xml │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── webapp/ │ │ │ └── index.html │ │ ├── war-layered-disabled/ │ │ │ ├── jar-release/ │ │ │ │ └── pom.xml │ │ │ ├── jar-snapshot/ │ │ │ │ └── pom.xml │ │ │ ├── pom.xml │ │ │ └── war/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── webapp/ │ │ │ └── index.html │ │ ├── war-no-tools/ │ │ │ ├── jar-release/ │ │ │ │ └── pom.xml │ │ │ ├── jar-snapshot/ │ │ │ │ └── pom.xml │ │ │ ├── pom.xml │ │ │ └── war/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── webapp/ │ │ │ └── index.html │ │ ├── war-output-timestamp/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── webapp/ │ │ │ └── index.html │ │ ├── war-reactor/ │ │ │ ├── jar/ │ │ │ │ └── pom.xml │ │ │ ├── pom.xml │ │ │ └── war/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── example/ │ │ │ │ └── SampleApplication.java │ │ │ └── webapp/ │ │ │ └── index.html │ │ ├── war-signed/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ ├── war-system-scope/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── test/ │ │ │ │ └── SampleApplication.java │ │ │ └── webapp/ │ │ │ └── index.html │ │ └── war-with-unpack/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── test/ │ │ │ └── SampleApplication.java │ │ └── webapp/ │ │ └── index.html │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── maven/ │ │ │ ├── AbstractAotMojo.java │ │ │ ├── AbstractDependencyFilterMojo.java │ │ │ ├── AbstractPackagerMojo.java │ │ │ ├── AbstractRunMojo.java │ │ │ ├── ArtifactsLibraries.java │ │ │ ├── BuildImageForkMojo.java │ │ │ ├── BuildImageMojo.java │ │ │ ├── BuildImageNoForkMojo.java │ │ │ ├── BuildInfoMojo.java │ │ │ ├── CacheInfo.java │ │ │ ├── ClassPath.java │ │ │ ├── CommandLineBuilder.java │ │ │ ├── CustomLayersProvider.java │ │ │ ├── DependencyFilter.java │ │ │ ├── Docker.java │ │ │ ├── EnvVariables.java │ │ │ ├── Exclude.java │ │ │ ├── ExcludeFilter.java │ │ │ ├── FilterableDependency.java │ │ │ ├── Image.java │ │ │ ├── Include.java │ │ │ ├── IncludeFilter.java │ │ │ ├── JarTypeFilter.java │ │ │ ├── JavaCompilerPluginConfiguration.java │ │ │ ├── JavaProcessExecutor.java │ │ │ ├── Layers.java │ │ │ ├── LoggingMainClassTimeoutWarningListener.java │ │ │ ├── MatchingGroupIdFilter.java │ │ │ ├── MavenBuildOutputTimestamp.java │ │ │ ├── ProcessAotMojo.java │ │ │ ├── ProcessTestAotMojo.java │ │ │ ├── PropertiesMergingResourceTransformer.java │ │ │ ├── RepackageMojo.java │ │ │ ├── RunArguments.java │ │ │ ├── RunMojo.java │ │ │ ├── SpringApplicationAdminClient.java │ │ │ ├── SpringBootApplicationClassFinder.java │ │ │ ├── StartMojo.java │ │ │ ├── StopMojo.java │ │ │ ├── SystemPropertyFormatter.java │ │ │ ├── TestRunMojo.java │ │ │ ├── VersionExtractor.java │ │ │ └── package-info.java │ │ ├── resources/ │ │ │ └── META-INF/ │ │ │ ├── m2e/ │ │ │ │ └── lifecycle-mapping-metadata.xml │ │ │ └── spring/ │ │ │ └── layers/ │ │ │ └── default.xml │ │ └── xsd/ │ │ ├── layers-2.3.xsd │ │ ├── layers-2.4.xsd │ │ ├── layers-2.5.xsd │ │ ├── layers-2.6.xsd │ │ ├── layers-2.7.xsd │ │ ├── layers-3.0.xsd │ │ ├── layers-3.1.xsd │ │ ├── layers-3.2.xsd │ │ ├── layers-3.3.xsd │ │ ├── layers-3.4.xsd │ │ ├── layers-3.5.xsd │ │ ├── layers-4.0.xsd │ │ └── layers-4.1.xsd │ ├── maven/ │ │ └── resources/ │ │ └── pom.xml │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── maven/ │ │ ├── ArtifactsLibrariesTests.java │ │ ├── ClassPathTests.java │ │ ├── CommandLineBuilderTests.java │ │ ├── CustomLayersProviderTests.java │ │ ├── DependencyFilterMojoTests.java │ │ ├── DependencyFilterTests.java │ │ ├── DockerTests.java │ │ ├── EnvVariablesTests.java │ │ ├── ExcludeFilterTests.java │ │ ├── ImageTests.java │ │ ├── IncludeFilterTests.java │ │ ├── JarTypeFilterTests.java │ │ ├── JavaCompilerPluginConfigurationTests.java │ │ ├── MavenBuildOutputTimestampTests.java │ │ ├── PropertiesMergingResourceTransformerTests.java │ │ ├── RunArgumentsTests.java │ │ ├── SystemPropertyFormatterTests.java │ │ └── sample/ │ │ ├── ClassWithMainMethod.java │ │ ├── ClassWithoutMainMethod.java │ │ └── package-info.java │ └── resources/ │ ├── application-layer-no-filter.xml │ ├── dependencies-layer-no-filter.xml │ ├── layers.xml │ └── resource-layer-no-filter.xml ├── build.gradle ├── buildSrc/ │ ├── SpringRepositorySupport.groovy │ ├── build.gradle │ ├── config/ │ │ └── checkstyle/ │ │ └── checkstyle.xml │ ├── settings.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── build/ │ │ │ ├── AntoraConventions.java │ │ │ ├── ConventionsPlugin.java │ │ │ ├── DeployedPlugin.java │ │ │ ├── EclipseConventions.java │ │ │ ├── EclipseSynchronizeJdtSettings.java │ │ │ ├── EclipseSynchronizeResourceSettings.java │ │ │ ├── EmptyPropertiesPersistableConfigurationObject.java │ │ │ ├── ExtractResources.java │ │ │ ├── JavaConventions.java │ │ │ ├── KotlinConventions.java │ │ │ ├── MavenPublishingConventions.java │ │ │ ├── MavenRepositoryPlugin.java │ │ │ ├── NoHttpConventions.java │ │ │ ├── RepositoryTransformersExtension.java │ │ │ ├── SyncAppSource.java │ │ │ ├── SystemRequirementsExtension.java │ │ │ ├── TestFixturesConventions.java │ │ │ ├── WarConventions.java │ │ │ ├── aggregation/ │ │ │ │ ├── Aggregate.java │ │ │ │ └── AggregatorPlugin.java │ │ │ ├── antora/ │ │ │ │ ├── AggregateContentContribution.java │ │ │ │ ├── AntoraAsciidocAttributes.java │ │ │ │ ├── AntoraContributorPlugin.java │ │ │ │ ├── AntoraDependenciesPlugin.java │ │ │ │ ├── CatalogContentContribution.java │ │ │ │ ├── CheckJavadocMacros.java │ │ │ │ ├── ConsumableContentContribution.java │ │ │ │ ├── ContentContribution.java │ │ │ │ ├── Contribution.java │ │ │ │ ├── CopyAntoraContent.java │ │ │ │ ├── Extensions.java │ │ │ │ ├── GenerateAntoraPlaybook.java │ │ │ │ ├── LocalAggregateContentContribution.java │ │ │ │ ├── SourceContribution.java │ │ │ │ └── SyncAntoraSource.java │ │ │ ├── architecture/ │ │ │ │ ├── ArchitectureCheck.java │ │ │ │ ├── ArchitectureCheckAnnotation.java │ │ │ │ ├── ArchitecturePlugin.java │ │ │ │ ├── ArchitectureRules.java │ │ │ │ └── AutoConfigurationChecker.java │ │ │ ├── artifacts/ │ │ │ │ └── ArtifactRelease.java │ │ │ ├── autoconfigure/ │ │ │ │ ├── AutoConfigurationClass.java │ │ │ │ ├── AutoConfigurationImportsTask.java │ │ │ │ ├── AutoConfigurationMetadata.java │ │ │ │ ├── AutoConfigurationPlugin.java │ │ │ │ ├── CheckAutoConfigurationClasses.java │ │ │ │ ├── CheckAutoConfigurationImports.java │ │ │ │ └── DocumentAutoConfigurationClasses.java │ │ │ ├── bom/ │ │ │ │ ├── BomExtension.java │ │ │ │ ├── BomPlugin.java │ │ │ │ ├── BomResolver.java │ │ │ │ ├── CheckBom.java │ │ │ │ ├── CheckLinks.java │ │ │ │ ├── CreateResolvedBom.java │ │ │ │ ├── Library.java │ │ │ │ ├── ResolvedBom.java │ │ │ │ ├── UpgradePolicy.java │ │ │ │ └── bomr/ │ │ │ │ ├── InteractiveUpgradeResolver.java │ │ │ │ ├── LibraryUpdateResolver.java │ │ │ │ ├── LibraryWithVersionOptions.java │ │ │ │ ├── MavenMetadataVersionResolver.java │ │ │ │ ├── MoveToSnapshots.java │ │ │ │ ├── MultithreadedLibraryUpdateResolver.java │ │ │ │ ├── ReleaseSchedule.java │ │ │ │ ├── StandardLibraryUpdateResolver.java │ │ │ │ ├── Upgrade.java │ │ │ │ ├── UpgradeApplicator.java │ │ │ │ ├── UpgradeBom.java │ │ │ │ ├── UpgradeDependencies.java │ │ │ │ ├── UpgradeResolver.java │ │ │ │ ├── VersionOption.java │ │ │ │ ├── VersionResolver.java │ │ │ │ ├── github/ │ │ │ │ │ ├── GitHub.java │ │ │ │ │ ├── GitHubRepository.java │ │ │ │ │ ├── Issue.java │ │ │ │ │ ├── Milestone.java │ │ │ │ │ ├── StandardGitHub.java │ │ │ │ │ └── StandardGitHubRepository.java │ │ │ │ └── version/ │ │ │ │ ├── AbstractDependencyVersion.java │ │ │ │ ├── ArtifactVersionDependencyVersion.java │ │ │ │ ├── CalendarVersionDependencyVersion.java │ │ │ │ ├── CombinedPatchAndQualifierDependencyVersion.java │ │ │ │ ├── DependencyVersion.java │ │ │ │ ├── LeadingZeroesDependencyVersion.java │ │ │ │ ├── MultipleComponentsDependencyVersion.java │ │ │ │ ├── ReleaseTrainDependencyVersion.java │ │ │ │ └── UnstructuredDependencyVersion.java │ │ │ ├── classpath/ │ │ │ │ ├── CheckClasspathForConflicts.java │ │ │ │ ├── CheckClasspathForProhibitedDependencies.java │ │ │ │ ├── CheckClasspathForUnconstrainedDirectDependencies.java │ │ │ │ └── CheckClasspathForUnnecessaryExclusions.java │ │ │ ├── cli/ │ │ │ │ └── HomebrewFormula.java │ │ │ ├── context/ │ │ │ │ └── properties/ │ │ │ │ ├── Asciidoc.java │ │ │ │ ├── CheckAdditionalSpringConfigurationMetadata.java │ │ │ │ ├── CheckAggregatedSpringConfigurationMetadata.java │ │ │ │ ├── CheckManualSpringConfigurationMetadata.java │ │ │ │ ├── CheckSpringConfigurationMetadata.java │ │ │ │ ├── CompoundRow.java │ │ │ │ ├── ConfigurationMetadataPlugin.java │ │ │ │ ├── ConfigurationProperties.java │ │ │ │ ├── ConfigurationPropertiesAnalyzer.java │ │ │ │ ├── ConfigurationPropertiesPlugin.java │ │ │ │ ├── ConfigurationProperty.java │ │ │ │ ├── DocumentConfigurationProperties.java │ │ │ │ ├── Row.java │ │ │ │ ├── SingleRow.java │ │ │ │ ├── Snippet.java │ │ │ │ ├── Snippets.java │ │ │ │ └── Table.java │ │ │ ├── devtools/ │ │ │ │ └── DocumentDevtoolsPropertyDefaults.java │ │ │ ├── docs/ │ │ │ │ ├── ApplicationRunner.java │ │ │ │ ├── ConfigureJavadocLinks.java │ │ │ │ ├── DocumentManagedDependencies.java │ │ │ │ └── DocumentVersionProperties.java │ │ │ ├── mavenplugin/ │ │ │ │ ├── DocumentPluginGoals.java │ │ │ │ ├── MavenExec.java │ │ │ │ ├── MavenPluginPlugin.java │ │ │ │ ├── PluginXmlParser.java │ │ │ │ └── PrepareMavenBinaries.java │ │ │ ├── optional/ │ │ │ │ └── OptionalDependenciesPlugin.java │ │ │ ├── processors/ │ │ │ │ └── AnnotationProcessorPlugin.java │ │ │ ├── properties/ │ │ │ │ ├── BuildProperties.java │ │ │ │ └── BuildType.java │ │ │ ├── springframework/ │ │ │ │ ├── CheckAotFactories.java │ │ │ │ ├── CheckFactoriesFile.java │ │ │ │ └── CheckSpringFactories.java │ │ │ ├── starters/ │ │ │ │ ├── DocumentStarters.java │ │ │ │ ├── StarterMetadata.java │ │ │ │ └── StarterPlugin.java │ │ │ ├── test/ │ │ │ │ ├── DockerTestBuildService.java │ │ │ │ ├── DockerTestPlugin.java │ │ │ │ ├── IntegrationTestPlugin.java │ │ │ │ ├── SystemTestPlugin.java │ │ │ │ └── autoconfigure/ │ │ │ │ ├── CheckAutoConfigureImports.java │ │ │ │ ├── DocumentTestSlices.java │ │ │ │ ├── GenerateTestSliceMetadata.java │ │ │ │ ├── TestAutoConfigurationPlugin.java │ │ │ │ ├── TestSliceMetadata.java │ │ │ │ └── TestSlicePlugin.java │ │ │ ├── testing/ │ │ │ │ ├── TestFailuresPlugin.java │ │ │ │ └── TestResultsOverview.java │ │ │ └── toolchain/ │ │ │ ├── ToolchainExtension.java │ │ │ └── ToolchainPlugin.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── build/ │ │ ├── antora/ │ │ │ ├── antora-asciidoc-attributes.properties │ │ │ └── antora-playbook-template.yml │ │ └── legal/ │ │ ├── LICENSE.txt │ │ └── NOTICE.txt │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── build/ │ │ ├── ConventionsPluginTests.java │ │ ├── antora/ │ │ │ ├── AntoraAsciidocAttributesTests.java │ │ │ └── GenerateAntoraPlaybookTests.java │ │ ├── architecture/ │ │ │ ├── ArchitectureCheckTests.java │ │ │ ├── annotations/ │ │ │ │ ├── TestConditionalOnClass.java │ │ │ │ ├── TestConditionalOnMissingBean.java │ │ │ │ ├── TestConfigurationProperties.java │ │ │ │ ├── TestConfigurationPropertiesBinding.java │ │ │ │ └── TestDeprecatedConfigurationProperty.java │ │ │ ├── assertj/ │ │ │ │ ├── checkReturnValue/ │ │ │ │ │ └── WithCheckReturnValue.java │ │ │ │ └── noCheckReturnValue/ │ │ │ │ └── NoCheckReturnValue.java │ │ │ ├── beans/ │ │ │ │ ├── privatebean/ │ │ │ │ │ └── PrivateBean.java │ │ │ │ └── regular/ │ │ │ │ └── RegularBean.java │ │ │ ├── bfpp/ │ │ │ │ ├── nonstatic/ │ │ │ │ │ └── NonStaticBeanFactoryPostProcessorConfiguration.java │ │ │ │ ├── noparameters/ │ │ │ │ │ └── NoParametersBeanFactoryPostProcessorConfiguration.java │ │ │ │ └── parameters/ │ │ │ │ └── ParametersBeanFactoryPostProcessorConfiguration.java │ │ │ ├── bpp/ │ │ │ │ ├── nonstatic/ │ │ │ │ │ └── NonStaticBeanPostProcessorConfiguration.java │ │ │ │ ├── noparameters/ │ │ │ │ │ └── NoParametersBeanPostProcessorConfiguration.java │ │ │ │ ├── safeparameters/ │ │ │ │ │ └── SafeParametersBeanPostProcessorConfiguration.java │ │ │ │ └── unsafeparameters/ │ │ │ │ └── UnsafeParametersBeanPostProcessorConfiguration.java │ │ │ ├── collectors/ │ │ │ │ └── toList/ │ │ │ │ └── CollectorsToList.java │ │ │ ├── conditionalonclass/ │ │ │ │ └── OnBeanMethod.java │ │ │ ├── conditionalonmissingbean/ │ │ │ │ ├── valueonly/ │ │ │ │ │ └── TypeSameAsMethodReturnType.java │ │ │ │ ├── withname/ │ │ │ │ │ └── WithNameAttribute.java │ │ │ │ └── withtype/ │ │ │ │ └── WithTypeAttribute.java │ │ │ ├── configurationproperties/ │ │ │ │ ├── bindingnonstatic/ │ │ │ │ │ └── BindingMethodNonStatic.java │ │ │ │ ├── classprefixandignore/ │ │ │ │ │ └── ConfigurationPropertiesWithPrefixAndIgnore.java │ │ │ │ ├── classprefixonly/ │ │ │ │ │ └── ConfigurationPropertiesWithPrefixOnly.java │ │ │ │ ├── classvalueonly/ │ │ │ │ │ └── ConfigurationPropertiesWithValueOnly.java │ │ │ │ ├── deprecatedsince/ │ │ │ │ │ └── DeprecatedConfigurationPropertySince.java │ │ │ │ ├── methodprefixandignore/ │ │ │ │ │ └── ConfigurationPropertiesWithPrefixAndIgnore.java │ │ │ │ ├── methodprefixonly/ │ │ │ │ │ └── ConfigurationPropertiesWithPrefixOnly.java │ │ │ │ └── methodvalueonly/ │ │ │ │ └── ConfigurationPropertiesWithValueOnly.java │ │ │ ├── junit/ │ │ │ │ └── enumsource/ │ │ │ │ ├── inferredfromparametertype/ │ │ │ │ │ └── EnumSourceInferredFromParameterType.java │ │ │ │ ├── sameasparametertype/ │ │ │ │ │ └── EnumSourceSameAsParameterType.java │ │ │ │ └── valuenecessary/ │ │ │ │ └── EnumSourceValueNecessary.java │ │ │ ├── nullmarked/ │ │ │ │ └── notannotated/ │ │ │ │ └── TestClass.java │ │ │ ├── objects/ │ │ │ │ ├── noRequireNonNull/ │ │ │ │ │ └── NoRequireNonNull.java │ │ │ │ ├── requireNonNullWithString/ │ │ │ │ │ └── RequireNonNullWithString.java │ │ │ │ └── requireNonNullWithSupplier/ │ │ │ │ └── RequireNonNullWithSupplier.java │ │ │ ├── resources/ │ │ │ │ ├── loads/ │ │ │ │ │ └── ResourceUtilsResourceLoader.java │ │ │ │ └── noloads/ │ │ │ │ └── ResourceUtilsWithoutLoading.java │ │ │ ├── string/ │ │ │ │ ├── toLowerCase/ │ │ │ │ │ └── ToLowerCase.java │ │ │ │ ├── toLowerCaseWithLocale/ │ │ │ │ │ └── ToLowerCaseWithLocale.java │ │ │ │ ├── toUpperCase/ │ │ │ │ │ └── ToUpperCase.java │ │ │ │ └── toUpperCaseWithLocale/ │ │ │ │ └── ToUpperCaseWithLocale.java │ │ │ ├── tangled/ │ │ │ │ ├── TangledOne.java │ │ │ │ └── sub/ │ │ │ │ └── TangledTwo.java │ │ │ ├── untangled/ │ │ │ │ ├── UntangledOne.java │ │ │ │ └── sub/ │ │ │ │ └── UntangledTwo.java │ │ │ └── url/ │ │ │ ├── decode/ │ │ │ │ └── UrlDecodeWithStringEncoding.java │ │ │ └── encode/ │ │ │ └── UrlEncodeWithStringEncoding.java │ │ ├── artifacts/ │ │ │ └── ArtifactReleaseTests.java │ │ ├── assertj/ │ │ │ └── NodeAssert.java │ │ ├── autoconfigure/ │ │ │ └── DocumentAutoConfigurationClassesTests.java │ │ ├── bom/ │ │ │ ├── BomPluginIntegrationTests.java │ │ │ ├── LibraryTests.java │ │ │ └── bomr/ │ │ │ ├── InteractiveUpgradeResolverTests.java │ │ │ ├── ReleaseScheduleTests.java │ │ │ ├── UpgradeApplicatorTests.java │ │ │ ├── UpgradeTests.java │ │ │ └── version/ │ │ │ ├── ArtifactVersionDependencyVersionTests.java │ │ │ ├── CalendarVersionDependencyVersionTests.java │ │ │ ├── DependencyVersionTests.java │ │ │ ├── DependencyVersionUpgradeTests.java │ │ │ ├── MultipleComponentsDependencyVersionTests.java │ │ │ └── ReleaseTrainDependencyVersionTests.java │ │ ├── context/ │ │ │ └── properties/ │ │ │ ├── CompoundRowTests.java │ │ │ ├── ConfigurationPropertiesAnalyzerTests.java │ │ │ ├── ConfigurationPropertiesTests.java │ │ │ ├── SingleRowTests.java │ │ │ └── TableTests.java │ │ ├── groovyscripts/ │ │ │ └── SpringRepositoriesExtensionTests.java │ │ ├── mavenplugin/ │ │ │ └── PluginXmlParserTests.java │ │ ├── optional/ │ │ │ └── OptionalDependenciesPluginIntegrationTests.java │ │ ├── test/ │ │ │ └── autoconfigure/ │ │ │ └── TestSliceMetadataTests.java │ │ └── testing/ │ │ └── TestFailuresPluginIntegrationTests.java │ └── resources/ │ ├── bom.gradle │ ├── gradle.properties │ ├── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── build/ │ │ └── antora/ │ │ └── expected-playbook.yml │ ├── plugin.xml │ ├── releases.json │ └── spring-configuration-metadata.json ├── buildpack/ │ └── spring-boot-buildpack-platform/ │ ├── build.gradle │ └── src/ │ ├── dockerTest/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── buildpack/ │ │ └── platform/ │ │ └── docker/ │ │ └── DockerApiIntegrationTests.java │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── buildpack/ │ │ └── platform/ │ │ ├── build/ │ │ │ ├── AbstractBuildLog.java │ │ │ ├── ApiVersions.java │ │ │ ├── BuildLog.java │ │ │ ├── BuildOwner.java │ │ │ ├── BuildRequest.java │ │ │ ├── Builder.java │ │ │ ├── BuilderBuildpack.java │ │ │ ├── BuilderDockerConfiguration.java │ │ │ ├── BuilderException.java │ │ │ ├── BuilderMetadata.java │ │ │ ├── Buildpack.java │ │ │ ├── BuildpackCoordinates.java │ │ │ ├── BuildpackLayersMetadata.java │ │ │ ├── BuildpackMetadata.java │ │ │ ├── BuildpackReference.java │ │ │ ├── BuildpackResolver.java │ │ │ ├── BuildpackResolverContext.java │ │ │ ├── BuildpackResolvers.java │ │ │ ├── Buildpacks.java │ │ │ ├── Cache.java │ │ │ ├── Creator.java │ │ │ ├── DirectoryBuildpack.java │ │ │ ├── EphemeralBuilder.java │ │ │ ├── ImageBuildpack.java │ │ │ ├── ImageType.java │ │ │ ├── Lifecycle.java │ │ │ ├── LifecycleVersion.java │ │ │ ├── Phase.java │ │ │ ├── PrintStreamBuildLog.java │ │ │ ├── PullPolicy.java │ │ │ ├── StackId.java │ │ │ ├── TarGzipBuildpack.java │ │ │ └── package-info.java │ │ ├── docker/ │ │ │ ├── ApiVersion.java │ │ │ ├── DockerApi.java │ │ │ ├── DockerLog.java │ │ │ ├── ExportedImageTar.java │ │ │ ├── ImagePlatform.java │ │ │ ├── ImageProgressUpdateEvent.java │ │ │ ├── LoadImageUpdateEvent.java │ │ │ ├── LogUpdateEvent.java │ │ │ ├── ProgressUpdateEvent.java │ │ │ ├── PullImageUpdateEvent.java │ │ │ ├── PushImageUpdateEvent.java │ │ │ ├── TotalProgressBar.java │ │ │ ├── TotalProgressEvent.java │ │ │ ├── TotalProgressListener.java │ │ │ ├── TotalProgressPullListener.java │ │ │ ├── TotalProgressPushListener.java │ │ │ ├── UpdateEvent.java │ │ │ ├── UpdateListener.java │ │ │ ├── configuration/ │ │ │ │ ├── Credential.java │ │ │ │ ├── CredentialHelper.java │ │ │ │ ├── DockerConfigurationMetadata.java │ │ │ │ ├── DockerConnectionConfiguration.java │ │ │ │ ├── DockerHost.java │ │ │ │ ├── DockerRegistryAuthentication.java │ │ │ │ ├── DockerRegistryConfigAuthentication.java │ │ │ │ ├── DockerRegistryTokenAuthentication.java │ │ │ │ ├── DockerRegistryUserAuthentication.java │ │ │ │ ├── JsonEncodedDockerRegistryAuthentication.java │ │ │ │ ├── ResolvedDockerHost.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── ssl/ │ │ │ │ ├── KeyStoreFactory.java │ │ │ │ ├── PemCertificateParser.java │ │ │ │ ├── PemPrivateKeyParser.java │ │ │ │ ├── SslContextFactory.java │ │ │ │ └── package-info.java │ │ │ ├── transport/ │ │ │ │ ├── DockerConnectionException.java │ │ │ │ ├── DockerEngineException.java │ │ │ │ ├── Errors.java │ │ │ │ ├── HttpClientTransport.java │ │ │ │ ├── HttpTransport.java │ │ │ │ ├── LocalHttpClientTransport.java │ │ │ │ ├── Message.java │ │ │ │ ├── RemoteHttpClientTransport.java │ │ │ │ └── package-info.java │ │ │ └── type/ │ │ │ ├── Binding.java │ │ │ ├── BlobReference.java │ │ │ ├── ContainerConfig.java │ │ │ ├── ContainerContent.java │ │ │ ├── ContainerReference.java │ │ │ ├── ContainerStatus.java │ │ │ ├── Image.java │ │ │ ├── ImageArchive.java │ │ │ ├── ImageArchiveIndex.java │ │ │ ├── ImageArchiveManifest.java │ │ │ ├── ImageConfig.java │ │ │ ├── ImageName.java │ │ │ ├── ImageReference.java │ │ │ ├── Layer.java │ │ │ ├── LayerId.java │ │ │ ├── Manifest.java │ │ │ ├── ManifestList.java │ │ │ ├── RandomString.java │ │ │ ├── Regex.java │ │ │ ├── VolumeName.java │ │ │ └── package-info.java │ │ ├── io/ │ │ │ ├── Content.java │ │ │ ├── DefaultOwner.java │ │ │ ├── FilePermissions.java │ │ │ ├── IOBiConsumer.java │ │ │ ├── IOConsumer.java │ │ │ ├── IOSupplier.java │ │ │ ├── InspectedContent.java │ │ │ ├── Layout.java │ │ │ ├── Owner.java │ │ │ ├── TarArchive.java │ │ │ ├── TarLayoutWriter.java │ │ │ ├── ZipFileTarArchive.java │ │ │ └── package-info.java │ │ ├── json/ │ │ │ ├── JsonStream.java │ │ │ ├── MappedObject.java │ │ │ ├── SharedJsonMapper.java │ │ │ └── package-info.java │ │ ├── socket/ │ │ │ ├── AbstractSocket.java │ │ │ ├── FileDescriptor.java │ │ │ ├── NamedPipeSocket.java │ │ │ ├── UnixDomainSocket.java │ │ │ └── package-info.java │ │ └── system/ │ │ ├── Environment.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── buildpack/ │ │ └── platform/ │ │ ├── build/ │ │ │ ├── ApiVersionsTests.java │ │ │ ├── BuildLogTests.java │ │ │ ├── BuildOwnerTests.java │ │ │ ├── BuildRequestTests.java │ │ │ ├── BuilderBuildpackTests.java │ │ │ ├── BuilderExceptionTests.java │ │ │ ├── BuilderMetadataTests.java │ │ │ ├── BuilderTests.java │ │ │ ├── BuildpackCoordinatesTests.java │ │ │ ├── BuildpackLayersMetadataTests.java │ │ │ ├── BuildpackMetadataTests.java │ │ │ ├── BuildpackReferenceTests.java │ │ │ ├── BuildpackResolversTests.java │ │ │ ├── BuildpacksTests.java │ │ │ ├── DirectoryBuildpackTests.java │ │ │ ├── EphemeralBuilderTests.java │ │ │ ├── ImageBuildpackTests.java │ │ │ ├── LifecycleTests.java │ │ │ ├── LifecycleVersionTests.java │ │ │ ├── PhaseTests.java │ │ │ ├── PrintStreamBuildLogTests.java │ │ │ ├── StackIdTests.java │ │ │ ├── TarGzipBuildpackTests.java │ │ │ ├── TestBuildpack.java │ │ │ └── TestTarGzip.java │ │ ├── docker/ │ │ │ ├── ApiVersionTests.java │ │ │ ├── DockerApiTests.java │ │ │ ├── DockerLogTests.java │ │ │ ├── ExportedImageTarTests.java │ │ │ ├── ImagePlatformTests.java │ │ │ ├── LoadImageUpdateEventTests.java │ │ │ ├── LogUpdateEventTests.java │ │ │ ├── ProgressUpdateEventTests.java │ │ │ ├── PullImageUpdateEventTests.java │ │ │ ├── PullUpdateEventTests.java │ │ │ ├── PushImageUpdateEventTests.java │ │ │ ├── TotalProgressBarTests.java │ │ │ ├── TotalProgressEventTests.java │ │ │ ├── TotalProgressListenerTests.java │ │ │ ├── configuration/ │ │ │ │ ├── CredentialHelperTests.java │ │ │ │ ├── CredentialTests.java │ │ │ │ ├── DockerConfigurationMetadataTests.java │ │ │ │ ├── DockerRegistryConfigAuthenticationTests.java │ │ │ │ ├── DockerRegistryTokenAuthenticationTests.java │ │ │ │ ├── DockerRegistryUserAuthenticationTests.java │ │ │ │ └── ResolvedDockerHostTests.java │ │ │ ├── ssl/ │ │ │ │ ├── KeyStoreFactoryTests.java │ │ │ │ ├── PemCertificateParserTests.java │ │ │ │ ├── PemFileWriter.java │ │ │ │ ├── PemPrivateKeyParserTests.java │ │ │ │ ├── SslContextFactoryTests.java │ │ │ │ └── SslSource.java │ │ │ ├── transport/ │ │ │ │ ├── DockerConnectionExceptionTests.java │ │ │ │ ├── DockerEngineExceptionTests.java │ │ │ │ ├── ErrorsTests.java │ │ │ │ ├── HttpClientTransportTests.java │ │ │ │ ├── HttpTransportTests.java │ │ │ │ ├── LocalHttpClientTransportTests.java │ │ │ │ ├── MessageTests.java │ │ │ │ ├── RemoteHttpClientTransportTests.java │ │ │ │ └── TestDockerEngineException.java │ │ │ └── type/ │ │ │ ├── BindingTests.java │ │ │ ├── ContainerConfigTests.java │ │ │ ├── ContainerContentTests.java │ │ │ ├── ContainerReferenceTests.java │ │ │ ├── ContainerStatusTests.java │ │ │ ├── ImageArchiveIndexTests.java │ │ │ ├── ImageArchiveManifestTests.java │ │ │ ├── ImageArchiveTests.java │ │ │ ├── ImageConfigTests.java │ │ │ ├── ImageNameTests.java │ │ │ ├── ImageReferenceTests.java │ │ │ ├── ImageTests.java │ │ │ ├── LayerIdTests.java │ │ │ ├── LayerTests.java │ │ │ ├── ManifestListTests.java │ │ │ ├── ManifestTests.java │ │ │ ├── RandomStringTests.java │ │ │ └── VolumeNameTests.java │ │ ├── io/ │ │ │ ├── ContentTests.java │ │ │ ├── DefaultOwnerTests.java │ │ │ ├── FilePermissionsTests.java │ │ │ ├── InspectedContentTests.java │ │ │ ├── OwnerTests.java │ │ │ ├── TarArchiveTests.java │ │ │ ├── TarLayoutWriterTests.java │ │ │ └── ZipFileTarArchiveTests.java │ │ ├── json/ │ │ │ ├── AbstractJsonTests.java │ │ │ ├── JsonStreamTests.java │ │ │ ├── MappedObjectTests.java │ │ │ └── SharedJsonMapperTests.java │ │ └── socket/ │ │ └── FileDescriptorTests.java │ └── resources/ │ └── org/ │ └── springframework/ │ └── boot/ │ └── buildpack/ │ └── platform/ │ ├── build/ │ │ ├── builder-metadata-platform-api-0.3.json │ │ ├── builder-metadata-supported-apis.json │ │ ├── builder-metadata-unsupported-api.json │ │ ├── builder-metadata-unsupported-apis.json │ │ ├── builder-metadata.json │ │ ├── buildpack-image.json │ │ ├── buildpack-layers-metadata.json │ │ ├── buildpack-metadata.json │ │ ├── buildpack.toml │ │ ├── image-with-empty-stack.json │ │ ├── image-with-no-run-image-tag.json │ │ ├── image-with-platform.json │ │ ├── image-with-run-image-different-registry.json │ │ ├── image-with-run-image-digest.json │ │ ├── image.json │ │ ├── lifecycle-analyzer-cache-bind-mounts.json │ │ ├── lifecycle-analyzer-cache-volumes.json │ │ ├── lifecycle-analyzer-inherit-local.json │ │ ├── lifecycle-analyzer-inherit-remote.json │ │ ├── lifecycle-analyzer-security-opts.json │ │ ├── lifecycle-analyzer.json │ │ ├── lifecycle-builder-app-dir.json │ │ ├── lifecycle-builder-cache-bind-mounts.json │ │ ├── lifecycle-builder-cache-volumes.json │ │ ├── lifecycle-builder.json │ │ ├── lifecycle-creator-app-dir.json │ │ ├── lifecycle-creator-bindings.json │ │ ├── lifecycle-creator-cache-bind-mounts.json │ │ ├── lifecycle-creator-cache-volumes.json │ │ ├── lifecycle-creator-clean-cache.json │ │ ├── lifecycle-creator-created-date.json │ │ ├── lifecycle-creator-inherit-local.json │ │ ├── lifecycle-creator-inherit-remote.json │ │ ├── lifecycle-creator-network.json │ │ ├── lifecycle-creator-platform-api-0.3.json │ │ ├── lifecycle-creator-security-opts.json │ │ ├── lifecycle-creator.json │ │ ├── lifecycle-detector-app-dir.json │ │ ├── lifecycle-detector-cache-bind-mounts.json │ │ ├── lifecycle-detector-cache-volumes.json │ │ ├── lifecycle-detector.json │ │ ├── lifecycle-exporter-app-dir.json │ │ ├── lifecycle-exporter-cache-bind-mounts.json │ │ ├── lifecycle-exporter-cache-volumes.json │ │ ├── lifecycle-exporter-created-date.json │ │ ├── lifecycle-exporter-inherit-local.json │ │ ├── lifecycle-exporter-inherit-remote.json │ │ ├── lifecycle-exporter-security-opts.json │ │ ├── lifecycle-exporter.json │ │ ├── lifecycle-restorer-cache-bind-mounts.json │ │ ├── lifecycle-restorer-cache-volumes.json │ │ ├── lifecycle-restorer-inherit-local.json │ │ ├── lifecycle-restorer-inherit-remote.json │ │ ├── lifecycle-restorer-security-opts.json │ │ ├── lifecycle-restorer.json │ │ ├── order.toml │ │ ├── print-stream-build-log.txt │ │ ├── run-image-with-bad-stack.json │ │ ├── run-image-with-platform.json │ │ └── run-image.json │ ├── docker/ │ │ ├── configuration/ │ │ │ ├── auth-token.json │ │ │ ├── auth-user-full.json │ │ │ ├── auth-user-minimal.json │ │ │ ├── docker-credential-test.bat │ │ │ ├── docker-credential-test.sh │ │ │ ├── with-auth/ │ │ │ │ └── config.json │ │ │ ├── with-context/ │ │ │ │ ├── config.json │ │ │ │ └── contexts/ │ │ │ │ └── meta/ │ │ │ │ └── ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/ │ │ │ │ └── meta.json │ │ │ ├── with-default-context/ │ │ │ │ ├── config.json │ │ │ │ └── contexts/ │ │ │ │ ├── meta/ │ │ │ │ │ └── ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/ │ │ │ │ │ └── meta.json │ │ │ │ └── tls/ │ │ │ │ └── ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/ │ │ │ │ └── docker/ │ │ │ │ ├── cert.pem │ │ │ │ └── key.pem │ │ │ └── without-context/ │ │ │ └── config.json │ │ ├── container-wait-response.json │ │ ├── create-container-response.json │ │ ├── load-error.json │ │ ├── load-stream.json │ │ ├── log-update-event-ansi.stream │ │ ├── log-update-event-invalid-stream-type.stream │ │ ├── log-update-event.stream │ │ ├── pull-stream.json │ │ ├── pull-update-full.json │ │ ├── pull-update-minimal.json │ │ ├── pull-with-empty-details.json │ │ ├── push-stream-with-error.json │ │ ├── push-stream.json │ │ ├── transport/ │ │ │ ├── errors.json │ │ │ ├── message-and-errors.json │ │ │ ├── message.json │ │ │ └── proxy-error.txt │ │ └── type/ │ │ ├── container-config.json │ │ ├── container-status-error.json │ │ ├── container-status-success.json │ │ ├── distribution-manifest-list.json │ │ ├── distribution-manifest.json │ │ ├── image-archive-config.json │ │ ├── image-archive-index.json │ │ ├── image-archive-manifest.json │ │ ├── image-config.json │ │ ├── image-empty-os.json │ │ ├── image-manifest.json │ │ ├── image-no-descriptor.json │ │ ├── image-no-digest.json │ │ ├── image-non-default-os.json │ │ ├── image-platform.json │ │ ├── image.json │ │ ├── manifest.json │ │ └── minimal-image-config.json │ └── json/ │ ├── stream.json │ └── test-mapped-object.json ├── cli/ │ └── spring-boot-cli/ │ ├── build.gradle │ └── src/ │ ├── intTest/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── cli/ │ │ │ ├── CommandLineIT.java │ │ │ └── infrastructure/ │ │ │ ├── CommandLineInvoker.java │ │ │ └── Versions.java │ │ └── resources/ │ │ └── settings.xml │ ├── json-shade/ │ │ ├── README.adoc │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── cli/ │ │ └── json/ │ │ ├── JSON.java │ │ ├── JSONArray.java │ │ ├── JSONException.java │ │ ├── JSONObject.java │ │ ├── JSONStringer.java │ │ ├── JSONTokener.java │ │ └── package-info.java │ ├── main/ │ │ ├── content/ │ │ │ ├── INSTALL.txt │ │ │ ├── LICENCE.txt │ │ │ ├── bin/ │ │ │ │ └── spring.bat │ │ │ ├── legal/ │ │ │ │ └── open_source_licenses.txt │ │ │ └── shell-completion/ │ │ │ ├── bash/ │ │ │ │ └── spring │ │ │ └── zsh/ │ │ │ └── _spring │ │ ├── executablecontent/ │ │ │ └── bin/ │ │ │ └── spring │ │ ├── homebrew/ │ │ │ └── spring-boot.rb │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── cli/ │ │ │ ├── DefaultCommandFactory.java │ │ │ ├── SpringCli.java │ │ │ ├── command/ │ │ │ │ ├── AbstractCommand.java │ │ │ │ ├── Command.java │ │ │ │ ├── CommandException.java │ │ │ │ ├── CommandFactory.java │ │ │ │ ├── CommandRunner.java │ │ │ │ ├── HelpExample.java │ │ │ │ ├── NoArgumentsException.java │ │ │ │ ├── NoHelpCommandArgumentsException.java │ │ │ │ ├── NoSuchCommandException.java │ │ │ │ ├── OptionParsingCommand.java │ │ │ │ ├── core/ │ │ │ │ │ ├── HelpCommand.java │ │ │ │ │ ├── HintCommand.java │ │ │ │ │ ├── VersionCommand.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── encodepassword/ │ │ │ │ │ ├── EncodePasswordCommand.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── init/ │ │ │ │ │ ├── Dependency.java │ │ │ │ │ ├── InitCommand.java │ │ │ │ │ ├── InitializrService.java │ │ │ │ │ ├── InitializrServiceMetadata.java │ │ │ │ │ ├── ProjectGenerationRequest.java │ │ │ │ │ ├── ProjectGenerationResponse.java │ │ │ │ │ ├── ProjectGenerator.java │ │ │ │ │ ├── ProjectType.java │ │ │ │ │ ├── ReportableException.java │ │ │ │ │ ├── ServiceCapabilitiesReportGenerator.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── options/ │ │ │ │ │ ├── OptionHandler.java │ │ │ │ │ ├── OptionHelp.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── shell/ │ │ │ │ │ ├── AnsiString.java │ │ │ │ │ ├── ClearCommand.java │ │ │ │ │ ├── CommandCompleter.java │ │ │ │ │ ├── EscapeAwareWhiteSpaceArgumentDelimiter.java │ │ │ │ │ ├── ExitCommand.java │ │ │ │ │ ├── ForkProcessCommand.java │ │ │ │ │ ├── PromptCommand.java │ │ │ │ │ ├── RunProcessCommand.java │ │ │ │ │ ├── Shell.java │ │ │ │ │ ├── ShellCommand.java │ │ │ │ │ ├── ShellExitException.java │ │ │ │ │ ├── ShellPrompts.java │ │ │ │ │ └── package-info.java │ │ │ │ └── status/ │ │ │ │ ├── ExitStatus.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── util/ │ │ │ ├── Log.java │ │ │ ├── LogListener.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── org.springframework.boot.cli.command.CommandFactory │ └── test/ │ ├── java/ │ │ ├── cli/ │ │ │ └── command/ │ │ │ ├── CustomCommand.java │ │ │ └── CustomCommandFactory.java │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── cli/ │ │ ├── command/ │ │ │ ├── CommandRunnerIntegrationTests.java │ │ │ ├── CommandRunnerTests.java │ │ │ ├── OptionParsingCommandTests.java │ │ │ ├── encodepassword/ │ │ │ │ └── EncodePasswordCommandTests.java │ │ │ ├── init/ │ │ │ │ ├── AbstractHttpClientMockTests.java │ │ │ │ ├── InitCommandTests.java │ │ │ │ ├── InitializrServiceMetadataTests.java │ │ │ │ ├── InitializrServiceTests.java │ │ │ │ ├── ProjectGenerationRequestTests.java │ │ │ │ └── ServiceCapabilitiesReportGeneratorTests.java │ │ │ └── shell/ │ │ │ └── EscapeAwareWhiteSpaceArgumentDelimiterTests.java │ │ └── util/ │ │ └── MockLog.java │ ├── plugins/ │ │ └── custom/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ └── org.springframework.boot.cli.CommandFactory │ │ └── custom/ │ │ └── 0.0.1/ │ │ └── custom-0.0.1.pom │ └── resources/ │ ├── .m2/ │ │ └── settings.xml │ ├── classloader-test-app.groovy │ ├── commands/ │ │ ├── closure.groovy │ │ ├── command.groovy │ │ ├── handler.groovy │ │ └── options.groovy │ ├── dependency-customizer-tests/ │ │ ├── resource1.txt │ │ └── resource2.txt │ ├── dir-sample/ │ │ └── code/ │ │ └── app.groovy │ ├── foo.pom │ ├── grab-samples/ │ │ ├── customDependencyManagement.groovy │ │ ├── duplicateDependencyManagementBom.groovy │ │ ├── grab.groovy │ │ └── repository/ │ │ └── test/ │ │ ├── child/ │ │ │ └── 1.0.0/ │ │ │ └── child-1.0.0.pom │ │ └── parent/ │ │ └── 1.0.0/ │ │ └── parent-1.0.0.pom │ ├── grab.groovy │ ├── init.groovy │ ├── maven-settings/ │ │ ├── active-profile-repositories/ │ │ │ └── .m2/ │ │ │ └── settings.xml │ │ ├── basic/ │ │ │ └── .m2/ │ │ │ └── settings.xml │ │ ├── encrypted/ │ │ │ └── .m2/ │ │ │ ├── settings-security.xml │ │ │ └── settings.xml │ │ └── property-interpolation/ │ │ └── .m2/ │ │ └── settings.xml │ ├── metadata/ │ │ ├── service-metadata-2.0.0.json │ │ ├── service-metadata-2.1.0.json │ │ ├── service-metadata-2.1.0.txt │ │ └── service-metadata-types-conflict.json │ ├── repro-samples/ │ │ ├── data-jpa.groovy │ │ ├── grab-ant-builder.groovy │ │ └── secure.groovy │ ├── resource-matcher/ │ │ ├── one/ │ │ │ ├── alpha/ │ │ │ │ └── nested/ │ │ │ │ ├── excluded │ │ │ │ └── fileA │ │ │ ├── bravo/ │ │ │ │ ├── fileC │ │ │ │ └── nested/ │ │ │ │ └── fileB │ │ │ └── fileD │ │ ├── three │ │ └── two/ │ │ ├── .file │ │ ├── bravo/ │ │ │ └── fileE │ │ └── fileF │ ├── run-command/ │ │ └── quiet.groovy │ ├── schema-all.sql │ ├── scripts/ │ │ ├── closure.groovy │ │ ├── command.groovy │ │ ├── commands.groovy │ │ ├── handler.groovy │ │ └── options.groovy │ └── templates/ │ ├── home.html │ └── test.txt ├── config/ │ ├── checkstyle/ │ │ ├── checkstyle-header.txt │ │ ├── checkstyle-suppressions.xml │ │ ├── checkstyle.xml │ │ └── import-control.xml │ ├── detekt/ │ │ └── config.yml │ └── nohttp/ │ ├── allowlist.lines │ ├── checkstyle.xml │ └── suppressions.xml ├── configuration-metadata/ │ ├── spring-boot-configuration-metadata/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── json-shade/ │ │ │ ├── README.adoc │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── configurationmetadata/ │ │ │ └── json/ │ │ │ ├── JSON.java │ │ │ ├── JSONArray.java │ │ │ ├── JSONException.java │ │ │ ├── JSONObject.java │ │ │ ├── JSONStringer.java │ │ │ └── JSONTokener.java │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── configurationmetadata/ │ │ │ ├── ConfigurationMetadataGroup.java │ │ │ ├── ConfigurationMetadataHint.java │ │ │ ├── ConfigurationMetadataItem.java │ │ │ ├── ConfigurationMetadataProperty.java │ │ │ ├── ConfigurationMetadataRepository.java │ │ │ ├── ConfigurationMetadataRepositoryJsonBuilder.java │ │ │ ├── ConfigurationMetadataSource.java │ │ │ ├── Deprecation.java │ │ │ ├── Hints.java │ │ │ ├── JsonReader.java │ │ │ ├── RawConfigurationMetadata.java │ │ │ ├── SentenceExtractor.java │ │ │ ├── SimpleConfigurationMetadataRepository.java │ │ │ ├── ValueHint.java │ │ │ ├── ValueProvider.java │ │ │ └── package-info.java │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── configurationmetadata/ │ │ │ ├── AbstractConfigurationMetadataTests.java │ │ │ ├── ConfigurationMetadataRepositoryJsonBuilderTests.java │ │ │ ├── JsonReaderTests.java │ │ │ └── SentenceExtractorTests.java │ │ └── resources/ │ │ └── metadata/ │ │ ├── configuration-metadata-bar.json │ │ ├── configuration-metadata-deprecated.json │ │ ├── configuration-metadata-empty-groups.json │ │ ├── configuration-metadata-empty.json │ │ ├── configuration-metadata-foo.json │ │ ├── configuration-metadata-foo2.json │ │ ├── configuration-metadata-foo3.json │ │ ├── configuration-metadata-invalid.json │ │ ├── configuration-metadata-map.json │ │ ├── configuration-metadata-multi-groups.json │ │ └── configuration-metadata-root.json │ ├── spring-boot-configuration-metadata-changelog-generator/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── configurationmetadata/ │ │ │ └── changelog/ │ │ │ ├── Changelog.java │ │ │ ├── ChangelogGenerator.java │ │ │ ├── ChangelogWriter.java │ │ │ ├── Difference.java │ │ │ ├── DifferenceType.java │ │ │ └── package-info.java │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── configurationmetadata/ │ │ │ └── changelog/ │ │ │ ├── ChangelogGeneratorTests.java │ │ │ ├── ChangelogTests.java │ │ │ ├── ChangelogWriterTests.java │ │ │ ├── DifferenceTests.java │ │ │ └── TestChangelog.java │ │ └── resources/ │ │ ├── sample-1.0.json │ │ ├── sample-2.0.json │ │ └── sample.adoc │ └── spring-boot-configuration-processor/ │ ├── build.gradle │ └── src/ │ ├── json-shade/ │ │ ├── README.adoc │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── configurationprocessor/ │ │ └── json/ │ │ ├── JSON.java │ │ ├── JSONArray.java │ │ ├── JSONException.java │ │ ├── JSONObject.java │ │ ├── JSONStringer.java │ │ └── JSONTokener.java │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── configurationprocessor/ │ │ │ ├── ConfigurationMetadataAnnotationProcessor.java │ │ │ ├── ConfigurationPropertiesSourceResolver.java │ │ │ ├── ConstructorParameterPropertyDescriptor.java │ │ │ ├── JavaBeanPropertyDescriptor.java │ │ │ ├── LombokPropertyDescriptor.java │ │ │ ├── MetadataCollector.java │ │ │ ├── MetadataCollectors.java │ │ │ ├── MetadataGenerationEnvironment.java │ │ │ ├── MetadataStore.java │ │ │ ├── ParameterPropertyDescriptor.java │ │ │ ├── PropertyDescriptor.java │ │ │ ├── PropertyDescriptorResolver.java │ │ │ ├── RecordParameterPropertyDescriptor.java │ │ │ ├── TypeElementMembers.java │ │ │ ├── TypeUtils.java │ │ │ ├── fieldvalues/ │ │ │ │ ├── FieldValuesParser.java │ │ │ │ ├── javac/ │ │ │ │ │ ├── ExpressionTree.java │ │ │ │ │ ├── JavaCompilerFieldValuesParser.java │ │ │ │ │ ├── ReflectionWrapper.java │ │ │ │ │ ├── Tree.java │ │ │ │ │ ├── TreeVisitor.java │ │ │ │ │ ├── Trees.java │ │ │ │ │ ├── VariableTree.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── metadata/ │ │ │ │ ├── ConfigurationMetadata.java │ │ │ │ ├── InvalidConfigurationMetadataException.java │ │ │ │ ├── ItemDeprecation.java │ │ │ │ ├── ItemHint.java │ │ │ │ ├── ItemIgnore.java │ │ │ │ ├── ItemMetadata.java │ │ │ │ ├── JsonConverter.java │ │ │ │ ├── JsonMarshaller.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── support/ │ │ │ ├── ConventionUtils.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── gradle/ │ │ │ └── incremental.annotation.processors │ │ └── services/ │ │ └── javax.annotation.processing.Processor │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ ├── configurationprocessor/ │ │ │ ├── AbstractMetadataGenerationTests.java │ │ │ ├── ConfigurationMetadataAnnotationProcessorTests.java │ │ │ ├── ConstructorParameterPropertyDescriptorTests.java │ │ │ ├── DeducedImmutablePropertiesMetadataGenerationTests.java │ │ │ ├── EndpointMetadataGenerationTests.java │ │ │ ├── GenericsMetadataGenerationTests.java │ │ │ ├── ImmutablePropertiesMetadataGenerationTests.java │ │ │ ├── IncrementalBuildMetadataGenerationTests.java │ │ │ ├── InheritanceMetadataGenerationTests.java │ │ │ ├── JavaBeanPropertyDescriptorTests.java │ │ │ ├── LombokMetadataGenerationTests.java │ │ │ ├── LombokPropertyDescriptorTests.java │ │ │ ├── MergeMetadataGenerationTests.java │ │ │ ├── MetadataCollectorTests.java │ │ │ ├── MetadataGenerationEnvironmentFactory.java │ │ │ ├── MetadataStoreTests.java │ │ │ ├── MethodBasedMetadataGenerationTests.java │ │ │ ├── NameAnnotationPropertiesTests.java │ │ │ ├── PropertyDescriptorResolverTests.java │ │ │ ├── PropertyDescriptorTests.java │ │ │ ├── TestProject.java │ │ │ ├── TypeUtilsTests.java │ │ │ ├── fieldvalues/ │ │ │ │ ├── AbstractFieldValuesProcessorTests.java │ │ │ │ └── javac/ │ │ │ │ └── JavaCompilerFieldValuesProcessorTests.java │ │ │ ├── metadata/ │ │ │ │ ├── ItemHintTests.java │ │ │ │ ├── ItemMetadataTests.java │ │ │ │ ├── JsonMarshallerTests.java │ │ │ │ ├── Metadata.java │ │ │ │ └── TestJsonConverter.java │ │ │ ├── support/ │ │ │ │ └── ConventionUtilsTests.java │ │ │ └── test/ │ │ │ ├── CompiledMetadataReader.java │ │ │ ├── ItemMetadataAssert.java │ │ │ ├── RoundEnvironmentTester.java │ │ │ ├── TestConfigurationMetadataAnnotationProcessor.java │ │ │ └── TestableAnnotationProcessor.java │ │ └── configurationsample/ │ │ ├── TestAccess.java │ │ ├── TestAutowired.java │ │ ├── TestConfigurationProperties.java │ │ ├── TestConfigurationPropertiesSource.java │ │ ├── TestConstructorBinding.java │ │ ├── TestControllerEndpoint.java │ │ ├── TestDefaultValue.java │ │ ├── TestDeprecatedConfigurationProperty.java │ │ ├── TestEndpoint.java │ │ ├── TestJmxEndpoint.java │ │ ├── TestName.java │ │ ├── TestNestedConfigurationProperty.java │ │ ├── TestReadOperation.java │ │ ├── TestRestControllerEndpoint.java │ │ ├── TestServletEndpoint.java │ │ ├── TestWebEndpoint.java │ │ ├── deprecation/ │ │ │ └── Dbcp2Configuration.java │ │ ├── endpoint/ │ │ │ ├── CamelCaseEndpoint.java │ │ │ ├── CustomPropertiesEndpoint.java │ │ │ ├── EnabledEndpoint.java │ │ │ ├── NoAccessEndpoint.java │ │ │ ├── NullableParameterEndpoint.java │ │ │ ├── ReadOnlyAccessEndpoint.java │ │ │ ├── SimpleEndpoint.java │ │ │ ├── SimpleEndpoint2.java │ │ │ ├── SimpleEndpoint3.java │ │ │ ├── SpecificEndpoint.java │ │ │ ├── UnrestrictedAccessEndpoint.java │ │ │ └── incremental/ │ │ │ ├── IncrementalEndpoint.java │ │ │ └── IncrementalSpecificEndpoint.java │ │ ├── fieldvalues/ │ │ │ ├── FieldValues.java │ │ │ └── UnknownElementType.java │ │ ├── generic/ │ │ │ ├── AbstractGenericProperties.java │ │ │ ├── AbstractIntermediateGenericProperties.java │ │ │ ├── ComplexGenericProperties.java │ │ │ ├── ConcreteBuilderProperties.java │ │ │ ├── GenericBuilderProperties.java │ │ │ ├── GenericConfig.java │ │ │ ├── MixGenericNameProperties.java │ │ │ ├── SimpleGenericProperties.java │ │ │ ├── UnresolvedGenericProperties.java │ │ │ ├── UpperBoundGenericPojo.java │ │ │ └── WildcardConfig.java │ │ ├── immutable/ │ │ │ ├── DeducedImmutableClassProperties.java │ │ │ ├── ImmutableClassConstructorBindingProperties.java │ │ │ ├── ImmutableCollectionProperties.java │ │ │ ├── ImmutableDeducedConstructorBindingProperties.java │ │ │ ├── ImmutableInnerClassProperties.java │ │ │ ├── ImmutableMultiConstructorProperties.java │ │ │ ├── ImmutablePrimitiveProperties.java │ │ │ ├── ImmutablePrimitiveWithDefaultsProperties.java │ │ │ ├── ImmutablePrimitiveWrapperWithDefaultsProperties.java │ │ │ └── ImmutableSimpleProperties.java │ │ ├── incremental/ │ │ │ ├── BarProperties.java │ │ │ ├── FooProperties.java │ │ │ └── RenamedBarProperties.java │ │ ├── inheritance/ │ │ │ ├── BaseProperties.java │ │ │ ├── ChildProperties.java │ │ │ ├── ChildPropertiesConfig.java │ │ │ ├── OverrideChildProperties.java │ │ │ └── OverrideChildPropertiesConfig.java │ │ ├── lombok/ │ │ │ ├── LombokAccessLevelOverwriteDataProperties.java │ │ │ ├── LombokAccessLevelOverwriteDefaultProperties.java │ │ │ ├── LombokAccessLevelOverwriteExplicitProperties.java │ │ │ ├── LombokAccessLevelProperties.java │ │ │ ├── LombokDefaultValueProperties.java │ │ │ ├── LombokDeprecatedProperties.java │ │ │ ├── LombokDeprecatedSingleProperty.java │ │ │ ├── LombokExplicitProperties.java │ │ │ ├── LombokInnerClassProperties.java │ │ │ ├── LombokInnerClassWithGetterProperties.java │ │ │ ├── LombokSimpleDataProperties.java │ │ │ ├── LombokSimpleProperties.java │ │ │ ├── LombokSimpleValueProperties.java │ │ │ └── SimpleLombokPojo.java │ │ ├── method/ │ │ │ ├── DeprecatedClassMethodConfig.java │ │ │ ├── DeprecatedMethodConfig.java │ │ │ ├── EmptyTypeMethodConfig.java │ │ │ ├── InvalidMethodConfig.java │ │ │ ├── MethodAndClassConfig.java │ │ │ ├── NestedPropertiesMethod.java │ │ │ ├── NestedPropertiesMethodImmutable.java │ │ │ ├── NestedProperty.java │ │ │ ├── PackagePrivateMethodConfig.java │ │ │ ├── PrivateMethodConfig.java │ │ │ ├── ProtectedMethodConfig.java │ │ │ ├── PublicMethodConfig.java │ │ │ └── SingleConstructorMethodConfig.java │ │ ├── name/ │ │ │ ├── ConstructorParameterNameAnnotationProperties.java │ │ │ ├── JavaBeanNameAnnotationProperties.java │ │ │ ├── LombokNameAnnotationProperties.java │ │ │ └── RecordComponentNameAnnotationProperties.java │ │ ├── record/ │ │ │ ├── ExampleRecord.java │ │ │ ├── NestedPropertiesRecord.java │ │ │ ├── NestedRecord.java │ │ │ └── RecordWithGetter.java │ │ ├── recursive/ │ │ │ └── RecursiveProperties.java │ │ ├── simple/ │ │ │ ├── AutowiredProperties.java │ │ │ ├── ClassWithNestedProperties.java │ │ │ ├── DeprecatedFieldSingleProperty.java │ │ │ ├── DeprecatedProperties.java │ │ │ ├── DeprecatedRecord.java │ │ │ ├── DeprecatedSingleProperty.java │ │ │ ├── DescriptionProperties.java │ │ │ ├── HierarchicalProperties.java │ │ │ ├── HierarchicalPropertiesGrandparent.java │ │ │ ├── HierarchicalPropertiesParent.java │ │ │ ├── IgnoredProperties.java │ │ │ ├── InnerClassWithPrivateConstructor.java │ │ │ ├── NotAnnotated.java │ │ │ ├── SimpleArrayProperties.java │ │ │ ├── SimpleCollectionProperties.java │ │ │ ├── SimplePrefixValueProperties.java │ │ │ ├── SimpleProperties.java │ │ │ └── SimpleTypeProperties.java │ │ ├── source/ │ │ │ ├── BaseSource.java │ │ │ ├── ConcreteProperties.java │ │ │ ├── ConcreteSource.java │ │ │ ├── ConcreteSourceAnnotated.java │ │ │ ├── ConventionSource.java │ │ │ ├── ConventionSourceAnnotated.java │ │ │ ├── ImmutableSource.java │ │ │ ├── ImmutableSourceAnnotated.java │ │ │ ├── LombokSource.java │ │ │ ├── LombokSourceAnnotated.java │ │ │ ├── ParentWithHintProperties.java │ │ │ ├── RecordSource.java │ │ │ ├── RecordSourceAnnotated.java │ │ │ ├── SimpleSource.java │ │ │ ├── SimpleSourceAnnotated.java │ │ │ └── generation/ │ │ │ ├── AbstractPropertiesSource.java │ │ │ ├── ConfigurationPropertySourcesContainer.java │ │ │ ├── ImmutablePropertiesSource.java │ │ │ ├── LombokPropertiesSource.java │ │ │ ├── NestedPropertiesSource.java │ │ │ ├── RecordPropertiesSources.java │ │ │ └── SimplePropertiesSource.java │ │ └── specific/ │ │ ├── AnnotatedGetter.java │ │ ├── BoxingPojo.java │ │ ├── BuilderPojo.java │ │ ├── DeprecatedLessPreciseTypePojo.java │ │ ├── DeprecatedSimplePojo.java │ │ ├── DeprecatedUnrelatedMethodPojo.java │ │ ├── DoubleRegistrationProperties.java │ │ ├── EmptyDefaultValueProperties.java │ │ ├── EnumValuesPojo.java │ │ ├── ExcludedTypesPojo.java │ │ ├── InnerClassAnnotatedGetterConfig.java │ │ ├── InnerClassHierarchicalProperties.java │ │ ├── InnerClassProperties.java │ │ ├── InnerClassRootConfig.java │ │ ├── InvalidAccessorProperties.java │ │ ├── InvalidDefaultValueCharacterProperties.java │ │ ├── InvalidDefaultValueFloatingPointProperties.java │ │ ├── InvalidDefaultValueNumberProperties.java │ │ ├── InvalidDoubleRegistrationProperties.java │ │ ├── MatchingConstructorNoDirectiveProperties.java │ │ ├── SimpleConflictingProperties.java │ │ ├── SimplePojo.java │ │ ├── StaticAccessor.java │ │ └── TwoConstructorsExample.java │ └── resources/ │ ├── META-INF/ │ │ └── spring/ │ │ └── configuration-metadata/ │ │ ├── org.springframework.boot.configurationsample.source.BaseSource.json │ │ ├── org.springframework.boot.configurationsample.source.ConventionSource.json │ │ ├── org.springframework.boot.configurationsample.source.ImmutableSource.json │ │ ├── org.springframework.boot.configurationsample.source.LombokSource.json │ │ ├── org.springframework.boot.configurationsample.source.RecordSource.json │ │ └── org.springframework.boot.configurationsample.source.SimpleSource.json │ └── org/ │ └── springframework/ │ └── boot/ │ └── configurationsample/ │ └── incremental/ │ └── BarProperties.snippet ├── core/ │ ├── spring-boot/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ ├── AotInitializerNotFoundException.java │ │ │ │ ├── ApplicationArguments.java │ │ │ │ ├── ApplicationContextFactory.java │ │ │ │ ├── ApplicationEnvironment.java │ │ │ │ ├── ApplicationInfoPropertySource.java │ │ │ │ ├── ApplicationProperties.java │ │ │ │ ├── ApplicationRunner.java │ │ │ │ ├── Banner.java │ │ │ │ ├── BeanDefinitionLoader.java │ │ │ │ ├── ClearCachesApplicationListener.java │ │ │ │ ├── CommandLineRunner.java │ │ │ │ ├── DefaultApplicationArguments.java │ │ │ │ ├── DefaultApplicationContextFactory.java │ │ │ │ ├── EnvironmentConverter.java │ │ │ │ ├── EnvironmentPostProcessor.java │ │ │ │ ├── ExitCodeEvent.java │ │ │ │ ├── ExitCodeExceptionMapper.java │ │ │ │ ├── ExitCodeGenerator.java │ │ │ │ ├── ExitCodeGenerators.java │ │ │ │ ├── LazyInitializationBeanFactoryPostProcessor.java │ │ │ │ ├── LazyInitializationExcludeFilter.java │ │ │ │ ├── ResourceBanner.java │ │ │ │ ├── Runner.java │ │ │ │ ├── SpringApplication.java │ │ │ │ ├── SpringApplicationAotProcessor.java │ │ │ │ ├── SpringApplicationBannerPrinter.java │ │ │ │ ├── SpringApplicationHook.java │ │ │ │ ├── SpringApplicationRunListener.java │ │ │ │ ├── SpringApplicationRunListeners.java │ │ │ │ ├── SpringApplicationShutdownHandlers.java │ │ │ │ ├── SpringApplicationShutdownHook.java │ │ │ │ ├── SpringBootBanner.java │ │ │ │ ├── SpringBootConfiguration.java │ │ │ │ ├── SpringBootExceptionHandler.java │ │ │ │ ├── SpringBootExceptionReporter.java │ │ │ │ ├── StartupInfoLogger.java │ │ │ │ ├── WebApplicationType.java │ │ │ │ ├── admin/ │ │ │ │ │ ├── SpringApplicationAdminMXBean.java │ │ │ │ │ ├── SpringApplicationAdminMXBeanRegistrar.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── ansi/ │ │ │ │ │ ├── Ansi8BitColor.java │ │ │ │ │ ├── AnsiBackground.java │ │ │ │ │ ├── AnsiColor.java │ │ │ │ │ ├── AnsiElement.java │ │ │ │ │ ├── AnsiOutput.java │ │ │ │ │ ├── AnsiPropertySource.java │ │ │ │ │ ├── AnsiStyle.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── availability/ │ │ │ │ │ ├── ApplicationAvailability.java │ │ │ │ │ ├── ApplicationAvailabilityBean.java │ │ │ │ │ ├── AvailabilityChangeEvent.java │ │ │ │ │ ├── AvailabilityState.java │ │ │ │ │ ├── LivenessState.java │ │ │ │ │ ├── ReadinessState.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── bootstrap/ │ │ │ │ │ ├── BootstrapContext.java │ │ │ │ │ ├── BootstrapContextClosedEvent.java │ │ │ │ │ ├── BootstrapRegistry.java │ │ │ │ │ ├── BootstrapRegistryInitializer.java │ │ │ │ │ ├── ConfigurableBootstrapContext.java │ │ │ │ │ ├── DefaultBootstrapContext.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── builder/ │ │ │ │ │ ├── ParentContextApplicationContextInitializer.java │ │ │ │ │ ├── ParentContextCloserApplicationListener.java │ │ │ │ │ ├── SpringApplicationBuilder.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── cloud/ │ │ │ │ │ ├── CloudFoundryVcapEnvironmentPostProcessor.java │ │ │ │ │ ├── CloudPlatform.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── context/ │ │ │ │ │ ├── ApplicationPidFileWriter.java │ │ │ │ │ ├── ConfigurationWarningsApplicationContextInitializer.java │ │ │ │ │ ├── ContextIdApplicationContextInitializer.java │ │ │ │ │ ├── FileEncodingApplicationListener.java │ │ │ │ │ ├── TypeExcludeFilter.java │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ ├── Configurations.java │ │ │ │ │ │ ├── DeterminableImports.java │ │ │ │ │ │ ├── ImportCandidates.java │ │ │ │ │ │ ├── UserConfigurations.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── ConfigData.java │ │ │ │ │ │ ├── ConfigDataActivationContext.java │ │ │ │ │ │ ├── ConfigDataEnvironment.java │ │ │ │ │ │ ├── ConfigDataEnvironmentContributor.java │ │ │ │ │ │ ├── ConfigDataEnvironmentContributorPlaceholdersResolver.java │ │ │ │ │ │ ├── ConfigDataEnvironmentContributors.java │ │ │ │ │ │ ├── ConfigDataEnvironmentPostProcessor.java │ │ │ │ │ │ ├── ConfigDataEnvironmentUpdateListener.java │ │ │ │ │ │ ├── ConfigDataException.java │ │ │ │ │ │ ├── ConfigDataImporter.java │ │ │ │ │ │ ├── ConfigDataLoader.java │ │ │ │ │ │ ├── ConfigDataLoaderContext.java │ │ │ │ │ │ ├── ConfigDataLoaders.java │ │ │ │ │ │ ├── ConfigDataLocation.java │ │ │ │ │ │ ├── ConfigDataLocationBindHandler.java │ │ │ │ │ │ ├── ConfigDataLocationNotFoundException.java │ │ │ │ │ │ ├── ConfigDataLocationResolver.java │ │ │ │ │ │ ├── ConfigDataLocationResolverContext.java │ │ │ │ │ │ ├── ConfigDataLocationResolvers.java │ │ │ │ │ │ ├── ConfigDataLocationRuntimeHints.java │ │ │ │ │ │ ├── ConfigDataNotFoundAction.java │ │ │ │ │ │ ├── ConfigDataNotFoundException.java │ │ │ │ │ │ ├── ConfigDataNotFoundFailureAnalyzer.java │ │ │ │ │ │ ├── ConfigDataProperties.java │ │ │ │ │ │ ├── ConfigDataPropertiesRuntimeHints.java │ │ │ │ │ │ ├── ConfigDataResolutionResult.java │ │ │ │ │ │ ├── ConfigDataResource.java │ │ │ │ │ │ ├── ConfigDataResourceNotFoundException.java │ │ │ │ │ │ ├── ConfigTreeConfigDataLoader.java │ │ │ │ │ │ ├── ConfigTreeConfigDataLocationResolver.java │ │ │ │ │ │ ├── ConfigTreeConfigDataResource.java │ │ │ │ │ │ ├── FileHint.java │ │ │ │ │ │ ├── InactiveConfigDataAccessException.java │ │ │ │ │ │ ├── InvalidConfigDataPropertyException.java │ │ │ │ │ │ ├── LocationResourceLoader.java │ │ │ │ │ │ ├── Profiles.java │ │ │ │ │ │ ├── ProfilesValidator.java │ │ │ │ │ │ ├── StandardConfigDataLoader.java │ │ │ │ │ │ ├── StandardConfigDataLocationResolver.java │ │ │ │ │ │ ├── StandardConfigDataReference.java │ │ │ │ │ │ ├── StandardConfigDataResource.java │ │ │ │ │ │ ├── SystemEnvironmentConfigDataLoader.java │ │ │ │ │ │ ├── SystemEnvironmentConfigDataLocationResolver.java │ │ │ │ │ │ ├── SystemEnvironmentConfigDataResource.java │ │ │ │ │ │ ├── UnsupportedConfigDataLocationException.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── event/ │ │ │ │ │ │ ├── ApplicationContextInitializedEvent.java │ │ │ │ │ │ ├── ApplicationEnvironmentPreparedEvent.java │ │ │ │ │ │ ├── ApplicationFailedEvent.java │ │ │ │ │ │ ├── ApplicationPreparedEvent.java │ │ │ │ │ │ ├── ApplicationReadyEvent.java │ │ │ │ │ │ ├── ApplicationStartedEvent.java │ │ │ │ │ │ ├── ApplicationStartingEvent.java │ │ │ │ │ │ ├── EventPublishingRunListener.java │ │ │ │ │ │ ├── SpringApplicationEvent.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── logging/ │ │ │ │ │ │ ├── LoggingApplicationListener.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ └── buffering/ │ │ │ │ │ │ ├── BufferedStartupStep.java │ │ │ │ │ │ ├── BufferingApplicationStartup.java │ │ │ │ │ │ ├── StartupTimeline.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── properties/ │ │ │ │ │ ├── BindMethodAttribute.java │ │ │ │ │ ├── BoundConfigurationProperties.java │ │ │ │ │ ├── ConfigurationProperties.java │ │ │ │ │ ├── ConfigurationPropertiesBean.java │ │ │ │ │ ├── ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java │ │ │ │ │ ├── ConfigurationPropertiesBeanRegistrar.java │ │ │ │ │ ├── ConfigurationPropertiesBeanRegistrationAotProcessor.java │ │ │ │ │ ├── ConfigurationPropertiesBindException.java │ │ │ │ │ ├── ConfigurationPropertiesBindHandlerAdvisor.java │ │ │ │ │ ├── ConfigurationPropertiesBinder.java │ │ │ │ │ ├── ConfigurationPropertiesBinding.java │ │ │ │ │ ├── ConfigurationPropertiesBindingPostProcessor.java │ │ │ │ │ ├── ConfigurationPropertiesCharSequenceToObjectConverter.java │ │ │ │ │ ├── ConfigurationPropertiesJsr303Validator.java │ │ │ │ │ ├── ConfigurationPropertiesScan.java │ │ │ │ │ ├── ConfigurationPropertiesScanRegistrar.java │ │ │ │ │ ├── ConfigurationPropertiesSource.java │ │ │ │ │ ├── ConstructorBound.java │ │ │ │ │ ├── ConversionServiceDeducer.java │ │ │ │ │ ├── DeprecatedConfigurationProperty.java │ │ │ │ │ ├── EnableConfigurationProperties.java │ │ │ │ │ ├── EnableConfigurationPropertiesRegistrar.java │ │ │ │ │ ├── IncompatibleConfigurationException.java │ │ │ │ │ ├── IncompatibleConfigurationFailureAnalyzer.java │ │ │ │ │ ├── NestedConfigurationProperty.java │ │ │ │ │ ├── NotConstructorBoundInjectionFailureAnalyzer.java │ │ │ │ │ ├── PropertyMapper.java │ │ │ │ │ ├── PropertySourcesDeducer.java │ │ │ │ │ ├── bind/ │ │ │ │ │ │ ├── AbstractBindHandler.java │ │ │ │ │ │ ├── AggregateBinder.java │ │ │ │ │ │ ├── AggregateElementBinder.java │ │ │ │ │ │ ├── ArrayBinder.java │ │ │ │ │ │ ├── BindConstructorProvider.java │ │ │ │ │ │ ├── BindContext.java │ │ │ │ │ │ ├── BindConverter.java │ │ │ │ │ │ ├── BindException.java │ │ │ │ │ │ ├── BindHandler.java │ │ │ │ │ │ ├── BindMethod.java │ │ │ │ │ │ ├── BindResult.java │ │ │ │ │ │ ├── Bindable.java │ │ │ │ │ │ ├── BindableRuntimeHintsRegistrar.java │ │ │ │ │ │ ├── Binder.java │ │ │ │ │ │ ├── BoundPropertiesTrackingBindHandler.java │ │ │ │ │ │ ├── CollectionBinder.java │ │ │ │ │ │ ├── ConstructorBinding.java │ │ │ │ │ │ ├── DataObjectBinder.java │ │ │ │ │ │ ├── DataObjectPropertyBinder.java │ │ │ │ │ │ ├── DataObjectPropertyName.java │ │ │ │ │ │ ├── DefaultBindConstructorProvider.java │ │ │ │ │ │ ├── DefaultValue.java │ │ │ │ │ │ ├── IndexedElementsBinder.java │ │ │ │ │ │ ├── JavaBeanBinder.java │ │ │ │ │ │ ├── MapBinder.java │ │ │ │ │ │ ├── Name.java │ │ │ │ │ │ ├── Nested.java │ │ │ │ │ │ ├── PlaceholdersResolver.java │ │ │ │ │ │ ├── PropertySourcesPlaceholdersResolver.java │ │ │ │ │ │ ├── UnboundConfigurationPropertiesException.java │ │ │ │ │ │ ├── ValueObjectBinder.java │ │ │ │ │ │ ├── handler/ │ │ │ │ │ │ │ ├── IgnoreErrorsBindHandler.java │ │ │ │ │ │ │ ├── IgnoreTopLevelConverterNotFoundBindHandler.java │ │ │ │ │ │ │ ├── NoUnboundElementsBindHandler.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── package-info.java │ │ │ │ │ │ └── validation/ │ │ │ │ │ │ ├── BindValidationException.java │ │ │ │ │ │ ├── OriginTrackedFieldError.java │ │ │ │ │ │ ├── ValidationBindHandler.java │ │ │ │ │ │ ├── ValidationErrors.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── source/ │ │ │ │ │ ├── AliasedConfigurationPropertySource.java │ │ │ │ │ ├── AliasedIterableConfigurationPropertySource.java │ │ │ │ │ ├── CachingConfigurationPropertySource.java │ │ │ │ │ ├── ConfigurationProperty.java │ │ │ │ │ ├── ConfigurationPropertyCaching.java │ │ │ │ │ ├── ConfigurationPropertyName.java │ │ │ │ │ ├── ConfigurationPropertyNameAliases.java │ │ │ │ │ ├── ConfigurationPropertySource.java │ │ │ │ │ ├── ConfigurationPropertySources.java │ │ │ │ │ ├── ConfigurationPropertySourcesCaching.java │ │ │ │ │ ├── ConfigurationPropertySourcesPropertyResolver.java │ │ │ │ │ ├── ConfigurationPropertySourcesPropertySource.java │ │ │ │ │ ├── ConfigurationPropertyState.java │ │ │ │ │ ├── DefaultPropertyMapper.java │ │ │ │ │ ├── FilteredConfigurationPropertiesSource.java │ │ │ │ │ ├── FilteredIterableConfigurationPropertiesSource.java │ │ │ │ │ ├── InvalidConfigurationPropertyNameException.java │ │ │ │ │ ├── InvalidConfigurationPropertyValueException.java │ │ │ │ │ ├── IterableConfigurationPropertySource.java │ │ │ │ │ ├── MapConfigurationPropertySource.java │ │ │ │ │ ├── MutuallyExclusiveConfigurationPropertiesException.java │ │ │ │ │ ├── PrefixedConfigurationPropertySource.java │ │ │ │ │ ├── PrefixedIterableConfigurationPropertySource.java │ │ │ │ │ ├── PropertyMapper.java │ │ │ │ │ ├── SoftReferenceConfigurationPropertyCache.java │ │ │ │ │ ├── SpringConfigurationPropertySource.java │ │ │ │ │ ├── SpringConfigurationPropertySources.java │ │ │ │ │ ├── SpringIterableConfigurationPropertySource.java │ │ │ │ │ ├── SystemEnvironmentPropertyMapper.java │ │ │ │ │ ├── UnboundElementsSourceFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── convert/ │ │ │ │ │ ├── ApplicationConversionService.java │ │ │ │ │ ├── ArrayToDelimitedStringConverter.java │ │ │ │ │ ├── CharArrayFormatter.java │ │ │ │ │ ├── CharSequenceToObjectConverter.java │ │ │ │ │ ├── CollectionToDelimitedStringConverter.java │ │ │ │ │ ├── DataSizeUnit.java │ │ │ │ │ ├── DelimitedStringToArrayConverter.java │ │ │ │ │ ├── DelimitedStringToCollectionConverter.java │ │ │ │ │ ├── Delimiter.java │ │ │ │ │ ├── DurationFormat.java │ │ │ │ │ ├── DurationStyle.java │ │ │ │ │ ├── DurationToNumberConverter.java │ │ │ │ │ ├── DurationToStringConverter.java │ │ │ │ │ ├── DurationUnit.java │ │ │ │ │ ├── InetAddressFormatter.java │ │ │ │ │ ├── InputStreamSourceToByteArrayConverter.java │ │ │ │ │ ├── IsoOffsetFormatter.java │ │ │ │ │ ├── LenientBooleanToEnumConverterFactory.java │ │ │ │ │ ├── LenientObjectToEnumConverterFactory.java │ │ │ │ │ ├── LenientStringToEnumConverterFactory.java │ │ │ │ │ ├── NumberToDataSizeConverter.java │ │ │ │ │ ├── NumberToDurationConverter.java │ │ │ │ │ ├── NumberToPeriodConverter.java │ │ │ │ │ ├── PeriodFormat.java │ │ │ │ │ ├── PeriodStyle.java │ │ │ │ │ ├── PeriodToStringConverter.java │ │ │ │ │ ├── PeriodUnit.java │ │ │ │ │ ├── StringToDataSizeConverter.java │ │ │ │ │ ├── StringToDurationConverter.java │ │ │ │ │ ├── StringToFileConverter.java │ │ │ │ │ ├── StringToPeriodConverter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── diagnostics/ │ │ │ │ │ ├── AbstractFailureAnalyzer.java │ │ │ │ │ ├── FailureAnalysis.java │ │ │ │ │ ├── FailureAnalysisReporter.java │ │ │ │ │ ├── FailureAnalyzedException.java │ │ │ │ │ ├── FailureAnalyzer.java │ │ │ │ │ ├── FailureAnalyzers.java │ │ │ │ │ ├── LoggingFailureAnalysisReporter.java │ │ │ │ │ ├── analyzer/ │ │ │ │ │ │ ├── AbstractInjectionFailureAnalyzer.java │ │ │ │ │ │ ├── AotInitializerNotFoundFailureAnalyzer.java │ │ │ │ │ │ ├── BeanCurrentlyInCreationFailureAnalyzer.java │ │ │ │ │ │ ├── BeanDefinitionOverrideFailureAnalyzer.java │ │ │ │ │ │ ├── BeanNotOfRequiredTypeFailureAnalyzer.java │ │ │ │ │ │ ├── BindFailureAnalyzer.java │ │ │ │ │ │ ├── BindValidationFailureAnalyzer.java │ │ │ │ │ │ ├── InvalidConfigurationPropertyNameFailureAnalyzer.java │ │ │ │ │ │ ├── InvalidConfigurationPropertyValueFailureAnalyzer.java │ │ │ │ │ │ ├── MissingParameterNamesFailureAnalyzer.java │ │ │ │ │ │ ├── MutuallyExclusiveConfigurationPropertiesFailureAnalyzer.java │ │ │ │ │ │ ├── NoSuchMethodFailureAnalyzer.java │ │ │ │ │ │ ├── NoUniqueBeanDefinitionFailureAnalyzer.java │ │ │ │ │ │ ├── PatternParseFailureAnalyzer.java │ │ │ │ │ │ ├── UnboundConfigurationPropertyFailureAnalyzer.java │ │ │ │ │ │ ├── ValidationExceptionFailureAnalyzer.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── env/ │ │ │ │ │ ├── ConfigTreePropertySource.java │ │ │ │ │ ├── DefaultPropertiesPropertySource.java │ │ │ │ │ ├── EnvironmentPostProcessor.java │ │ │ │ │ ├── OriginTrackedMapPropertySource.java │ │ │ │ │ ├── OriginTrackedPropertiesLoader.java │ │ │ │ │ ├── OriginTrackedYamlLoader.java │ │ │ │ │ ├── PropertiesPropertySourceLoader.java │ │ │ │ │ ├── PropertySourceInfo.java │ │ │ │ │ ├── PropertySourceLoader.java │ │ │ │ │ ├── PropertySourceRuntimeHints.java │ │ │ │ │ ├── RandomValuePropertySource.java │ │ │ │ │ ├── YamlPropertySourceLoader.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── info/ │ │ │ │ │ ├── BuildProperties.java │ │ │ │ │ ├── GitProperties.java │ │ │ │ │ ├── InfoProperties.java │ │ │ │ │ ├── JavaInfo.java │ │ │ │ │ ├── OsInfo.java │ │ │ │ │ ├── ProcessInfo.java │ │ │ │ │ ├── SslInfo.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── io/ │ │ │ │ │ ├── ApplicationResourceLoader.java │ │ │ │ │ ├── Base64ProtocolResolver.java │ │ │ │ │ ├── ClassPathResourceFilePathResolver.java │ │ │ │ │ ├── ProtocolResolverApplicationContextInitializer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── json/ │ │ │ │ │ ├── AbstractJsonParser.java │ │ │ │ │ ├── BasicJsonParser.java │ │ │ │ │ ├── GsonJsonParser.java │ │ │ │ │ ├── JacksonJsonParser.java │ │ │ │ │ ├── JsonParseException.java │ │ │ │ │ ├── JsonParser.java │ │ │ │ │ ├── JsonParserFactory.java │ │ │ │ │ ├── JsonValueWriter.java │ │ │ │ │ ├── JsonWriter.java │ │ │ │ │ ├── JsonWriterFiltersAndProcessors.java │ │ │ │ │ ├── WritableJson.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── logging/ │ │ │ │ │ ├── AbstractLoggingSystem.java │ │ │ │ │ ├── CorrelationIdFormatter.java │ │ │ │ │ ├── DeferredLog.java │ │ │ │ │ ├── DeferredLogFactory.java │ │ │ │ │ ├── DeferredLogs.java │ │ │ │ │ ├── DelegatingLoggingSystemFactory.java │ │ │ │ │ ├── LogFile.java │ │ │ │ │ ├── LogLevel.java │ │ │ │ │ ├── LoggerConfiguration.java │ │ │ │ │ ├── LoggerConfigurationComparator.java │ │ │ │ │ ├── LoggerGroup.java │ │ │ │ │ ├── LoggerGroups.java │ │ │ │ │ ├── LoggingInitializationContext.java │ │ │ │ │ ├── LoggingSystem.java │ │ │ │ │ ├── LoggingSystemFactory.java │ │ │ │ │ ├── LoggingSystemProperties.java │ │ │ │ │ ├── LoggingSystemProperty.java │ │ │ │ │ ├── StackTracePrinter.java │ │ │ │ │ ├── StandardStackTracePrinter.java │ │ │ │ │ ├── java/ │ │ │ │ │ │ ├── JavaLoggingSystem.java │ │ │ │ │ │ ├── JavaLoggingSystemRuntimeHints.java │ │ │ │ │ │ ├── SimpleFormatter.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── log4j2/ │ │ │ │ │ │ ├── ColorConverter.java │ │ │ │ │ │ ├── CorrelationIdConverter.java │ │ │ │ │ │ ├── ElasticCommonSchemaStructuredLogFormatter.java │ │ │ │ │ │ ├── EnclosedInSquareBracketsConverter.java │ │ │ │ │ │ ├── ExtendedWhitespaceThrowablePatternConverter.java │ │ │ │ │ │ ├── Extractor.java │ │ │ │ │ │ ├── GraylogExtendedLogFormatStructuredLogFormatter.java │ │ │ │ │ │ ├── Log4J2LoggingSystem.java │ │ │ │ │ │ ├── Log4J2RuntimeHints.java │ │ │ │ │ │ ├── Log4j2LoggingSystemProperties.java │ │ │ │ │ │ ├── LogstashStructuredLogFormatter.java │ │ │ │ │ │ ├── RollingPolicyStrategy.java │ │ │ │ │ │ ├── RollingPolicySystemProperty.java │ │ │ │ │ │ ├── SpringBootConfigurationFactory.java │ │ │ │ │ │ ├── SpringBootPropertySource.java │ │ │ │ │ │ ├── SpringBootTriggeringPolicy.java │ │ │ │ │ │ ├── SpringEnvironmentLookup.java │ │ │ │ │ │ ├── SpringEnvironmentPropertySource.java │ │ │ │ │ │ ├── SpringProfileArbiter.java │ │ │ │ │ │ ├── StructuredLogLayout.java │ │ │ │ │ │ ├── StructuredMessage.java │ │ │ │ │ │ ├── WhitespaceThrowablePatternConverter.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── logback/ │ │ │ │ │ │ ├── ColorConverter.java │ │ │ │ │ │ ├── CorrelationIdConverter.java │ │ │ │ │ │ ├── DebugLogbackConfigurator.java │ │ │ │ │ │ ├── DefaultLogbackConfiguration.java │ │ │ │ │ │ ├── ElasticCommonSchemaStructuredLogFormatter.java │ │ │ │ │ │ ├── EnclosedInSquareBracketsConverter.java │ │ │ │ │ │ ├── ExtendedWhitespaceThrowableProxyConverter.java │ │ │ │ │ │ ├── Extractor.java │ │ │ │ │ │ ├── GraylogExtendedLogFormatStructuredLogFormatter.java │ │ │ │ │ │ ├── LogbackConfigurator.java │ │ │ │ │ │ ├── LogbackLoggingSystem.java │ │ │ │ │ │ ├── LogbackLoggingSystemProperties.java │ │ │ │ │ │ ├── LogbackRuntimeHints.java │ │ │ │ │ │ ├── LogstashStructuredLogFormatter.java │ │ │ │ │ │ ├── RollingPolicySystemProperty.java │ │ │ │ │ │ ├── RootLogLevelConfigurator.java │ │ │ │ │ │ ├── SpringBootJoranConfigurator.java │ │ │ │ │ │ ├── SpringProfileAction.java │ │ │ │ │ │ ├── SpringProfileIfNestedWithinSecondPhaseElementSanityChecker.java │ │ │ │ │ │ ├── SpringProfileModel.java │ │ │ │ │ │ ├── SpringProfileModelHandler.java │ │ │ │ │ │ ├── SpringPropertyAction.java │ │ │ │ │ │ ├── SpringPropertyModel.java │ │ │ │ │ │ ├── SpringPropertyModelHandler.java │ │ │ │ │ │ ├── StructuredLogEncoder.java │ │ │ │ │ │ ├── SystemStatusListener.java │ │ │ │ │ │ ├── WhitespaceThrowableProxyConverter.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── structured/ │ │ │ │ │ ├── CommonStructuredLogFormat.java │ │ │ │ │ ├── ContextPairs.java │ │ │ │ │ ├── ElasticCommonSchemaProperties.java │ │ │ │ │ ├── GraylogExtendedLogFormatProperties.java │ │ │ │ │ ├── JsonWriterStructuredLogFormatter.java │ │ │ │ │ ├── StructuredLogFormatter.java │ │ │ │ │ ├── StructuredLogFormatterFactory.java │ │ │ │ │ ├── StructuredLoggingJsonMembersCustomizer.java │ │ │ │ │ ├── StructuredLoggingJsonProperties.java │ │ │ │ │ ├── StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessor.java │ │ │ │ │ ├── StructuredLoggingJsonPropertiesJsonMembersCustomizer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── origin/ │ │ │ │ │ ├── JarUri.java │ │ │ │ │ ├── Origin.java │ │ │ │ │ ├── OriginLookup.java │ │ │ │ │ ├── OriginProvider.java │ │ │ │ │ ├── OriginTrackedResource.java │ │ │ │ │ ├── OriginTrackedValue.java │ │ │ │ │ ├── PropertySourceOrigin.java │ │ │ │ │ ├── SystemEnvironmentOrigin.java │ │ │ │ │ ├── TextResourceOrigin.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── retry/ │ │ │ │ │ ├── RetryPolicySettings.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── ssl/ │ │ │ │ │ ├── AliasKeyManagerFactory.java │ │ │ │ │ ├── DefaultSslBundleRegistry.java │ │ │ │ │ ├── DefaultSslManagerBundle.java │ │ │ │ │ ├── FixedTrustManagerFactory.java │ │ │ │ │ ├── NoSuchSslBundleException.java │ │ │ │ │ ├── SslBundle.java │ │ │ │ │ ├── SslBundleKey.java │ │ │ │ │ ├── SslBundleRegistry.java │ │ │ │ │ ├── SslBundles.java │ │ │ │ │ ├── SslManagerBundle.java │ │ │ │ │ ├── SslOptions.java │ │ │ │ │ ├── SslStoreBundle.java │ │ │ │ │ ├── jks/ │ │ │ │ │ │ ├── JksSslStoreBundle.java │ │ │ │ │ │ ├── JksSslStoreDetails.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── pem/ │ │ │ │ │ ├── LoadedPemSslStore.java │ │ │ │ │ ├── PemCertificateParser.java │ │ │ │ │ ├── PemContent.java │ │ │ │ │ ├── PemPrivateKeyParser.java │ │ │ │ │ ├── PemSslStore.java │ │ │ │ │ ├── PemSslStoreBundle.java │ │ │ │ │ ├── PemSslStoreDetails.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── support/ │ │ │ │ │ ├── AnsiOutputApplicationListener.java │ │ │ │ │ ├── EnvironmentPostProcessorApplicationListener.java │ │ │ │ │ ├── EnvironmentPostProcessorsFactory.java │ │ │ │ │ ├── RandomValuePropertySourceEnvironmentPostProcessor.java │ │ │ │ │ ├── ReflectionEnvironmentPostProcessorsFactory.java │ │ │ │ │ ├── SpringApplicationJsonEnvironmentPostProcessor.java │ │ │ │ │ ├── SpringFactoriesEnvironmentPostProcessorsFactory.java │ │ │ │ │ ├── SystemEnvironmentPropertySourceEnvironmentPostProcessor.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── system/ │ │ │ │ │ ├── ApplicationHome.java │ │ │ │ │ ├── ApplicationPid.java │ │ │ │ │ ├── ApplicationTemp.java │ │ │ │ │ ├── JavaVersion.java │ │ │ │ │ ├── SystemProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── task/ │ │ │ │ │ ├── SimpleAsyncTaskExecutorBuilder.java │ │ │ │ │ ├── SimpleAsyncTaskExecutorCustomizer.java │ │ │ │ │ ├── SimpleAsyncTaskSchedulerBuilder.java │ │ │ │ │ ├── SimpleAsyncTaskSchedulerCustomizer.java │ │ │ │ │ ├── ThreadPoolTaskExecutorBuilder.java │ │ │ │ │ ├── ThreadPoolTaskExecutorCustomizer.java │ │ │ │ │ ├── ThreadPoolTaskSchedulerBuilder.java │ │ │ │ │ ├── ThreadPoolTaskSchedulerCustomizer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── thread/ │ │ │ │ │ ├── Threading.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── util/ │ │ │ │ │ ├── Instantiator.java │ │ │ │ │ ├── LambdaSafe.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── validation/ │ │ │ │ │ ├── MessageInterpolatorFactory.java │ │ │ │ │ ├── MessageSourceMessageInterpolator.java │ │ │ │ │ ├── beanvalidation/ │ │ │ │ │ │ ├── FilteredMethodValidationPostProcessor.java │ │ │ │ │ │ ├── MethodValidationExcludeFilter.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── context/ │ │ │ │ │ ├── reactive/ │ │ │ │ │ │ ├── AnnotationConfigReactiveWebApplicationContext.java │ │ │ │ │ │ ├── ConfigurableReactiveWebApplicationContext.java │ │ │ │ │ │ ├── ConfigurableReactiveWebEnvironment.java │ │ │ │ │ │ ├── FilteredReactiveWebContextResource.java │ │ │ │ │ │ ├── FilteredReactiveWebContextResourceFilePathResolver.java │ │ │ │ │ │ ├── GenericReactiveWebApplicationContext.java │ │ │ │ │ │ ├── ReactiveWebApplicationContext.java │ │ │ │ │ │ ├── StandardReactiveWebEnvironment.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── servlet/ │ │ │ │ │ ├── AnnotationConfigServletWebApplicationContext.java │ │ │ │ │ ├── ApplicationServletEnvironment.java │ │ │ │ │ ├── ServletContextResourceFilePathResolver.java │ │ │ │ │ ├── WebApplicationContextInitializer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── error/ │ │ │ │ │ ├── Error.java │ │ │ │ │ ├── ErrorAttributeOptions.java │ │ │ │ │ ├── ErrorPage.java │ │ │ │ │ ├── ErrorPageRegistrar.java │ │ │ │ │ ├── ErrorPageRegistrarBeanPostProcessor.java │ │ │ │ │ ├── ErrorPageRegistry.java │ │ │ │ │ └── package-info.java │ │ │ │ └── servlet/ │ │ │ │ ├── AbstractFilterRegistrationBean.java │ │ │ │ ├── DelegatingFilterProxyRegistrationBean.java │ │ │ │ ├── DispatcherType.java │ │ │ │ ├── DynamicRegistrationBean.java │ │ │ │ ├── FilterRegistration.java │ │ │ │ ├── FilterRegistrationBean.java │ │ │ │ ├── RegistrationBean.java │ │ │ │ ├── ServletContextInitializer.java │ │ │ │ ├── ServletContextInitializerBeans.java │ │ │ │ ├── ServletListenerRegistrationBean.java │ │ │ │ ├── ServletRegistration.java │ │ │ │ ├── ServletRegistrationBean.java │ │ │ │ ├── package-info.java │ │ │ │ └── support/ │ │ │ │ ├── ErrorPageFilter.java │ │ │ │ ├── ErrorPageFilterConfiguration.java │ │ │ │ ├── ServletContextApplicationContextInitializer.java │ │ │ │ ├── SpringBootServletInitializer.java │ │ │ │ └── package-info.java │ │ │ ├── javaTemplates/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ ├── SpringBootVersion.java │ │ │ │ └── ssl/ │ │ │ │ └── SpringBootProviderVersion.java │ │ │ ├── kotlin/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── SpringApplicationExtensions.kt │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ │ ├── services/ │ │ │ │ │ ├── ch.qos.logback.classic.spi.Configurator │ │ │ │ │ └── org.apache.logging.log4j.util.PropertySource │ │ │ │ ├── spring/ │ │ │ │ │ └── aot.factories │ │ │ │ └── spring.factories │ │ │ ├── log4j2.springboot │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── logging/ │ │ │ ├── java/ │ │ │ │ ├── logging-file.properties │ │ │ │ └── logging.properties │ │ │ ├── log4j2/ │ │ │ │ ├── log4j2-file.xml │ │ │ │ └── log4j2.xml │ │ │ └── logback/ │ │ │ ├── base.xml │ │ │ ├── console-appender.xml │ │ │ ├── defaults.xml │ │ │ ├── file-appender.xml │ │ │ ├── structured-console-appender.xml │ │ │ └── structured-file-appender.xml │ │ ├── test/ │ │ │ ├── java/ │ │ │ │ ├── org/ │ │ │ │ │ └── springframework/ │ │ │ │ │ └── boot/ │ │ │ │ │ ├── ApplicationEnvironmentTests.java │ │ │ │ │ ├── ApplicationInfoPropertySourceTests.java │ │ │ │ │ ├── ApplicationPropertiesTests.java │ │ │ │ │ ├── BannerTests.java │ │ │ │ │ ├── BeanDefinitionLoaderTests.java │ │ │ │ │ ├── DefaultApplicationArgumentsTests.java │ │ │ │ │ ├── EnvironmentConverterTests.java │ │ │ │ │ ├── ExitCodeGeneratorsTests.java │ │ │ │ │ ├── LazyInitializationBeanFactoryPostProcessorTests.java │ │ │ │ │ ├── LazyInitializationExcludeFilterTests.java │ │ │ │ │ ├── MockApplicationEnvironment.java │ │ │ │ │ ├── OverrideSourcesTests.java │ │ │ │ │ ├── ResourceBannerTests.java │ │ │ │ │ ├── SimpleMainTests.java │ │ │ │ │ ├── SpringApplicationAotProcessorTests.java │ │ │ │ │ ├── SpringApplicationBannerPrinterTests.java │ │ │ │ │ ├── SpringApplicationNoWebTests.java │ │ │ │ │ ├── SpringApplicationShutdownHookInstance.java │ │ │ │ │ ├── SpringApplicationShutdownHookTests.java │ │ │ │ │ ├── SpringApplicationTests.java │ │ │ │ │ ├── SpringBootConfigurationTests.java │ │ │ │ │ ├── SpringBootExceptionHandlerTests.java │ │ │ │ │ ├── SpringBootVersionTests.java │ │ │ │ │ ├── StartupInfoLoggerTests.java │ │ │ │ │ ├── TestApplicationEnvironment.java │ │ │ │ │ ├── WithSampleBeansXmlResource.java │ │ │ │ │ ├── admin/ │ │ │ │ │ │ └── SpringApplicationAdminMXBeanRegistrarTests.java │ │ │ │ │ ├── ansi/ │ │ │ │ │ │ ├── Ansi8BitColorTests.java │ │ │ │ │ │ ├── AnsiOutputEnabledValue.java │ │ │ │ │ │ ├── AnsiOutputTests.java │ │ │ │ │ │ └── AnsiPropertySourceTests.java │ │ │ │ │ ├── availability/ │ │ │ │ │ │ ├── ApplicationAvailabilityBeanTests.java │ │ │ │ │ │ └── AvailabilityChangeEventTests.java │ │ │ │ │ ├── bootstrap/ │ │ │ │ │ │ └── DefaultBootstrapContextTests.java │ │ │ │ │ ├── builder/ │ │ │ │ │ │ └── SpringApplicationBuilderTests.java │ │ │ │ │ ├── cloud/ │ │ │ │ │ │ ├── CloudFoundryVcapEnvironmentPostProcessorTests.java │ │ │ │ │ │ └── CloudPlatformTests.java │ │ │ │ │ ├── context/ │ │ │ │ │ │ ├── ApplicationPidFileWriterTests.java │ │ │ │ │ │ ├── ConfigurationWarningsApplicationContextInitializerTests.java │ │ │ │ │ │ ├── ContextIdApplicationContextInitializerTests.java │ │ │ │ │ │ ├── FileEncodingApplicationListenerTests.java │ │ │ │ │ │ ├── TypeExcludeFilterTests.java │ │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ │ ├── ConfigurationsTests.java │ │ │ │ │ │ │ ├── ImportCandidatesTests.java │ │ │ │ │ │ │ └── UserConfigurationsTests.java │ │ │ │ │ │ ├── config/ │ │ │ │ │ │ │ ├── ConfigDataActivationContextTests.java │ │ │ │ │ │ │ ├── ConfigDataEnvironmentContributorPlaceholdersResolverTests.java │ │ │ │ │ │ │ ├── ConfigDataEnvironmentContributorTests.java │ │ │ │ │ │ │ ├── ConfigDataEnvironmentContributorsTests.java │ │ │ │ │ │ │ ├── ConfigDataEnvironmentPostProcessorBootstrapContextIntegrationTests.java │ │ │ │ │ │ │ ├── ConfigDataEnvironmentPostProcessorImportCombinedWithProfileSpecificIntegrationTests.java │ │ │ │ │ │ │ ├── ConfigDataEnvironmentPostProcessorIntegrationTests.java │ │ │ │ │ │ │ ├── ConfigDataEnvironmentPostProcessorTests.java │ │ │ │ │ │ │ ├── ConfigDataEnvironmentTests.java │ │ │ │ │ │ │ ├── ConfigDataImporterTests.java │ │ │ │ │ │ │ ├── ConfigDataLoaderTests.java │ │ │ │ │ │ │ ├── ConfigDataLoadersTests.java │ │ │ │ │ │ │ ├── ConfigDataLocationBindHandlerTests.java │ │ │ │ │ │ │ ├── ConfigDataLocationNotFoundExceptionTests.java │ │ │ │ │ │ │ ├── ConfigDataLocationResolverTests.java │ │ │ │ │ │ │ ├── ConfigDataLocationResolversTests.java │ │ │ │ │ │ │ ├── ConfigDataLocationRuntimeHintsTests.java │ │ │ │ │ │ │ ├── ConfigDataLocationTests.java │ │ │ │ │ │ │ ├── ConfigDataNotFoundFailureAnalyzerTests.java │ │ │ │ │ │ │ ├── ConfigDataPropertiesRuntimeHintsTests.java │ │ │ │ │ │ │ ├── ConfigDataPropertiesTests.java │ │ │ │ │ │ │ ├── ConfigDataResourceNotFoundExceptionTests.java │ │ │ │ │ │ │ ├── ConfigDataTests.java │ │ │ │ │ │ │ ├── ConfigTreeConfigDataLoaderTests.java │ │ │ │ │ │ │ ├── ConfigTreeConfigDataLocationResolverTests.java │ │ │ │ │ │ │ ├── ConfigTreeConfigDataResourceTests.java │ │ │ │ │ │ │ ├── FileHintTests.java │ │ │ │ │ │ │ ├── InactiveConfigDataAccessExceptionTests.java │ │ │ │ │ │ │ ├── InvalidConfigDataPropertyExceptionTests.java │ │ │ │ │ │ │ ├── LocationResourceLoaderTests.java │ │ │ │ │ │ │ ├── ProfilesTests.java │ │ │ │ │ │ │ ├── ProfilesValidatorTests.java │ │ │ │ │ │ │ ├── StandardConfigDataLoaderTests.java │ │ │ │ │ │ │ ├── StandardConfigDataLocationResolverTests.java │ │ │ │ │ │ │ ├── StandardConfigDataResourceTests.java │ │ │ │ │ │ │ ├── SystemEnvironmentConfigDataLoaderTests.java │ │ │ │ │ │ │ ├── SystemEnvironmentConfigDataLocationResolverTests.java │ │ │ │ │ │ │ ├── SystemEnvironmentConfigDataResourceTests.java │ │ │ │ │ │ │ ├── TestConfigDataBootstrap.java │ │ │ │ │ │ │ ├── TestConfigDataEnvironmentUpdateListener.java │ │ │ │ │ │ │ ├── TestPropertySourceLoader1.java │ │ │ │ │ │ │ ├── TestPropertySourceLoader2.java │ │ │ │ │ │ │ └── UnsupportedConfigDataLocationExceptionTests.java │ │ │ │ │ │ ├── configwarnings/ │ │ │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ │ │ ├── MetaComponentScan.java │ │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ │ ├── dflt/ │ │ │ │ │ │ │ │ ├── InDefaultPackageConfiguration.java │ │ │ │ │ │ │ │ ├── InDefaultPackageWithBasePackageClassesConfiguration.java │ │ │ │ │ │ │ │ ├── InDefaultPackageWithBasePackagesConfiguration.java │ │ │ │ │ │ │ │ ├── InDefaultPackageWithMetaAnnotationConfiguration.java │ │ │ │ │ │ │ │ ├── InDefaultPackageWithValueConfiguration.java │ │ │ │ │ │ │ │ ├── InDefaultPackageWithoutScanConfiguration.java │ │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ │ ├── orgspring/ │ │ │ │ │ │ │ │ ├── InOrgSpringPackageConfiguration.java │ │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ │ └── real/ │ │ │ │ │ │ │ ├── InRealButScanningProblemPackages.java │ │ │ │ │ │ │ ├── InRealPackageConfiguration.java │ │ │ │ │ │ │ ├── nested/ │ │ │ │ │ │ │ │ ├── ExampleBean.java │ │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── event/ │ │ │ │ │ │ │ └── EventPublishingRunListenerTests.java │ │ │ │ │ │ ├── filtersample/ │ │ │ │ │ │ │ ├── ExampleComponent.java │ │ │ │ │ │ │ ├── ExampleFilteredComponent.java │ │ │ │ │ │ │ ├── SampleTypeExcludeFilter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── logging/ │ │ │ │ │ │ │ ├── LoggingApplicationListenerIntegrationTests.java │ │ │ │ │ │ │ └── LoggingApplicationListenerTests.java │ │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ │ └── buffering/ │ │ │ │ │ │ │ └── BufferingApplicationStartupTests.java │ │ │ │ │ │ └── properties/ │ │ │ │ │ │ ├── ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java │ │ │ │ │ │ ├── ConfigurationPropertiesBeanRegistrarTests.java │ │ │ │ │ │ ├── ConfigurationPropertiesBeanRegistrationAotProcessorTests.java │ │ │ │ │ │ ├── ConfigurationPropertiesBeanTests.java │ │ │ │ │ │ ├── ConfigurationPropertiesBindExceptionTests.java │ │ │ │ │ │ ├── ConfigurationPropertiesBindHandlerAdvisorTests.java │ │ │ │ │ │ ├── ConfigurationPropertiesCharSequenceToObjectConverterTests.java │ │ │ │ │ │ ├── ConfigurationPropertiesScanRegistrarTests.java │ │ │ │ │ │ ├── ConfigurationPropertiesScanTests.java │ │ │ │ │ │ ├── ConfigurationPropertiesTestBeanTests.java │ │ │ │ │ │ ├── ConfigurationPropertiesTests.java │ │ │ │ │ │ ├── ConversionServiceDeducerTests.java │ │ │ │ │ │ ├── EnableConfigurationPropertiesRegistrarTests.java │ │ │ │ │ │ ├── IncompatibleConfigurationFailureAnalyzerTests.java │ │ │ │ │ │ ├── MultiConstructorConfigurationProperties.java │ │ │ │ │ │ ├── NotConstructorBoundInjectionFailureAnalyzerTests.java │ │ │ │ │ │ ├── PropertyMapperTests.java │ │ │ │ │ │ ├── PropertySourcesDeducerTests.java │ │ │ │ │ │ ├── ValidatorPropertiesWithDefaultValues.java │ │ │ │ │ │ ├── WithPublicObjectToObjectMethod.java │ │ │ │ │ │ ├── WithPublicStringConstructorProperties.java │ │ │ │ │ │ ├── bind/ │ │ │ │ │ │ │ ├── ArrayBinderTests.java │ │ │ │ │ │ │ ├── BackCompatibilityBinderIntegrationTests.java │ │ │ │ │ │ │ ├── BindConverterTests.java │ │ │ │ │ │ │ ├── BindResultTests.java │ │ │ │ │ │ │ ├── BindableRuntimeHintsRegistrarTests.java │ │ │ │ │ │ │ ├── BindableTests.java │ │ │ │ │ │ │ ├── BinderTests.java │ │ │ │ │ │ │ ├── BoundPropertiesTrackingBindHandlerTests.java │ │ │ │ │ │ │ ├── CollectionBinderTests.java │ │ │ │ │ │ │ ├── DataObjectPropertyNameTests.java │ │ │ │ │ │ │ ├── DefaultBindConstructorProviderTests.java │ │ │ │ │ │ │ ├── JavaBeanBinderTests.java │ │ │ │ │ │ │ ├── JavaBeanWithPublicConstructor.java │ │ │ │ │ │ │ ├── MapBinderTests.java │ │ │ │ │ │ │ ├── PropertySourcesPlaceholdersResolverTests.java │ │ │ │ │ │ │ ├── ValueObjectBinderTests.java │ │ │ │ │ │ │ ├── handler/ │ │ │ │ │ │ │ │ ├── IgnoreErrorsBindHandlerTests.java │ │ │ │ │ │ │ │ ├── IgnoreTopLevelConverterNotFoundBindHandlerTests.java │ │ │ │ │ │ │ │ └── NoUnboundElementsBindHandlerTests.java │ │ │ │ │ │ │ ├── test/ │ │ │ │ │ │ │ │ ├── PackagePrivateBeanBindingTests.java │ │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ │ └── validation/ │ │ │ │ │ │ │ ├── BindValidationExceptionTests.java │ │ │ │ │ │ │ ├── OriginTrackedFieldErrorTests.java │ │ │ │ │ │ │ ├── ValidationBindHandlerTests.java │ │ │ │ │ │ │ └── ValidationErrorsTests.java │ │ │ │ │ │ ├── scan/ │ │ │ │ │ │ │ ├── combined/ │ │ │ │ │ │ │ │ ├── c/ │ │ │ │ │ │ │ │ │ ├── CombinedConfiguration.java │ │ │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ │ │ └── d/ │ │ │ │ │ │ │ │ ├── OtherCombinedConfiguration.java │ │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ │ └── valid/ │ │ │ │ │ │ │ ├── ConfigurationPropertiesScanConfiguration.java │ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ │ ├── AScanConfiguration.java │ │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ │ ├── BScanConfiguration.java │ │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── source/ │ │ │ │ │ │ ├── AbstractPropertyMapperTests.java │ │ │ │ │ │ ├── AliasedConfigurationPropertySourceTests.java │ │ │ │ │ │ ├── AliasedIterableConfigurationPropertySourceTests.java │ │ │ │ │ │ ├── CachingConfigurationPropertySourceTests.java │ │ │ │ │ │ ├── ConfigurationPropertyCachingTests.java │ │ │ │ │ │ ├── ConfigurationPropertyNameAliasesTests.java │ │ │ │ │ │ ├── ConfigurationPropertyNameTests.java │ │ │ │ │ │ ├── ConfigurationPropertySourceTests.java │ │ │ │ │ │ ├── ConfigurationPropertySourcesCachingTests.java │ │ │ │ │ │ ├── ConfigurationPropertySourcesPropertyResolverTests.java │ │ │ │ │ │ ├── ConfigurationPropertySourcesPropertySourceTests.java │ │ │ │ │ │ ├── ConfigurationPropertySourcesTests.java │ │ │ │ │ │ ├── ConfigurationPropertyStateTests.java │ │ │ │ │ │ ├── ConfigurationPropertyTests.java │ │ │ │ │ │ ├── DefaultPropertyMapperTests.java │ │ │ │ │ │ ├── FilteredConfigurationPropertiesSourceTests.java │ │ │ │ │ │ ├── FilteredIterableConfigurationPropertiesSourceTests.java │ │ │ │ │ │ ├── KnownAncestorsConfigurationPropertySource.java │ │ │ │ │ │ ├── MapConfigurationPropertySourceTests.java │ │ │ │ │ │ ├── MockConfigurationPropertySource.java │ │ │ │ │ │ ├── MutuallyExclusiveConfigurationPropertiesExceptionTests.java │ │ │ │ │ │ ├── PrefixedConfigurationPropertySourceTests.java │ │ │ │ │ │ ├── PrefixedIterableConfigurationPropertySourceTests.java │ │ │ │ │ │ ├── SoftReferenceConfigurationPropertyCacheTests.java │ │ │ │ │ │ ├── SpringConfigurationPropertySourceTests.java │ │ │ │ │ │ ├── SpringConfigurationPropertySourcesTests.java │ │ │ │ │ │ ├── SpringIterableConfigurationPropertySourceTests.java │ │ │ │ │ │ ├── SystemEnvironmentPropertyMapperTests.java │ │ │ │ │ │ ├── TestPropertyMapper.java │ │ │ │ │ │ └── UnboundElementsSourceFilterTests.java │ │ │ │ │ ├── convert/ │ │ │ │ │ │ ├── ApplicationConversionServiceTests.java │ │ │ │ │ │ ├── ArrayToDelimitedStringConverterTests.java │ │ │ │ │ │ ├── CharArrayFormatterTests.java │ │ │ │ │ │ ├── CharSequenceToObjectConverterTests.java │ │ │ │ │ │ ├── CollectionToDelimitedStringConverterTests.java │ │ │ │ │ │ ├── ConversionServiceArguments.java │ │ │ │ │ │ ├── ConversionServiceTest.java │ │ │ │ │ │ ├── DelimitedStringToArrayConverterTests.java │ │ │ │ │ │ ├── DelimitedStringToCollectionConverterTests.java │ │ │ │ │ │ ├── DurationStyleTests.java │ │ │ │ │ │ ├── DurationToNumberConverterTests.java │ │ │ │ │ │ ├── DurationToStringConverterTests.java │ │ │ │ │ │ ├── InetAddressFormatterTests.java │ │ │ │ │ │ ├── InputStreamSourceToByteArrayConverterTests.java │ │ │ │ │ │ ├── IsoOffsetFormatterTests.java │ │ │ │ │ │ ├── LenientBooleanToEnumConverterFactoryTests.java │ │ │ │ │ │ ├── LenientStringToEnumConverterFactoryTests.java │ │ │ │ │ │ ├── MockDataSizeTypeDescriptor.java │ │ │ │ │ │ ├── MockDurationTypeDescriptor.java │ │ │ │ │ │ ├── MockPeriodTypeDescriptor.java │ │ │ │ │ │ ├── NumberToDataSizeConverterTests.java │ │ │ │ │ │ ├── NumberToDurationConverterTests.java │ │ │ │ │ │ ├── NumberToPeriodConverterTests.java │ │ │ │ │ │ ├── PeriodStyleTests.java │ │ │ │ │ │ ├── PeriodToStringConverterTests.java │ │ │ │ │ │ ├── StringToDataSizeConverterTests.java │ │ │ │ │ │ ├── StringToDurationConverterTests.java │ │ │ │ │ │ ├── StringToFileConverterTests.java │ │ │ │ │ │ └── StringToPeriodConverterTests.java │ │ │ │ │ ├── diagnostics/ │ │ │ │ │ │ ├── AbstractFailureAnalyzerTests.java │ │ │ │ │ │ ├── FailureAnalyzersIntegrationTests.java │ │ │ │ │ │ ├── FailureAnalyzersTests.java │ │ │ │ │ │ └── analyzer/ │ │ │ │ │ │ ├── AotInitializerNotFoundFailureAnalyzerTests.java │ │ │ │ │ │ ├── BeanCurrentlyInCreationFailureAnalyzerTests.java │ │ │ │ │ │ ├── BeanDefinitionOverrideFailureAnalyzerTests.java │ │ │ │ │ │ ├── BeanNotOfRequiredTypeFailureAnalyzerTests.java │ │ │ │ │ │ ├── BindFailureAnalyzerTests.java │ │ │ │ │ │ ├── BindValidationFailureAnalyzerTests.java │ │ │ │ │ │ ├── InvalidConfigurationPropertyNameFailureAnalyzerTests.java │ │ │ │ │ │ ├── InvalidConfigurationPropertyValueFailureAnalyzerTests.java │ │ │ │ │ │ ├── JakartaApiValidationExceptionFailureAnalyzerTests.java │ │ │ │ │ │ ├── MissingParameterNamesFailureAnalyzerTests.java │ │ │ │ │ │ ├── MutuallyExclusiveConfigurationPropertiesFailureAnalyzerTests.java │ │ │ │ │ │ ├── NoSuchMethodFailureAnalyzerTests.java │ │ │ │ │ │ ├── NoUniqueBeanDefinitionFailureAnalyzerTests.java │ │ │ │ │ │ ├── PatternParseFailureAnalyzerTests.java │ │ │ │ │ │ ├── UnboundConfigurationPropertyFailureAnalyzerTests.java │ │ │ │ │ │ └── nounique/ │ │ │ │ │ │ ├── BarTestBean.java │ │ │ │ │ │ ├── FooTestBean.java │ │ │ │ │ │ ├── TestBean.java │ │ │ │ │ │ ├── TestBeanConsumer.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── env/ │ │ │ │ │ │ ├── ConfigTreePropertySourceTests.java │ │ │ │ │ │ ├── DefaultPropertiesPropertySourceTests.java │ │ │ │ │ │ ├── EnvironmentPostProcessorsFactoryTests.java │ │ │ │ │ │ ├── NoSnakeYamlPropertySourceLoaderTests.java │ │ │ │ │ │ ├── OriginTrackedMapPropertySourceTests.java │ │ │ │ │ │ ├── OriginTrackedPropertiesLoaderTests.java │ │ │ │ │ │ ├── OriginTrackedYamlLoaderTests.java │ │ │ │ │ │ ├── PropertiesPropertySourceLoaderTests.java │ │ │ │ │ │ ├── RandomValuePropertySourceTests.java │ │ │ │ │ │ └── YamlPropertySourceLoaderTests.java │ │ │ │ │ ├── info/ │ │ │ │ │ │ ├── BuildPropertiesTests.java │ │ │ │ │ │ ├── GitPropertiesTests.java │ │ │ │ │ │ ├── InfoPropertiesTests.java │ │ │ │ │ │ ├── JavaInfoTests.java │ │ │ │ │ │ ├── OsInfoTests.java │ │ │ │ │ │ ├── ProcessInfoTests.java │ │ │ │ │ │ └── SslInfoTests.java │ │ │ │ │ ├── io/ │ │ │ │ │ │ ├── ApplicationResourceLoaderTests.java │ │ │ │ │ │ ├── Base64ProtocolResolverTests.java │ │ │ │ │ │ ├── ProtocolResolverApplicationContextInitializerIntegrationTests.java │ │ │ │ │ │ ├── ProtocolResolverApplicationContextInitializerTests.java │ │ │ │ │ │ └── ReverseStringProtocolResolver.java │ │ │ │ │ ├── json/ │ │ │ │ │ │ ├── AbstractJsonParserTests.java │ │ │ │ │ │ ├── BasicJsonParserTests.java │ │ │ │ │ │ ├── GsonJsonParserTests.java │ │ │ │ │ │ ├── JacksonJsonParserTests.java │ │ │ │ │ │ ├── JsonValueWriterTests.java │ │ │ │ │ │ ├── JsonWriterTests.java │ │ │ │ │ │ └── WritableJsonTests.java │ │ │ │ │ ├── logging/ │ │ │ │ │ │ ├── AbstractLoggingSystemTests.java │ │ │ │ │ │ ├── CorrelationIdFormatterTests.java │ │ │ │ │ │ ├── DeferredLogFactoryTests.java │ │ │ │ │ │ ├── DeferredLogTests.java │ │ │ │ │ │ ├── DeferredLogsTests.java │ │ │ │ │ │ ├── DelegatingLoggingSystemFactoryTests.java │ │ │ │ │ │ ├── LogFileTests.java │ │ │ │ │ │ ├── LogLevelTests.java │ │ │ │ │ │ ├── LogbackAndLog4J2ExcludedLoggingSystemTests.java │ │ │ │ │ │ ├── LoggerConfigurationComparatorTests.java │ │ │ │ │ │ ├── LoggerConfigurationTests.java │ │ │ │ │ │ ├── LoggerGroupsTests.java │ │ │ │ │ │ ├── LoggingSystemPropertiesTests.java │ │ │ │ │ │ ├── LoggingSystemTests.java │ │ │ │ │ │ ├── StandardStackTracePrinterTests.java │ │ │ │ │ │ ├── TestException.java │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ ├── JavaLoggingSystemTests.java │ │ │ │ │ │ │ └── TestFormatter.java │ │ │ │ │ │ ├── log4j2/ │ │ │ │ │ │ │ ├── AbstractStructuredLoggingTests.java │ │ │ │ │ │ │ ├── ColorConverterTests.java │ │ │ │ │ │ │ ├── CorrelationIdConverterTests.java │ │ │ │ │ │ │ ├── ElasticCommonSchemaStructuredLogFormatterTests.java │ │ │ │ │ │ │ ├── EnclosedInSquareBracketsConverterTests.java │ │ │ │ │ │ │ ├── ExtendedWhitespaceThrowablePatternConverterTests.java │ │ │ │ │ │ │ ├── ExtractorTests.java │ │ │ │ │ │ │ ├── GraylogExtendedLogFormatStructuredLogFormatterTests.java │ │ │ │ │ │ │ ├── Log4J2LoggingSystemTests.java │ │ │ │ │ │ │ ├── Log4J2RuntimeHintsTests.java │ │ │ │ │ │ │ ├── Log4j2FileXmlTests.java │ │ │ │ │ │ │ ├── Log4j2LoggingSystemPropertiesTests.java │ │ │ │ │ │ │ ├── Log4j2XmlTests.java │ │ │ │ │ │ │ ├── LogstashStructuredLogFormatterTests.java │ │ │ │ │ │ │ ├── SimpleStackTracePrinter.java │ │ │ │ │ │ │ ├── SpringBootPropertySourceTests.java │ │ │ │ │ │ │ ├── SpringEnvironmentLookupTests.java │ │ │ │ │ │ │ ├── SpringEnvironmentPropertySourceTests.java │ │ │ │ │ │ │ ├── SpringProfileArbiterTests.java │ │ │ │ │ │ │ ├── StructuredLogLayoutTests.java │ │ │ │ │ │ │ ├── TestLog4J2LoggingSystem.java │ │ │ │ │ │ │ └── WhitespaceThrowablePatternConverterTests.java │ │ │ │ │ │ ├── logback/ │ │ │ │ │ │ │ ├── AbstractStructuredLoggingTests.java │ │ │ │ │ │ │ ├── ColorConverterTests.java │ │ │ │ │ │ │ ├── CorrelationIdConverterTests.java │ │ │ │ │ │ │ ├── DefaultLogbackConfigurationTests.java │ │ │ │ │ │ │ ├── ElasticCommonSchemaStructuredLogFormatterTests.java │ │ │ │ │ │ │ ├── EnclosedInSquareBracketsConverterTests.java │ │ │ │ │ │ │ ├── ExtendedWhitespaceThrowableProxyConverterTests.java │ │ │ │ │ │ │ ├── ExtractorTests.java │ │ │ │ │ │ │ ├── GraylogExtendedLogFormatStructuredLogFormatterTests.java │ │ │ │ │ │ │ ├── LogbackConfigurationAotContributionTests.java │ │ │ │ │ │ │ ├── LogbackConfigurationTests.java │ │ │ │ │ │ │ ├── LogbackLoggingSystemParallelInitializationTests.java │ │ │ │ │ │ │ ├── LogbackLoggingSystemPropertiesTests.java │ │ │ │ │ │ │ ├── LogbackLoggingSystemTests.java │ │ │ │ │ │ │ ├── LogbackRuntimeHintsTests.java │ │ │ │ │ │ │ ├── LogstashStructuredLogFormatterTests.java │ │ │ │ │ │ │ ├── RootLogLevelConfiguratorTests.java │ │ │ │ │ │ │ ├── SimpleStackTracePrinter.java │ │ │ │ │ │ │ ├── SpringBootJoranConfiguratorTests.java │ │ │ │ │ │ │ ├── SpringProfileModelHandlerTests.java │ │ │ │ │ │ │ ├── StructuredLogEncoderTests.java │ │ │ │ │ │ │ ├── SystemStatusListenerTests.java │ │ │ │ │ │ │ └── WhitespaceThrowableProxyConverterTests.java │ │ │ │ │ │ └── structured/ │ │ │ │ │ │ ├── CommonStructuredLogFormatTests.java │ │ │ │ │ │ ├── ContextPairsTests.java │ │ │ │ │ │ ├── ElasticCommonSchemaPropertiesTests.java │ │ │ │ │ │ ├── GraylogExtendedLogFormatPropertiesTests.java │ │ │ │ │ │ ├── MockStructuredLoggingJsonMembersCustomizerBuilder.java │ │ │ │ │ │ ├── StructuredLogFormatterFactoryTests.java │ │ │ │ │ │ ├── StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessorTests.java │ │ │ │ │ │ ├── StructuredLoggingJsonPropertiesJsonMembersCustomizerTests.java │ │ │ │ │ │ ├── StructuredLoggingJsonPropertiesTests.java │ │ │ │ │ │ └── TestContextPairs.java │ │ │ │ │ ├── origin/ │ │ │ │ │ │ ├── JarUriTests.java │ │ │ │ │ │ ├── MockOrigin.java │ │ │ │ │ │ ├── OriginLookupTests.java │ │ │ │ │ │ ├── OriginTests.java │ │ │ │ │ │ ├── OriginTrackedResourceTests.java │ │ │ │ │ │ ├── OriginTrackedValueTests.java │ │ │ │ │ │ ├── PropertySourceOriginTests.java │ │ │ │ │ │ ├── SystemEnvironmentOriginTests.java │ │ │ │ │ │ └── TextResourceOriginTests.java │ │ │ │ │ ├── retry/ │ │ │ │ │ │ └── RetryPolicySettingsTests.java │ │ │ │ │ ├── sampleconfig/ │ │ │ │ │ │ ├── MyComponent.java │ │ │ │ │ │ ├── MyNamedComponent.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── ssl/ │ │ │ │ │ │ ├── AliasKeyManagerFactoryTests.java │ │ │ │ │ │ ├── DefaultSslBundleRegistryTests.java │ │ │ │ │ │ ├── DefaultSslManagerBundleTests.java │ │ │ │ │ │ ├── FixedTrustManagerFactoryTests.java │ │ │ │ │ │ ├── NoSuchSslBundleExceptionTests.java │ │ │ │ │ │ ├── SslBundleKeyTests.java │ │ │ │ │ │ ├── SslBundleTests.java │ │ │ │ │ │ ├── SslManagerBundleTests.java │ │ │ │ │ │ ├── SslOptionsTests.java │ │ │ │ │ │ ├── SslStoreBundleTests.java │ │ │ │ │ │ ├── jks/ │ │ │ │ │ │ │ └── JksSslStoreBundleTests.java │ │ │ │ │ │ └── pem/ │ │ │ │ │ │ ├── LoadedPemSslStoreTests.java │ │ │ │ │ │ ├── PemCertificateParserTests.java │ │ │ │ │ │ ├── PemContentTests.java │ │ │ │ │ │ ├── PemPrivateKeyParserTests.java │ │ │ │ │ │ ├── PemSslStoreBundleTests.java │ │ │ │ │ │ └── PemSslStoreTests.java │ │ │ │ │ ├── support/ │ │ │ │ │ │ ├── AnsiOutputApplicationListenerTests.java │ │ │ │ │ │ ├── EnvironmentPostProcessorApplicationListenerTests.java │ │ │ │ │ │ ├── RandomValuePropertySourceEnvironmentPostProcessorTests.java │ │ │ │ │ │ ├── ReflectionEnvironmentPostProcessorsFactoryTests.java │ │ │ │ │ │ ├── SpringApplicationJsonEnvironmentPostProcessorTests.java │ │ │ │ │ │ └── SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java │ │ │ │ │ ├── system/ │ │ │ │ │ │ ├── ApplicationHomeTests.java │ │ │ │ │ │ ├── ApplicationPidTests.java │ │ │ │ │ │ ├── ApplicationTempTests.java │ │ │ │ │ │ ├── JavaVersionTests.java │ │ │ │ │ │ └── MockApplicationPid.java │ │ │ │ │ ├── task/ │ │ │ │ │ │ ├── SimpleAsyncTaskExecutorBuilderTests.java │ │ │ │ │ │ ├── SimpleAsyncTaskSchedulerBuilderTests.java │ │ │ │ │ │ ├── ThreadPoolTaskExecutorBuilderTests.java │ │ │ │ │ │ └── ThreadPoolTaskSchedulerBuilderTests.java │ │ │ │ │ ├── util/ │ │ │ │ │ │ ├── InstantiatorTests.java │ │ │ │ │ │ ├── LambdaSafeTests.java │ │ │ │ │ │ └── WithDefaultConstructor.java │ │ │ │ │ ├── validation/ │ │ │ │ │ │ ├── MessageInterpolatorFactoryTests.java │ │ │ │ │ │ ├── MessageInterpolatorFactoryWithoutElIntegrationTests.java │ │ │ │ │ │ ├── MessageSourceMessageInterpolatorIntegrationTests.java │ │ │ │ │ │ ├── MessageSourceMessageInterpolatorTests.java │ │ │ │ │ │ └── beanvalidation/ │ │ │ │ │ │ └── MethodValidationExcludeFilterTests.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── context/ │ │ │ │ │ │ ├── reactive/ │ │ │ │ │ │ │ ├── FilteredReactiveWebContextResourceFilePathResolverIntegrationTests.java │ │ │ │ │ │ │ └── GenericReactiveWebApplicationContextTests.java │ │ │ │ │ │ └── servlet/ │ │ │ │ │ │ └── ServletContextResourceFilePathResolverIntegrationTests.java │ │ │ │ │ ├── error/ │ │ │ │ │ │ ├── ErrorAttributesOptionsTests.java │ │ │ │ │ │ └── ErrorTests.java │ │ │ │ │ └── servlet/ │ │ │ │ │ ├── AbstractFilterRegistrationBeanTests.java │ │ │ │ │ ├── DelegatingFilterProxyRegistrationBeanTests.java │ │ │ │ │ ├── DynamicRegistrationBeanTests.java │ │ │ │ │ ├── FilterRegistrationBeanTests.java │ │ │ │ │ ├── FilterRegistrationIntegrationTests.java │ │ │ │ │ ├── NoSpringWebFilterRegistrationBeanTests.java │ │ │ │ │ ├── ServletContextInitializerBeansTests.java │ │ │ │ │ ├── ServletListenerRegistrationBeanTests.java │ │ │ │ │ ├── ServletRegistrationBeanTests.java │ │ │ │ │ ├── TestServletAndFilterAndListener.java │ │ │ │ │ └── support/ │ │ │ │ │ ├── ErrorPageFilterTests.java │ │ │ │ │ ├── ServletContextApplicationContextInitializerTests.java │ │ │ │ │ └── SpringBootServletInitializerTests.java │ │ │ │ └── sampleconfig/ │ │ │ │ ├── MyComponentInPackageWithoutDot.java │ │ │ │ └── package-info.java │ │ │ ├── kotlin/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ ├── SpringApplicationExtensionsTests.kt │ │ │ │ ├── context/ │ │ │ │ │ └── properties/ │ │ │ │ │ ├── KotlinConfigurationPropertiesBeanRegistrarTests.kt │ │ │ │ │ ├── KotlinConfigurationPropertiesTests.kt │ │ │ │ │ └── bind/ │ │ │ │ │ ├── KotlinBindableRuntimeHintsRegistrarTests.kt │ │ │ │ │ ├── KotlinConstructorParametersBinderTests.kt │ │ │ │ │ └── KotlinDefaultBindConstructorProviderTests.kt │ │ │ │ └── kotlinsample/ │ │ │ │ └── TestKotlinApplication.kt │ │ │ └── resources/ │ │ │ ├── log4j2-test.xml │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ ├── env/ │ │ │ │ └── test-properties.properties │ │ │ ├── info/ │ │ │ │ ├── keystore.jks │ │ │ │ ├── test-expired.p12 │ │ │ │ ├── test-not-yet-valid.p12 │ │ │ │ ├── test.p12 │ │ │ │ ├── truststore.jks │ │ │ │ └── will-expire-soon.p12 │ │ │ ├── json/ │ │ │ │ ├── deeply-nested-map-json.txt │ │ │ │ ├── large-malformed-json.txt │ │ │ │ └── repeated-open-array.txt │ │ │ └── ssl/ │ │ │ ├── jks/ │ │ │ │ ├── test.jks │ │ │ │ └── test.p12 │ │ │ └── pem/ │ │ │ ├── ca.crt │ │ │ ├── ca.pem │ │ │ ├── dsa.key │ │ │ ├── file.txt │ │ │ ├── pkcs1/ │ │ │ │ ├── dsa.key │ │ │ │ ├── rsa-aes-256-cbc.key │ │ │ │ └── rsa.key │ │ │ ├── pkcs8/ │ │ │ │ ├── brainpoolP256r1.key │ │ │ │ ├── brainpoolP256t1.key │ │ │ │ ├── brainpoolP320r1.key │ │ │ │ ├── brainpoolP320t1.key │ │ │ │ ├── brainpoolP384r1.key │ │ │ │ ├── brainpoolP384t1.key │ │ │ │ ├── brainpoolP512r1.key │ │ │ │ ├── brainpoolP512t1.key │ │ │ │ ├── dsa-aes-128-cbc.key │ │ │ │ ├── dsa.key │ │ │ │ ├── ed25519-aes-256-cbc.key │ │ │ │ ├── ed25519.key │ │ │ │ ├── ed448.key │ │ │ │ ├── key-rsa-encrypted.pem │ │ │ │ ├── prime256v1-aes-256-cbc.key │ │ │ │ ├── prime256v1.key │ │ │ │ ├── rsa-aes-256-cbc.key │ │ │ │ ├── rsa-des-ede3-cbc.key │ │ │ │ ├── rsa-pss.key │ │ │ │ ├── rsa-scrypt.key │ │ │ │ ├── rsa.key │ │ │ │ ├── secp224r1.key │ │ │ │ ├── secp256k1.key │ │ │ │ ├── secp256r1.key │ │ │ │ ├── secp384r1.key │ │ │ │ ├── secp521r1.key │ │ │ │ ├── x25519.key │ │ │ │ ├── x448-aes-256-cbc.key │ │ │ │ └── x448.key │ │ │ ├── sec1/ │ │ │ │ ├── brainpoolP256r1.key │ │ │ │ ├── brainpoolP256t1.key │ │ │ │ ├── brainpoolP320r1.key │ │ │ │ ├── brainpoolP320t1.key │ │ │ │ ├── brainpoolP384r1.key │ │ │ │ ├── brainpoolP384t1.key │ │ │ │ ├── brainpoolP512r1.key │ │ │ │ ├── brainpoolP512t1.key │ │ │ │ ├── prime256v1-aes-128-cbc.key │ │ │ │ ├── prime256v1.key │ │ │ │ ├── secp224r1.key │ │ │ │ ├── secp256k1.key │ │ │ │ ├── secp256r1.key │ │ │ │ ├── secp384r1.key │ │ │ │ └── secp521r1.key │ │ │ ├── test-cert-chain.pem │ │ │ ├── test-cert.pem │ │ │ └── test-key.pem │ │ └── testFixtures/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ ├── AbstractApplicationEnvironmentTests.java │ │ └── web/ │ │ └── servlet/ │ │ └── mock/ │ │ ├── MockFilter.java │ │ ├── MockServlet.java │ │ └── package-info.java │ ├── spring-boot-autoconfigure/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AbstractDependsOnBeanFactoryPostProcessor.java │ │ │ │ ├── AutoConfiguration.java │ │ │ │ ├── AutoConfigurationExcludeFilter.java │ │ │ │ ├── AutoConfigurationImportEvent.java │ │ │ │ ├── AutoConfigurationImportFilter.java │ │ │ │ ├── AutoConfigurationImportListener.java │ │ │ │ ├── AutoConfigurationImportSelector.java │ │ │ │ ├── AutoConfigurationMetadata.java │ │ │ │ ├── AutoConfigurationMetadataLoader.java │ │ │ │ ├── AutoConfigurationPackage.java │ │ │ │ ├── AutoConfigurationPackages.java │ │ │ │ ├── AutoConfigurationReplacements.java │ │ │ │ ├── AutoConfigurationSorter.java │ │ │ │ ├── AutoConfigurations.java │ │ │ │ ├── AutoConfigureAfter.java │ │ │ │ ├── AutoConfigureBefore.java │ │ │ │ ├── AutoConfigureOrder.java │ │ │ │ ├── EnableAutoConfiguration.java │ │ │ │ ├── ImportAutoConfiguration.java │ │ │ │ ├── ImportAutoConfigurationImportSelector.java │ │ │ │ ├── SharedMetadataReaderFactoryContextInitializer.java │ │ │ │ ├── SpringBootApplication.java │ │ │ │ ├── admin/ │ │ │ │ │ ├── SpringApplicationAdminJmxAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── aop/ │ │ │ │ │ ├── AopAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── availability/ │ │ │ │ │ ├── ApplicationAvailabilityAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── cache/ │ │ │ │ │ ├── CacheType.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── condition/ │ │ │ │ │ ├── AbstractNestedCondition.java │ │ │ │ │ ├── AllNestedConditions.java │ │ │ │ │ ├── AnyNestedCondition.java │ │ │ │ │ ├── ConditionEvaluationReport.java │ │ │ │ │ ├── ConditionEvaluationReportAutoConfigurationImportListener.java │ │ │ │ │ ├── ConditionMessage.java │ │ │ │ │ ├── ConditionOutcome.java │ │ │ │ │ ├── ConditionalOnBean.java │ │ │ │ │ ├── ConditionalOnBooleanProperties.java │ │ │ │ │ ├── ConditionalOnBooleanProperty.java │ │ │ │ │ ├── ConditionalOnCheckpointRestore.java │ │ │ │ │ ├── ConditionalOnClass.java │ │ │ │ │ ├── ConditionalOnCloudPlatform.java │ │ │ │ │ ├── ConditionalOnExpression.java │ │ │ │ │ ├── ConditionalOnJava.java │ │ │ │ │ ├── ConditionalOnJndi.java │ │ │ │ │ ├── ConditionalOnMissingBean.java │ │ │ │ │ ├── ConditionalOnMissingClass.java │ │ │ │ │ ├── ConditionalOnMissingFilterBean.java │ │ │ │ │ ├── ConditionalOnNotWarDeployment.java │ │ │ │ │ ├── ConditionalOnNotWebApplication.java │ │ │ │ │ ├── ConditionalOnProperties.java │ │ │ │ │ ├── ConditionalOnProperty.java │ │ │ │ │ ├── ConditionalOnResource.java │ │ │ │ │ ├── ConditionalOnSingleCandidate.java │ │ │ │ │ ├── ConditionalOnThreading.java │ │ │ │ │ ├── ConditionalOnWarDeployment.java │ │ │ │ │ ├── ConditionalOnWebApplication.java │ │ │ │ │ ├── FilteringSpringBootCondition.java │ │ │ │ │ ├── NoneNestedConditions.java │ │ │ │ │ ├── OnBeanCondition.java │ │ │ │ │ ├── OnClassCondition.java │ │ │ │ │ ├── OnCloudPlatformCondition.java │ │ │ │ │ ├── OnExpressionCondition.java │ │ │ │ │ ├── OnJavaCondition.java │ │ │ │ │ ├── OnJndiCondition.java │ │ │ │ │ ├── OnPropertyCondition.java │ │ │ │ │ ├── OnPropertyListCondition.java │ │ │ │ │ ├── OnResourceCondition.java │ │ │ │ │ ├── OnThreadingCondition.java │ │ │ │ │ ├── OnWarDeploymentCondition.java │ │ │ │ │ ├── OnWebApplicationCondition.java │ │ │ │ │ ├── ResourceCondition.java │ │ │ │ │ ├── SearchStrategy.java │ │ │ │ │ ├── SpringBootCondition.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── container/ │ │ │ │ │ ├── ContainerImageMetadata.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── context/ │ │ │ │ │ ├── ConfigurationPropertiesAutoConfiguration.java │ │ │ │ │ ├── LifecycleAutoConfiguration.java │ │ │ │ │ ├── LifecycleProperties.java │ │ │ │ │ ├── MessageSourceAutoConfiguration.java │ │ │ │ │ ├── MessageSourceProperties.java │ │ │ │ │ ├── PropertyPlaceholderAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── data/ │ │ │ │ │ ├── AbstractRepositoryConfigurationSourceSupport.java │ │ │ │ │ ├── ConditionalOnRepositoryType.java │ │ │ │ │ ├── OnRepositoryTypeCondition.java │ │ │ │ │ ├── RepositoryType.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── diagnostics/ │ │ │ │ │ └── analyzer/ │ │ │ │ │ ├── NoSuchBeanDefinitionFailureAnalyzer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── info/ │ │ │ │ │ ├── ProjectInfoAutoConfiguration.java │ │ │ │ │ ├── ProjectInfoProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── jmx/ │ │ │ │ │ ├── JmxAutoConfiguration.java │ │ │ │ │ ├── JmxProperties.java │ │ │ │ │ ├── ParentAwareNamingStrategy.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── logging/ │ │ │ │ │ ├── ConditionEvaluationReportLogger.java │ │ │ │ │ ├── ConditionEvaluationReportLoggingListener.java │ │ │ │ │ ├── ConditionEvaluationReportLoggingProcessor.java │ │ │ │ │ ├── ConditionEvaluationReportMessage.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── preinitialize/ │ │ │ │ │ ├── BackgroundPreinitializer.java │ │ │ │ │ ├── BackgroundPreinitializingApplicationListener.java │ │ │ │ │ ├── CharsetsBackgroundPreinitializer.java │ │ │ │ │ ├── ConversionServiceBackgroundPreinitializer.java │ │ │ │ │ ├── ZoneIdBackgroundPreinitializer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── service/ │ │ │ │ │ └── connection/ │ │ │ │ │ ├── ConnectionDetails.java │ │ │ │ │ ├── ConnectionDetailsFactories.java │ │ │ │ │ ├── ConnectionDetailsFactory.java │ │ │ │ │ ├── ConnectionDetailsFactoryNotFoundException.java │ │ │ │ │ ├── ConnectionDetailsNotFoundException.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── ssl/ │ │ │ │ │ ├── BundleContentNotWatchableException.java │ │ │ │ │ ├── BundleContentNotWatchableFailureAnalyzer.java │ │ │ │ │ ├── BundleContentProperty.java │ │ │ │ │ ├── CertificateMatcher.java │ │ │ │ │ ├── FileWatcher.java │ │ │ │ │ ├── JksSslBundleProperties.java │ │ │ │ │ ├── PemSslBundleProperties.java │ │ │ │ │ ├── PropertiesSslBundle.java │ │ │ │ │ ├── SslAutoConfiguration.java │ │ │ │ │ ├── SslBundleProperties.java │ │ │ │ │ ├── SslBundleRegistrar.java │ │ │ │ │ ├── SslProperties.java │ │ │ │ │ ├── SslPropertiesBundleRegistrar.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── task/ │ │ │ │ │ ├── DefaultTaskSchedulerConfiguration.java │ │ │ │ │ ├── ScheduledBeanLazyInitializationExcludeFilter.java │ │ │ │ │ ├── TaskExecutionAutoConfiguration.java │ │ │ │ │ ├── TaskExecutionProperties.java │ │ │ │ │ ├── TaskExecutorConfigurations.java │ │ │ │ │ ├── TaskSchedulingAutoConfiguration.java │ │ │ │ │ ├── TaskSchedulingConfigurations.java │ │ │ │ │ ├── TaskSchedulingProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── template/ │ │ │ │ │ ├── PathBasedTemplateAvailabilityProvider.java │ │ │ │ │ ├── TemplateAvailabilityProvider.java │ │ │ │ │ ├── TemplateAvailabilityProviders.java │ │ │ │ │ ├── TemplateLocation.java │ │ │ │ │ ├── TemplateRuntimeHints.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── ConditionalOnEnabledResourceChain.java │ │ │ │ ├── ErrorProperties.java │ │ │ │ ├── OnEnabledResourceChainCondition.java │ │ │ │ ├── WebProperties.java │ │ │ │ ├── WebResourcesRuntimeHints.java │ │ │ │ ├── format/ │ │ │ │ │ ├── DateTimeFormatters.java │ │ │ │ │ ├── WebConversionService.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── aot.factories │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ ├── spring-devtools.properties │ │ │ └── spring.factories │ │ ├── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AbstractDependsOnBeanFactoryPostProcessorTests.java │ │ │ │ ├── AutoConfigurationExcludeFilterTests.java │ │ │ │ ├── AutoConfigurationImportSelectorIntegrationTests.java │ │ │ │ ├── AutoConfigurationImportSelectorTests.java │ │ │ │ ├── AutoConfigurationMetadataLoaderTests.java │ │ │ │ ├── AutoConfigurationPackagesTests.java │ │ │ │ ├── AutoConfigurationReplacementsTests.java │ │ │ │ ├── AutoConfigurationSorterTests.java │ │ │ │ ├── AutoConfigurationsTests.java │ │ │ │ ├── EarlyInitFactoryBean.java │ │ │ │ ├── ImportAutoConfigurationImportSelectorTests.java │ │ │ │ ├── ImportAutoConfigurationTests.java │ │ │ │ ├── SharedMetadataReaderFactoryContextInitializerTests.java │ │ │ │ ├── SpringBootApplicationTests.java │ │ │ │ ├── TestAutoConfigurationSorter.java │ │ │ │ ├── admin/ │ │ │ │ │ └── SpringApplicationAdminJmxAutoConfigurationTests.java │ │ │ │ ├── aop/ │ │ │ │ │ ├── AopAutoConfigurationTests.java │ │ │ │ │ └── NonAspectJAopAutoConfigurationTests.java │ │ │ │ ├── availability/ │ │ │ │ │ └── ApplicationAvailabilityAutoConfigurationTests.java │ │ │ │ ├── condition/ │ │ │ │ │ ├── AbstractNestedConditionTests.java │ │ │ │ │ ├── AllNestedConditionsTests.java │ │ │ │ │ ├── AnyNestedConditionTests.java │ │ │ │ │ ├── ConditionEvaluationReportAutoConfigurationImportListenerTests.java │ │ │ │ │ ├── ConditionEvaluationReportTests.java │ │ │ │ │ ├── ConditionMessageTests.java │ │ │ │ │ ├── ConditionalOnBeanTests.java │ │ │ │ │ ├── ConditionalOnBooleanPropertyTests.java │ │ │ │ │ ├── ConditionalOnCheckpointRestoreTests.java │ │ │ │ │ ├── ConditionalOnClassTests.java │ │ │ │ │ ├── ConditionalOnCloudPlatformTests.java │ │ │ │ │ ├── ConditionalOnExpressionTests.java │ │ │ │ │ ├── ConditionalOnJavaTests.java │ │ │ │ │ ├── ConditionalOnJndiTests.java │ │ │ │ │ ├── ConditionalOnMissingBeanTests.java │ │ │ │ │ ├── ConditionalOnMissingBeanWithFilteredClasspathTests.java │ │ │ │ │ ├── ConditionalOnMissingClassTests.java │ │ │ │ │ ├── ConditionalOnMissingFilterBeanTests.java │ │ │ │ │ ├── ConditionalOnNotWarDeploymentTests.java │ │ │ │ │ ├── ConditionalOnNotWebApplicationTests.java │ │ │ │ │ ├── ConditionalOnPropertyTests.java │ │ │ │ │ ├── ConditionalOnResourceTests.java │ │ │ │ │ ├── ConditionalOnSingleCandidateTests.java │ │ │ │ │ ├── ConditionalOnThreadingTests.java │ │ │ │ │ ├── ConditionalOnWarDeploymentTests.java │ │ │ │ │ ├── ConditionalOnWebApplicationTests.java │ │ │ │ │ ├── NoneNestedConditionsTests.java │ │ │ │ │ ├── OnBeanConditionTypeDeductionFailureTests.java │ │ │ │ │ ├── OnClassConditionAutoConfigurationImportFilterTests.java │ │ │ │ │ ├── OnPropertyListConditionTests.java │ │ │ │ │ ├── ResourceConditionTests.java │ │ │ │ │ ├── SpringBootConditionTests.java │ │ │ │ │ ├── TestParameterizedContainer.java │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── UniqueShortNameAutoConfiguration.java │ │ │ │ │ │ ├── first/ │ │ │ │ │ │ │ ├── SampleAutoConfiguration.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── package-info.java │ │ │ │ │ │ └── second/ │ │ │ │ │ │ ├── SampleAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── scan/ │ │ │ │ │ ├── ScanBean.java │ │ │ │ │ ├── ScanFactoryBean.java │ │ │ │ │ ├── ScannedFactoryBeanConfiguration.java │ │ │ │ │ ├── ScannedFactoryBeanWithBeanMethodArgumentsConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── container/ │ │ │ │ │ └── ContainerImageMetadataTests.java │ │ │ │ ├── context/ │ │ │ │ │ ├── ConfigurationPropertiesAutoConfigurationTests.java │ │ │ │ │ ├── LifecycleAutoConfigurationTests.java │ │ │ │ │ ├── MessageSourceAutoConfigurationTests.java │ │ │ │ │ ├── PropertyPlaceholderAutoConfigurationTests.java │ │ │ │ │ └── filtersample/ │ │ │ │ │ ├── ExampleConfiguration.java │ │ │ │ │ ├── ExampleFilteredAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── data/ │ │ │ │ │ └── ConditionalOnRepositoryTypeTests.java │ │ │ │ ├── diagnostics/ │ │ │ │ │ └── analyzer/ │ │ │ │ │ └── NoSuchBeanDefinitionFailureAnalyzerTests.java │ │ │ │ ├── info/ │ │ │ │ │ └── ProjectInfoAutoConfigurationTests.java │ │ │ │ ├── jmx/ │ │ │ │ │ ├── JmxAutoConfigurationTests.java │ │ │ │ │ └── ParentAwareNamingStrategyTests.java │ │ │ │ ├── logging/ │ │ │ │ │ ├── ConditionEvaluationReportLoggerTests.java │ │ │ │ │ ├── ConditionEvaluationReportLoggingListenerTests.java │ │ │ │ │ └── ConditionEvaluationReportLoggingProcessorTests.java │ │ │ │ ├── packagestest/ │ │ │ │ │ ├── one/ │ │ │ │ │ │ ├── FirstConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── two/ │ │ │ │ │ ├── SecondConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── service/ │ │ │ │ │ └── connection/ │ │ │ │ │ └── ConnectionDetailsFactoriesTests.java │ │ │ │ ├── ssl/ │ │ │ │ │ ├── BundleContentNotWatchableFailureAnalyzerTests.java │ │ │ │ │ ├── BundleContentPropertyTests.java │ │ │ │ │ ├── CertificateMatcherTests.java │ │ │ │ │ ├── CertificateMatchingTest.java │ │ │ │ │ ├── CertificateMatchingTestSource.java │ │ │ │ │ ├── FileWatcherTests.java │ │ │ │ │ ├── PropertiesSslBundleTests.java │ │ │ │ │ ├── SslAutoConfigurationTests.java │ │ │ │ │ └── SslPropertiesBundleRegistrarTests.java │ │ │ │ ├── task/ │ │ │ │ │ ├── OrderedTaskDecorator.java │ │ │ │ │ ├── ScheduledBeanLazyInitializationExcludeFilterTests.java │ │ │ │ │ ├── TaskExecutionAutoConfigurationTests.java │ │ │ │ │ └── TaskSchedulingAutoConfigurationTests.java │ │ │ │ ├── template/ │ │ │ │ │ ├── TemplateAvailabilityProvidersTests.java │ │ │ │ │ └── TemplateRuntimeHintsTests.java │ │ │ │ └── web/ │ │ │ │ ├── ConditionalOnEnabledResourceChainTests.java │ │ │ │ ├── WebPropertiesResourcesBindingTests.java │ │ │ │ ├── WebPropertiesResourcesTests.java │ │ │ │ ├── WebResourcesRuntimeHintsTests.java │ │ │ │ └── format/ │ │ │ │ └── WebConversionServiceTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── condition/ │ │ │ │ │ ├── factorybean.xml │ │ │ │ │ └── foo.xml │ │ │ │ ├── info/ │ │ │ │ │ ├── build-info.properties │ │ │ │ │ ├── git-epoch.properties │ │ │ │ │ ├── git-no-data.properties │ │ │ │ │ └── git.properties │ │ │ │ └── ssl/ │ │ │ │ ├── ed25519-cert.pem │ │ │ │ ├── ed25519-key.pem │ │ │ │ ├── key1.crt │ │ │ │ ├── key1.pem │ │ │ │ ├── key2-chain.crt │ │ │ │ ├── key2.crt │ │ │ │ ├── key2.pem │ │ │ │ ├── keystore.jks │ │ │ │ ├── keystore.pkcs12 │ │ │ │ ├── rsa-cert.pem │ │ │ │ ├── rsa-key.pem │ │ │ │ └── test.jks │ │ │ └── cache/ │ │ │ └── autoconfigure/ │ │ │ └── hazelcast-specific.xml │ │ └── testFixtures/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── autoconfigure/ │ │ ├── AutoConfigurationImportedCondition.java │ │ ├── TestAutoConfigurationPackage.java │ │ ├── TestAutoConfigurationPackageRegistrar.java │ │ └── jndi/ │ │ ├── JndiPropertiesHidingClassLoader.java │ │ └── TestableInitialContextFactory.java │ ├── spring-boot-autoconfigure-processor/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── autoconfigureprocessor/ │ │ │ │ ├── AutoConfigureAnnotationProcessor.java │ │ │ │ ├── Elements.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── gradle/ │ │ │ │ └── incremental.annotation.processors │ │ │ └── services/ │ │ │ └── javax.annotation.processing.Processor │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── autoconfigureprocessor/ │ │ ├── AutoConfigureAnnotationProcessorTests.java │ │ ├── TestAutoConfiguration.java │ │ ├── TestAutoConfigurationConfiguration.java │ │ ├── TestAutoConfigurationOnlyConfiguration.java │ │ ├── TestAutoConfigureAfter.java │ │ ├── TestAutoConfigureAnnotationProcessor.java │ │ ├── TestAutoConfigureBefore.java │ │ ├── TestAutoConfigureOrder.java │ │ ├── TestClassConfiguration.java │ │ ├── TestConditionalOnBean.java │ │ ├── TestConditionalOnClass.java │ │ ├── TestConditionalOnSingleCandidate.java │ │ ├── TestConditionalOnWebApplication.java │ │ ├── TestMergedAutoConfigurationConfiguration.java │ │ ├── TestMethodConfiguration.java │ │ ├── TestOnBeanWithNameClassConfiguration.java │ │ └── TestOrderedClassConfiguration.java │ ├── spring-boot-docker-compose/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── docker/ │ │ │ │ └── compose/ │ │ │ │ └── core/ │ │ │ │ ├── DefaultDockerComposeIntegrationTests.java │ │ │ │ └── DockerCliIntegrationTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── docker/ │ │ │ └── compose/ │ │ │ └── core/ │ │ │ ├── 1.yaml │ │ │ ├── 2.yaml │ │ │ ├── 3.yaml │ │ │ ├── profiles.yaml │ │ │ └── redis-compose.yaml │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── docker/ │ │ │ │ └── compose/ │ │ │ │ ├── core/ │ │ │ │ │ ├── ConnectionPorts.java │ │ │ │ │ ├── DefaultConnectionPorts.java │ │ │ │ │ ├── DefaultDockerCompose.java │ │ │ │ │ ├── DefaultRunningService.java │ │ │ │ │ ├── DockerCli.java │ │ │ │ │ ├── DockerCliCommand.java │ │ │ │ │ ├── DockerCliComposeConfigResponse.java │ │ │ │ │ ├── DockerCliComposePsResponse.java │ │ │ │ │ ├── DockerCliComposeVersionResponse.java │ │ │ │ │ ├── DockerCliContextResponse.java │ │ │ │ │ ├── DockerCliInspectResponse.java │ │ │ │ │ ├── DockerCompose.java │ │ │ │ │ ├── DockerComposeFile.java │ │ │ │ │ ├── DockerComposeOrigin.java │ │ │ │ │ ├── DockerEnv.java │ │ │ │ │ ├── DockerException.java │ │ │ │ │ ├── DockerHost.java │ │ │ │ │ ├── DockerJson.java │ │ │ │ │ ├── DockerNotRunningException.java │ │ │ │ │ ├── DockerOutputParseException.java │ │ │ │ │ ├── DockerProcessStartException.java │ │ │ │ │ ├── ImageName.java │ │ │ │ │ ├── ImageReference.java │ │ │ │ │ ├── ProcessExitException.java │ │ │ │ │ ├── ProcessRunner.java │ │ │ │ │ ├── ProcessStartException.java │ │ │ │ │ ├── Regex.java │ │ │ │ │ ├── RunningService.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── lifecycle/ │ │ │ │ │ ├── DockerComposeLifecycleManager.java │ │ │ │ │ ├── DockerComposeListener.java │ │ │ │ │ ├── DockerComposeProperties.java │ │ │ │ │ ├── DockerComposeServicesReadyEvent.java │ │ │ │ │ ├── DockerComposeSkipCheck.java │ │ │ │ │ ├── LifecycleManagement.java │ │ │ │ │ ├── ReadinessTimeoutException.java │ │ │ │ │ ├── ServiceNotReadyException.java │ │ │ │ │ ├── ServiceReadinessChecks.java │ │ │ │ │ ├── StartCommand.java │ │ │ │ │ ├── StopCommand.java │ │ │ │ │ ├── TcpConnectServiceReadinessCheck.java │ │ │ │ │ └── package-info.java │ │ │ │ └── service/ │ │ │ │ └── connection/ │ │ │ │ ├── ConnectionNamePredicate.java │ │ │ │ ├── DockerComposeConnectionDetailsFactory.java │ │ │ │ ├── DockerComposeConnectionSource.java │ │ │ │ ├── DockerComposeServiceConnectionsApplicationListener.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring-devtools.properties │ │ │ └── spring.factories │ │ ├── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── docker/ │ │ │ │ └── compose/ │ │ │ │ ├── core/ │ │ │ │ │ ├── DefaultConnectionPortsTests.java │ │ │ │ │ ├── DefaultDockerComposeTests.java │ │ │ │ │ ├── DefaultRunningServiceTests.java │ │ │ │ │ ├── DockerCliCommandTests.java │ │ │ │ │ ├── DockerCliComposeConfigResponseTests.java │ │ │ │ │ ├── DockerCliComposePsResponseTests.java │ │ │ │ │ ├── DockerCliComposeVersionResponseTests.java │ │ │ │ │ ├── DockerCliContextResponseTests.java │ │ │ │ │ ├── DockerCliInspectResponseTests.java │ │ │ │ │ ├── DockerComposeFileTests.java │ │ │ │ │ ├── DockerComposeOriginTests.java │ │ │ │ │ ├── DockerEnvTests.java │ │ │ │ │ ├── DockerHostTests.java │ │ │ │ │ ├── DockerJsonTests.java │ │ │ │ │ ├── ImageNameTests.java │ │ │ │ │ ├── ImageReferenceTests.java │ │ │ │ │ └── ProcessRunnerTests.java │ │ │ │ ├── lifecycle/ │ │ │ │ │ ├── DockerComposeLifecycleManagerTests.java │ │ │ │ │ ├── DockerComposeListenerTests.java │ │ │ │ │ ├── DockerComposePropertiesTests.java │ │ │ │ │ ├── DockerComposeServicesReadyEventTests.java │ │ │ │ │ ├── LifecycleManagementTests.java │ │ │ │ │ ├── ReadinessTimeoutExceptionTests.java │ │ │ │ │ ├── ServiceNotReadyExceptionTests.java │ │ │ │ │ ├── ServiceReadinessChecksTests.java │ │ │ │ │ ├── StartCommandTests.java │ │ │ │ │ ├── StopCommandTests.java │ │ │ │ │ └── TcpConnectServiceReadinessCheckTests.java │ │ │ │ └── service/ │ │ │ │ └── connection/ │ │ │ │ └── ConnectionNamePredicateTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── docker/ │ │ │ └── compose/ │ │ │ └── core/ │ │ │ ├── docker-compose-config.json │ │ │ ├── docker-compose-ps.json │ │ │ ├── docker-compose-version.json │ │ │ ├── docker-context-podman.json │ │ │ ├── docker-context.json │ │ │ ├── docker-inspect-bridge-network.json │ │ │ ├── docker-inspect-host-network.json │ │ │ └── docker-inspect.json │ │ └── testFixtures/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── docker/ │ │ └── compose/ │ │ └── service/ │ │ └── connection/ │ │ └── test/ │ │ ├── DockerComposeTest.java │ │ └── DockerComposeTestExtension.java │ ├── spring-boot-properties-migrator/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── context/ │ │ │ │ └── properties/ │ │ │ │ └── migrator/ │ │ │ │ ├── PropertiesMigrationListener.java │ │ │ │ ├── PropertiesMigrationReport.java │ │ │ │ ├── PropertiesMigrationReporter.java │ │ │ │ ├── PropertyMigration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── context/ │ │ │ └── properties/ │ │ │ └── migrator/ │ │ │ ├── PropertiesMigrationListenerTests.java │ │ │ └── PropertiesMigrationReporterTests.java │ │ └── resources/ │ │ ├── config/ │ │ │ ├── config-error-invalid-replacement.properties │ │ │ ├── config-error-no-compatible-type.properties │ │ │ ├── config-error-no-replacement.properties │ │ │ ├── config-error.properties │ │ │ ├── config-relaxed.properties │ │ │ └── config-warnings.properties │ │ └── metadata/ │ │ ├── sample-metadata-invalid-name.json │ │ ├── sample-metadata-invalid-replacement.json │ │ ├── sample-metadata.json │ │ └── type-conversion-metadata.json │ ├── spring-boot-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── test/ │ │ │ │ ├── context/ │ │ │ │ │ ├── AnnotatedClassFinder.java │ │ │ │ │ ├── AnnotationsPropertySource.java │ │ │ │ │ ├── ConfigDataApplicationContextInitializer.java │ │ │ │ │ ├── FilteredClassLoader.java │ │ │ │ │ ├── ImportsContextCustomizer.java │ │ │ │ │ ├── ImportsContextCustomizerFactory.java │ │ │ │ │ ├── PropertyMapping.java │ │ │ │ │ ├── PropertyMappingContextCustomizer.java │ │ │ │ │ ├── PropertyMappingContextCustomizerFactory.java │ │ │ │ │ ├── ReactiveWebMergedContextConfiguration.java │ │ │ │ │ ├── SpringBootContextLoader.java │ │ │ │ │ ├── SpringBootTest.java │ │ │ │ │ ├── SpringBootTestAnnotation.java │ │ │ │ │ ├── SpringBootTestAotProcessor.java │ │ │ │ │ ├── SpringBootTestContextBootstrapper.java │ │ │ │ │ ├── TestComponent.java │ │ │ │ │ ├── TestConfiguration.java │ │ │ │ │ ├── assertj/ │ │ │ │ │ │ ├── ApplicationContextAssert.java │ │ │ │ │ │ ├── ApplicationContextAssertProvider.java │ │ │ │ │ │ ├── AssertProviderApplicationContextInvocationHandler.java │ │ │ │ │ │ ├── AssertableApplicationContext.java │ │ │ │ │ │ ├── AssertableReactiveWebApplicationContext.java │ │ │ │ │ │ ├── AssertableWebApplicationContext.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── filter/ │ │ │ │ │ │ ├── ExcludeFilterApplicationContextInitializer.java │ │ │ │ │ │ ├── ExcludeFilterContextCustomizer.java │ │ │ │ │ │ ├── ExcludeFilterContextCustomizerFactory.java │ │ │ │ │ │ ├── TestTypeExcludeFilter.java │ │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ │ ├── AnnotationCustomizableTypeExcludeFilter.java │ │ │ │ │ │ │ ├── FilterAnnotations.java │ │ │ │ │ │ │ ├── StandardAnnotationCustomizableTypeExcludeFilter.java │ │ │ │ │ │ │ ├── TypeExcludeFilters.java │ │ │ │ │ │ │ ├── TypeExcludeFiltersContextCustomizer.java │ │ │ │ │ │ │ ├── TypeExcludeFiltersContextCustomizerFactory.java │ │ │ │ │ │ │ ├── TypeIncludes.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── runner/ │ │ │ │ │ ├── AbstractApplicationContextRunner.java │ │ │ │ │ ├── ApplicationContextRunner.java │ │ │ │ │ ├── ContextConsumer.java │ │ │ │ │ ├── ReactiveWebApplicationContextRunner.java │ │ │ │ │ ├── WebApplicationContextRunner.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── http/ │ │ │ │ │ ├── client/ │ │ │ │ │ │ ├── DisableReactorResourceFactoryGlobalResourcesBeanPostProcessor.java │ │ │ │ │ │ ├── DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── server/ │ │ │ │ │ ├── LazyUriBuilderFactory.java │ │ │ │ │ ├── LocalTestWebServer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── json/ │ │ │ │ │ ├── AbstractJsonMarshalTester.java │ │ │ │ │ ├── BasicJsonTester.java │ │ │ │ │ ├── DuplicateJsonObjectContextCustomizerFactory.java │ │ │ │ │ ├── GsonTester.java │ │ │ │ │ ├── Jackson2Tester.java │ │ │ │ │ ├── JacksonTester.java │ │ │ │ │ ├── JsonContent.java │ │ │ │ │ ├── JsonContentAssert.java │ │ │ │ │ ├── JsonLoader.java │ │ │ │ │ ├── JsonbTester.java │ │ │ │ │ ├── ObjectContent.java │ │ │ │ │ ├── ObjectContentAssert.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── mock/ │ │ │ │ │ └── web/ │ │ │ │ │ ├── SpringBootMockServletContext.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── system/ │ │ │ │ │ ├── CapturedOutput.java │ │ │ │ │ ├── OutputCapture.java │ │ │ │ │ ├── OutputCaptureExtension.java │ │ │ │ │ ├── OutputCaptureRule.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── util/ │ │ │ │ │ ├── ApplicationContextTestUtils.java │ │ │ │ │ ├── TestPropertyValues.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── htmlunit/ │ │ │ │ │ ├── UriBuilderFactoryWebClient.java │ │ │ │ │ ├── UriBuilderFactoryWebConnectionHtmlUnitDriver.java │ │ │ │ │ └── package-info.java │ │ │ │ └── server/ │ │ │ │ ├── LocalManagementPort.java │ │ │ │ ├── LocalServerPort.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── test/ │ │ │ ├── context/ │ │ │ │ ├── AnnotatedClassFinderTests.java │ │ │ │ ├── AnnotationsPropertySourceTests.java │ │ │ │ ├── ConfigDataApplicationContextInitializerTests.java │ │ │ │ ├── ConfigDataApplicationContextInitializerWithLegacySwitchTests.java │ │ │ │ ├── ExampleMapping.java │ │ │ │ ├── FilteredClassLoaderTests.java │ │ │ │ ├── ImportsContextCustomizerFactoryIntegrationTests.java │ │ │ │ ├── ImportsContextCustomizerFactoryTests.java │ │ │ │ ├── ImportsContextCustomizerTests.java │ │ │ │ ├── PropertyMappingContextCustomizerFactoryTests.java │ │ │ │ ├── PropertyMappingTests.java │ │ │ │ ├── SpringBootContextLoaderAotTests.java │ │ │ │ ├── SpringBootContextLoaderMockMvcTests.java │ │ │ │ ├── SpringBootContextLoaderTests.java │ │ │ │ ├── SpringBootTestActiveProfileTests.java │ │ │ │ ├── SpringBootTestArgsTests.java │ │ │ │ ├── SpringBootTestContextHierarchyTests.java │ │ │ │ ├── SpringBootTestCustomConfigNameTests.java │ │ │ │ ├── SpringBootTestCustomPortTests.java │ │ │ │ ├── SpringBootTestDefaultConfigurationTests.java │ │ │ │ ├── SpringBootTestGroovyConfigurationTests.java │ │ │ │ ├── SpringBootTestGroovyConventionConfigurationTests.java │ │ │ │ ├── SpringBootTestJmxTests.java │ │ │ │ ├── SpringBootTestMixedConfigurationTests.java │ │ │ │ ├── SpringBootTestUseMainMethodWithPropertiesTests.java │ │ │ │ ├── SpringBootTestWebEnvironmentMockTests.java │ │ │ │ ├── SpringBootTestWebEnvironmentMockWithWebAppConfigurationTests.java │ │ │ │ ├── SpringBootTestWithActiveProfilesAndEnvironmentPropertyTests.java │ │ │ │ ├── SpringBootTestWithActiveProfilesAndSystemEnvironmentPropertyTests.java │ │ │ │ ├── SpringBootTestWithClassesIntegrationTests.java │ │ │ │ ├── SpringBootTestWithContextConfigurationIntegrationTests.java │ │ │ │ ├── SpringBootTestWithCustomEnvironmentTests.java │ │ │ │ ├── SpringBootTestWithTestPropertySourceTests.java │ │ │ │ ├── SpringBootTestXmlConventionConfigurationTests.java │ │ │ │ ├── TestConfigurationTests.java │ │ │ │ ├── assertj/ │ │ │ │ │ ├── AdditionalContextInterface.java │ │ │ │ │ ├── ApplicationContextAssertProviderTests.java │ │ │ │ │ ├── ApplicationContextAssertTests.java │ │ │ │ │ ├── AssertableApplicationContextTests.java │ │ │ │ │ ├── AssertableReactiveWebApplicationContextTests.java │ │ │ │ │ └── AssertableWebApplicationContextTests.java │ │ │ │ ├── bootstrap/ │ │ │ │ │ ├── SpringBootTestContextBootstrapperExampleConfig.java │ │ │ │ │ ├── SpringBootTestContextBootstrapperIntegrationTests.java │ │ │ │ │ ├── SpringBootTestContextBootstrapperTests.java │ │ │ │ │ ├── SpringBootTestContextBootstrapperWithContextConfigurationTests.java │ │ │ │ │ ├── SpringBootTestContextBootstrapperWithInitializersTests.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── example/ │ │ │ │ │ ├── ExampleConfig.java │ │ │ │ │ ├── duplicate/ │ │ │ │ │ │ ├── first/ │ │ │ │ │ │ │ ├── EmptyConfig.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── second/ │ │ │ │ │ │ ├── EmptyConfig.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── scan/ │ │ │ │ │ ├── Example.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── sub/ │ │ │ │ │ ├── SubExampleConfig.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── filter/ │ │ │ │ │ ├── AbstractJupiterTestWithConfigAndExtendWith.java │ │ │ │ │ ├── AbstractJupiterTestWithConfigAndTestable.java │ │ │ │ │ ├── AbstractTestNgTestWithConfig.java │ │ │ │ │ ├── AbstractTestWithConfigAndRunWith.java │ │ │ │ │ ├── ExcludeFilterApplicationContextInitializerTests.java │ │ │ │ │ ├── JupiterRepeatedTestExample.java │ │ │ │ │ ├── JupiterTestExample.java │ │ │ │ │ ├── JupiterTestFactoryExample.java │ │ │ │ │ ├── SampleConfig.java │ │ │ │ │ ├── SampleTestConfig.java │ │ │ │ │ ├── TestTypeExcludeFilterTests.java │ │ │ │ │ └── annotation/ │ │ │ │ │ ├── FilterAnnotationsTests.java │ │ │ │ │ ├── TypeExcludeFiltersContextCustomizerFactoryTests.java │ │ │ │ │ └── TypeIncludesTests.java │ │ │ │ ├── nestedtests/ │ │ │ │ │ ├── InheritedNestedTestConfigurationTests.java │ │ │ │ │ └── package-info.java │ │ │ │ └── runner/ │ │ │ │ ├── AbstractApplicationContextRunnerTests.java │ │ │ │ ├── AdditionalContextInterface.java │ │ │ │ ├── ApplicationContextRunnerTests.java │ │ │ │ ├── ContextConsumerTests.java │ │ │ │ ├── ReactiveWebApplicationContextRunnerTests.java │ │ │ │ └── WebApplicationContextRunnerTests.java │ │ │ ├── http/ │ │ │ │ ├── client/ │ │ │ │ │ └── DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactoryTests.java │ │ │ │ └── server/ │ │ │ │ └── LocalTestWebServerTests.java │ │ │ ├── json/ │ │ │ │ ├── AbstractJsonMarshalTesterTests.java │ │ │ │ ├── BasicJsonTesterTests.java │ │ │ │ ├── DuplicateJsonObjectContextCustomizerFactoryTests.java │ │ │ │ ├── ExampleObject.java │ │ │ │ ├── ExampleObjectWithView.java │ │ │ │ ├── GsonTesterIntegrationTests.java │ │ │ │ ├── GsonTesterTests.java │ │ │ │ ├── Jackson2TesterTests.java │ │ │ │ ├── JacksonTesterIntegrationTests.java │ │ │ │ ├── JacksonTesterTests.java │ │ │ │ ├── JsonContentAssertTests.java │ │ │ │ ├── JsonContentTests.java │ │ │ │ ├── JsonbTesterTests.java │ │ │ │ ├── ObjectContentAssertTests.java │ │ │ │ └── ObjectContentTests.java │ │ │ ├── mock/ │ │ │ │ └── web/ │ │ │ │ └── SpringBootMockServletContextTests.java │ │ │ ├── system/ │ │ │ │ ├── OutputCaptureRuleTests.java │ │ │ │ ├── OutputCaptureTests.java │ │ │ │ └── OutputExtensionExtendWithTests.java │ │ │ ├── util/ │ │ │ │ ├── ApplicationContextTestUtilsTests.java │ │ │ │ └── TestPropertyValuesTests.java │ │ │ └── web/ │ │ │ ├── htmlunit/ │ │ │ │ ├── UriBuilderFactoryWebClientTests.java │ │ │ │ └── UriBuilderFactoryWebConnectionHtmlUnitDriverTests.java │ │ │ └── server/ │ │ │ ├── LocalManagementPortTests.java │ │ │ └── LocalServerPortTests.java │ │ ├── kotlin/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── test/ │ │ │ └── context/ │ │ │ ├── KotlinApplicationWithMainThrowingException.kt │ │ │ └── SpringBootContextLoaderKotlinTests.kt │ │ ├── resources/ │ │ │ ├── META-INF/ │ │ │ │ ├── resources/ │ │ │ │ │ └── inmetainfresources │ │ │ │ └── spring.factories │ │ │ ├── application.properties │ │ │ ├── custom-config-name.properties │ │ │ ├── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── test/ │ │ │ │ ├── SpringApplicationConfigurationGroovyConventionConfigurationTestsContext.groovy │ │ │ │ ├── SpringApplicationConfigurationXmlConventionConfigurationTests-context.xml │ │ │ │ ├── context/ │ │ │ │ │ ├── FilteredClassLoaderTestsResource.txt │ │ │ │ │ ├── SpringBootTestGroovyConventionConfigurationTestsContext.groovy │ │ │ │ │ └── SpringBootTestXmlConventionConfigurationTests-context.xml │ │ │ │ └── json/ │ │ │ │ ├── different.json │ │ │ │ ├── example.json │ │ │ │ ├── lenient-same.json │ │ │ │ ├── nulls.json │ │ │ │ ├── simpsons.json │ │ │ │ ├── source.json │ │ │ │ └── types.json │ │ │ ├── public/ │ │ │ │ └── inpublic │ │ │ ├── resources/ │ │ │ │ └── inresources │ │ │ ├── static/ │ │ │ │ └── instatic │ │ │ ├── test-property-source-annotation.properties │ │ │ └── test.groovy │ │ └── webapp/ │ │ └── inwebapp │ ├── spring-boot-test-autoconfigure/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── OnFailureConditionReportContextCustomizerFactory.java │ │ │ │ ├── OverrideAutoConfiguration.java │ │ │ │ ├── OverrideAutoConfigurationContextCustomizerFactory.java │ │ │ │ ├── TestSliceTestContextBootstrapper.java │ │ │ │ ├── jdbc/ │ │ │ │ │ ├── AutoConfigureDataSourceInitialization.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── json/ │ │ │ │ │ ├── AutoConfigureJson.java │ │ │ │ │ ├── AutoConfigureJsonTesters.java │ │ │ │ │ ├── ConditionalOnJsonTesters.java │ │ │ │ │ ├── JsonMarshalTesterRuntimeHints.java │ │ │ │ │ ├── JsonTest.java │ │ │ │ │ ├── JsonTestContextBootstrapper.java │ │ │ │ │ ├── JsonTesterFactoryBean.java │ │ │ │ │ ├── JsonTestersAutoConfiguration.java │ │ │ │ │ ├── JsonTypeExcludeFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports │ │ │ ├── spring-configuration-metadata.json │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ ├── json/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ └── app/ │ │ │ ├── ExampleBasicObject.java │ │ │ ├── ExampleJsonApplication.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── autoconfigure/ │ │ ├── ExampleSpringBootApplication.java │ │ ├── ExampleTest.java │ │ ├── ExampleTestConfig.java │ │ ├── ExampleTestContextBootstrapper.java │ │ ├── ImportsContextCustomizerFactoryWithAutoConfigurationTests.java │ │ ├── OnFailureConditionReportContextCustomizerFactoryTests.java │ │ ├── OverrideAutoConfigurationContextCustomizerFactoryTests.java │ │ ├── json/ │ │ │ ├── ExampleJsonApplication.java │ │ │ ├── JsonTestIntegrationTests.java │ │ │ ├── JsonTestPropertiesIntegrationTests.java │ │ │ ├── JsonTestWithAutoConfigureJsonTestersTests.java │ │ │ ├── JsonTestersAutoConfigurationTests.java │ │ │ └── SpringBootTestWithAutoConfigureJsonTestersTests.java │ │ └── override/ │ │ ├── OverrideAutoConfigurationEnabledFalseIntegrationTests.java │ │ ├── OverrideAutoConfigurationEnabledTrueIntegrationTests.java │ │ ├── OverrideAutoConfigurationSpringBootApplication.java │ │ └── package-info.java │ └── spring-boot-testcontainers/ │ ├── build.gradle │ └── src/ │ ├── dockerTest/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── testcontainers/ │ │ │ ├── ImportTestcontainersTests.java │ │ │ ├── LoadTimeWeaverAwareConsumerContainers.java │ │ │ ├── LoadTimeWeaverAwareConsumerImportTestcontainersTests.java │ │ │ ├── lifecycle/ │ │ │ │ ├── ResetStartablesExtension.java │ │ │ │ ├── TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests.java │ │ │ │ ├── TestcontainersLifecycleOrderIntegrationTests.java │ │ │ │ ├── TestcontainersLifecycleOrderWithScopeIntegrationTests.java │ │ │ │ ├── TestcontainersParallelStartupIntegrationTests.java │ │ │ │ └── TestcontainersParallelStartupWithImportTestcontainersIntegrationTests.java │ │ │ ├── properties/ │ │ │ │ ├── TestcontainersPropertySourceAutoConfigurationTests.java │ │ │ │ └── TestcontainersPropertySourceAutoConfigurationWithSpringBootTestIntegrationTest.java │ │ │ └── service/ │ │ │ └── connection/ │ │ │ ├── ServiceConnectionAutoConfigurationTests.java │ │ │ └── ServiceConnectionStartsConnectionOnceIntegrationTests.java │ │ └── resources/ │ │ └── logback-test.xml │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── testcontainers/ │ │ │ ├── beans/ │ │ │ │ ├── TestcontainerBeanDefinition.java │ │ │ │ └── package-info.java │ │ │ ├── context/ │ │ │ │ ├── ContainerFieldsImporter.java │ │ │ │ ├── DynamicPropertySourceMethodsImporter.java │ │ │ │ ├── ImportTestcontainers.java │ │ │ │ ├── ImportTestcontainersRegistrar.java │ │ │ │ ├── TestcontainerFieldBeanDefinition.java │ │ │ │ └── package-info.java │ │ │ ├── lifecycle/ │ │ │ │ ├── DockerEnvironmentNotFoundFailureAnalyzer.java │ │ │ │ ├── TestcontainersLifecycleApplicationContextInitializer.java │ │ │ │ ├── TestcontainersLifecycleBeanFactoryPostProcessor.java │ │ │ │ ├── TestcontainersLifecycleBeanPostProcessor.java │ │ │ │ ├── TestcontainersStartup.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── properties/ │ │ │ │ ├── TestcontainersPropertySourceAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── service/ │ │ │ └── connection/ │ │ │ ├── BeanOrigin.java │ │ │ ├── ConnectionDetailsRegistrar.java │ │ │ ├── ContainerConnectionDetailsFactory.java │ │ │ ├── ContainerConnectionSource.java │ │ │ ├── FieldOrigin.java │ │ │ ├── JksKeyStore.java │ │ │ ├── JksTrustStore.java │ │ │ ├── PemKeyStore.java │ │ │ ├── PemTrustStore.java │ │ │ ├── ServiceConnection.java │ │ │ ├── ServiceConnectionAutoConfiguration.java │ │ │ ├── ServiceConnectionAutoConfigurationRegistrar.java │ │ │ ├── ServiceConnectionContextCustomizer.java │ │ │ ├── ServiceConnectionContextCustomizerFactory.java │ │ │ ├── Ssl.java │ │ │ ├── SslBundleSource.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── additional-spring-configuration-metadata.json │ │ ├── spring/ │ │ │ ├── aot.factories │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── spring.factories │ ├── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── testcontainers/ │ │ │ ├── lifecycle/ │ │ │ │ ├── DockerEnvironmentNotFoundFailureAnalyzerTests.java │ │ │ │ ├── TestcontainersLifecycleApplicationContextInitializerTests.java │ │ │ │ └── TestcontainersStartupTests.java │ │ │ └── service/ │ │ │ └── connection/ │ │ │ ├── ConnectionDetailsRegistrarTests.java │ │ │ ├── ContainerConnectionDetailsFactoryTests.java │ │ │ ├── ContainerConnectionSourceTests.java │ │ │ ├── DatabaseConnectionDetails.java │ │ │ ├── DatabaseContainerDatabaseConnectionDetails.java │ │ │ ├── FieldOriginTests.java │ │ │ ├── ServiceConnectionContextCustomizerFactoryTests.java │ │ │ ├── ServiceConnectionContextCustomizerTests.java │ │ │ └── TestDatabaseConnectionDetails.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── spring.factories │ │ ├── logback-test.xml │ │ └── spring.properties │ └── testFixtures/ │ └── java/ │ └── org/ │ └── springframework/ │ └── boot/ │ └── testcontainers/ │ └── service/ │ └── connection/ │ ├── ContainerConnectionDetailsFactoryHints.java │ └── TestContainerConnectionSource.java ├── documentation/ │ ├── spring-boot-actuator-docs/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── docs/ │ │ │ └── antora/ │ │ │ ├── antora.yml │ │ │ ├── local-nav.adoc │ │ │ └── modules/ │ │ │ └── api/ │ │ │ ├── pages/ │ │ │ │ └── rest/ │ │ │ │ └── actuator/ │ │ │ │ ├── auditevents.adoc │ │ │ │ ├── beans.adoc │ │ │ │ ├── caches.adoc │ │ │ │ ├── conditions.adoc │ │ │ │ ├── configprops.adoc │ │ │ │ ├── env.adoc │ │ │ │ ├── flyway.adoc │ │ │ │ ├── health.adoc │ │ │ │ ├── heapdump.adoc │ │ │ │ ├── httpexchanges.adoc │ │ │ │ ├── index.adoc │ │ │ │ ├── info.adoc │ │ │ │ ├── integrationgraph.adoc │ │ │ │ ├── liquibase.adoc │ │ │ │ ├── logfile.adoc │ │ │ │ ├── loggers.adoc │ │ │ │ ├── mappings.adoc │ │ │ │ ├── metrics.adoc │ │ │ │ ├── prometheus.adoc │ │ │ │ ├── quartz.adoc │ │ │ │ ├── sbom.adoc │ │ │ │ ├── scheduledtasks.adoc │ │ │ │ ├── sessions.adoc │ │ │ │ ├── shutdown.adoc │ │ │ │ ├── startup.adoc │ │ │ │ └── threaddump.adoc │ │ │ └── partials/ │ │ │ └── nav-actuator-rest-api.adoc │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── actuate/ │ │ │ └── docs/ │ │ │ ├── AbstractEndpointDocumentationTests.java │ │ │ ├── MockMvcEndpointDocumentationTests.java │ │ │ ├── audit/ │ │ │ │ └── AuditEventsEndpointDocumentationTests.java │ │ │ ├── beans/ │ │ │ │ └── BeansEndpointDocumentationTests.java │ │ │ ├── cache/ │ │ │ │ └── CachesEndpointDocumentationTests.java │ │ │ ├── condition/ │ │ │ │ └── ConditionsReportEndpointDocumentationTests.java │ │ │ ├── context/ │ │ │ │ ├── ShutdownEndpointDocumentationTests.java │ │ │ │ └── properties/ │ │ │ │ └── ConfigurationPropertiesReportEndpointDocumentationTests.java │ │ │ ├── env/ │ │ │ │ └── EnvironmentEndpointDocumentationTests.java │ │ │ ├── flyway/ │ │ │ │ └── FlywayEndpointDocumentationTests.java │ │ │ ├── health/ │ │ │ │ └── HealthEndpointDocumentationTests.java │ │ │ ├── info/ │ │ │ │ └── InfoEndpointDocumentationTests.java │ │ │ ├── integration/ │ │ │ │ └── IntegrationGraphEndpointDocumentationTests.java │ │ │ ├── liquibase/ │ │ │ │ └── LiquibaseEndpointDocumentationTests.java │ │ │ ├── logging/ │ │ │ │ ├── LogFileWebEndpointDocumentationTests.java │ │ │ │ └── LoggersEndpointDocumentationTests.java │ │ │ ├── management/ │ │ │ │ ├── HeapDumpWebEndpointDocumentationTests.java │ │ │ │ └── ThreadDumpEndpointDocumentationTests.java │ │ │ ├── metrics/ │ │ │ │ ├── MetricsEndpointDocumentationTests.java │ │ │ │ └── export/ │ │ │ │ └── prometheus/ │ │ │ │ └── PrometheusScrapeEndpointDocumentationTests.java │ │ │ ├── quartz/ │ │ │ │ └── QuartzEndpointDocumentationTests.java │ │ │ ├── sbom/ │ │ │ │ └── SbomEndpointDocumentationTests.java │ │ │ ├── scheduling/ │ │ │ │ └── ScheduledTasksEndpointDocumentationTests.java │ │ │ ├── session/ │ │ │ │ └── SessionsEndpointDocumentationTests.java │ │ │ ├── startup/ │ │ │ │ └── StartupEndpointDocumentationTests.java │ │ │ └── web/ │ │ │ ├── exchanges/ │ │ │ │ └── HttpExchangesEndpointDocumentationTests.java │ │ │ └── mappings/ │ │ │ ├── MappingsEndpointReactiveDocumentationTests.java │ │ │ └── MappingsEndpointServletDocumentationTests.java │ │ └── resources/ │ │ ├── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── actuate/ │ │ │ └── docs/ │ │ │ ├── env/ │ │ │ │ └── application.properties │ │ │ ├── flyway/ │ │ │ │ └── V1__init.sql │ │ │ ├── liquibase/ │ │ │ │ └── db.changelog-master.yaml │ │ │ ├── logging/ │ │ │ │ └── sample.log │ │ │ └── sbom/ │ │ │ └── cyclonedx.json │ │ └── test.p12 │ └── spring-boot-docs/ │ ├── build.gradle │ └── src/ │ ├── docs/ │ │ ├── antora/ │ │ │ ├── antora.yml │ │ │ ├── modules/ │ │ │ │ ├── ROOT/ │ │ │ │ │ ├── pages/ │ │ │ │ │ │ ├── community.adoc │ │ │ │ │ │ ├── documentation.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ ├── installing.adoc │ │ │ │ │ │ ├── redirect.adoc │ │ │ │ │ │ ├── system-requirements.adoc │ │ │ │ │ │ └── upgrading.adoc │ │ │ │ │ └── partials/ │ │ │ │ │ └── nav-root.adoc │ │ │ │ ├── api/ │ │ │ │ │ └── partials/ │ │ │ │ │ ├── nav-java-api.adoc │ │ │ │ │ ├── nav-kotlin-api.adoc │ │ │ │ │ └── nav-rest-api.adoc │ │ │ │ ├── appendix/ │ │ │ │ │ ├── pages/ │ │ │ │ │ │ ├── application-properties/ │ │ │ │ │ │ │ └── index.adoc │ │ │ │ │ │ ├── auto-configuration-classes/ │ │ │ │ │ │ │ └── index.adoc │ │ │ │ │ │ ├── dependency-versions/ │ │ │ │ │ │ │ ├── coordinates.adoc │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ └── properties.adoc │ │ │ │ │ │ ├── deprecated-application-properties/ │ │ │ │ │ │ │ └── index.adoc │ │ │ │ │ │ └── test-auto-configuration/ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ └── slices.adoc │ │ │ │ │ └── partials/ │ │ │ │ │ └── nav-appendix.adoc │ │ │ │ ├── build-tool-plugin/ │ │ │ │ │ ├── pages/ │ │ │ │ │ │ ├── antlib.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ └── other-build-systems.adoc │ │ │ │ │ └── partials/ │ │ │ │ │ └── nav-build-tool-plugin.adoc │ │ │ │ ├── cli/ │ │ │ │ │ ├── pages/ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ ├── installation.adoc │ │ │ │ │ │ └── using-the-cli.adoc │ │ │ │ │ └── partials/ │ │ │ │ │ └── nav-cli.adoc │ │ │ │ ├── how-to/ │ │ │ │ │ ├── pages/ │ │ │ │ │ │ ├── actuator.adoc │ │ │ │ │ │ ├── aot-cache.adoc │ │ │ │ │ │ ├── aot.adoc │ │ │ │ │ │ ├── application.adoc │ │ │ │ │ │ ├── batch.adoc │ │ │ │ │ │ ├── build.adoc │ │ │ │ │ │ ├── data-access.adoc │ │ │ │ │ │ ├── data-initialization.adoc │ │ │ │ │ │ ├── deployment/ │ │ │ │ │ │ │ ├── cloud.adoc │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ ├── installing.adoc │ │ │ │ │ │ │ └── traditional-deployment.adoc │ │ │ │ │ │ ├── docker-compose.adoc │ │ │ │ │ │ ├── hotswapping.adoc │ │ │ │ │ │ ├── http-clients.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ ├── jersey.adoc │ │ │ │ │ │ ├── logging.adoc │ │ │ │ │ │ ├── messaging.adoc │ │ │ │ │ │ ├── native-image/ │ │ │ │ │ │ │ ├── developing-your-first-application.adoc │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ └── testing-native-applications.adoc │ │ │ │ │ │ ├── nosql.adoc │ │ │ │ │ │ ├── properties-and-configuration.adoc │ │ │ │ │ │ ├── security.adoc │ │ │ │ │ │ ├── spring-mvc.adoc │ │ │ │ │ │ ├── testing.adoc │ │ │ │ │ │ └── webserver.adoc │ │ │ │ │ └── partials/ │ │ │ │ │ └── nav-how-to.adoc │ │ │ │ ├── reference/ │ │ │ │ │ ├── pages/ │ │ │ │ │ │ ├── actuator/ │ │ │ │ │ │ │ ├── auditing.adoc │ │ │ │ │ │ │ ├── cloud-foundry.adoc │ │ │ │ │ │ │ ├── enabling.adoc │ │ │ │ │ │ │ ├── endpoints.adoc │ │ │ │ │ │ │ ├── http-exchanges.adoc │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ ├── jmx.adoc │ │ │ │ │ │ │ ├── loggers.adoc │ │ │ │ │ │ │ ├── metrics.adoc │ │ │ │ │ │ │ ├── monitoring.adoc │ │ │ │ │ │ │ ├── observability.adoc │ │ │ │ │ │ │ ├── process-monitoring.adoc │ │ │ │ │ │ │ └── tracing.adoc │ │ │ │ │ │ ├── data/ │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ ├── nosql.adoc │ │ │ │ │ │ │ └── sql.adoc │ │ │ │ │ │ ├── features/ │ │ │ │ │ │ │ ├── aop.adoc │ │ │ │ │ │ │ ├── dev-services.adoc │ │ │ │ │ │ │ ├── developing-auto-configuration.adoc │ │ │ │ │ │ │ ├── external-config.adoc │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ ├── internationalization.adoc │ │ │ │ │ │ │ ├── json.adoc │ │ │ │ │ │ │ ├── kotlin.adoc │ │ │ │ │ │ │ ├── logging.adoc │ │ │ │ │ │ │ ├── profiles.adoc │ │ │ │ │ │ │ ├── spring-application.adoc │ │ │ │ │ │ │ ├── ssl.adoc │ │ │ │ │ │ │ └── task-execution-and-scheduling.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ ├── io/ │ │ │ │ │ │ │ ├── caching.adoc │ │ │ │ │ │ │ ├── email.adoc │ │ │ │ │ │ │ ├── hazelcast.adoc │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ ├── jta.adoc │ │ │ │ │ │ │ ├── quartz.adoc │ │ │ │ │ │ │ ├── rest-client.adoc │ │ │ │ │ │ │ ├── spring-batch.adoc │ │ │ │ │ │ │ ├── validation.adoc │ │ │ │ │ │ │ └── webservices.adoc │ │ │ │ │ │ ├── messaging/ │ │ │ │ │ │ │ ├── amqp.adoc │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ ├── jms.adoc │ │ │ │ │ │ │ ├── kafka.adoc │ │ │ │ │ │ │ ├── pulsar.adoc │ │ │ │ │ │ │ ├── rsocket.adoc │ │ │ │ │ │ │ ├── spring-integration.adoc │ │ │ │ │ │ │ └── websockets.adoc │ │ │ │ │ │ ├── packaging/ │ │ │ │ │ │ │ ├── aot-cache.adoc │ │ │ │ │ │ │ ├── aot.adoc │ │ │ │ │ │ │ ├── checkpoint-restore.adoc │ │ │ │ │ │ │ ├── container-images/ │ │ │ │ │ │ │ │ ├── cloud-native-buildpacks.adoc │ │ │ │ │ │ │ │ ├── dockerfiles.adoc │ │ │ │ │ │ │ │ ├── efficient-images.adoc │ │ │ │ │ │ │ │ └── index.adoc │ │ │ │ │ │ │ ├── efficient.adoc │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ └── native-image/ │ │ │ │ │ │ │ ├── advanced-topics.adoc │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ └── introducing-graalvm-native-images.adoc │ │ │ │ │ │ ├── testing/ │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ ├── spring-applications.adoc │ │ │ │ │ │ │ ├── spring-boot-applications.adoc │ │ │ │ │ │ │ ├── test-modules.adoc │ │ │ │ │ │ │ ├── test-scope-dependencies.adoc │ │ │ │ │ │ │ ├── test-utilities.adoc │ │ │ │ │ │ │ └── testcontainers.adoc │ │ │ │ │ │ ├── using/ │ │ │ │ │ │ │ ├── auto-configuration.adoc │ │ │ │ │ │ │ ├── build-systems.adoc │ │ │ │ │ │ │ ├── configuration-classes.adoc │ │ │ │ │ │ │ ├── devtools.adoc │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ ├── packaging-for-production.adoc │ │ │ │ │ │ │ ├── running-your-application.adoc │ │ │ │ │ │ │ ├── spring-beans-and-dependency-injection.adoc │ │ │ │ │ │ │ ├── structuring-your-code.adoc │ │ │ │ │ │ │ └── using-the-springbootapplication-annotation.adoc │ │ │ │ │ │ └── web/ │ │ │ │ │ │ ├── graceful-shutdown.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ ├── reactive.adoc │ │ │ │ │ │ ├── servlet.adoc │ │ │ │ │ │ ├── spring-graphql.adoc │ │ │ │ │ │ ├── spring-hateoas.adoc │ │ │ │ │ │ ├── spring-security.adoc │ │ │ │ │ │ └── spring-session.adoc │ │ │ │ │ └── partials/ │ │ │ │ │ ├── dockerfile │ │ │ │ │ └── nav-reference.adoc │ │ │ │ ├── specification/ │ │ │ │ │ ├── pages/ │ │ │ │ │ │ ├── configuration-metadata/ │ │ │ │ │ │ │ ├── annotation-processor.adoc │ │ │ │ │ │ │ ├── format.adoc │ │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ │ └── manual-hints.adoc │ │ │ │ │ │ └── executable-jar/ │ │ │ │ │ │ ├── alternatives.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ ├── jarfile-class.adoc │ │ │ │ │ │ ├── launching.adoc │ │ │ │ │ │ ├── nested-jars.adoc │ │ │ │ │ │ ├── property-launcher.adoc │ │ │ │ │ │ └── restrictions.adoc │ │ │ │ │ └── partials/ │ │ │ │ │ └── nav-specification.adoc │ │ │ │ └── tutorial/ │ │ │ │ ├── pages/ │ │ │ │ │ ├── first-application/ │ │ │ │ │ │ └── index.adoc │ │ │ │ │ └── index.adoc │ │ │ │ └── partials/ │ │ │ │ └── nav-tutorial.adoc │ │ │ └── nav.adoc │ │ └── dokka/ │ │ └── dokka-overview.md │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── docs/ │ │ │ ├── actuator/ │ │ │ │ ├── cloudfoundry/ │ │ │ │ │ └── customcontextpath/ │ │ │ │ │ ├── MyCloudFoundryConfiguration.java │ │ │ │ │ └── MyReactiveCloudFoundryConfiguration.java │ │ │ │ ├── endpoints/ │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── reactivehealthindicators/ │ │ │ │ │ │ │ └── MyReactiveHealthIndicator.java │ │ │ │ │ │ └── writingcustomhealthindicators/ │ │ │ │ │ │ └── MyHealthIndicator.java │ │ │ │ │ ├── implementingcustom/ │ │ │ │ │ │ ├── CustomData.java │ │ │ │ │ │ └── MyEndpoint.java │ │ │ │ │ ├── info/ │ │ │ │ │ │ └── writingcustominfocontributors/ │ │ │ │ │ │ └── MyInfoContributor.java │ │ │ │ │ └── security/ │ │ │ │ │ ├── exposeall/ │ │ │ │ │ │ └── MySecurityConfiguration.java │ │ │ │ │ └── typical/ │ │ │ │ │ └── MySecurityConfiguration.java │ │ │ │ ├── loggers/ │ │ │ │ │ └── opentelemetry/ │ │ │ │ │ └── OpenTelemetryAppenderInitializer.java │ │ │ │ ├── metrics/ │ │ │ │ │ ├── customizing/ │ │ │ │ │ │ └── MyMetricsFilterConfiguration.java │ │ │ │ │ ├── export/ │ │ │ │ │ │ ├── graphite/ │ │ │ │ │ │ │ └── MyGraphiteConfiguration.java │ │ │ │ │ │ └── jmx/ │ │ │ │ │ │ └── MyJmxConfiguration.java │ │ │ │ │ ├── gettingstarted/ │ │ │ │ │ │ ├── commontags/ │ │ │ │ │ │ │ └── MyMeterRegistryConfiguration.java │ │ │ │ │ │ └── specifictype/ │ │ │ │ │ │ └── MyMeterRegistryConfiguration.java │ │ │ │ │ ├── registeringcustom/ │ │ │ │ │ │ ├── Dictionary.java │ │ │ │ │ │ ├── MyBean.java │ │ │ │ │ │ ├── MyMeterBinderConfiguration.java │ │ │ │ │ │ └── Queue.java │ │ │ │ │ └── supported/ │ │ │ │ │ └── mongodb/ │ │ │ │ │ ├── command/ │ │ │ │ │ │ ├── CustomCommandTagsProvider.java │ │ │ │ │ │ └── MyCommandTagsProviderConfiguration.java │ │ │ │ │ └── connectionpool/ │ │ │ │ │ ├── CustomConnectionPoolTagsProvider.java │ │ │ │ │ └── MyConnectionPoolTagsProviderConfiguration.java │ │ │ │ ├── micrometertracing/ │ │ │ │ │ ├── baggage/ │ │ │ │ │ │ └── CreatingBaggage.java │ │ │ │ │ ├── creatingspans/ │ │ │ │ │ │ └── CustomObservation.java │ │ │ │ │ └── gettingstarted/ │ │ │ │ │ └── MyApplication.java │ │ │ │ └── observability/ │ │ │ │ ├── MyCustomObservation.java │ │ │ │ ├── contextpropagation/ │ │ │ │ │ └── ContextPropagationConfiguration.java │ │ │ │ ├── opentelemetry/ │ │ │ │ │ ├── environmentvariables/ │ │ │ │ │ │ └── unsupported/ │ │ │ │ │ │ └── AutoConfiguredOpenTelemetrySdkConfiguration.java │ │ │ │ │ └── metrics/ │ │ │ │ │ └── apiandsdk/ │ │ │ │ │ └── OpenTelemetryMetricsConfiguration.java │ │ │ │ └── preventingobservations/ │ │ │ │ └── MyObservationPredicate.java │ │ │ ├── appendix/ │ │ │ │ └── configurationmetadata/ │ │ │ │ ├── annotationprocessor/ │ │ │ │ │ └── automaticmetadatageneration/ │ │ │ │ │ ├── MyMessagingProperties.java │ │ │ │ │ ├── MyServerProperties.java │ │ │ │ │ ├── nestedproperties/ │ │ │ │ │ │ └── MyServerProperties.java │ │ │ │ │ └── source/ │ │ │ │ │ └── Host.java │ │ │ │ ├── format/ │ │ │ │ │ └── property/ │ │ │ │ │ └── MyProperties.java │ │ │ │ └── manualhints/ │ │ │ │ └── valuehint/ │ │ │ │ └── MyProperties.java │ │ │ ├── buildtoolplugins/ │ │ │ │ └── otherbuildsystems/ │ │ │ │ └── examplerepackageimplementation/ │ │ │ │ └── MyBuildTool.java │ │ │ ├── data/ │ │ │ │ ├── nosql/ │ │ │ │ │ ├── cassandra/ │ │ │ │ │ │ └── connecting/ │ │ │ │ │ │ ├── MyBean.java │ │ │ │ │ │ └── User.java │ │ │ │ │ ├── couchbase/ │ │ │ │ │ │ └── repositories/ │ │ │ │ │ │ ├── CouchbaseProperties.java │ │ │ │ │ │ ├── MyBean.java │ │ │ │ │ │ ├── MyConverter.java │ │ │ │ │ │ └── MyCouchbaseConfiguration.java │ │ │ │ │ ├── elasticsearch/ │ │ │ │ │ │ └── connectingusingspringdata/ │ │ │ │ │ │ ├── MyBean.java │ │ │ │ │ │ └── User.java │ │ │ │ │ ├── ldap/ │ │ │ │ │ │ └── repositories/ │ │ │ │ │ │ ├── MyBean.java │ │ │ │ │ │ └── User.java │ │ │ │ │ ├── mongodb/ │ │ │ │ │ │ ├── connecting/ │ │ │ │ │ │ │ └── MyBean.java │ │ │ │ │ │ ├── repositories/ │ │ │ │ │ │ │ ├── City.java │ │ │ │ │ │ │ └── CityRepository.java │ │ │ │ │ │ └── template/ │ │ │ │ │ │ └── MyBean.java │ │ │ │ │ ├── neo4j/ │ │ │ │ │ │ ├── connecting/ │ │ │ │ │ │ │ └── MyBean.java │ │ │ │ │ │ └── repositories/ │ │ │ │ │ │ ├── City.java │ │ │ │ │ │ ├── CityRepository.java │ │ │ │ │ │ └── MyNeo4jConfiguration.java │ │ │ │ │ └── redis/ │ │ │ │ │ └── connecting/ │ │ │ │ │ └── MyBean.java │ │ │ │ └── sql/ │ │ │ │ ├── h2webconsole/ │ │ │ │ │ └── springsecurity/ │ │ │ │ │ └── DevProfileSecurityConfiguration.java │ │ │ │ ├── jdbcclient/ │ │ │ │ │ └── MyBean.java │ │ │ │ ├── jdbctemplate/ │ │ │ │ │ └── MyBean.java │ │ │ │ ├── jooq/ │ │ │ │ │ └── dslcontext/ │ │ │ │ │ ├── MyBean.java │ │ │ │ │ └── Tables.java │ │ │ │ ├── jpaandspringdata/ │ │ │ │ │ ├── entityclasses/ │ │ │ │ │ │ ├── City.java │ │ │ │ │ │ └── Country.java │ │ │ │ │ ├── enversrepositories/ │ │ │ │ │ │ └── CountryRepository.java │ │ │ │ │ └── repositories/ │ │ │ │ │ └── CityRepository.java │ │ │ │ └── r2dbc/ │ │ │ │ ├── MyPostgresR2dbcConfiguration.java │ │ │ │ ├── MyR2dbcConfiguration.java │ │ │ │ ├── repositories/ │ │ │ │ │ ├── City.java │ │ │ │ │ └── CityRepository.java │ │ │ │ └── usingdatabaseclient/ │ │ │ │ └── MyBean.java │ │ │ ├── features/ │ │ │ │ ├── developingautoconfiguration/ │ │ │ │ │ ├── conditionannotations/ │ │ │ │ │ │ ├── beanconditions/ │ │ │ │ │ │ │ ├── MyAutoConfiguration.java │ │ │ │ │ │ │ └── SomeService.java │ │ │ │ │ │ └── classconditions/ │ │ │ │ │ │ ├── MyAutoConfiguration.java │ │ │ │ │ │ └── SomeService.java │ │ │ │ │ ├── customstarter/ │ │ │ │ │ │ └── configurationkeys/ │ │ │ │ │ │ └── AcmeProperties.java │ │ │ │ │ └── testing/ │ │ │ │ │ ├── MyConditionEvaluationReportingTests.java │ │ │ │ │ ├── MyService.java │ │ │ │ │ ├── MyServiceAutoConfiguration.java │ │ │ │ │ └── MyServiceAutoConfigurationTests.java │ │ │ │ ├── devservices/ │ │ │ │ │ └── testcontainers/ │ │ │ │ │ └── atdevelopmenttime/ │ │ │ │ │ ├── devtools/ │ │ │ │ │ │ └── MyContainersConfiguration.java │ │ │ │ │ ├── dynamicproperties/ │ │ │ │ │ │ └── MyContainersConfiguration.java │ │ │ │ │ ├── importingcontainerdeclarations/ │ │ │ │ │ │ ├── MyContainers.java │ │ │ │ │ │ └── MyContainersConfiguration.java │ │ │ │ │ ├── launch/ │ │ │ │ │ │ ├── MyApplication.java │ │ │ │ │ │ └── TestMyApplication.java │ │ │ │ │ └── test/ │ │ │ │ │ ├── MyApplication.java │ │ │ │ │ ├── MyContainersConfiguration.java │ │ │ │ │ └── TestMyApplication.java │ │ │ │ ├── externalconfig/ │ │ │ │ │ ├── MyBean.java │ │ │ │ │ └── typesafeconfigurationproperties/ │ │ │ │ │ ├── constructorbinding/ │ │ │ │ │ │ ├── MyProperties.java │ │ │ │ │ │ ├── defaultvalues/ │ │ │ │ │ │ │ └── nonnull/ │ │ │ │ │ │ │ └── MyProperties.java │ │ │ │ │ │ └── primaryconstructor/ │ │ │ │ │ │ ├── MyBean.java │ │ │ │ │ │ └── MyProperties.java │ │ │ │ │ ├── conversion/ │ │ │ │ │ │ ├── datasizes/ │ │ │ │ │ │ │ ├── constructorbinding/ │ │ │ │ │ │ │ │ └── MyProperties.java │ │ │ │ │ │ │ └── javabeanbinding/ │ │ │ │ │ │ │ └── MyProperties.java │ │ │ │ │ │ └── durations/ │ │ │ │ │ │ ├── constructorbinding/ │ │ │ │ │ │ │ └── MyProperties.java │ │ │ │ │ │ └── javabeanbinding/ │ │ │ │ │ │ └── MyProperties.java │ │ │ │ │ ├── enablingannotatedtypes/ │ │ │ │ │ │ ├── MyApplication.java │ │ │ │ │ │ ├── MyConfiguration.java │ │ │ │ │ │ └── SomeProperties.java │ │ │ │ │ ├── javabeanbinding/ │ │ │ │ │ │ └── MyProperties.java │ │ │ │ │ ├── mergingcomplextypes/ │ │ │ │ │ │ ├── list/ │ │ │ │ │ │ │ ├── MyPojo.java │ │ │ │ │ │ │ └── MyProperties.java │ │ │ │ │ │ └── map/ │ │ │ │ │ │ ├── MyPojo.java │ │ │ │ │ │ └── MyProperties.java │ │ │ │ │ ├── relaxedbinding/ │ │ │ │ │ │ ├── MyPersonProperties.java │ │ │ │ │ │ └── mapsfromenvironmentvariables/ │ │ │ │ │ │ └── MyMapsProperties.java │ │ │ │ │ ├── thirdpartyconfiguration/ │ │ │ │ │ │ ├── AnotherComponent.java │ │ │ │ │ │ └── ThirdPartyConfiguration.java │ │ │ │ │ ├── usingannotatedtypes/ │ │ │ │ │ │ ├── MyProperties.java │ │ │ │ │ │ ├── MyService.java │ │ │ │ │ │ └── Server.java │ │ │ │ │ └── validation/ │ │ │ │ │ ├── MyProperties.java │ │ │ │ │ └── nested/ │ │ │ │ │ └── MyProperties.java │ │ │ │ ├── json/ │ │ │ │ │ └── jackson/ │ │ │ │ │ └── customserializersanddeserializers/ │ │ │ │ │ ├── MyJacksonComponent.java │ │ │ │ │ ├── MyObject.java │ │ │ │ │ └── object/ │ │ │ │ │ ├── MyJacksonComponent.java │ │ │ │ │ └── MyObject.java │ │ │ │ ├── logexample/ │ │ │ │ │ └── MyApplication.java │ │ │ │ ├── logging/ │ │ │ │ │ └── structured/ │ │ │ │ │ └── otherformats/ │ │ │ │ │ └── MyCustomFormat.java │ │ │ │ ├── profiles/ │ │ │ │ │ └── ProductionConfiguration.java │ │ │ │ ├── springapplication/ │ │ │ │ │ ├── MyApplication.java │ │ │ │ │ ├── applicationarguments/ │ │ │ │ │ │ └── MyBean.java │ │ │ │ │ ├── applicationavailability/ │ │ │ │ │ │ └── managing/ │ │ │ │ │ │ ├── CacheCompletelyBrokenException.java │ │ │ │ │ │ ├── MyLocalCacheVerifier.java │ │ │ │ │ │ └── MyReadinessStateExporter.java │ │ │ │ │ ├── applicationexit/ │ │ │ │ │ │ └── MyApplication.java │ │ │ │ │ ├── commandlinerunner/ │ │ │ │ │ │ └── MyCommandLineRunner.java │ │ │ │ │ ├── customizingspringapplication/ │ │ │ │ │ │ └── MyApplication.java │ │ │ │ │ ├── fluentbuilderapi/ │ │ │ │ │ │ └── MyApplication.java │ │ │ │ │ └── startuptracking/ │ │ │ │ │ └── MyApplication.java │ │ │ │ ├── ssl/ │ │ │ │ │ └── bundles/ │ │ │ │ │ └── MyComponent.java │ │ │ │ └── taskexecutionandscheduling/ │ │ │ │ ├── application/ │ │ │ │ │ └── MyTaskExecutorConfiguration.java │ │ │ │ ├── async/ │ │ │ │ │ └── MyTaskExecutorConfiguration.java │ │ │ │ ├── builder/ │ │ │ │ │ └── MyTaskExecutorConfiguration.java │ │ │ │ ├── defaultcandidate/ │ │ │ │ │ └── MyTaskExecutorConfiguration.java │ │ │ │ └── multiple/ │ │ │ │ └── MyTaskExecutorConfiguration.java │ │ │ ├── gettingstarted/ │ │ │ │ └── firstapplication/ │ │ │ │ └── code/ │ │ │ │ └── MyApplication.java │ │ │ ├── howto/ │ │ │ │ ├── actuator/ │ │ │ │ │ └── maphealthindicatorstometrics/ │ │ │ │ │ ├── MetricsHealthMicrometerExport.java │ │ │ │ │ └── MyHealthMetricsExportConfiguration.java │ │ │ │ ├── application/ │ │ │ │ │ └── customizetheenvironmentorapplicationcontext/ │ │ │ │ │ └── MyEnvironmentPostProcessor.java │ │ │ │ ├── dataaccess/ │ │ │ │ │ ├── configureacomponentthatisusedbyjpa/ │ │ │ │ │ │ └── ElasticsearchEntityManagerFactoryDependsOnPostProcessor.java │ │ │ │ │ ├── configurecustomdatasource/ │ │ │ │ │ │ ├── builder/ │ │ │ │ │ │ │ └── MyDataSourceConfiguration.java │ │ │ │ │ │ ├── configurable/ │ │ │ │ │ │ │ └── MyDataSourceConfiguration.java │ │ │ │ │ │ ├── custom/ │ │ │ │ │ │ │ ├── MyDataSourceConfiguration.java │ │ │ │ │ │ │ └── SomeDataSource.java │ │ │ │ │ │ └── simple/ │ │ │ │ │ │ └── MyDataSourceConfiguration.java │ │ │ │ │ ├── configurehibernatenamingstrategy/ │ │ │ │ │ │ ├── spring/ │ │ │ │ │ │ │ └── MyHibernateConfiguration.java │ │ │ │ │ │ └── standard/ │ │ │ │ │ │ └── MyHibernateConfiguration.java │ │ │ │ │ ├── configurehibernatesecondlevelcaching/ │ │ │ │ │ │ └── MyHibernateSecondLevelCacheConfiguration.java │ │ │ │ │ ├── configuretwodatasources/ │ │ │ │ │ │ ├── MyAdditionalDataSourceConfiguration.java │ │ │ │ │ │ └── MyCompleteAdditionalDataSourceConfiguration.java │ │ │ │ │ ├── filterscannedentitydefinitions/ │ │ │ │ │ │ └── MyEntityScanConfiguration.java │ │ │ │ │ ├── separateentitydefinitionsfromspringconfiguration/ │ │ │ │ │ │ ├── City.java │ │ │ │ │ │ └── MyApplication.java │ │ │ │ │ └── usemultipleentitymanagers/ │ │ │ │ │ ├── Customer.java │ │ │ │ │ ├── CustomerConfiguration.java │ │ │ │ │ ├── MyAdditionalEntityManagerFactoryConfiguration.java │ │ │ │ │ ├── Order.java │ │ │ │ │ └── OrderConfiguration.java │ │ │ │ ├── deployment/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── cloudfoundry/ │ │ │ │ │ └── bindingtoservices/ │ │ │ │ │ └── MyBean.java │ │ │ │ ├── httpclients/ │ │ │ │ │ └── webclientreactornettycustomization/ │ │ │ │ │ └── MyReactorNettyClientConfiguration.java │ │ │ │ ├── jersey/ │ │ │ │ │ ├── alongsideanotherwebframework/ │ │ │ │ │ │ ├── Endpoint.java │ │ │ │ │ │ └── JerseyConfig.java │ │ │ │ │ └── springsecurity/ │ │ │ │ │ ├── Endpoint.java │ │ │ │ │ └── JerseySetStatusOverSendErrorConfig.java │ │ │ │ ├── messaging/ │ │ │ │ │ └── disabletransactedjmssession/ │ │ │ │ │ └── MyJmsConfiguration.java │ │ │ │ ├── nativeimage/ │ │ │ │ │ └── developingyourfirstapplication/ │ │ │ │ │ └── sampleapplication/ │ │ │ │ │ └── MyApplication.java │ │ │ │ ├── propertiesandconfiguration/ │ │ │ │ │ └── externalizeconfiguration/ │ │ │ │ │ ├── application/ │ │ │ │ │ │ └── MyApplication.java │ │ │ │ │ └── builder/ │ │ │ │ │ └── MyApplication.java │ │ │ │ ├── security/ │ │ │ │ │ └── enablehttps/ │ │ │ │ │ └── MySecurityConfig.java │ │ │ │ ├── springmvc/ │ │ │ │ │ ├── writejsonrestservice/ │ │ │ │ │ │ ├── MyController.java │ │ │ │ │ │ └── MyThing.java │ │ │ │ │ └── writexmlrestservice/ │ │ │ │ │ └── MyThing.java │ │ │ │ ├── testing/ │ │ │ │ │ ├── slicetests/ │ │ │ │ │ │ ├── MyConfiguration.java │ │ │ │ │ │ ├── MyDatasourceConfiguration.java │ │ │ │ │ │ └── MySecurityConfiguration.java │ │ │ │ │ └── withspringsecurity/ │ │ │ │ │ ├── MySecurityTests.java │ │ │ │ │ └── UserController.java │ │ │ │ ├── traditionaldeployment/ │ │ │ │ │ ├── convertexistingapplication/ │ │ │ │ │ │ ├── MyApplication.java │ │ │ │ │ │ └── both/ │ │ │ │ │ │ └── MyApplication.java │ │ │ │ │ ├── war/ │ │ │ │ │ │ └── MyApplication.java │ │ │ │ │ └── weblogic/ │ │ │ │ │ └── MyApplication.java │ │ │ │ └── webserver/ │ │ │ │ ├── addservletfilterlistener/ │ │ │ │ │ └── springbean/ │ │ │ │ │ └── disable/ │ │ │ │ │ ├── MyFilter.java │ │ │ │ │ └── MyFilterConfiguration.java │ │ │ │ ├── builduritestwebserver/ │ │ │ │ │ └── MyWebIntegrationTests.java │ │ │ │ ├── configure/ │ │ │ │ │ └── MyTomcatWebServerCustomizer.java │ │ │ │ ├── createwebsocketendpointsusingserverendpoint/ │ │ │ │ │ └── MyWebSocketConfiguration.java │ │ │ │ ├── discoverport/ │ │ │ │ │ └── MyWebIntegrationTests.java │ │ │ │ └── enablemultipleconnectorsintomcat/ │ │ │ │ └── MyTomcatConfiguration.java │ │ │ ├── io/ │ │ │ │ ├── caching/ │ │ │ │ │ ├── MyMathService.java │ │ │ │ │ ├── provider/ │ │ │ │ │ │ ├── MyCacheManagerConfiguration.java │ │ │ │ │ │ ├── cache2k/ │ │ │ │ │ │ │ └── MyCache2kDefaultsConfiguration.java │ │ │ │ │ │ ├── couchbase/ │ │ │ │ │ │ │ └── MyCouchbaseCacheManagerConfiguration.java │ │ │ │ │ │ └── redis/ │ │ │ │ │ │ └── MyRedisCacheManagerConfiguration.java │ │ │ │ │ └── testing/ │ │ │ │ │ └── MyIntegrationTests.java │ │ │ │ ├── jta/ │ │ │ │ │ └── mixingxaandnonxaconnections/ │ │ │ │ │ ├── nonxa/ │ │ │ │ │ │ └── MyBean.java │ │ │ │ │ ├── primary/ │ │ │ │ │ │ └── MyBean.java │ │ │ │ │ └── xa/ │ │ │ │ │ └── MyBean.java │ │ │ │ ├── quartz/ │ │ │ │ │ ├── MySampleJob.java │ │ │ │ │ └── MyService.java │ │ │ │ ├── restclient/ │ │ │ │ │ ├── clienthttprequestfactory/ │ │ │ │ │ │ └── configuration/ │ │ │ │ │ │ └── MyClientHttpConfiguration.java │ │ │ │ │ ├── httpservice/ │ │ │ │ │ │ ├── EchoService.java │ │ │ │ │ │ ├── customization/ │ │ │ │ │ │ │ └── MyHttpServiceGroupConfiguration.java │ │ │ │ │ │ ├── groups/ │ │ │ │ │ │ │ ├── EchoService.java │ │ │ │ │ │ │ ├── MyApplication.java │ │ │ │ │ │ │ └── repeat/ │ │ │ │ │ │ │ ├── EchoService.java │ │ │ │ │ │ │ ├── MyApplication.java │ │ │ │ │ │ │ └── OtherService.java │ │ │ │ │ │ └── importing/ │ │ │ │ │ │ └── MyApplication.java │ │ │ │ │ ├── restclient/ │ │ │ │ │ │ ├── Details.java │ │ │ │ │ │ ├── MyService.java │ │ │ │ │ │ └── ssl/ │ │ │ │ │ │ ├── Details.java │ │ │ │ │ │ ├── MyService.java │ │ │ │ │ │ └── settings/ │ │ │ │ │ │ ├── Details.java │ │ │ │ │ │ └── MyService.java │ │ │ │ │ ├── resttemplate/ │ │ │ │ │ │ ├── Details.java │ │ │ │ │ │ ├── MyService.java │ │ │ │ │ │ ├── customization/ │ │ │ │ │ │ │ ├── MyRestTemplateBuilderConfiguration.java │ │ │ │ │ │ │ └── MyRestTemplateCustomizer.java │ │ │ │ │ │ └── ssl/ │ │ │ │ │ │ └── MyService.java │ │ │ │ │ └── webclient/ │ │ │ │ │ ├── Details.java │ │ │ │ │ ├── MyService.java │ │ │ │ │ ├── configuration/ │ │ │ │ │ │ └── MyConnectorHttpConfiguration.java │ │ │ │ │ └── ssl/ │ │ │ │ │ ├── Details.java │ │ │ │ │ └── MyService.java │ │ │ │ ├── validation/ │ │ │ │ │ ├── Archive.java │ │ │ │ │ ├── Author.java │ │ │ │ │ └── MyBean.java │ │ │ │ └── webservices/ │ │ │ │ └── template/ │ │ │ │ ├── MyService.java │ │ │ │ ├── MyWebServiceTemplateConfiguration.java │ │ │ │ ├── SomeRequest.java │ │ │ │ └── SomeResponse.java │ │ │ ├── messaging/ │ │ │ │ ├── amqp/ │ │ │ │ │ ├── receiving/ │ │ │ │ │ │ ├── MyBean.java │ │ │ │ │ │ └── custom/ │ │ │ │ │ │ ├── MyBean.java │ │ │ │ │ │ ├── MyMessageConverter.java │ │ │ │ │ │ └── MyRabbitConfiguration.java │ │ │ │ │ └── sending/ │ │ │ │ │ └── MyBean.java │ │ │ │ ├── jms/ │ │ │ │ │ ├── receiving/ │ │ │ │ │ │ ├── MyBean.java │ │ │ │ │ │ └── custom/ │ │ │ │ │ │ ├── MyBean.java │ │ │ │ │ │ ├── MyJmsConfiguration.java │ │ │ │ │ │ └── MyMessageConverter.java │ │ │ │ │ └── sending/ │ │ │ │ │ └── MyBean.java │ │ │ │ ├── kafka/ │ │ │ │ │ ├── embedded/ │ │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ │ └── MyTest.java │ │ │ │ │ │ └── property/ │ │ │ │ │ │ └── MyTest.java │ │ │ │ │ ├── receiving/ │ │ │ │ │ │ └── MyBean.java │ │ │ │ │ ├── sending/ │ │ │ │ │ │ └── MyBean.java │ │ │ │ │ └── streams/ │ │ │ │ │ └── MyKafkaStreamsConfiguration.java │ │ │ │ ├── pulsar/ │ │ │ │ │ ├── reading/ │ │ │ │ │ │ └── MyBean.java │ │ │ │ │ ├── receiving/ │ │ │ │ │ │ └── MyBean.java │ │ │ │ │ └── sending/ │ │ │ │ │ └── MyBean.java │ │ │ │ └── rsocket/ │ │ │ │ └── requester/ │ │ │ │ ├── MyService.java │ │ │ │ └── User.java │ │ │ ├── packaging/ │ │ │ │ └── nativeimage/ │ │ │ │ ├── advanced/ │ │ │ │ │ ├── customhints/ │ │ │ │ │ │ ├── MyClass.java │ │ │ │ │ │ ├── MyInterface.java │ │ │ │ │ │ ├── MyRuntimeHints.java │ │ │ │ │ │ ├── MySerializableClass.java │ │ │ │ │ │ └── testing/ │ │ │ │ │ │ └── MyRuntimeHintsTests.java │ │ │ │ │ └── nestedconfigurationproperties/ │ │ │ │ │ ├── MyProperties.java │ │ │ │ │ ├── MyPropertiesCtor.java │ │ │ │ │ ├── MyPropertiesRecord.java │ │ │ │ │ └── Nested.java │ │ │ │ └── introducinggraalvmnativeimages/ │ │ │ │ └── understandingaotprocessing/ │ │ │ │ └── sourcecodegeneration/ │ │ │ │ ├── MyBean.java │ │ │ │ ├── MyConfiguration.java │ │ │ │ └── MyConfiguration__BeanDefinitions.java │ │ │ ├── testing/ │ │ │ │ ├── springbootapplications/ │ │ │ │ │ ├── additionalautoconfigurationandslicing/ │ │ │ │ │ │ └── MyJdbcTests.java │ │ │ │ │ ├── autoconfiguredjdbc/ │ │ │ │ │ │ └── MyTransactionalTests.java │ │ │ │ │ ├── autoconfiguredjooq/ │ │ │ │ │ │ └── MyJooqTests.java │ │ │ │ │ ├── autoconfiguredrestclient/ │ │ │ │ │ │ ├── MyRestClientServiceTests.java │ │ │ │ │ │ ├── MyRestTemplateServiceTests.java │ │ │ │ │ │ └── RemoteVehicleDetailsService.java │ │ │ │ │ ├── autoconfiguredspringdatacassandra/ │ │ │ │ │ │ ├── MyDataCassandraTests.java │ │ │ │ │ │ └── SomeRepository.java │ │ │ │ │ ├── autoconfiguredspringdatacouchbase/ │ │ │ │ │ │ ├── MyDataCouchbaseTests.java │ │ │ │ │ │ └── SomeRepository.java │ │ │ │ │ ├── autoconfiguredspringdataelasticsearch/ │ │ │ │ │ │ ├── MyDataElasticsearchTests.java │ │ │ │ │ │ └── SomeRepository.java │ │ │ │ │ ├── autoconfiguredspringdatajpa/ │ │ │ │ │ │ ├── MyNonTransactionalTests.java │ │ │ │ │ │ ├── withdb/ │ │ │ │ │ │ │ └── MyRepositoryTests.java │ │ │ │ │ │ └── withoutdb/ │ │ │ │ │ │ ├── MyRepositoryTests.java │ │ │ │ │ │ ├── User.java │ │ │ │ │ │ └── UserRepository.java │ │ │ │ │ ├── autoconfiguredspringdataldap/ │ │ │ │ │ │ ├── inmemory/ │ │ │ │ │ │ │ └── MyDataLdapTests.java │ │ │ │ │ │ └── server/ │ │ │ │ │ │ └── MyDataLdapTests.java │ │ │ │ │ ├── autoconfiguredspringdatamongodb/ │ │ │ │ │ │ └── MyDataMongoDbTests.java │ │ │ │ │ ├── autoconfiguredspringdataneo4j/ │ │ │ │ │ │ ├── nopropagation/ │ │ │ │ │ │ │ └── MyDataNeo4jTests.java │ │ │ │ │ │ └── propagation/ │ │ │ │ │ │ ├── MyDataNeo4jTests.java │ │ │ │ │ │ └── SomeRepository.java │ │ │ │ │ ├── autoconfiguredspringdataredis/ │ │ │ │ │ │ ├── MyDataRedisTests.java │ │ │ │ │ │ └── SomeRepository.java │ │ │ │ │ ├── autoconfiguredspringrestdocs/ │ │ │ │ │ │ ├── withmockmvc/ │ │ │ │ │ │ │ ├── MyRestDocsConfiguration.java │ │ │ │ │ │ │ ├── MyResultHandlerConfiguration.java │ │ │ │ │ │ │ ├── assertj/ │ │ │ │ │ │ │ │ ├── MyUserDocumentationTests.java │ │ │ │ │ │ │ │ └── UserController.java │ │ │ │ │ │ │ └── hamcrest/ │ │ │ │ │ │ │ ├── MyUserDocumentationTests.java │ │ │ │ │ │ │ └── UserController.java │ │ │ │ │ │ └── withwebtestclient/ │ │ │ │ │ │ ├── MyRestDocsConfiguration.java │ │ │ │ │ │ ├── MyUsersDocumentationTests.java │ │ │ │ │ │ └── MyWebTestClientBuilderCustomizerConfiguration.java │ │ │ │ │ ├── autoconfiguredwebservices/ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ ├── MyWebServiceClientTests.java │ │ │ │ │ │ │ ├── Request.java │ │ │ │ │ │ │ ├── Response.java │ │ │ │ │ │ │ └── SomeWebService.java │ │ │ │ │ │ └── server/ │ │ │ │ │ │ ├── ExampleEndpoint.java │ │ │ │ │ │ └── MyWebServiceServerTests.java │ │ │ │ │ ├── detectingwebapptype/ │ │ │ │ │ │ └── MyWebFluxTests.java │ │ │ │ │ ├── excludingconfiguration/ │ │ │ │ │ │ ├── MyTests.java │ │ │ │ │ │ └── MyTestsConfiguration.java │ │ │ │ │ ├── jmx/ │ │ │ │ │ │ ├── MyJmxTests.java │ │ │ │ │ │ └── SampleApp.java │ │ │ │ │ ├── jsontests/ │ │ │ │ │ │ ├── MyJsonAssertJTests.java │ │ │ │ │ │ ├── MyJsonTests.java │ │ │ │ │ │ ├── SomeObject.java │ │ │ │ │ │ └── VehicleDetails.java │ │ │ │ │ ├── springgraphqltests/ │ │ │ │ │ │ ├── GraphQlIntegrationTests.java │ │ │ │ │ │ └── GreetingControllerTests.java │ │ │ │ │ ├── springmvctests/ │ │ │ │ │ │ ├── MyControllerTests.java │ │ │ │ │ │ ├── MyHtmlUnitTests.java │ │ │ │ │ │ ├── UserVehicleController.java │ │ │ │ │ │ ├── UserVehicleService.java │ │ │ │ │ │ └── VehicleDetails.java │ │ │ │ │ ├── springwebfluxtests/ │ │ │ │ │ │ ├── MyControllerTests.java │ │ │ │ │ │ ├── UserVehicleController.java │ │ │ │ │ │ ├── UserVehicleService.java │ │ │ │ │ │ └── VehicleDetails.java │ │ │ │ │ ├── userconfigurationandslicing/ │ │ │ │ │ │ ├── MyApplication.java │ │ │ │ │ │ ├── MyMongoConfiguration.java │ │ │ │ │ │ ├── MyWebConfiguration.java │ │ │ │ │ │ ├── MyWebMvcConfigurer.java │ │ │ │ │ │ └── scan/ │ │ │ │ │ │ └── MyApplication.java │ │ │ │ │ ├── usingapplicationarguments/ │ │ │ │ │ │ └── MyApplicationArgumentTests.java │ │ │ │ │ ├── usingmain/ │ │ │ │ │ │ ├── always/ │ │ │ │ │ │ │ └── MyApplicationTests.java │ │ │ │ │ │ ├── custom/ │ │ │ │ │ │ │ └── MyApplication.java │ │ │ │ │ │ └── typical/ │ │ │ │ │ │ └── MyApplication.java │ │ │ │ │ ├── withmockenvironment/ │ │ │ │ │ │ ├── MyMockMvcTests.java │ │ │ │ │ │ └── MyMockWebTestClientTests.java │ │ │ │ │ └── withrunningserver/ │ │ │ │ │ ├── MyRandomPortRestTestClientAssertJTests.java │ │ │ │ │ ├── MyRandomPortRestTestClientTests.java │ │ │ │ │ ├── MyRandomPortTestRestTemplateTests.java │ │ │ │ │ └── MyRandomPortWebTestClientTests.java │ │ │ │ ├── testcontainers/ │ │ │ │ │ ├── dynamicproperties/ │ │ │ │ │ │ └── MyIntegrationTests.java │ │ │ │ │ ├── importingconfigurationinterfaces/ │ │ │ │ │ │ ├── MyContainers.java │ │ │ │ │ │ └── MyTestConfiguration.java │ │ │ │ │ ├── junitextension/ │ │ │ │ │ │ └── MyIntegrationTests.java │ │ │ │ │ ├── serviceconnections/ │ │ │ │ │ │ ├── MyIntegrationTests.java │ │ │ │ │ │ ├── MyRedisConfiguration.java │ │ │ │ │ │ └── ssl/ │ │ │ │ │ │ ├── MyElasticsearchWithSslIntegrationTests.java │ │ │ │ │ │ ├── MyRedisWithSslIntegrationTests.java │ │ │ │ │ │ └── SecureRedisContainer.java │ │ │ │ │ └── springbeans/ │ │ │ │ │ ├── MyIntegrationTests.java │ │ │ │ │ └── MyTestConfiguration.java │ │ │ │ └── utilities/ │ │ │ │ ├── configdataapplicationcontextinitializer/ │ │ │ │ │ ├── Config.java │ │ │ │ │ └── MyConfigFileTests.java │ │ │ │ ├── outputcapture/ │ │ │ │ │ └── MyOutputCaptureTests.java │ │ │ │ ├── testpropertyvalues/ │ │ │ │ │ └── MyEnvironmentTests.java │ │ │ │ └── testresttemplate/ │ │ │ │ ├── MySpringBootTests.java │ │ │ │ ├── MySpringBootTestsConfiguration.java │ │ │ │ └── MyTests.java │ │ │ ├── using/ │ │ │ │ ├── autoconfiguration/ │ │ │ │ │ └── disablingspecific/ │ │ │ │ │ └── MyApplication.java │ │ │ │ ├── devtools/ │ │ │ │ │ └── restart/ │ │ │ │ │ └── disable/ │ │ │ │ │ └── MyApplication.java │ │ │ │ ├── springbeansanddependencyinjection/ │ │ │ │ │ ├── multipleconstructors/ │ │ │ │ │ │ ├── AccountService.java │ │ │ │ │ │ ├── MyAccountService.java │ │ │ │ │ │ └── RiskAssessor.java │ │ │ │ │ └── singleconstructor/ │ │ │ │ │ ├── AccountService.java │ │ │ │ │ ├── MyAccountService.java │ │ │ │ │ └── RiskAssessor.java │ │ │ │ ├── structuringyourcode/ │ │ │ │ │ └── locatingthemainclass/ │ │ │ │ │ └── MyApplication.java │ │ │ │ └── usingthespringbootapplicationannotation/ │ │ │ │ ├── individualannotations/ │ │ │ │ │ ├── AnotherConfiguration.java │ │ │ │ │ ├── MyApplication.java │ │ │ │ │ └── SomeConfiguration.java │ │ │ │ └── springapplication/ │ │ │ │ └── MyApplication.java │ │ │ └── web/ │ │ │ ├── graphql/ │ │ │ │ ├── runtimewiring/ │ │ │ │ │ └── GreetingController.java │ │ │ │ └── transports/ │ │ │ │ └── rsocket/ │ │ │ │ └── RSocketGraphQlClientExample.java │ │ │ ├── reactive/ │ │ │ │ ├── reactiveserver/ │ │ │ │ │ └── customizing/ │ │ │ │ │ └── programmatic/ │ │ │ │ │ ├── MyNettyWebServerFactoryCustomizer.java │ │ │ │ │ └── MyWebServerFactoryCustomizer.java │ │ │ │ └── webflux/ │ │ │ │ ├── Customer.java │ │ │ │ ├── CustomerRepository.java │ │ │ │ ├── MyRestController.java │ │ │ │ ├── MyRoutingConfiguration.java │ │ │ │ ├── MyUserHandler.java │ │ │ │ ├── User.java │ │ │ │ ├── UserRepository.java │ │ │ │ ├── errorhandling/ │ │ │ │ │ └── MyErrorWebExceptionHandler.java │ │ │ │ └── httpcodecs/ │ │ │ │ └── MyCodecsConfiguration.java │ │ │ ├── security/ │ │ │ │ ├── oauth2/ │ │ │ │ │ └── client/ │ │ │ │ │ └── MyOAuthClientConfiguration.java │ │ │ │ ├── saml2/ │ │ │ │ │ └── relyingparty/ │ │ │ │ │ └── MySamlRelyingPartyConfiguration.java │ │ │ │ └── springwebflux/ │ │ │ │ └── MyWebFluxSecurityConfiguration.java │ │ │ └── servlet/ │ │ │ ├── embeddedcontainer/ │ │ │ │ ├── applicationcontext/ │ │ │ │ │ └── MyDemoBean.java │ │ │ │ └── customizing/ │ │ │ │ ├── programmatic/ │ │ │ │ │ ├── MyTomcatWebServerFactoryCustomizer.java │ │ │ │ │ └── MyWebServerFactoryCustomizer.java │ │ │ │ └── samesite/ │ │ │ │ └── MySameSiteConfiguration.java │ │ │ ├── jersey/ │ │ │ │ ├── MyEndpoint.java │ │ │ │ └── MyJerseyConfig.java │ │ │ └── springmvc/ │ │ │ ├── Customer.java │ │ │ ├── CustomerRepository.java │ │ │ ├── MyRestController.java │ │ │ ├── MyRoutingConfiguration.java │ │ │ ├── MyUserHandler.java │ │ │ ├── User.java │ │ │ ├── UserRepository.java │ │ │ ├── cors/ │ │ │ │ └── MyCorsConfiguration.java │ │ │ ├── errorhandling/ │ │ │ │ ├── CustomException.java │ │ │ │ ├── MyControllerAdvice.java │ │ │ │ ├── MyErrorBody.java │ │ │ │ ├── MyException.java │ │ │ │ ├── SomeController.java │ │ │ │ ├── errorpages/ │ │ │ │ │ └── MyErrorViewResolver.java │ │ │ │ └── errorpageswithoutspringmvc/ │ │ │ │ ├── MyErrorPagesConfiguration.java │ │ │ │ ├── MyFilter.java │ │ │ │ └── MyFilterConfiguration.java │ │ │ └── messageconverters/ │ │ │ ├── AdditionalHttpMessageConverter.java │ │ │ ├── AnotherHttpMessageConverter.java │ │ │ └── MyHttpMessageConvertersConfiguration.java │ │ ├── kotlin/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── docs/ │ │ │ ├── actuator/ │ │ │ │ ├── cloudfoundry/ │ │ │ │ │ └── customcontextpath/ │ │ │ │ │ ├── MyCloudFoundryConfiguration.kt │ │ │ │ │ └── MyReactiveCloudFoundryConfiguration.kt │ │ │ │ ├── endpoints/ │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── reactivehealthindicators/ │ │ │ │ │ │ │ └── MyReactiveHealthIndicator.kt │ │ │ │ │ │ └── writingcustomhealthindicators/ │ │ │ │ │ │ └── MyHealthIndicator.kt │ │ │ │ │ ├── implementingcustom/ │ │ │ │ │ │ ├── CustomData.kt │ │ │ │ │ │ └── MyEndpoint.kt │ │ │ │ │ ├── info/ │ │ │ │ │ │ └── writingcustominfocontributors/ │ │ │ │ │ │ └── MyInfoContributor.kt │ │ │ │ │ └── security/ │ │ │ │ │ ├── exposeall/ │ │ │ │ │ │ └── MySecurityConfiguration.kt │ │ │ │ │ └── typical/ │ │ │ │ │ └── MySecurityConfiguration.kt │ │ │ │ ├── loggers/ │ │ │ │ │ └── opentelemetry/ │ │ │ │ │ └── OpenTelemetryAppenderInitializer.kt │ │ │ │ ├── metrics/ │ │ │ │ │ ├── customizing/ │ │ │ │ │ │ └── MyMetricsFilterConfiguration.kt │ │ │ │ │ ├── export/ │ │ │ │ │ │ ├── graphite/ │ │ │ │ │ │ │ └── MyGraphiteConfiguration.kt │ │ │ │ │ │ └── jmx/ │ │ │ │ │ │ └── MyJmxConfiguration.kt │ │ │ │ │ ├── gettingstarted/ │ │ │ │ │ │ ├── commontags/ │ │ │ │ │ │ │ └── MyMeterRegistryConfiguration.kt │ │ │ │ │ │ └── specifictype/ │ │ │ │ │ │ └── MyMeterRegistryConfiguration.kt │ │ │ │ │ ├── registeringcustom/ │ │ │ │ │ │ ├── Dictionary.kt │ │ │ │ │ │ ├── MyBean.kt │ │ │ │ │ │ ├── MyMeterBinderConfiguration.kt │ │ │ │ │ │ └── Queue.kt │ │ │ │ │ └── supported/ │ │ │ │ │ └── mongodb/ │ │ │ │ │ ├── command/ │ │ │ │ │ │ ├── CustomCommandTagsProvider.kt │ │ │ │ │ │ └── MyCommandTagsProviderConfiguration.kt │ │ │ │ │ └── connectionpool/ │ │ │ │ │ ├── CustomConnectionPoolTagsProvider.kt │ │ │ │ │ └── MyConnectionPoolTagsProviderConfiguration.kt │ │ │ │ ├── micrometertracing/ │ │ │ │ │ ├── baggage/ │ │ │ │ │ │ └── CreatingBaggage.kt │ │ │ │ │ ├── creatingspans/ │ │ │ │ │ │ └── CustomObservation.kt │ │ │ │ │ └── gettingstarted/ │ │ │ │ │ └── MyApplication.kt │ │ │ │ └── observability/ │ │ │ │ ├── MyCustomObservation.kt │ │ │ │ ├── contextpropagation/ │ │ │ │ │ └── ContextPropagationConfiguration.kt │ │ │ │ └── preventingobservations/ │ │ │ │ └── MyObservationPredicate.kt │ │ │ ├── appendix/ │ │ │ │ └── configurationmetadata/ │ │ │ │ ├── annotationprocessor/ │ │ │ │ │ └── automaticmetadatageneration/ │ │ │ │ │ ├── MyMessagingProperties.kt │ │ │ │ │ ├── MyServerProperties.kt │ │ │ │ │ ├── nestedproperties/ │ │ │ │ │ │ └── MyServerProperties.kt │ │ │ │ │ └── source/ │ │ │ │ │ └── Host.kt │ │ │ │ ├── format/ │ │ │ │ │ └── property/ │ │ │ │ │ └── MyProperties.kt │ │ │ │ └── manualhints/ │ │ │ │ └── valuehint/ │ │ │ │ └── MyProperties.kt │ │ │ ├── buildtoolplugins/ │ │ │ │ └── otherbuildsystems/ │ │ │ │ └── examplerepackageimplementation/ │ │ │ │ └── MyBuildTool.kt │ │ │ ├── data/ │ │ │ │ ├── nosql/ │ │ │ │ │ ├── cassandra/ │ │ │ │ │ │ └── connecting/ │ │ │ │ │ │ ├── MyBean.kt │ │ │ │ │ │ └── User.kt │ │ │ │ │ ├── couchbase/ │ │ │ │ │ │ └── repositories/ │ │ │ │ │ │ ├── CouchbaseProperties.kt │ │ │ │ │ │ ├── MyBean.kt │ │ │ │ │ │ ├── MyConverter.kt │ │ │ │ │ │ └── MyCouchbaseConfiguration.kt │ │ │ │ │ ├── elasticsearch/ │ │ │ │ │ │ └── connectingusingspringdata/ │ │ │ │ │ │ ├── MyBean.kt │ │ │ │ │ │ └── User.kt │ │ │ │ │ ├── ldap/ │ │ │ │ │ │ └── repositories/ │ │ │ │ │ │ ├── MyBean.kt │ │ │ │ │ │ └── User.kt │ │ │ │ │ ├── mongodb/ │ │ │ │ │ │ ├── connecting/ │ │ │ │ │ │ │ └── MyBean.kt │ │ │ │ │ │ ├── repositories/ │ │ │ │ │ │ │ ├── City.kt │ │ │ │ │ │ │ └── CityRepository.kt │ │ │ │ │ │ └── template/ │ │ │ │ │ │ └── MyBean.kt │ │ │ │ │ ├── neo4j/ │ │ │ │ │ │ ├── connecting/ │ │ │ │ │ │ │ └── MyBean.kt │ │ │ │ │ │ └── repositories/ │ │ │ │ │ │ ├── City.kt │ │ │ │ │ │ ├── CityRepository.kt │ │ │ │ │ │ └── MyNeo4jConfiguration.kt │ │ │ │ │ └── redis/ │ │ │ │ │ └── connecting/ │ │ │ │ │ └── MyBean.kt │ │ │ │ └── sql/ │ │ │ │ ├── h2webconsole/ │ │ │ │ │ └── springsecurity/ │ │ │ │ │ └── DevProfileSecurityConfiguration.kt │ │ │ │ ├── jdbcclient/ │ │ │ │ │ └── MyBean.kt │ │ │ │ ├── jdbctemplate/ │ │ │ │ │ └── MyBean.kt │ │ │ │ ├── jooq/ │ │ │ │ │ └── dslcontext/ │ │ │ │ │ ├── MyBean.kt │ │ │ │ │ └── Tables.kt │ │ │ │ ├── jpaandspringdata/ │ │ │ │ │ ├── entityclasses/ │ │ │ │ │ │ ├── City.kt │ │ │ │ │ │ └── Country.kt │ │ │ │ │ ├── enversrepositories/ │ │ │ │ │ │ └── CountryRepository.kt │ │ │ │ │ └── repositories/ │ │ │ │ │ └── CityRepository.kt │ │ │ │ └── r2dbc/ │ │ │ │ ├── MyPostgresR2dbcConfiguration.kt │ │ │ │ ├── MyR2dbcConfiguration.kt │ │ │ │ ├── repositories/ │ │ │ │ │ ├── City.kt │ │ │ │ │ └── CityRepository.kt │ │ │ │ └── usingdatabaseclient/ │ │ │ │ └── MyBean.kt │ │ │ ├── devtools/ │ │ │ │ └── restart/ │ │ │ │ └── disable/ │ │ │ │ └── MyApplication.kt │ │ │ ├── features/ │ │ │ │ ├── developingautoconfiguration/ │ │ │ │ │ ├── conditionannotations/ │ │ │ │ │ │ ├── beanconditions/ │ │ │ │ │ │ │ ├── MyAutoConfiguration.kt │ │ │ │ │ │ │ └── SomeService.kt │ │ │ │ │ │ └── classconditions/ │ │ │ │ │ │ ├── MyAutoConfiguration.kt │ │ │ │ │ │ └── SomeService.kt │ │ │ │ │ ├── customstarter/ │ │ │ │ │ │ └── configurationkeys/ │ │ │ │ │ │ └── AcmeProperties.kt │ │ │ │ │ └── testing/ │ │ │ │ │ ├── MyConditionEvaluationReportingTests.kt │ │ │ │ │ ├── MyService.kt │ │ │ │ │ ├── MyServiceAutoConfiguration.kt │ │ │ │ │ └── MyServiceAutoConfigurationTests.kt │ │ │ │ ├── devservices/ │ │ │ │ │ └── testcontainers/ │ │ │ │ │ └── atdevelopmenttime/ │ │ │ │ │ ├── devtools/ │ │ │ │ │ │ └── MyContainersConfiguration.kt │ │ │ │ │ ├── dynamicproperties/ │ │ │ │ │ │ └── MyContainersConfiguration.kt │ │ │ │ │ ├── importingcontainerdeclarations/ │ │ │ │ │ │ ├── MyContainers.kt │ │ │ │ │ │ └── MyContainersConfiguration.kt │ │ │ │ │ ├── launch/ │ │ │ │ │ │ ├── MyApplication.kt │ │ │ │ │ │ └── TestMyApplication.kt │ │ │ │ │ └── test/ │ │ │ │ │ ├── MyApplication.kt │ │ │ │ │ ├── MyContainersConfiguration.kt │ │ │ │ │ └── TestMyApplication.kt │ │ │ │ ├── externalconfig/ │ │ │ │ │ ├── MyBean.kt │ │ │ │ │ └── typesafeconfigurationproperties/ │ │ │ │ │ ├── constructorbinding/ │ │ │ │ │ │ ├── MyProperties.kt │ │ │ │ │ │ ├── defaultvalues/ │ │ │ │ │ │ │ └── nonnull/ │ │ │ │ │ │ │ └── MyProperties.kt │ │ │ │ │ │ └── primaryconstructor/ │ │ │ │ │ │ └── MyProperties.kt │ │ │ │ │ ├── conversion/ │ │ │ │ │ │ ├── datasizes/ │ │ │ │ │ │ │ ├── constructorbinding/ │ │ │ │ │ │ │ │ └── MyProperties.kt │ │ │ │ │ │ │ └── javabeanbinding/ │ │ │ │ │ │ │ └── MyProperties.kt │ │ │ │ │ │ └── durations/ │ │ │ │ │ │ ├── constructorbinding/ │ │ │ │ │ │ │ └── MyProperties.kt │ │ │ │ │ │ └── javabeanbinding/ │ │ │ │ │ │ └── MyProperties.kt │ │ │ │ │ ├── enablingannotatedtypes/ │ │ │ │ │ │ ├── MyApplication.kt │ │ │ │ │ │ ├── MyConfiguration.kt │ │ │ │ │ │ └── SomeProperties.kt │ │ │ │ │ ├── javabeanbinding/ │ │ │ │ │ │ └── MyProperties.kt │ │ │ │ │ ├── mergingcomplextypes/ │ │ │ │ │ │ ├── list/ │ │ │ │ │ │ │ ├── MyPojo.kt │ │ │ │ │ │ │ └── MyProperties.kt │ │ │ │ │ │ └── map/ │ │ │ │ │ │ ├── MyPojo.kt │ │ │ │ │ │ └── MyProperties.kt │ │ │ │ │ ├── relaxedbinding/ │ │ │ │ │ │ ├── MyPersonProperties.kt │ │ │ │ │ │ └── mapsfromenvironmentvariables/ │ │ │ │ │ │ └── MyMapsProperties.kt │ │ │ │ │ ├── thirdpartyconfiguration/ │ │ │ │ │ │ ├── AnotherComponent.kt │ │ │ │ │ │ └── ThirdPartyConfiguration.kt │ │ │ │ │ ├── usingannotatedtypes/ │ │ │ │ │ │ ├── MyProperties.kt │ │ │ │ │ │ ├── MyService.kt │ │ │ │ │ │ └── Server.kt │ │ │ │ │ ├── validate/ │ │ │ │ │ │ ├── MyProperties.kt │ │ │ │ │ │ └── nested/ │ │ │ │ │ │ └── MyProperties.kt │ │ │ │ │ └── validation/ │ │ │ │ │ ├── MyProperties.kt │ │ │ │ │ └── nested/ │ │ │ │ │ └── MyProperties.kt │ │ │ │ ├── json/ │ │ │ │ │ └── jackson/ │ │ │ │ │ └── customserializersanddeserializers/ │ │ │ │ │ ├── MyJacksonComponent.kt │ │ │ │ │ ├── MyObject.kt │ │ │ │ │ └── object/ │ │ │ │ │ ├── MyJacksonComponent.kt │ │ │ │ │ └── MyObject.kt │ │ │ │ ├── logging/ │ │ │ │ │ └── structured/ │ │ │ │ │ └── otherformats/ │ │ │ │ │ └── MyCustomFormat.kt │ │ │ │ ├── profiles/ │ │ │ │ │ └── ProductionConfiguration.kt │ │ │ │ ├── springapplication/ │ │ │ │ │ ├── MyApplication.kt │ │ │ │ │ ├── applicationarguments/ │ │ │ │ │ │ └── MyBean.kt │ │ │ │ │ ├── applicationavailability/ │ │ │ │ │ │ └── managing/ │ │ │ │ │ │ ├── CacheCompletelyBrokenException.kt │ │ │ │ │ │ ├── MyLocalCacheVerifier.kt │ │ │ │ │ │ └── MyReadinessStateExporter.kt │ │ │ │ │ ├── applicationexit/ │ │ │ │ │ │ └── MyApplication.kt │ │ │ │ │ ├── commandlinerunner/ │ │ │ │ │ │ └── MyCommandLineRunner.kt │ │ │ │ │ ├── customizingspringapplication/ │ │ │ │ │ │ └── MyApplication.kt │ │ │ │ │ ├── fluentbuilderapi/ │ │ │ │ │ │ └── MyApplication.kt │ │ │ │ │ └── startuptracking/ │ │ │ │ │ └── MyApplication.kt │ │ │ │ ├── ssl/ │ │ │ │ │ └── bundles/ │ │ │ │ │ └── MyComponent.kt │ │ │ │ └── taskexecutionandscheduling/ │ │ │ │ ├── application/ │ │ │ │ │ └── MyTaskExecutorConfiguration.kt │ │ │ │ ├── async/ │ │ │ │ │ └── MyTaskExecutorConfiguration.kt │ │ │ │ ├── builder/ │ │ │ │ │ └── MyTaskExecutorConfiguration.kt │ │ │ │ ├── defaultcandidate/ │ │ │ │ │ └── MyTaskExecutorConfiguration.kt │ │ │ │ └── multiple/ │ │ │ │ └── MyTaskExecutorConfiguration.kt │ │ │ ├── gettingstarted/ │ │ │ │ └── firstapplication/ │ │ │ │ └── code/ │ │ │ │ └── MyApplication.kt │ │ │ ├── howto/ │ │ │ │ ├── actuator/ │ │ │ │ │ └── maphealthindicatorstometrics/ │ │ │ │ │ ├── MetricsHealthMicrometerExport.kt │ │ │ │ │ └── MyHealthMetricsExportConfiguration.kt │ │ │ │ ├── application/ │ │ │ │ │ └── customizetheenvironmentorapplicationcontext/ │ │ │ │ │ └── MyEnvironmentPostProcessor.kt │ │ │ │ ├── dataaccess/ │ │ │ │ │ ├── configureacomponentthatisusedbyjpa/ │ │ │ │ │ │ └── ElasticsearchEntityManagerFactoryDependsOnPostProcessor.kt │ │ │ │ │ ├── configurecustomdatasource/ │ │ │ │ │ │ ├── builder/ │ │ │ │ │ │ │ └── MyDataSourceConfiguration.kt │ │ │ │ │ │ ├── configurable/ │ │ │ │ │ │ │ └── MyDataSourceConfiguration.kt │ │ │ │ │ │ ├── custom/ │ │ │ │ │ │ │ ├── MyDataSourceConfiguration.kt │ │ │ │ │ │ │ └── SomeDataSource.kt │ │ │ │ │ │ └── simple/ │ │ │ │ │ │ └── MyDataSourceConfiguration.kt │ │ │ │ │ ├── configurehibernatenamingstrategy/ │ │ │ │ │ │ ├── spring/ │ │ │ │ │ │ │ └── MyHibernateConfiguration.kt │ │ │ │ │ │ └── standard/ │ │ │ │ │ │ └── MyHibernateConfiguration.kt │ │ │ │ │ ├── configurehibernatesecondlevelcaching/ │ │ │ │ │ │ └── MyHibernateSecondLevelCacheConfiguration.kt │ │ │ │ │ ├── configuretwodatasources/ │ │ │ │ │ │ ├── MyAdditionalDataSourceConfiguration.kt │ │ │ │ │ │ └── MyCompleteAdditionalDataSourceConfiguration.kt │ │ │ │ │ ├── filterscannedentitydefinitions/ │ │ │ │ │ │ └── MyEntityScanConfiguration.kt │ │ │ │ │ ├── separateentitydefinitionsfromspringconfiguration/ │ │ │ │ │ │ ├── City.kt │ │ │ │ │ │ └── MyApplication.kt │ │ │ │ │ └── usemultipleentitymanagers/ │ │ │ │ │ ├── Customer.kt │ │ │ │ │ ├── CustomerConfiguration.kt │ │ │ │ │ ├── MyAdditionalEntityManagerFactoryConfiguration.kt │ │ │ │ │ ├── Order.kt │ │ │ │ │ └── OrderConfiguration.kt │ │ │ │ ├── deployment/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── cloudfoundry/ │ │ │ │ │ └── bindingtoservices/ │ │ │ │ │ └── MyBean.kt │ │ │ │ ├── httpclients/ │ │ │ │ │ └── webclientreactornettycustomization/ │ │ │ │ │ └── MyReactorNettyClientConfiguration.kt │ │ │ │ ├── messaging/ │ │ │ │ │ └── disabletransactedjmssession/ │ │ │ │ │ └── MyJmsConfiguration.kt │ │ │ │ ├── propertiesandconfiguration/ │ │ │ │ │ └── externalizeconfiguration/ │ │ │ │ │ ├── application/ │ │ │ │ │ │ └── MyApplication.kt │ │ │ │ │ └── builder/ │ │ │ │ │ └── MyApplication.kt │ │ │ │ ├── security/ │ │ │ │ │ └── enablehttps/ │ │ │ │ │ └── MySecurityConfig.kt │ │ │ │ ├── springmvc/ │ │ │ │ │ ├── writejsonrestservice/ │ │ │ │ │ │ ├── MyController.kt │ │ │ │ │ │ └── MyThing.kt │ │ │ │ │ └── writexmlrestservice/ │ │ │ │ │ └── MyThing.kt │ │ │ │ ├── testing/ │ │ │ │ │ └── withspringsecurity/ │ │ │ │ │ ├── MySecurityTests.kt │ │ │ │ │ └── UserController.kt │ │ │ │ ├── traditionaldeployment/ │ │ │ │ │ ├── convertexistingapplication/ │ │ │ │ │ │ ├── MyApplication.kt │ │ │ │ │ │ └── both/ │ │ │ │ │ │ └── MyApplication.kt │ │ │ │ │ ├── war/ │ │ │ │ │ │ └── MyApplication.kt │ │ │ │ │ └── weblogic/ │ │ │ │ │ └── MyApplication.kt │ │ │ │ └── webserver/ │ │ │ │ ├── addservletfilterlistener/ │ │ │ │ │ └── springbean/ │ │ │ │ │ └── disable/ │ │ │ │ │ ├── MyFilter.kt │ │ │ │ │ └── MyFilterConfiguration.kt │ │ │ │ ├── builduritestwebserver/ │ │ │ │ │ └── MyWebIntegrationTests.kt │ │ │ │ ├── configure/ │ │ │ │ │ └── MyTomcatWebServerCustomizer.kt │ │ │ │ ├── createwebsocketendpointsusingserverendpoint/ │ │ │ │ │ └── MyWebSocketConfiguration.kt │ │ │ │ ├── discoverport/ │ │ │ │ │ └── MyWebIntegrationTests.kt │ │ │ │ └── enablemultipleconnectorsintomcat/ │ │ │ │ └── MyTomcatConfiguration.kt │ │ │ ├── io/ │ │ │ │ ├── caching/ │ │ │ │ │ ├── MyMathService.kt │ │ │ │ │ ├── provider/ │ │ │ │ │ │ ├── MyCacheManagerConfiguration.kt │ │ │ │ │ │ ├── cache2k/ │ │ │ │ │ │ │ └── MyCache2kDefaultsConfiguration.kt │ │ │ │ │ │ ├── couchbase/ │ │ │ │ │ │ │ └── MyCouchbaseCacheManagerConfiguration.kt │ │ │ │ │ │ └── redis/ │ │ │ │ │ │ └── MyRedisCacheManagerConfiguration.kt │ │ │ │ │ └── testing/ │ │ │ │ │ └── MyIntegrationTests.kt │ │ │ │ ├── jta/ │ │ │ │ │ └── mixingxaandnonxaconnections/ │ │ │ │ │ ├── nonxa/ │ │ │ │ │ │ └── MyBean.kt │ │ │ │ │ ├── primary/ │ │ │ │ │ │ └── MyBean.kt │ │ │ │ │ └── xa/ │ │ │ │ │ └── MyBean.kt │ │ │ │ ├── quartz/ │ │ │ │ │ ├── MySampleJob.kt │ │ │ │ │ └── MyService.kt │ │ │ │ ├── restclient/ │ │ │ │ │ ├── clienthttprequestfactory/ │ │ │ │ │ │ └── configuration/ │ │ │ │ │ │ └── MyClientHttpConfiguration.kt │ │ │ │ │ ├── httpservice/ │ │ │ │ │ │ ├── EchoService.kt │ │ │ │ │ │ ├── customization/ │ │ │ │ │ │ │ └── MyHttpServiceGroupConfiguration.kt │ │ │ │ │ │ ├── groups/ │ │ │ │ │ │ │ ├── EchoService.kt │ │ │ │ │ │ │ ├── MyApplication.kt │ │ │ │ │ │ │ └── repeat/ │ │ │ │ │ │ │ ├── EchoService.kt │ │ │ │ │ │ │ ├── MyApplication.kt │ │ │ │ │ │ │ └── OtherService.kt │ │ │ │ │ │ └── importing/ │ │ │ │ │ │ └── MyApplication.kt │ │ │ │ │ ├── restclient/ │ │ │ │ │ │ ├── Details.kt │ │ │ │ │ │ ├── MyService.kt │ │ │ │ │ │ └── ssl/ │ │ │ │ │ │ ├── Details.kt │ │ │ │ │ │ ├── MyService.kt │ │ │ │ │ │ └── settings/ │ │ │ │ │ │ ├── Details.kt │ │ │ │ │ │ └── MyService.kt │ │ │ │ │ ├── resttemplate/ │ │ │ │ │ │ ├── Details.kt │ │ │ │ │ │ ├── MyService.kt │ │ │ │ │ │ ├── customization/ │ │ │ │ │ │ │ ├── MyRestTemplateBuilderConfiguration.kt │ │ │ │ │ │ │ └── MyRestTemplateCustomizer.kt │ │ │ │ │ │ └── ssl/ │ │ │ │ │ │ └── MyService.kt │ │ │ │ │ └── webclient/ │ │ │ │ │ ├── Details.kt │ │ │ │ │ ├── MyService.kt │ │ │ │ │ ├── configuration/ │ │ │ │ │ │ └── MyConnectorHttpConfiguration.kt │ │ │ │ │ └── ssl/ │ │ │ │ │ ├── Details.kt │ │ │ │ │ └── MyService.kt │ │ │ │ ├── validation/ │ │ │ │ │ ├── Archive.kt │ │ │ │ │ ├── Author.kt │ │ │ │ │ └── MyBean.kt │ │ │ │ └── webservices/ │ │ │ │ └── template/ │ │ │ │ ├── MyService.kt │ │ │ │ ├── MyWebServiceTemplateConfiguration.kt │ │ │ │ ├── SomeRequest.kt │ │ │ │ └── SomeResponse.kt │ │ │ ├── messaging/ │ │ │ │ ├── amqp/ │ │ │ │ │ ├── receiving/ │ │ │ │ │ │ ├── MyBean.kt │ │ │ │ │ │ └── custom/ │ │ │ │ │ │ ├── MyBean.kt │ │ │ │ │ │ ├── MyMessageConverter.kt │ │ │ │ │ │ └── MyRabbitConfiguration.kt │ │ │ │ │ └── sending/ │ │ │ │ │ └── MyBean.kt │ │ │ │ ├── jms/ │ │ │ │ │ ├── receiving/ │ │ │ │ │ │ ├── MyBean.kt │ │ │ │ │ │ └── custom/ │ │ │ │ │ │ ├── MyBean.kt │ │ │ │ │ │ ├── MyJmsConfiguration.kt │ │ │ │ │ │ └── MyMessageConverter.kt │ │ │ │ │ └── sending/ │ │ │ │ │ └── MyBean.kt │ │ │ │ ├── kafka/ │ │ │ │ │ ├── embedded/ │ │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ │ └── MyTest.kt │ │ │ │ │ │ └── property/ │ │ │ │ │ │ └── MyTest.kt │ │ │ │ │ ├── receiving/ │ │ │ │ │ │ └── MyBean.kt │ │ │ │ │ ├── sending/ │ │ │ │ │ │ └── MyBean.kt │ │ │ │ │ └── streams/ │ │ │ │ │ └── MyKafkaStreamsConfiguration.kt │ │ │ │ ├── pulsar/ │ │ │ │ │ ├── reading/ │ │ │ │ │ │ └── MyBean.kt │ │ │ │ │ ├── receiving/ │ │ │ │ │ │ └── MyBean.kt │ │ │ │ │ └── sending/ │ │ │ │ │ └── MyBean.kt │ │ │ │ └── rsocket/ │ │ │ │ └── requester/ │ │ │ │ ├── MyService.kt │ │ │ │ └── User.kt │ │ │ ├── packaging/ │ │ │ │ └── nativeimage/ │ │ │ │ └── advanced/ │ │ │ │ └── nestedconfigurationproperties/ │ │ │ │ ├── MyPropertiesKotlin.kt │ │ │ │ └── Nested.kt │ │ │ ├── testing/ │ │ │ │ ├── springbootapplications/ │ │ │ │ │ ├── additionalautoconfigurationandslicing/ │ │ │ │ │ │ └── MyJdbcTests.kt │ │ │ │ │ ├── autoconfiguredjdbc/ │ │ │ │ │ │ └── MyTransactionalTests.kt │ │ │ │ │ ├── autoconfiguredjooq/ │ │ │ │ │ │ └── MyJooqTests.kt │ │ │ │ │ ├── autoconfiguredrestclient/ │ │ │ │ │ │ ├── MyRestClientServiceTests.kt │ │ │ │ │ │ ├── MyRestTemplateServiceTests.kt │ │ │ │ │ │ └── RemoteVehicleDetailsService.kt │ │ │ │ │ ├── autoconfiguredspringdatacassandra/ │ │ │ │ │ │ ├── MyDataCassandraTests.kt │ │ │ │ │ │ └── SomeRepository.kt │ │ │ │ │ ├── autoconfiguredspringdatacouchbase/ │ │ │ │ │ │ ├── MyDataCouchbaseTests.kt │ │ │ │ │ │ └── SomeRepository.kt │ │ │ │ │ ├── autoconfiguredspringdataelasticsearch/ │ │ │ │ │ │ ├── MyDataElasticsearchTests.kt │ │ │ │ │ │ └── SomeRepository.kt │ │ │ │ │ ├── autoconfiguredspringdatajpa/ │ │ │ │ │ │ ├── MyNonTransactionalTests.kt │ │ │ │ │ │ ├── withdb/ │ │ │ │ │ │ │ └── MyRepositoryTests.kt │ │ │ │ │ │ └── withoutdb/ │ │ │ │ │ │ ├── MyRepositoryTests.kt │ │ │ │ │ │ ├── User.kt │ │ │ │ │ │ └── UserRepository.kt │ │ │ │ │ ├── autoconfiguredspringdataldap/ │ │ │ │ │ │ ├── inmemory/ │ │ │ │ │ │ │ └── MyDataLdapTests.kt │ │ │ │ │ │ └── server/ │ │ │ │ │ │ └── MyDataLdapTests.kt │ │ │ │ │ ├── autoconfiguredspringdatamongodb/ │ │ │ │ │ │ └── MyDataMongoDbTests.kt │ │ │ │ │ ├── autoconfiguredspringdataneo4j/ │ │ │ │ │ │ ├── nopropagation/ │ │ │ │ │ │ │ └── MyDataNeo4jTests.kt │ │ │ │ │ │ └── propagation/ │ │ │ │ │ │ ├── MyDataNeo4jTests.kt │ │ │ │ │ │ └── SomeRepository.kt │ │ │ │ │ ├── autoconfiguredspringdataredis/ │ │ │ │ │ │ ├── MyDataRedisTests.kt │ │ │ │ │ │ └── SomeRepository.kt │ │ │ │ │ ├── autoconfiguredspringrestdocs/ │ │ │ │ │ │ ├── withmockmvc/ │ │ │ │ │ │ │ ├── MyRestDocsConfiguration.kt │ │ │ │ │ │ │ ├── MyResultHandlerConfiguration.kt │ │ │ │ │ │ │ ├── MyUserDocumentationTests.kt │ │ │ │ │ │ │ └── UserController.kt │ │ │ │ │ │ └── withwebtestclient/ │ │ │ │ │ │ ├── MyRestDocsConfiguration.kt │ │ │ │ │ │ ├── MyUsersDocumentationTests.kt │ │ │ │ │ │ └── MyWebTestClientBuilderCustomizerConfiguration.kt │ │ │ │ │ ├── autoconfiguredwebservices/ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ ├── MyWebServiceClientTests.kt │ │ │ │ │ │ │ ├── Request.kt │ │ │ │ │ │ │ ├── Response.kt │ │ │ │ │ │ │ └── SomeWebService.kt │ │ │ │ │ │ └── server/ │ │ │ │ │ │ ├── ExampleEndpoint.kt │ │ │ │ │ │ └── MyWebServiceServerTests.kt │ │ │ │ │ ├── detectingwebapptype/ │ │ │ │ │ │ └── MyWebFluxTests.kt │ │ │ │ │ ├── excludingconfiguration/ │ │ │ │ │ │ ├── MyTests.kt │ │ │ │ │ │ └── MyTestsConfiguration.kt │ │ │ │ │ ├── jmx/ │ │ │ │ │ │ ├── MyJmxTests.kt │ │ │ │ │ │ └── SampleApp.kt │ │ │ │ │ ├── jsontests/ │ │ │ │ │ │ ├── MyJsonAssertJTests.kt │ │ │ │ │ │ ├── MyJsonTests.kt │ │ │ │ │ │ ├── SomeObject.kt │ │ │ │ │ │ └── VehicleDetails.kt │ │ │ │ │ ├── springgraphqltests/ │ │ │ │ │ │ ├── GraphQlIntegrationTests.kt │ │ │ │ │ │ └── GreetingControllerTests.kt │ │ │ │ │ ├── springmvctests/ │ │ │ │ │ │ ├── MyControllerTests.kt │ │ │ │ │ │ ├── MyHtmlUnitTests.kt │ │ │ │ │ │ ├── UserVehicleController.kt │ │ │ │ │ │ ├── UserVehicleService.kt │ │ │ │ │ │ └── VehicleDetails.kt │ │ │ │ │ ├── springwebfluxtests/ │ │ │ │ │ │ ├── MyControllerTests.kt │ │ │ │ │ │ ├── UserVehicleController.kt │ │ │ │ │ │ ├── UserVehicleService.kt │ │ │ │ │ │ └── VehicleDetails.kt │ │ │ │ │ ├── userconfigurationandslicing/ │ │ │ │ │ │ ├── MyApplication.kt │ │ │ │ │ │ ├── MyMongoConfiguration.kt │ │ │ │ │ │ ├── MyWebConfiguration.kt │ │ │ │ │ │ ├── MyWebMvcConfigurer.kt │ │ │ │ │ │ └── scan/ │ │ │ │ │ │ └── MyApplication.kt │ │ │ │ │ ├── usingapplicationarguments/ │ │ │ │ │ │ └── MyApplicationArgumentTests.kt │ │ │ │ │ ├── usingmain/ │ │ │ │ │ │ ├── always/ │ │ │ │ │ │ │ └── MyApplicationTests.kt │ │ │ │ │ │ ├── custom/ │ │ │ │ │ │ │ └── MyApplication.kt │ │ │ │ │ │ └── typical/ │ │ │ │ │ │ └── MyApplication.kt │ │ │ │ │ ├── withmockenvironment/ │ │ │ │ │ │ ├── MyMockMvcTests.kt │ │ │ │ │ │ └── MyMockWebTestClientTests.kt │ │ │ │ │ └── withrunningserver/ │ │ │ │ │ ├── MyRandomPortRestTestClientAssertJTests.kt │ │ │ │ │ ├── MyRandomPortRestTestClientTests.kt │ │ │ │ │ ├── MyRandomPortTestRestTemplateTests.kt │ │ │ │ │ └── MyRandomPortWebTestClientTests.kt │ │ │ │ ├── testcontainers/ │ │ │ │ │ ├── dynamicproperties/ │ │ │ │ │ │ └── MyIntegrationTests.kt │ │ │ │ │ ├── importingconfigurationinterfaces/ │ │ │ │ │ │ ├── MyContainers.kt │ │ │ │ │ │ └── MyTestConfiguration.kt │ │ │ │ │ ├── junitextension/ │ │ │ │ │ │ └── MyIntegrationTests.kt │ │ │ │ │ ├── serviceconnections/ │ │ │ │ │ │ ├── MyIntegrationTests.kt │ │ │ │ │ │ └── MyRedisConfiguration.kt │ │ │ │ │ └── springbeans/ │ │ │ │ │ ├── MyIntegrationTests.kt │ │ │ │ │ └── MyTestConfiguration.kt │ │ │ │ └── utilities/ │ │ │ │ ├── configdataapplicationcontextinitializer/ │ │ │ │ │ ├── Config.kt │ │ │ │ │ └── MyConfigFileTests.kt │ │ │ │ ├── outputcapture/ │ │ │ │ │ └── MyOutputCaptureTests.kt │ │ │ │ ├── testpropertyvalues/ │ │ │ │ │ └── MyEnvironmentTests.kt │ │ │ │ └── testresttemplate/ │ │ │ │ ├── MySpringBootTests.kt │ │ │ │ ├── MySpringBootTestsConfiguration.kt │ │ │ │ └── MyTests.kt │ │ │ ├── using/ │ │ │ │ ├── autoconfiguration/ │ │ │ │ │ └── disablingspecific/ │ │ │ │ │ └── MyApplication.kt │ │ │ │ ├── devtools/ │ │ │ │ │ └── restart/ │ │ │ │ │ └── disable/ │ │ │ │ │ └── MyApplication.kt │ │ │ │ ├── springbeansanddependencyinjection/ │ │ │ │ │ ├── multipleconstructors/ │ │ │ │ │ │ ├── AccountService.kt │ │ │ │ │ │ ├── MyAccountService.kt │ │ │ │ │ │ └── RiskAssessor.kt │ │ │ │ │ └── singleconstructor/ │ │ │ │ │ ├── AccountService.kt │ │ │ │ │ ├── MyAccountService.kt │ │ │ │ │ └── RiskAssessor.kt │ │ │ │ ├── structuringyourcode/ │ │ │ │ │ └── locatingthemainclass/ │ │ │ │ │ └── MyApplication.kt │ │ │ │ └── usingthespringbootapplicationannotation/ │ │ │ │ ├── individualannotations/ │ │ │ │ │ ├── AnotherConfiguration.kt │ │ │ │ │ ├── MyApplication.kt │ │ │ │ │ └── SomeConfiguration.kt │ │ │ │ └── springapplication/ │ │ │ │ └── MyApplication.kt │ │ │ └── web/ │ │ │ ├── graphql/ │ │ │ │ ├── runtimewiring/ │ │ │ │ │ └── GreetingController.kt │ │ │ │ └── transports/ │ │ │ │ └── rsocket/ │ │ │ │ └── RSocketGraphQlClientExample.kt │ │ │ ├── reactive/ │ │ │ │ ├── reactiveserver/ │ │ │ │ │ └── customizing/ │ │ │ │ │ └── programmatic/ │ │ │ │ │ ├── MyNettyWebServerFactoryCustomizer.kt │ │ │ │ │ └── MyWebServerFactoryCustomizer.kt │ │ │ │ └── webflux/ │ │ │ │ ├── Customer.kt │ │ │ │ ├── CustomerRepository.kt │ │ │ │ ├── MyRestController.kt │ │ │ │ ├── MyRoutingConfiguration.kt │ │ │ │ ├── MyUserHandler.kt │ │ │ │ ├── User.kt │ │ │ │ ├── UserRepository.kt │ │ │ │ ├── errorhandling/ │ │ │ │ │ └── MyErrorWebExceptionHandler.kt │ │ │ │ └── httpcodecs/ │ │ │ │ └── MyCodecsConfiguration.kt │ │ │ ├── security/ │ │ │ │ ├── oauth2/ │ │ │ │ │ └── client/ │ │ │ │ │ └── MyOAuthClientConfiguration.kt │ │ │ │ └── springwebflux/ │ │ │ │ └── MyWebFluxSecurityConfiguration.kt │ │ │ └── servlet/ │ │ │ ├── embeddedcontainer/ │ │ │ │ ├── applicationcontext/ │ │ │ │ │ └── MyDemoBean.kt │ │ │ │ └── customizing/ │ │ │ │ ├── programmatic/ │ │ │ │ │ ├── MyTomcatWebServerFactoryCustomizer.kt │ │ │ │ │ └── MyWebServerFactoryCustomizer.kt │ │ │ │ └── samesite/ │ │ │ │ └── MySameSiteConfiguration.kt │ │ │ ├── jersey/ │ │ │ │ ├── MyEndpoint.kt │ │ │ │ └── MyJerseyConfig.kt │ │ │ └── springmvc/ │ │ │ ├── Customer.kt │ │ │ ├── CustomerRepository.kt │ │ │ ├── MyRestController.kt │ │ │ ├── MyRoutingConfiguration.kt │ │ │ ├── MyUserHandler.kt │ │ │ ├── User.kt │ │ │ ├── UserRepository.kt │ │ │ ├── cors/ │ │ │ │ └── MyCorsConfiguration.kt │ │ │ ├── errorhandling/ │ │ │ │ ├── CustomException.kt │ │ │ │ ├── MyControllerAdvice.kt │ │ │ │ ├── MyErrorBody.kt │ │ │ │ ├── MyException.kt │ │ │ │ ├── SomeController.kt │ │ │ │ ├── errorpages/ │ │ │ │ │ └── MyErrorViewResolver.kt │ │ │ │ └── errorpageswithoutspringmvc/ │ │ │ │ ├── MyErrorPagesConfiguration.kt │ │ │ │ ├── MyFilter.kt │ │ │ │ ├── MyFilterConfiguration.kt │ │ │ │ └── SomeController.kt │ │ │ └── messageconverters/ │ │ │ ├── AdditionalHttpMessageConverter.kt │ │ │ ├── AnotherHttpMessageConverter.kt │ │ │ └── MyHttpMessageConvertersConfiguration.kt │ │ └── resources/ │ │ └── graphql/ │ │ └── schema.graphqls │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── docs/ │ │ ├── features/ │ │ │ ├── developingautoconfiguration/ │ │ │ │ └── testing/ │ │ │ │ └── MyServiceAutoConfigurationTestsTests.java │ │ │ ├── externalconfig/ │ │ │ │ └── typesafeconfigurationproperties/ │ │ │ │ └── conversion/ │ │ │ │ └── durations/ │ │ │ │ ├── constructorbinding/ │ │ │ │ │ └── MyPropertiesTests.java │ │ │ │ └── javabeanbinding/ │ │ │ │ └── MyPropertiesTests.java │ │ │ └── springapplication/ │ │ │ └── fluentbuilderapi/ │ │ │ └── MyApplicationTests.java │ │ ├── howto/ │ │ │ ├── actuator/ │ │ │ │ └── maphealthindicatorstometrics/ │ │ │ │ └── MetricsHealthMicrometerExportTests.java │ │ │ ├── application/ │ │ │ │ └── customizetheenvironmentorapplicationcontext/ │ │ │ │ └── MyEnvironmentPostProcessorTests.java │ │ │ └── dataaccess/ │ │ │ ├── SampleApp.java │ │ │ ├── configurecustomdatasource/ │ │ │ │ └── builder/ │ │ │ │ ├── MyDataSourceConfigurationTests.java │ │ │ │ ├── configurable/ │ │ │ │ │ └── MyDataSourceConfigurationTests.java │ │ │ │ └── simple/ │ │ │ │ └── MyDataSourceConfigurationTests.java │ │ │ └── configuretwodatasources/ │ │ │ ├── MyCompleteDataSourcesConfigurationTests.java │ │ │ └── MyDataSourcesConfigurationTests.java │ │ └── testing/ │ │ ├── springbootapplications/ │ │ │ └── jmx/ │ │ │ └── MyJmxTestsTests.java │ │ └── utilities/ │ │ ├── outputcapture/ │ │ │ └── MyOutputCaptureTestsTests.java │ │ └── testresttemplate/ │ │ └── MySpringBootTestsTests.java │ └── resources/ │ └── com/ │ └── example/ │ └── myapp/ │ └── config.yml ├── eclipse/ │ ├── eclipse.properties │ └── spring-boot-project.setup ├── git/ │ └── hooks/ │ ├── forward-merge │ └── prepare-forward-merge ├── gradle/ │ ├── plugins/ │ │ ├── config/ │ │ │ └── checkstyle/ │ │ │ └── checkstyle.xml │ │ ├── cycle-detection-plugin/ │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── build/ │ │ │ └── cycledetection/ │ │ │ └── CycleDetectionPlugin.java │ │ └── settings.gradle │ └── wrapper/ │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── integration-test/ │ ├── spring-boot-actuator-integration-tests/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── actuate/ │ │ │ ├── audit/ │ │ │ │ └── AuditEventsEndpointWebIntegrationTests.java │ │ │ ├── autoconfigure/ │ │ │ │ └── endpoint/ │ │ │ │ ├── jmx/ │ │ │ │ │ ├── JmxEndpointAccessIntegrationTests.java │ │ │ │ │ └── JmxEndpointIntegrationTests.java │ │ │ │ └── web/ │ │ │ │ └── WebEndpointsAutoConfigurationIntegrationTests.java │ │ │ ├── context/ │ │ │ │ └── properties/ │ │ │ │ └── ConfigurationPropertiesReportEndpointWebIntegrationTests.java │ │ │ ├── endpoint/ │ │ │ │ └── web/ │ │ │ │ ├── annotation/ │ │ │ │ │ ├── AbstractWebEndpointIntegrationTests.java │ │ │ │ │ └── BaseConfiguration.java │ │ │ │ ├── jersey/ │ │ │ │ │ └── JerseyWebEndpointIntegrationTests.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── ControllerEndpointHandlerMappingIntegrationTests.java │ │ │ │ │ └── WebFluxEndpointIntegrationTests.java │ │ │ │ └── servlet/ │ │ │ │ ├── ControllerEndpointHandlerMappingIntegrationTests.java │ │ │ │ └── MvcWebEndpointIntegrationTests.java │ │ │ ├── env/ │ │ │ │ └── EnvironmentEndpointWebIntegrationTests.java │ │ │ ├── health/ │ │ │ │ ├── HealthEndpointWebIntegrationTests.java │ │ │ │ └── TestHealthEndpointGroup.java │ │ │ ├── info/ │ │ │ │ └── InfoEndpointWebIntegrationTests.java │ │ │ ├── logging/ │ │ │ │ ├── LogFileWebEndpointWebIntegrationTests.java │ │ │ │ └── LoggersEndpointWebIntegrationTests.java │ │ │ ├── management/ │ │ │ │ ├── HeapDumpWebEndpointWebIntegrationTests.java │ │ │ │ └── ThreadDumpEndpointWebIntegrationTests.java │ │ │ ├── metrics/ │ │ │ │ ├── MetricsEndpointWebIntegrationTests.java │ │ │ │ └── export/ │ │ │ │ └── prometheus/ │ │ │ │ └── PrometheusScrapeEndpointIntegrationTests.java │ │ │ ├── sbom/ │ │ │ │ ├── SbomEndpointCycloneDxWebIntegrationTests.java │ │ │ │ ├── SbomEndpointSpdxWebIntegrationTests.java │ │ │ │ ├── SbomEndpointSyftWebIntegrationTests.java │ │ │ │ └── SbomEndpointWebIntegrationTests.java │ │ │ └── web/ │ │ │ └── mappings/ │ │ │ └── MappingsEndpointIntegrationTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── actuate/ │ │ └── sbom/ │ │ ├── cyclonedx.json │ │ ├── spdx.json │ │ └── syft.json │ ├── spring-boot-configuration-processor-integration-tests/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── sample/ │ │ │ ├── AnnotatedSample.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── configurationprocessor/ │ │ └── tests/ │ │ └── ConfigurationProcessorIntegrationTests.java │ ├── spring-boot-integration-tests/ │ │ ├── build.gradle │ │ └── src/ │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── web/ │ │ └── servlet/ │ │ └── support/ │ │ └── ErrorPageFilterIntegrationTests.java │ ├── spring-boot-loader-integration-tests/ │ │ ├── build.gradle │ │ ├── spring-boot-loader-tests-app/ │ │ │ ├── build.gradle │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── loaderapp/ │ │ │ │ └── LoaderTestApplication.java │ │ │ └── resources/ │ │ │ └── gh-7161/ │ │ │ └── example.txt │ │ ├── spring-boot-loader-tests-signed-jar/ │ │ │ ├── build.gradle │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── loaderapp/ │ │ │ └── LoaderSignedJarTestApplication.java │ │ └── src/ │ │ └── dockerTest/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── loader/ │ │ │ └── LoaderIntegrationTests.java │ │ └── resources/ │ │ ├── conf/ │ │ │ └── oracle-jdk-17/ │ │ │ ├── Dockerfile │ │ │ └── README.adoc │ │ └── logback.xml │ ├── spring-boot-server-integration-tests/ │ │ ├── build.gradle │ │ ├── spring-boot-server-tests-app/ │ │ │ ├── build.gradle │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ ├── autoconfig/ │ │ │ │ │ └── ExampleAutoConfiguration.java │ │ │ │ └── example/ │ │ │ │ ├── JettyServerCustomizerConfig.java │ │ │ │ └── ResourceHandlingApplication.java │ │ │ ├── resources/ │ │ │ │ └── META-INF/ │ │ │ │ └── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── webapp/ │ │ │ └── webapp-resource.txt │ │ └── src/ │ │ └── intTest/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── context/ │ │ └── embedded/ │ │ ├── AbstractApplicationLauncher.java │ │ ├── Application.java │ │ ├── BootRunApplicationLauncher.java │ │ ├── EmbeddedServerContainerInvocationContextProvider.java │ │ ├── EmbeddedServletContainerJarDevelopmentIntegrationTests.java │ │ ├── EmbeddedServletContainerJarPackagingIntegrationTests.java │ │ ├── EmbeddedServletContainerTest.java │ │ ├── EmbeddedServletContainerWarDevelopmentIntegrationTests.java │ │ ├── EmbeddedServletContainerWarPackagingIntegrationTests.java │ │ ├── ExplodedApplicationLauncher.java │ │ ├── IdeApplicationLauncher.java │ │ └── PackagedApplicationLauncher.java │ ├── spring-boot-sni-integration-tests/ │ │ ├── build.gradle │ │ ├── create-certs.sh │ │ ├── spring-boot-sni-client-app/ │ │ │ ├── build.gradle │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── sni/ │ │ │ │ └── client/ │ │ │ │ └── SniClientApplication.java │ │ │ └── resources/ │ │ │ ├── application.yml │ │ │ └── ca/ │ │ │ └── test-ca.crt │ │ ├── spring-boot-sni-reactive-app/ │ │ │ ├── build.gradle │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── sni/ │ │ │ │ └── server/ │ │ │ │ ├── HelloController.java │ │ │ │ └── SniServerApplication.java │ │ │ └── resources/ │ │ │ ├── alt/ │ │ │ │ ├── test-hello-alt-server.crt │ │ │ │ └── test-hello-alt-server.key │ │ │ ├── application.yml │ │ │ ├── ca/ │ │ │ │ └── test-ca.crt │ │ │ └── default/ │ │ │ ├── test-hello-server.crt │ │ │ └── test-hello-server.key │ │ ├── spring-boot-sni-servlet-app/ │ │ │ ├── build.gradle │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── sni/ │ │ │ │ └── server/ │ │ │ │ ├── HelloController.java │ │ │ │ └── SniServerApplication.java │ │ │ └── resources/ │ │ │ ├── alt/ │ │ │ │ ├── test-hello-alt-server.crt │ │ │ │ └── test-hello-alt-server.key │ │ │ ├── application.yml │ │ │ ├── ca/ │ │ │ │ └── test-ca.crt │ │ │ └── default/ │ │ │ ├── test-hello-server.crt │ │ │ └── test-hello-server.key │ │ └── src/ │ │ └── intTest/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── sni/ │ │ └── SniIntegrationTests.java │ └── spring-boot-test-integration-tests/ │ ├── build.gradle │ └── src/ │ └── test/ │ └── java/ │ └── org/ │ └── springframework/ │ └── boot/ │ └── web/ │ └── server/ │ └── test/ │ ├── AbstractSpringBootTestEmbeddedReactiveWebEnvironmentTests.java │ ├── AbstractSpringBootTestWebServerWebEnvironmentTests.java │ ├── AutoConfigureWebServerWebMvcIntegrationTests.java │ ├── SpringBootTestReactiveWebEnvironmentDefinedPortTests.java │ ├── SpringBootTestReactiveWebEnvironmentRandomPortTests.java │ ├── SpringBootTestReactiveWebEnvironmentUserDefinedTestRestTemplateTests.java │ ├── SpringBootTestUserDefinedTestRestTemplateTests.java │ ├── SpringBootTestWebEnvironmentContextHierarchyTests.java │ ├── SpringBootTestWebEnvironmentDefinedPortTests.java │ ├── SpringBootTestWebEnvironmentRandomPortCustomPortTests.java │ └── SpringBootTestWebEnvironmentRandomPortTests.java ├── loader/ │ ├── spring-boot-jarmode-tools/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jarmode/ │ │ │ │ └── tools/ │ │ │ │ ├── Command.java │ │ │ │ ├── Context.java │ │ │ │ ├── ExtractCommand.java │ │ │ │ ├── ExtractLayersCommand.java │ │ │ │ ├── HelpCommand.java │ │ │ │ ├── IndexedJarStructure.java │ │ │ │ ├── IndexedLayers.java │ │ │ │ ├── JarStructure.java │ │ │ │ ├── Layers.java │ │ │ │ ├── ListCommand.java │ │ │ │ ├── ListLayersCommand.java │ │ │ │ ├── MissingValueException.java │ │ │ │ ├── Runner.java │ │ │ │ ├── ToolsJarMode.java │ │ │ │ ├── UnknownOptionException.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── jarmode/ │ │ │ └── tools/ │ │ │ ├── AbstractJarModeTests.java │ │ │ ├── CommandTests.java │ │ │ ├── ContextTests.java │ │ │ ├── ExtractCommandTests.java │ │ │ ├── ExtractLayersCommandTests.java │ │ │ ├── HelpCommandTests.java │ │ │ ├── IndexedJarStructureTests.java │ │ │ ├── IndexedLayersTests.java │ │ │ ├── ListCommandTests.java │ │ │ ├── ListLayersCommandTests.java │ │ │ ├── TestCommand.java │ │ │ ├── TestPrintStream.java │ │ │ └── ToolsJarModeTests.java │ │ └── resources/ │ │ ├── jar-contents/ │ │ │ ├── JarLauncher │ │ │ ├── application.properties │ │ │ ├── build-info.properties │ │ │ ├── classpath.idx │ │ │ ├── dependency-1 │ │ │ ├── dependency-2 │ │ │ ├── dependency-3-SNAPSHOT │ │ │ ├── empty-file │ │ │ └── layers.idx │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── jarmode/ │ │ └── tools/ │ │ ├── help-output.txt │ │ ├── help-test-output.txt │ │ ├── list-layers-output.txt │ │ ├── list-output-without-deprecation.txt │ │ ├── test-layers.idx │ │ ├── test-manifest.MF │ │ ├── test-war-layers.idx │ │ ├── test-war-manifest.MF │ │ ├── tools-error-command-unknown-output.txt │ │ ├── tools-error-option-missing-value-output.txt │ │ ├── tools-error-option-unknown-output.txt │ │ ├── tools-help-extract-output.txt │ │ ├── tools-help-help-output.txt │ │ ├── tools-help-list-layers-output.txt │ │ ├── tools-help-output.txt │ │ └── tools-help-unknown-command-output.txt │ ├── spring-boot-loader/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── loader/ │ │ │ │ ├── jar/ │ │ │ │ │ ├── JarEntriesStream.java │ │ │ │ │ ├── ManifestInfo.java │ │ │ │ │ ├── MetaInfVersionsInfo.java │ │ │ │ │ ├── NestedJarFile.java │ │ │ │ │ ├── NestedJarFileResources.java │ │ │ │ │ ├── SecurityInfo.java │ │ │ │ │ ├── ZipInflaterInputStream.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── jarmode/ │ │ │ │ │ ├── JarMode.java │ │ │ │ │ ├── JarModeErrorException.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── launch/ │ │ │ │ │ ├── Archive.java │ │ │ │ │ ├── ClassPathIndexFile.java │ │ │ │ │ ├── ExecutableArchiveLauncher.java │ │ │ │ │ ├── ExplodedArchive.java │ │ │ │ │ ├── JarFileArchive.java │ │ │ │ │ ├── JarLauncher.java │ │ │ │ │ ├── JarModeRunner.java │ │ │ │ │ ├── LaunchedClassLoader.java │ │ │ │ │ ├── Launcher.java │ │ │ │ │ ├── PropertiesLauncher.java │ │ │ │ │ ├── SystemPropertyUtils.java │ │ │ │ │ ├── WarLauncher.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── log/ │ │ │ │ │ ├── DebugLogger.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── net/ │ │ │ │ │ ├── protocol/ │ │ │ │ │ │ ├── Handlers.java │ │ │ │ │ │ ├── jar/ │ │ │ │ │ │ │ ├── Canonicalizer.java │ │ │ │ │ │ │ ├── Handler.java │ │ │ │ │ │ │ ├── JarFileUrlKey.java │ │ │ │ │ │ │ ├── JarUrl.java │ │ │ │ │ │ │ ├── JarUrlClassLoader.java │ │ │ │ │ │ │ ├── JarUrlConnection.java │ │ │ │ │ │ │ ├── LazyDelegatingInputStream.java │ │ │ │ │ │ │ ├── Optimizations.java │ │ │ │ │ │ │ ├── UrlJarEntry.java │ │ │ │ │ │ │ ├── UrlJarFile.java │ │ │ │ │ │ │ ├── UrlJarFileFactory.java │ │ │ │ │ │ │ ├── UrlJarFiles.java │ │ │ │ │ │ │ ├── UrlJarManifest.java │ │ │ │ │ │ │ ├── UrlNestedJarFile.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── nested/ │ │ │ │ │ │ │ ├── Handler.java │ │ │ │ │ │ │ ├── NestedLocation.java │ │ │ │ │ │ │ ├── NestedUrlConnection.java │ │ │ │ │ │ │ ├── NestedUrlConnectionResources.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── util/ │ │ │ │ │ ├── UrlDecoder.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── nio/ │ │ │ │ │ └── file/ │ │ │ │ │ ├── NestedByteChannel.java │ │ │ │ │ ├── NestedFileStore.java │ │ │ │ │ ├── NestedFileSystem.java │ │ │ │ │ ├── NestedFileSystemProvider.java │ │ │ │ │ ├── NestedPath.java │ │ │ │ │ ├── UriPathEncoder.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── ref/ │ │ │ │ │ ├── Cleaner.java │ │ │ │ │ ├── DefaultCleaner.java │ │ │ │ │ └── package-info.java │ │ │ │ └── zip/ │ │ │ │ ├── ByteArrayDataBlock.java │ │ │ │ ├── CloseableDataBlock.java │ │ │ │ ├── DataBlock.java │ │ │ │ ├── DataBlockInputStream.java │ │ │ │ ├── FileDataBlock.java │ │ │ │ ├── NameOffsetLookups.java │ │ │ │ ├── VirtualDataBlock.java │ │ │ │ ├── VirtualZipDataBlock.java │ │ │ │ ├── Zip64EndOfCentralDirectoryLocator.java │ │ │ │ ├── Zip64EndOfCentralDirectoryRecord.java │ │ │ │ ├── ZipCentralDirectoryFileHeaderRecord.java │ │ │ │ ├── ZipContent.java │ │ │ │ ├── ZipDataDescriptorRecord.java │ │ │ │ ├── ZipEndOfCentralDirectoryRecord.java │ │ │ │ ├── ZipLocalFileHeaderRecord.java │ │ │ │ ├── ZipString.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── java.nio.file.spi.FileSystemProvider │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── loader/ │ │ │ ├── jar/ │ │ │ │ ├── ManifestInfoTests.java │ │ │ │ ├── MetaInfVersionsInfoTests.java │ │ │ │ ├── NestedJarFileTests.java │ │ │ │ └── SecurityInfoTests.java │ │ │ ├── jarmode/ │ │ │ │ └── TestJarMode.java │ │ │ ├── launch/ │ │ │ │ ├── AbstractLauncherTests.java │ │ │ │ ├── ArchiveTests.java │ │ │ │ ├── ClassPathIndexFileTests.java │ │ │ │ ├── ExplodedArchiveTests.java │ │ │ │ ├── JarFileArchiveTests.java │ │ │ │ ├── JarLauncherTests.java │ │ │ │ ├── LaunchedClassLoaderTests.java │ │ │ │ ├── LauncherTests.java │ │ │ │ ├── PropertiesLauncherTests.java │ │ │ │ └── WarLauncherTests.java │ │ │ ├── net/ │ │ │ │ ├── protocol/ │ │ │ │ │ ├── jar/ │ │ │ │ │ │ ├── CanonicalizerTests.java │ │ │ │ │ │ ├── HandlerTests.java │ │ │ │ │ │ ├── JarFileUrlKeyTests.java │ │ │ │ │ │ ├── JarUrlClassLoaderTests.java │ │ │ │ │ │ ├── JarUrlConnectionTests.java │ │ │ │ │ │ ├── JarUrlTests.java │ │ │ │ │ │ ├── LazyDelegatingInputStreamTests.java │ │ │ │ │ │ ├── OptimizationsTests.java │ │ │ │ │ │ ├── UrlJarEntryTests.java │ │ │ │ │ │ ├── UrlJarFileFactoryTests.java │ │ │ │ │ │ ├── UrlJarFileTests.java │ │ │ │ │ │ ├── UrlJarFilesTests.java │ │ │ │ │ │ ├── UrlJarManifestTests.java │ │ │ │ │ │ └── UrlNestedJarFileTests.java │ │ │ │ │ └── nested/ │ │ │ │ │ ├── HandlerTests.java │ │ │ │ │ ├── NestedLocationTests.java │ │ │ │ │ └── NestedUrlConnectionTests.java │ │ │ │ └── util/ │ │ │ │ └── UrlDecoderTests.java │ │ │ ├── nio/ │ │ │ │ └── file/ │ │ │ │ ├── NestedByteChannelTests.java │ │ │ │ ├── NestedFileStoreTests.java │ │ │ │ ├── NestedFileSystemProviderTests.java │ │ │ │ ├── NestedFileSystemTests.java │ │ │ │ ├── NestedFileSystemZipFileSystemIntegrationTests.java │ │ │ │ ├── NestedPathTests.java │ │ │ │ └── UriPathEncoderTests.java │ │ │ ├── ref/ │ │ │ │ └── DefaultCleanerTracking.java │ │ │ ├── testsupport/ │ │ │ │ └── TestJar.java │ │ │ └── zip/ │ │ │ ├── AssertFileChannelDataBlocksClosed.java │ │ │ ├── AssertFileChannelDataBlocksClosedExtension.java │ │ │ ├── ByteArrayDataBlockTests.java │ │ │ ├── DataBlockInputStreamTests.java │ │ │ ├── DataBlockTests.java │ │ │ ├── FileChannelDataBlockManagedFileChannel.java │ │ │ ├── FileDataBlockTests.java │ │ │ ├── VirtualDataBlockTests.java │ │ │ ├── VirtualZipDataBlockTests.java │ │ │ ├── VirtualZipPerformanceTests.java │ │ │ ├── Zip64EndOfCentralDirectoryLocatorTests.java │ │ │ ├── Zip64EndOfCentralDirectoryRecordTests.java │ │ │ ├── ZipCentralDirectoryFileHeaderRecordTests.java │ │ │ ├── ZipContentTests.java │ │ │ ├── ZipDataDescriptorRecordTests.java │ │ │ ├── ZipEndOfCentralDirectoryRecordTests.java │ │ │ ├── ZipLocalFileHeaderRecordTests.java │ │ │ └── ZipStringTests.java │ │ └── resources/ │ │ ├── BOOT-INF/ │ │ │ └── classes/ │ │ │ ├── application.properties │ │ │ ├── bar.properties │ │ │ ├── foo.properties │ │ │ └── loader.properties │ │ ├── META-INF/ │ │ │ └── spring.factories │ │ ├── bar.properties │ │ ├── explodedsample/ │ │ │ └── ExampleClass.txt │ │ ├── home/ │ │ │ └── loader.properties │ │ ├── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── loader/ │ │ │ └── launch/ │ │ │ └── classpath-index-file.idx │ │ ├── placeholders/ │ │ │ └── loader.properties │ │ └── root/ │ │ └── META-INF/ │ │ └── spring/ │ │ └── application.xml │ └── spring-boot-loader-tools/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── loader/ │ │ └── tools/ │ │ ├── AbstractJarWriter.java │ │ ├── BuildPropertiesWriter.java │ │ ├── CustomLoaderLayout.java │ │ ├── DefaultLayoutFactory.java │ │ ├── DefaultLibraryCoordinates.java │ │ ├── DefaultTimeZoneOffset.java │ │ ├── EntryWriter.java │ │ ├── FileUtils.java │ │ ├── ImagePackager.java │ │ ├── ImplicitLayerResolver.java │ │ ├── InputStreamSupplier.java │ │ ├── JarModeLibrary.java │ │ ├── JarWriter.java │ │ ├── JavaExecutable.java │ │ ├── Layer.java │ │ ├── Layers.java │ │ ├── LayersIndex.java │ │ ├── Layout.java │ │ ├── LayoutFactory.java │ │ ├── Layouts.java │ │ ├── Libraries.java │ │ ├── Library.java │ │ ├── LibraryCallback.java │ │ ├── LibraryCoordinates.java │ │ ├── LibraryScope.java │ │ ├── LoaderClassesWriter.java │ │ ├── LogbackInitializer.java │ │ ├── MainClassFinder.java │ │ ├── NativeImageArgFile.java │ │ ├── Packager.java │ │ ├── ReachabilityMetadataProperties.java │ │ ├── Repackager.java │ │ ├── RepackagingLayout.java │ │ ├── RunProcess.java │ │ ├── SignalUtils.java │ │ ├── SizeCalculatingEntryWriter.java │ │ ├── StandardLayers.java │ │ ├── ZipHeaderPeekInputStream.java │ │ ├── layer/ │ │ │ ├── ApplicationContentFilter.java │ │ │ ├── ContentFilter.java │ │ │ ├── ContentSelector.java │ │ │ ├── CustomLayers.java │ │ │ ├── IncludeExcludeContentSelector.java │ │ │ ├── LibraryContentFilter.java │ │ │ └── package-info.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── loader/ │ │ └── tools/ │ │ ├── AbstractPackagerTests.java │ │ ├── DefaultTimeZoneOffsetTests.java │ │ ├── FileUtilsTests.java │ │ ├── ImagePackagerTests.java │ │ ├── ImplicitLayerResolverTests.java │ │ ├── LayerTests.java │ │ ├── LayersIndexTests.java │ │ ├── LayoutsTests.java │ │ ├── LibraryCoordinatesTests.java │ │ ├── MainClassFinderTests.java │ │ ├── NativeImageArgFileTests.java │ │ ├── ReachabilityMetadataPropertiesTests.java │ │ ├── RepackagerTests.java │ │ ├── SizeCalculatingEntryWriterTests.java │ │ ├── TestJarFile.java │ │ ├── ZipHeaderPeekInputStreamTests.java │ │ ├── layer/ │ │ │ ├── ApplicationContentFilterTests.java │ │ │ ├── IncludeExcludeContentSelectorTests.java │ │ │ └── LibraryContentFilterTests.java │ │ └── sample/ │ │ ├── AnnotatedClassWithMainMethod.java │ │ ├── ClassWithMainMethod.java │ │ ├── ClassWithoutMainMethod.java │ │ ├── SomeApplication.java │ │ └── package-info.java │ └── resources/ │ ├── example.script │ └── org/ │ └── springframework/ │ └── boot/ │ └── loader/ │ └── tools/ │ ├── LayersIndexTests-.txt │ ├── LayersIndexTests-writeToWhenAllFilesInDirectoryAreInNotInSameLayerUsesFiles.txt │ ├── LayersIndexTests-writeToWhenAllFilesInDirectoryAreInSameLayerUsesDirectory.txt │ ├── LayersIndexTests-writeToWhenLayerNotUsedDoesNotSkipLayer.txt │ ├── LayersIndexTests-writeToWhenSimpleNamesSortsAlphabetically.txt │ ├── LayersIndexTests-writeToWhenSpaceInFileName.txt │ ├── LayersIndexTests-writeToWritesLayersInIteratorOrder.txt │ └── signed-manifest.mf ├── module/ │ ├── spring-boot-activemq/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── activemq/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── ActiveMQClassicDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ └── ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── ActiveMQClassicContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── ActiveMQContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── activemq/ │ │ │ └── docker/ │ │ │ └── compose/ │ │ │ ├── activemq-classic-compose.yaml │ │ │ ├── activemq-compose.yaml │ │ │ └── artemis-compose.yaml │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── activemq/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ActiveMQAutoConfiguration.java │ │ │ │ │ ├── ActiveMQConnectionDetails.java │ │ │ │ │ ├── ActiveMQConnectionFactoryConfiguration.java │ │ │ │ │ ├── ActiveMQConnectionFactoryConfigurer.java │ │ │ │ │ ├── ActiveMQConnectionFactoryCustomizer.java │ │ │ │ │ ├── ActiveMQProperties.java │ │ │ │ │ ├── ActiveMQXAConnectionFactoryConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── ActiveMQClassicDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── ActiveMQClassicEnvironment.java │ │ │ │ │ ├── ActiveMQDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── ActiveMQEnvironment.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── ActiveMQClassicContainerConnectionDetailsFactory.java │ │ │ │ ├── ActiveMQContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── activemq/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── ActiveMQAutoConfigurationTests.java │ │ │ │ └── ActiveMQPropertiesTests.java │ │ │ └── docker/ │ │ │ └── compose/ │ │ │ ├── ActiveMQClassicEnvironmentTests.java │ │ │ └── ActiveMQEnvironmentTests.java │ │ └── resources/ │ │ └── logback-test.xml │ ├── spring-boot-actuator/ │ │ ├── README.adoc │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── actuate/ │ │ │ │ ├── audit/ │ │ │ │ │ ├── AuditEvent.java │ │ │ │ │ ├── AuditEventRepository.java │ │ │ │ │ ├── AuditEventsEndpoint.java │ │ │ │ │ ├── InMemoryAuditEventRepository.java │ │ │ │ │ ├── listener/ │ │ │ │ │ │ ├── AbstractAuditListener.java │ │ │ │ │ │ ├── AuditApplicationEvent.java │ │ │ │ │ │ ├── AuditListener.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── beans/ │ │ │ │ │ ├── BeansEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── context/ │ │ │ │ │ ├── ShutdownEndpoint.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── properties/ │ │ │ │ │ ├── BeanSerializer.java │ │ │ │ │ ├── ConfigurationPropertiesReportEndpoint.java │ │ │ │ │ ├── ConfigurationPropertiesReportEndpointWebExtension.java │ │ │ │ │ ├── Jackson2BeanSerializer.java │ │ │ │ │ ├── JacksonBeanSerializer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── endpoint/ │ │ │ │ │ ├── AbstractExposableEndpoint.java │ │ │ │ │ ├── Access.java │ │ │ │ │ ├── ApiVersion.java │ │ │ │ │ ├── EndpointAccessResolver.java │ │ │ │ │ ├── EndpointFilter.java │ │ │ │ │ ├── EndpointId.java │ │ │ │ │ ├── EndpointsSupplier.java │ │ │ │ │ ├── ExposableEndpoint.java │ │ │ │ │ ├── InvalidEndpointRequestException.java │ │ │ │ │ ├── InvocationContext.java │ │ │ │ │ ├── Operation.java │ │ │ │ │ ├── OperationArgumentResolver.java │ │ │ │ │ ├── OperationFilter.java │ │ │ │ │ ├── OperationResponseBody.java │ │ │ │ │ ├── OperationResponseBodyMap.java │ │ │ │ │ ├── OperationType.java │ │ │ │ │ ├── Producible.java │ │ │ │ │ ├── ProducibleOperationArgumentResolver.java │ │ │ │ │ ├── SanitizableData.java │ │ │ │ │ ├── Sanitizer.java │ │ │ │ │ ├── SanitizingFunction.java │ │ │ │ │ ├── SecurityContext.java │ │ │ │ │ ├── Show.java │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ ├── AbstractDiscoveredEndpoint.java │ │ │ │ │ │ ├── AbstractDiscoveredOperation.java │ │ │ │ │ │ ├── DeleteOperation.java │ │ │ │ │ │ ├── DiscoveredEndpoint.java │ │ │ │ │ │ ├── DiscoveredOperationMethod.java │ │ │ │ │ │ ├── DiscoveredOperationsFactory.java │ │ │ │ │ │ ├── DiscovererEndpointFilter.java │ │ │ │ │ │ ├── Endpoint.java │ │ │ │ │ │ ├── EndpointConverter.java │ │ │ │ │ │ ├── EndpointDiscoverer.java │ │ │ │ │ │ ├── EndpointExtension.java │ │ │ │ │ │ ├── FilteredEndpoint.java │ │ │ │ │ │ ├── OperationReflectiveProcessor.java │ │ │ │ │ │ ├── ReadOperation.java │ │ │ │ │ │ ├── Selector.java │ │ │ │ │ │ ├── WriteOperation.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── invoke/ │ │ │ │ │ │ ├── MissingParametersException.java │ │ │ │ │ │ ├── OperationInvoker.java │ │ │ │ │ │ ├── OperationInvokerAdvisor.java │ │ │ │ │ │ ├── OperationParameter.java │ │ │ │ │ │ ├── OperationParameters.java │ │ │ │ │ │ ├── ParameterMappingException.java │ │ │ │ │ │ ├── ParameterValueMapper.java │ │ │ │ │ │ ├── convert/ │ │ │ │ │ │ │ ├── ConversionServiceParameterValueMapper.java │ │ │ │ │ │ │ ├── IsoOffsetDateTimeConverter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── package-info.java │ │ │ │ │ │ └── reflect/ │ │ │ │ │ │ ├── OperationMethod.java │ │ │ │ │ │ ├── OperationMethodParameter.java │ │ │ │ │ │ ├── OperationMethodParameters.java │ │ │ │ │ │ ├── ReflectiveOperationInvoker.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── invoker/ │ │ │ │ │ │ └── cache/ │ │ │ │ │ │ ├── CachingOperationInvoker.java │ │ │ │ │ │ ├── CachingOperationInvokerAdvisor.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── jackson/ │ │ │ │ │ │ ├── EndpointJackson2ObjectMapper.java │ │ │ │ │ │ ├── EndpointJsonMapper.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── jmx/ │ │ │ │ │ │ ├── EndpointMBean.java │ │ │ │ │ │ ├── EndpointObjectNameFactory.java │ │ │ │ │ │ ├── ExposableJmxEndpoint.java │ │ │ │ │ │ ├── Jackson2JmxOperationResponseMapper.java │ │ │ │ │ │ ├── JacksonJmxOperationResponseMapper.java │ │ │ │ │ │ ├── JmxEndpointExporter.java │ │ │ │ │ │ ├── JmxEndpointsSupplier.java │ │ │ │ │ │ ├── JmxOperation.java │ │ │ │ │ │ ├── JmxOperationParameter.java │ │ │ │ │ │ ├── JmxOperationResponseMapper.java │ │ │ │ │ │ ├── MBeanInfoFactory.java │ │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ │ ├── DiscoveredJmxEndpoint.java │ │ │ │ │ │ │ ├── DiscoveredJmxOperation.java │ │ │ │ │ │ │ ├── EndpointJmxExtension.java │ │ │ │ │ │ │ ├── JmxEndpoint.java │ │ │ │ │ │ │ ├── JmxEndpointDiscoverer.java │ │ │ │ │ │ │ ├── JmxEndpointFilter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── AdditionalPathsMapper.java │ │ │ │ │ ├── EndpointLinksResolver.java │ │ │ │ │ ├── EndpointMapping.java │ │ │ │ │ ├── EndpointMediaTypes.java │ │ │ │ │ ├── EndpointServlet.java │ │ │ │ │ ├── ExposableServletEndpoint.java │ │ │ │ │ ├── ExposableWebEndpoint.java │ │ │ │ │ ├── Link.java │ │ │ │ │ ├── PathMappedEndpoint.java │ │ │ │ │ ├── PathMappedEndpoints.java │ │ │ │ │ ├── PathMapper.java │ │ │ │ │ ├── ServletEndpointRegistrar.java │ │ │ │ │ ├── WebEndpointHttpMethod.java │ │ │ │ │ ├── WebEndpointResponse.java │ │ │ │ │ ├── WebEndpointsSupplier.java │ │ │ │ │ ├── WebOperation.java │ │ │ │ │ ├── WebOperationRequestPredicate.java │ │ │ │ │ ├── WebServerNamespace.java │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ ├── ControllerEndpoint.java │ │ │ │ │ │ ├── ControllerEndpointDiscoverer.java │ │ │ │ │ │ ├── ControllerEndpointFilter.java │ │ │ │ │ │ ├── ControllerEndpointsSupplier.java │ │ │ │ │ │ ├── DiscoveredControllerEndpoint.java │ │ │ │ │ │ ├── DiscoveredServletEndpoint.java │ │ │ │ │ │ ├── DiscoveredWebEndpoint.java │ │ │ │ │ │ ├── DiscoveredWebOperation.java │ │ │ │ │ │ ├── EndpointWebExtension.java │ │ │ │ │ │ ├── ExposableControllerEndpoint.java │ │ │ │ │ │ ├── RequestPredicateFactory.java │ │ │ │ │ │ ├── RestControllerEndpoint.java │ │ │ │ │ │ ├── ServletEndpoint.java │ │ │ │ │ │ ├── ServletEndpointDiscoverer.java │ │ │ │ │ │ ├── ServletEndpointFilter.java │ │ │ │ │ │ ├── ServletEndpointsSupplier.java │ │ │ │ │ │ ├── WebEndpoint.java │ │ │ │ │ │ ├── WebEndpointDiscoverer.java │ │ │ │ │ │ ├── WebEndpointFilter.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── env/ │ │ │ │ │ ├── EnvironmentEndpoint.java │ │ │ │ │ ├── EnvironmentEndpointWebExtension.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── info/ │ │ │ │ │ ├── BuildInfoContributor.java │ │ │ │ │ ├── EnvironmentInfoContributor.java │ │ │ │ │ ├── GitInfoContributor.java │ │ │ │ │ ├── Info.java │ │ │ │ │ ├── InfoContributor.java │ │ │ │ │ ├── InfoEndpoint.java │ │ │ │ │ ├── InfoPropertiesInfoContributor.java │ │ │ │ │ ├── JavaInfoContributor.java │ │ │ │ │ ├── MapInfoContributor.java │ │ │ │ │ ├── OsInfoContributor.java │ │ │ │ │ ├── ProcessInfoContributor.java │ │ │ │ │ ├── SimpleInfoContributor.java │ │ │ │ │ ├── SslInfoContributor.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── logging/ │ │ │ │ │ ├── LogFileWebEndpoint.java │ │ │ │ │ ├── LoggersEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── management/ │ │ │ │ │ ├── HeapDumpWebEndpoint.java │ │ │ │ │ ├── PlainTextThreadDumpFormatter.java │ │ │ │ │ ├── ThreadDumpEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── sbom/ │ │ │ │ │ ├── SbomEndpoint.java │ │ │ │ │ ├── SbomEndpointWebExtension.java │ │ │ │ │ ├── SbomProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── scheduling/ │ │ │ │ │ ├── ScheduledTasksEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── security/ │ │ │ │ │ ├── AbstractAuthenticationAuditListener.java │ │ │ │ │ ├── AbstractAuthorizationAuditListener.java │ │ │ │ │ ├── AuthenticationAuditListener.java │ │ │ │ │ ├── AuthorizationAuditListener.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── startup/ │ │ │ │ │ ├── StartupEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── exchanges/ │ │ │ │ │ ├── HttpExchange.java │ │ │ │ │ ├── HttpExchangeRepository.java │ │ │ │ │ ├── HttpExchangesEndpoint.java │ │ │ │ │ ├── InMemoryHttpExchangeRepository.java │ │ │ │ │ ├── Include.java │ │ │ │ │ ├── RecordableHttpRequest.java │ │ │ │ │ ├── RecordableHttpResponse.java │ │ │ │ │ └── package-info.java │ │ │ │ └── mappings/ │ │ │ │ ├── HandlerMethodDescription.java │ │ │ │ ├── MappingDescriptionProvider.java │ │ │ │ ├── MappingsEndpoint.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── additional-spring-configuration-metadata.json │ │ ├── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── actuate/ │ │ │ │ ├── audit/ │ │ │ │ │ ├── AuditEventTests.java │ │ │ │ │ ├── AuditEventsEndpointTests.java │ │ │ │ │ ├── InMemoryAuditEventRepositoryTests.java │ │ │ │ │ └── listener/ │ │ │ │ │ └── AuditListenerTests.java │ │ │ │ ├── beans/ │ │ │ │ │ └── BeansEndpointTests.java │ │ │ │ ├── context/ │ │ │ │ │ ├── ShutdownEndpointTests.java │ │ │ │ │ └── properties/ │ │ │ │ │ ├── ConfigurationPropertiesReportEndpointFilteringTests.java │ │ │ │ │ ├── ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java │ │ │ │ │ ├── ConfigurationPropertiesReportEndpointParentTests.java │ │ │ │ │ ├── ConfigurationPropertiesReportEndpointProxyTests.java │ │ │ │ │ ├── ConfigurationPropertiesReportEndpointSerializationTests.java │ │ │ │ │ ├── ConfigurationPropertiesReportEndpointTests.java │ │ │ │ │ ├── ConfigurationPropertiesReportEndpointWebExtensionTests.java │ │ │ │ │ └── ValidatedConstructorBindingProperties.java │ │ │ │ ├── endpoint/ │ │ │ │ │ ├── AccessTests.java │ │ │ │ │ ├── EndpointIdTests.java │ │ │ │ │ ├── InvocationContextTests.java │ │ │ │ │ ├── OperationFilterTests.java │ │ │ │ │ ├── OperationResponseBodyTests.java │ │ │ │ │ ├── ProducibleOperationArgumentResolverTests.java │ │ │ │ │ ├── SanitizableDataTests.java │ │ │ │ │ ├── SanitizerTests.java │ │ │ │ │ ├── SanitizingFunctionTests.java │ │ │ │ │ ├── ShowTests.java │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ ├── DiscoveredOperationMethodTests.java │ │ │ │ │ │ ├── DiscoveredOperationsFactoryTests.java │ │ │ │ │ │ ├── DiscovererEndpointFilterTests.java │ │ │ │ │ │ ├── EndpointDiscovererTests.java │ │ │ │ │ │ └── OperationReflectiveProcessorTests.java │ │ │ │ │ ├── invoke/ │ │ │ │ │ │ ├── convert/ │ │ │ │ │ │ │ ├── ConversionServiceParameterValueMapperTests.java │ │ │ │ │ │ │ └── IsoOffsetDateTimeConverterTests.java │ │ │ │ │ │ └── reflect/ │ │ │ │ │ │ ├── OperationMethodParameterTests.java │ │ │ │ │ │ ├── OperationMethodParametersTests.java │ │ │ │ │ │ ├── OperationMethodTests.java │ │ │ │ │ │ └── ReflectiveOperationInvokerTests.java │ │ │ │ │ ├── invoker/ │ │ │ │ │ │ └── cache/ │ │ │ │ │ │ ├── CachingOperationInvokerAdvisorTests.java │ │ │ │ │ │ └── CachingOperationInvokerTests.java │ │ │ │ │ ├── jmx/ │ │ │ │ │ │ ├── EndpointMBeanTests.java │ │ │ │ │ │ ├── JacksonJmxOperationResponseMapperTests.java │ │ │ │ │ │ ├── JmxEndpointExporterTests.java │ │ │ │ │ │ ├── MBeanInfoFactoryTests.java │ │ │ │ │ │ ├── TestExposableJmxEndpoint.java │ │ │ │ │ │ ├── TestJmxOperation.java │ │ │ │ │ │ ├── TestJmxOperationResponseMapper.java │ │ │ │ │ │ └── annotation/ │ │ │ │ │ │ ├── DiscoveredJmxOperationTests.java │ │ │ │ │ │ └── JmxEndpointDiscovererTests.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── EndpointLinksResolverTests.java │ │ │ │ │ ├── EndpointMappingTests.java │ │ │ │ │ ├── EndpointMediaTypesTests.java │ │ │ │ │ ├── EndpointServletTests.java │ │ │ │ │ ├── LinkTests.java │ │ │ │ │ ├── PathMappedEndpointsTests.java │ │ │ │ │ ├── ServletEndpointRegistrarTests.java │ │ │ │ │ ├── WebEndpointResponseTests.java │ │ │ │ │ ├── WebOperationRequestPredicateTests.java │ │ │ │ │ ├── WebServerNamespaceTests.java │ │ │ │ │ └── annotation/ │ │ │ │ │ ├── BaseConfiguration.java │ │ │ │ │ ├── ControllerEndpointDiscovererTests.java │ │ │ │ │ ├── RequestPredicateFactoryTests.java │ │ │ │ │ ├── ServletEndpointDiscovererTests.java │ │ │ │ │ └── WebEndpointDiscovererTests.java │ │ │ │ ├── env/ │ │ │ │ │ ├── EnvironmentEndpointTests.java │ │ │ │ │ └── EnvironmentEndpointWebExtensionTests.java │ │ │ │ ├── info/ │ │ │ │ │ ├── BuildInfoContributorTests.java │ │ │ │ │ ├── EnvironmentInfoContributorTests.java │ │ │ │ │ ├── GitInfoContributorTests.java │ │ │ │ │ ├── InfoEndpointTests.java │ │ │ │ │ ├── InfoTests.java │ │ │ │ │ ├── JavaInfoContributorTests.java │ │ │ │ │ ├── OsInfoContributorTests.java │ │ │ │ │ ├── ProcessInfoContributorTests.java │ │ │ │ │ ├── SimpleInfoContributorTests.java │ │ │ │ │ └── SslInfoContributorTests.java │ │ │ │ ├── logging/ │ │ │ │ │ ├── LogFileWebEndpointTests.java │ │ │ │ │ └── LoggersEndpointTests.java │ │ │ │ ├── management/ │ │ │ │ │ ├── HeapDumpWebEndpointTests.java │ │ │ │ │ └── ThreadDumpEndpointTests.java │ │ │ │ ├── sbom/ │ │ │ │ │ ├── SbomEndpointTests.java │ │ │ │ │ └── SbomEndpointWebExtensionTests.java │ │ │ │ ├── scheduling/ │ │ │ │ │ └── ScheduledTasksEndpointTests.java │ │ │ │ ├── security/ │ │ │ │ │ ├── AuthenticationAuditListenerTests.java │ │ │ │ │ └── AuthorizationAuditListenerTests.java │ │ │ │ ├── startup/ │ │ │ │ │ └── StartupEndpointTests.java │ │ │ │ └── web/ │ │ │ │ └── exchanges/ │ │ │ │ ├── HttpExchangeTests.java │ │ │ │ ├── HttpExchangesEndpointTests.java │ │ │ │ └── InMemoryHttpExchangeRepositoryTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── actuate/ │ │ │ └── sbom/ │ │ │ ├── cyclonedx.json │ │ │ ├── spdx.json │ │ │ └── syft.json │ │ └── testFixtures/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── actuate/ │ │ └── endpoint/ │ │ └── web/ │ │ └── test/ │ │ ├── WebEndpointInfrastructureProvider.java │ │ ├── WebEndpointTest.java │ │ ├── WebEndpointTestInvocationContextProvider.java │ │ └── package-info.java │ ├── spring-boot-actuator-autoconfigure/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── actuate/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── OnEndpointElementCondition.java │ │ │ │ ├── audit/ │ │ │ │ │ ├── AuditAutoConfiguration.java │ │ │ │ │ ├── AuditEventsEndpointAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── beans/ │ │ │ │ │ ├── BeansEndpointAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── condition/ │ │ │ │ │ ├── ConditionsReportEndpoint.java │ │ │ │ │ ├── ConditionsReportEndpointAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── context/ │ │ │ │ │ ├── ShutdownEndpointAutoConfiguration.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── properties/ │ │ │ │ │ ├── ConfigurationPropertiesReportEndpointAutoConfiguration.java │ │ │ │ │ ├── ConfigurationPropertiesReportEndpointProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── endpoint/ │ │ │ │ │ ├── EndpointAutoConfiguration.java │ │ │ │ │ ├── EndpointIdTimeToLivePropertyFunction.java │ │ │ │ │ ├── PropertiesEndpointAccessResolver.java │ │ │ │ │ ├── condition/ │ │ │ │ │ │ ├── ConditionalOnAvailableEndpoint.java │ │ │ │ │ │ ├── EndpointExposureOutcomeContributor.java │ │ │ │ │ │ ├── OnAvailableEndpointCondition.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── expose/ │ │ │ │ │ │ ├── EndpointExposure.java │ │ │ │ │ │ ├── IncludeExcludeEndpointFilter.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── jackson/ │ │ │ │ │ │ ├── Jackson2EndpointAutoConfiguration.java │ │ │ │ │ │ ├── JacksonEndpointAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── jmx/ │ │ │ │ │ │ ├── DefaultEndpointObjectNameFactory.java │ │ │ │ │ │ ├── JmxEndpointAutoConfiguration.java │ │ │ │ │ │ ├── JmxEndpointProperties.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── CorsEndpointProperties.java │ │ │ │ │ ├── MappingWebEndpointPathMapper.java │ │ │ │ │ ├── WebEndpointAutoConfiguration.java │ │ │ │ │ ├── WebEndpointProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── env/ │ │ │ │ │ ├── EnvironmentEndpointAutoConfiguration.java │ │ │ │ │ ├── EnvironmentEndpointProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── info/ │ │ │ │ │ ├── ConditionalOnEnabledInfoContributor.java │ │ │ │ │ ├── InfoContributorAutoConfiguration.java │ │ │ │ │ ├── InfoContributorFallback.java │ │ │ │ │ ├── InfoContributorProperties.java │ │ │ │ │ ├── InfoEndpointAutoConfiguration.java │ │ │ │ │ ├── OnEnabledInfoContributorCondition.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── logging/ │ │ │ │ │ ├── LogFileWebEndpointAutoConfiguration.java │ │ │ │ │ ├── LogFileWebEndpointProperties.java │ │ │ │ │ ├── LoggersEndpointAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── management/ │ │ │ │ │ ├── HeapDumpWebEndpointAutoConfiguration.java │ │ │ │ │ ├── ThreadDumpEndpointAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── sbom/ │ │ │ │ │ ├── SbomEndpointAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── scheduling/ │ │ │ │ │ ├── ScheduledTasksEndpointAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── startup/ │ │ │ │ │ ├── StartupEndpointAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── ManagementContextConfiguration.java │ │ │ │ ├── ManagementContextType.java │ │ │ │ ├── exchanges/ │ │ │ │ │ ├── HttpExchangesEndpointAutoConfiguration.java │ │ │ │ │ ├── HttpExchangesProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── mappings/ │ │ │ │ │ ├── MappingsEndpointAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── server/ │ │ │ │ ├── AccessLogCustomizer.java │ │ │ │ ├── ChildManagementContextInitializer.java │ │ │ │ ├── ConditionalOnManagementPort.java │ │ │ │ ├── EnableChildManagementContextConfiguration.java │ │ │ │ ├── EnableManagementContext.java │ │ │ │ ├── ManagementContextAutoConfiguration.java │ │ │ │ ├── ManagementContextConfigurationImportSelector.java │ │ │ │ ├── ManagementContextFactory.java │ │ │ │ ├── ManagementPortType.java │ │ │ │ ├── ManagementServerProperties.java │ │ │ │ ├── ManagementWebServerFactoryCustomizer.java │ │ │ │ ├── OnManagementPortCondition.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ ├── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── actuate/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── audit/ │ │ │ │ │ ├── AuditAutoConfigurationTests.java │ │ │ │ │ └── AuditEventsEndpointAutoConfigurationTests.java │ │ │ │ ├── beans/ │ │ │ │ │ └── BeansEndpointAutoConfigurationTests.java │ │ │ │ ├── condition/ │ │ │ │ │ ├── ConditionsReportEndpointAutoConfigurationTests.java │ │ │ │ │ └── ConditionsReportEndpointTests.java │ │ │ │ ├── context/ │ │ │ │ │ ├── ShutdownEndpointAutoConfigurationTests.java │ │ │ │ │ └── properties/ │ │ │ │ │ └── ConfigurationPropertiesReportEndpointAutoConfigurationTests.java │ │ │ │ ├── endpoint/ │ │ │ │ │ ├── EndpointAutoConfigurationTests.java │ │ │ │ │ ├── EndpointIdTimeToLivePropertyFunctionTests.java │ │ │ │ │ ├── PropertiesEndpointAccessResolverTests.java │ │ │ │ │ ├── condition/ │ │ │ │ │ │ └── ConditionalOnAvailableEndpointTests.java │ │ │ │ │ ├── expose/ │ │ │ │ │ │ └── IncludeExcludeEndpointFilterTests.java │ │ │ │ │ ├── jackson/ │ │ │ │ │ │ ├── Jackson2EndpointAutoConfigurationTests.java │ │ │ │ │ │ └── JacksonEndpointAutoConfigurationTests.java │ │ │ │ │ ├── jmx/ │ │ │ │ │ │ ├── DefaultEndpointObjectNameFactoryTests.java │ │ │ │ │ │ └── JmxEndpointAutoConfigurationTests.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── MappingWebEndpointPathMapperTests.java │ │ │ │ │ ├── WebEndpointAutoConfigurationTests.java │ │ │ │ │ └── WebEndpointPropertiesTests.java │ │ │ │ ├── env/ │ │ │ │ │ └── EnvironmentEndpointAutoConfigurationTests.java │ │ │ │ ├── info/ │ │ │ │ │ ├── InfoContributorAutoConfigurationTests.java │ │ │ │ │ └── InfoEndpointAutoConfigurationTests.java │ │ │ │ ├── logging/ │ │ │ │ │ ├── LogFileWebEndpointAutoConfigurationTests.java │ │ │ │ │ └── LoggersEndpointAutoConfigurationTests.java │ │ │ │ ├── management/ │ │ │ │ │ ├── HeapDumpWebEndpointAutoConfigurationTests.java │ │ │ │ │ └── ThreadDumpEndpointAutoConfigurationTests.java │ │ │ │ ├── sbom/ │ │ │ │ │ └── SbomEndpointAutoConfigurationTests.java │ │ │ │ ├── scheduling/ │ │ │ │ │ └── ScheduledTasksEndpointAutoConfigurationTests.java │ │ │ │ ├── startup/ │ │ │ │ │ └── StartupEndpointAutoConfigurationTests.java │ │ │ │ └── web/ │ │ │ │ ├── ManagementContextConfigurationTests.java │ │ │ │ ├── exchanges/ │ │ │ │ │ └── HttpExchangesEndpointAutoConfigurationTests.java │ │ │ │ ├── mappings/ │ │ │ │ │ └── MappingsEndpointAutoConfigurationTests.java │ │ │ │ └── server/ │ │ │ │ ├── ChildManagementContextInitializerAotTests.java │ │ │ │ ├── ManagementContextConfigurationImportSelectorTests.java │ │ │ │ └── ManagementServerPropertiesTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── actuate/ │ │ │ └── autoconfigure/ │ │ │ ├── env/ │ │ │ │ └── application.properties │ │ │ └── sbom/ │ │ │ └── cyclonedx.json │ │ └── testFixtures/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── actuate/ │ │ └── autoconfigure/ │ │ ├── endpoint/ │ │ │ └── condition/ │ │ │ └── WithTestEndpointOutcomeExposureContributor.java │ │ └── integrationtest/ │ │ └── AbstractHealthEndpointAdditionalPathIntegrationTests.java │ ├── spring-boot-amqp/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── amqp/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── RabbitAndRabbitStreamDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ ├── RabbitDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ ├── RabbitStreamDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ └── SingleServiceRabbitAndRabbitStreamDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── DeprecatedRabbitContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ ├── RabbitContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ ├── RabbitStreamContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ ├── RabbitStreamWithSslContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ ├── SeparateContainersRabbitAndRabbitStreamContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── SingleContainerRabbitAndRabbitStreamContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ ├── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── amqp/ │ │ │ │ ├── ca.crt │ │ │ │ ├── client.crt │ │ │ │ ├── client.key │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── rabbit-and-rabbit-stream-separate-services-compose.yaml │ │ │ │ │ ├── rabbit-and-rabbit-stream-single-service-compose.yaml │ │ │ │ │ ├── rabbit-compose.yaml │ │ │ │ │ ├── rabbit-ssl-compose.yaml │ │ │ │ │ ├── rabbit-stream-compose.yaml │ │ │ │ │ ├── rabbit-stream-ssl-compose.yaml │ │ │ │ │ ├── rabbitmq-ssl.conf │ │ │ │ │ └── rabbitmq-stream-ssl.conf │ │ │ │ ├── server.crt │ │ │ │ ├── server.key │ │ │ │ └── testcontainers/ │ │ │ │ └── rabbitmq-stream-ssl.conf │ │ │ └── spring.properties │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── amqp/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── AbstractConnectionFactoryConfigurer.java │ │ │ │ │ ├── AbstractRabbitListenerContainerFactoryConfigurer.java │ │ │ │ │ ├── CachingConnectionFactoryConfigurer.java │ │ │ │ │ ├── ConnectionFactoryCustomizer.java │ │ │ │ │ ├── DirectRabbitListenerContainerFactoryConfigurer.java │ │ │ │ │ ├── EnvironmentBuilderCustomizer.java │ │ │ │ │ ├── PropertiesRabbitConnectionDetails.java │ │ │ │ │ ├── RabbitAnnotationDrivenConfiguration.java │ │ │ │ │ ├── RabbitAutoConfiguration.java │ │ │ │ │ ├── RabbitConnectionDetails.java │ │ │ │ │ ├── RabbitConnectionFactoryBeanConfigurer.java │ │ │ │ │ ├── RabbitListenerRetrySettingsCustomizer.java │ │ │ │ │ ├── RabbitProperties.java │ │ │ │ │ ├── RabbitRetryTemplateSettings.java │ │ │ │ │ ├── RabbitStreamConfiguration.java │ │ │ │ │ ├── RabbitStreamConnectionDetails.java │ │ │ │ │ ├── RabbitStreamTemplateConfigurer.java │ │ │ │ │ ├── RabbitTemplateConfigurer.java │ │ │ │ │ ├── RabbitTemplateCustomizer.java │ │ │ │ │ ├── RabbitTemplateRetrySettingsCustomizer.java │ │ │ │ │ ├── SimpleRabbitListenerContainerFactoryConfigurer.java │ │ │ │ │ ├── SslBundleRabbitConnectionFactoryBean.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── RabbitHealthContributorAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── RabbitConnectionFactoryMetricsPostProcessor.java │ │ │ │ │ │ ├── RabbitMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── RabbitDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── RabbitEnvironment.java │ │ │ │ │ ├── RabbitStreamDockerComposeConnectionDetailsFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── RabbitHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── metrics/ │ │ │ │ │ ├── RabbitMetrics.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── DeprecatedRabbitContainerConnectionDetailsFactory.java │ │ │ │ ├── RabbitContainerConnectionDetailsFactory.java │ │ │ │ ├── RabbitStreamContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── amqp/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── PropertiesRabbitConnectionDetailsTests.java │ │ │ │ ├── RabbitAutoConfigurationTests.java │ │ │ │ ├── RabbitPropertiesTests.java │ │ │ │ ├── RabbitStreamConfigurationTests.java │ │ │ │ ├── health/ │ │ │ │ │ └── RabbitHealthContributorAutoConfigurationTests.java │ │ │ │ └── metrics/ │ │ │ │ ├── RabbitMetricsAutoConfigurationMeterBinderCycleIntegrationTests.java │ │ │ │ └── RabbitMetricsAutoConfigurationTests.java │ │ │ ├── docker/ │ │ │ │ └── compose/ │ │ │ │ └── RabbitEnvironmentTests.java │ │ │ ├── health/ │ │ │ │ └── RabbitHealthIndicatorTests.java │ │ │ └── metrics/ │ │ │ └── RabbitMetricsTests.java │ │ └── resources/ │ │ ├── logback-test.xml │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── amqp/ │ │ └── autoconfigure/ │ │ └── test.jks │ ├── spring-boot-artemis/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── artemis/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── ArtemisDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ └── ArtemisContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── artemis/ │ │ │ └── docker/ │ │ │ └── compose/ │ │ │ └── artemis-compose.yaml │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── artemis/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ArtemisAutoConfiguration.java │ │ │ │ │ ├── ArtemisConfigurationCustomizer.java │ │ │ │ │ ├── ArtemisConnectionDetails.java │ │ │ │ │ ├── ArtemisConnectionFactoryConfiguration.java │ │ │ │ │ ├── ArtemisConnectionFactoryFactory.java │ │ │ │ │ ├── ArtemisEmbeddedConfigurationFactory.java │ │ │ │ │ ├── ArtemisEmbeddedServerConfiguration.java │ │ │ │ │ ├── ArtemisMode.java │ │ │ │ │ ├── ArtemisNoOpBindingRegistry.java │ │ │ │ │ ├── ArtemisProperties.java │ │ │ │ │ ├── ArtemisXAConnectionFactoryConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── ArtemisDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── ArtemisEnvironment.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── ArtemisContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── artemis/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── ArtemisAutoConfigurationTests.java │ │ │ │ └── ArtemisEmbeddedConfigurationFactoryTests.java │ │ │ └── docker/ │ │ │ └── compose/ │ │ │ └── ArtemisEnvironmentTests.java │ │ └── resources/ │ │ └── logback-test.xml │ ├── spring-boot-autoconfigure-classic/ │ │ └── build.gradle │ ├── spring-boot-autoconfigure-classic-modules/ │ │ └── build.gradle │ ├── spring-boot-batch/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── batch/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── BatchAutoConfiguration.java │ │ │ │ ├── BatchConversionServiceCustomizer.java │ │ │ │ ├── BatchJobLauncherAutoConfiguration.java │ │ │ │ ├── BatchProperties.java │ │ │ │ ├── BatchTaskExecutor.java │ │ │ │ ├── BatchTransactionManager.java │ │ │ │ ├── JobExecutionEvent.java │ │ │ │ ├── JobExecutionExitCodeGenerator.java │ │ │ │ ├── JobLauncherApplicationRunner.java │ │ │ │ ├── observation/ │ │ │ │ │ ├── BatchObservationAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── batch/ │ │ └── autoconfigure/ │ │ ├── BatchAutoConfigurationTests.java │ │ ├── BatchJobLauncherAutoConfigurationTests.java │ │ ├── JobExecutionExitCodeGeneratorTests.java │ │ ├── JobLauncherApplicationRunnerTests.java │ │ └── observation/ │ │ └── BatchObservationAutoConfigurationTests.java │ ├── spring-boot-batch-data-mongodb/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── batch/ │ │ │ └── mongodb/ │ │ │ └── autoconfigure/ │ │ │ └── BatchDataMongoAutoConfigurationIntegrationTests.java │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── batch/ │ │ │ │ └── mongodb/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── BatchDataMongoAutoConfiguration.java │ │ │ │ ├── BatchDataMongoProperties.java │ │ │ │ ├── BatchMongoSchemaInitializationException.java │ │ │ │ ├── BatchMongoSchemaInitializer.java │ │ │ │ ├── JobRepositoryDependsOnSchemaInitializationDetector.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── batch/ │ │ └── mongodb/ │ │ └── autoconfigure/ │ │ ├── BatchDataMongoAutoConfigurationTests.java │ │ └── BatchMongoSchemaInitializerTests.java │ ├── spring-boot-batch-jdbc/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── batch/ │ │ │ │ └── jdbc/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── BatchDataSource.java │ │ │ │ ├── BatchDataSourceScriptDatabaseInitializer.java │ │ │ │ ├── BatchJdbcAutoConfiguration.java │ │ │ │ ├── BatchJdbcProperties.java │ │ │ │ ├── JobRepositoryDependsOnDatabaseInitializationDetector.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── batch/ │ │ │ └── jdbc/ │ │ │ └── autoconfigure/ │ │ │ ├── BatchDataSourceScriptDatabaseInitializerTests.java │ │ │ ├── BatchJdbcAutoConfigurationTests.java │ │ │ ├── BatchJdbcAutoConfigurationWithoutJpaTests.java │ │ │ ├── BatchJdbcPropertiesTests.java │ │ │ └── domain/ │ │ │ ├── City.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── batch/ │ │ └── jdbc/ │ │ └── autoconfigure/ │ │ └── custom-schema.sql │ ├── spring-boot-cache/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── cache/ │ │ │ └── metrics/ │ │ │ └── RedisCacheMetricsTests.java │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── cache/ │ │ │ │ ├── actuate/ │ │ │ │ │ └── endpoint/ │ │ │ │ │ ├── CachesEndpoint.java │ │ │ │ │ ├── CachesEndpointWebExtension.java │ │ │ │ │ ├── NonUniqueCacheException.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── Cache2kBuilderCustomizer.java │ │ │ │ │ ├── Cache2kCacheConfiguration.java │ │ │ │ │ ├── CacheAutoConfiguration.java │ │ │ │ │ ├── CacheCondition.java │ │ │ │ │ ├── CacheConfigurations.java │ │ │ │ │ ├── CacheManagerCustomizer.java │ │ │ │ │ ├── CacheManagerCustomizers.java │ │ │ │ │ ├── CacheProperties.java │ │ │ │ │ ├── CachesEndpointAutoConfiguration.java │ │ │ │ │ ├── CaffeineCacheConfiguration.java │ │ │ │ │ ├── CouchbaseCacheConfiguration.java │ │ │ │ │ ├── CouchbaseCacheManagerBuilderCustomizer.java │ │ │ │ │ ├── GenericCacheConfiguration.java │ │ │ │ │ ├── HazelcastCacheConfiguration.java │ │ │ │ │ ├── HazelcastJCacheCustomizationConfiguration.java │ │ │ │ │ ├── InfinispanCacheConfiguration.java │ │ │ │ │ ├── JCacheCacheConfiguration.java │ │ │ │ │ ├── JCacheManagerCustomizer.java │ │ │ │ │ ├── JCachePropertiesCustomizer.java │ │ │ │ │ ├── NoOpCacheConfiguration.java │ │ │ │ │ ├── RedisCacheConfiguration.java │ │ │ │ │ ├── RedisCacheManagerBuilderCustomizer.java │ │ │ │ │ ├── SimpleCacheConfiguration.java │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── CacheMeterBinderProvidersConfiguration.java │ │ │ │ │ │ ├── CacheMetricsAutoConfiguration.java │ │ │ │ │ │ ├── CacheMetricsRegistrarConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ └── metrics/ │ │ │ │ ├── Cache2kCacheMeterBinderProvider.java │ │ │ │ ├── CacheMeterBinderProvider.java │ │ │ │ ├── CacheMetricsRegistrar.java │ │ │ │ ├── CaffeineCacheMeterBinderProvider.java │ │ │ │ ├── HazelcastCacheMeterBinderProvider.java │ │ │ │ ├── JCacheCacheMeterBinderProvider.java │ │ │ │ ├── RedisCacheMeterBinderProvider.java │ │ │ │ ├── RedisCacheMetrics.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── cache/ │ │ │ ├── actuate/ │ │ │ │ └── endpoint/ │ │ │ │ ├── CachesEndpointTests.java │ │ │ │ └── CachesEndpointWebIntegrationTests.java │ │ │ ├── autoconfigure/ │ │ │ │ ├── AbstractCacheAutoConfigurationTests.java │ │ │ │ ├── CacheAutoConfigurationTests.java │ │ │ │ ├── CacheManagerCustomizersTests.java │ │ │ │ ├── CachesEndpointAutoConfigurationTests.java │ │ │ │ ├── EhCache3CacheAutoConfigurationTests.java │ │ │ │ ├── MockCachingProvider.java │ │ │ │ └── metrics/ │ │ │ │ └── CacheMetricsAutoConfigurationTests.java │ │ │ └── metrics/ │ │ │ ├── Cache2kCacheMeterBinderProviderTests.java │ │ │ ├── CacheMetricsRegistrarTests.java │ │ │ ├── CaffeineCacheMeterBinderProviderTests.java │ │ │ ├── HazelcastCacheMeterBinderProviderTests.java │ │ │ ├── JCacheCacheMeterBinderProviderTests.java │ │ │ └── RedisCacheMeterBinderProviderTests.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ └── javax.cache.spi.CachingProvider │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── cache/ │ │ └── autoconfigure/ │ │ └── hazelcast-specific.xml │ ├── spring-boot-cache-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── cache/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureCache.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.cache.test.autoconfigure.AutoConfigureCache.imports │ │ │ └── spring-configuration-metadata.json │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── cache/ │ │ └── test/ │ │ └── autoconfigure/ │ │ ├── AutoConfigureCacheIntegrationTests.java │ │ └── AutoConfigureCacheWithExistingCacheManagerIntegrationTests.java │ ├── spring-boot-cassandra/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── cassandra/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── CassandraAutoConfigurationIntegrationTests.java │ │ │ │ │ └── CassandraAutoConfigurationWithPasswordAuthenticationIntegrationTests.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── CassandraDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ └── CassandraContainerConnectionDetailsFactoryTests.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ ├── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── cassandra/ │ │ │ │ └── docker/ │ │ │ │ └── compose/ │ │ │ │ ├── cassandra-compose.yaml │ │ │ │ ├── cassandra-ssl-compose.yaml │ │ │ │ ├── client-keystore.p12 │ │ │ │ ├── client-truststore.p12 │ │ │ │ ├── server-keystore.p12 │ │ │ │ └── server-truststore.p12 │ │ │ └── spring.properties │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── cassandra/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── CassandraAutoConfiguration.java │ │ │ │ │ ├── CassandraConnectionDetails.java │ │ │ │ │ ├── CassandraProperties.java │ │ │ │ │ ├── CqlSessionBuilderCustomizer.java │ │ │ │ │ ├── DriverConfigLoaderBuilderCustomizer.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── CassandraHealthContributorAutoConfiguration.java │ │ │ │ │ │ ├── CassandraHealthContributorConfigurations.java │ │ │ │ │ │ ├── CassandraReactiveHealthContributorAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── CassandraDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── CassandraEnvironment.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── CassandraDriverHealthIndicator.java │ │ │ │ │ ├── CassandraDriverReactiveHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── CassandraContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── cassandra/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── CassandraAutoConfigurationTests.java │ │ │ │ ├── CassandraPropertiesTests.java │ │ │ │ └── health/ │ │ │ │ ├── CassandraHealthContributorAutoConfigurationTests.java │ │ │ │ └── CassandraReactiveHealthContributorAutoConfigurationTests.java │ │ │ ├── docker/ │ │ │ │ └── compose/ │ │ │ │ └── CassandraEnvironmentTests.java │ │ │ └── health/ │ │ │ ├── CassandraDriverHealthIndicatorTests.java │ │ │ └── CassandraDriverReactiveHealthIndicatorTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── cassandra/ │ │ └── autoconfigure/ │ │ ├── override-defaults.conf │ │ ├── profiles.conf │ │ ├── simple.conf │ │ └── test.jks │ ├── spring-boot-cloudfoundry/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── cloudfoundry/ │ │ │ │ └── autoconfigure/ │ │ │ │ └── actuate/ │ │ │ │ └── endpoint/ │ │ │ │ ├── AccessLevel.java │ │ │ │ ├── CloudFoundryAuthorizationException.java │ │ │ │ ├── CloudFoundryEndpointExposureOutcomeContributor.java │ │ │ │ ├── CloudFoundryEndpointFilter.java │ │ │ │ ├── CloudFoundryWebEndpointDiscoverer.java │ │ │ │ ├── EndpointCloudFoundryExtension.java │ │ │ │ ├── SecurityResponse.java │ │ │ │ ├── Token.java │ │ │ │ ├── package-info.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── CloudFoundryReactiveActuatorAutoConfiguration.java │ │ │ │ │ ├── CloudFoundryReactiveHealthEndpointWebExtension.java │ │ │ │ │ ├── CloudFoundryWebFluxEndpointHandlerMapping.java │ │ │ │ │ ├── SecurityInterceptor.java │ │ │ │ │ ├── SecurityService.java │ │ │ │ │ ├── TokenValidator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── servlet/ │ │ │ │ ├── CloudFoundryActuatorAutoConfiguration.java │ │ │ │ ├── CloudFoundryHealthEndpointWebExtension.java │ │ │ │ ├── CloudFoundryInfoEndpointWebExtension.java │ │ │ │ ├── CloudFoundryWebEndpointServletHandlerMapping.java │ │ │ │ ├── SecurityInterceptor.java │ │ │ │ ├── SecurityService.java │ │ │ │ ├── SkipSslVerificationHttpRequestFactory.java │ │ │ │ ├── TokenValidator.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── cloudfoundry/ │ │ │ └── autoconfigure/ │ │ │ └── actuate/ │ │ │ └── endpoint/ │ │ │ ├── AccessLevelTests.java │ │ │ ├── CloudFoundryAuthorizationExceptionTests.java │ │ │ ├── CloudFoundryConditionalOnAvailableEndpointTests.java │ │ │ ├── CloudFoundryEndpointFilterTests.java │ │ │ ├── CloudFoundryWebEndpointDiscovererTests.java │ │ │ ├── TokenTests.java │ │ │ ├── reactive/ │ │ │ │ ├── CloudFoundryReactiveActuatorAutoConfigurationTests.java │ │ │ │ ├── CloudFoundryReactiveHealthEndpointWebExtensionTests.java │ │ │ │ ├── CloudFoundryWebFluxEndpointHandlerMappingTests.java │ │ │ │ ├── CloudFoundryWebFluxEndpointIntegrationTests.java │ │ │ │ ├── SecurityInterceptorTests.java │ │ │ │ ├── SecurityServiceTests.java │ │ │ │ └── TokenValidatorTests.java │ │ │ └── servlet/ │ │ │ ├── CloudFoundryActuatorAutoConfigurationTests.java │ │ │ ├── CloudFoundryHealthEndpointWebExtensionTests.java │ │ │ ├── CloudFoundryInfoEndpointWebExtensionTests.java │ │ │ ├── CloudFoundryMvcWebEndpointIntegrationTests.java │ │ │ ├── CloudFoundryWebEndpointServletHandlerMappingTests.java │ │ │ ├── SecurityInterceptorTests.java │ │ │ ├── SecurityServiceTests.java │ │ │ ├── SkipSslVerificationHttpRequestFactoryTests.java │ │ │ └── TokenValidatorTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── cloudfoundry/ │ │ └── autoconfigure/ │ │ └── actuate/ │ │ └── endpoint/ │ │ ├── reactive/ │ │ │ └── test.jks │ │ └── servlet/ │ │ └── test.jks │ ├── spring-boot-couchbase/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── couchbase/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ └── CouchbaseAutoConfigurationIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ └── CouchbaseContainerConnectionDetailsFactoryTests.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ └── spring.properties │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── couchbase/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ClusterEnvironmentBuilderCustomizer.java │ │ │ │ │ ├── CouchbaseAutoConfiguration.java │ │ │ │ │ ├── CouchbaseConnectionDetails.java │ │ │ │ │ ├── CouchbaseProperties.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── CouchbaseHealthContributorAutoConfiguration.java │ │ │ │ │ │ ├── CouchbaseReactiveHealthContributorAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── CouchbaseHealth.java │ │ │ │ │ ├── CouchbaseHealthIndicator.java │ │ │ │ │ ├── CouchbaseReactiveHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── CouchbaseContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── couchbase/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── CouchbaseAutoConfigurationTests.java │ │ │ │ ├── CouchbasePropertiesTests.java │ │ │ │ ├── CouchbaseTestConfiguration.java │ │ │ │ └── health/ │ │ │ │ ├── CouchbaseHealthContributorAutoConfigurationTests.java │ │ │ │ └── CouchbaseReactiveHealthContributorAutoConfigurationTests.java │ │ │ └── health/ │ │ │ ├── CouchbaseHealthIndicatorTests.java │ │ │ └── CouchbaseReactiveHealthIndicatorTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── couchbase/ │ │ └── autoconfigure/ │ │ ├── key.crt │ │ ├── key.pem │ │ ├── keystore.jks │ │ └── test.jks │ ├── spring-boot-data-cassandra/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── cassandra/ │ │ │ └── autoconfigure/ │ │ │ └── DataCassandraAutoConfigurationIntegrationTests.java │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── cassandra/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── DataCassandraAutoConfiguration.java │ │ │ │ ├── DataCassandraReactiveAutoConfiguration.java │ │ │ │ ├── DataCassandraReactiveRepositoriesAutoConfiguration.java │ │ │ │ ├── DataCassandraReactiveRepositoriesRegistrar.java │ │ │ │ ├── DataCassandraRepositoriesAutoConfiguration.java │ │ │ │ ├── DataCassandraRepositoriesRegistrar.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── cassandra/ │ │ ├── autoconfigure/ │ │ │ ├── CassandraMockConfiguration.java │ │ │ ├── DataCassandraAutoConfigurationTests.java │ │ │ ├── DataCassandraReactiveAutoConfigurationTests.java │ │ │ ├── DataCassandraReactiveRepositoriesAutoConfigurationTests.java │ │ │ └── DataCassandraRepositoriesAutoConfigurationTests.java │ │ └── domain/ │ │ ├── city/ │ │ │ ├── City.java │ │ │ ├── CityRepository.java │ │ │ ├── ReactiveCityRepository.java │ │ │ └── package-info.java │ │ └── empty/ │ │ ├── EmptyDataPackage.java │ │ └── package-info.java │ ├── spring-boot-data-cassandra-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ └── data/ │ │ │ └── cassandra/ │ │ │ ├── DataCassandraTestIntegrationTests.java │ │ │ ├── DataCassandraTestWithIncludeFilterIntegrationTests.java │ │ │ ├── ExampleCassandraApplication.java │ │ │ ├── ExampleEntity.java │ │ │ ├── ExampleRepository.java │ │ │ ├── ExampleService.java │ │ │ └── package-info.java │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── cassandra/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureDataCassandra.java │ │ │ │ ├── DataCassandraTest.java │ │ │ │ ├── DataCassandraTestContextBootstrapper.java │ │ │ │ ├── DataCassandraTypeExcludeFilter.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.data.cassandra.test.autoconfigure.AutoConfigureDataCassandra.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── cassandra/ │ │ └── test/ │ │ └── autoconfigure/ │ │ ├── DataCassandraTestApplication.java │ │ └── DataCassandraTestPropertiesIntegrationTests.java │ ├── spring-boot-data-commons/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── DataMetricsProperties.java │ │ │ │ │ │ ├── DataRepositoryMetricsAutoConfiguration.java │ │ │ │ │ │ ├── MetricsRepositoryMethodInvocationListenerBeanPostProcessor.java │ │ │ │ │ │ ├── PropertiesAutoTimer.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── DataWebAutoConfiguration.java │ │ │ │ │ ├── DataWebProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ └── metrics/ │ │ │ │ ├── AutoTimer.java │ │ │ │ ├── DefaultRepositoryTagsProvider.java │ │ │ │ ├── MetricsRepositoryMethodInvocationListener.java │ │ │ │ ├── RepositoryTagsProvider.java │ │ │ │ ├── TimedAnnotations.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureWebMvc.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ ├── autoconfigure/ │ │ │ ├── metrics/ │ │ │ │ ├── DataRepositoryMetricsAutoConfigurationIntegrationTests.java │ │ │ │ ├── DataRepositoryMetricsAutoConfigurationTests.java │ │ │ │ └── MetricsRepositoryMethodInvocationListenerBeanPostProcessorTests.java │ │ │ └── web/ │ │ │ ├── DataWebAutoConfigurationTests.java │ │ │ └── DataWebWebMvcTestIntegrationTests.java │ │ ├── domain/ │ │ │ └── city/ │ │ │ ├── City.java │ │ │ ├── CityRepository.java │ │ │ └── package-info.java │ │ └── metrics/ │ │ ├── DefaultRepositoryTagsProviderTests.java │ │ ├── MetricsRepositoryMethodInvocationListenerTests.java │ │ └── TimedAnnotationsTests.java │ ├── spring-boot-data-couchbase/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── couchbase/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── CouchbaseClientFactoryConfiguration.java │ │ │ │ ├── CouchbaseClientFactoryDependentConfiguration.java │ │ │ │ ├── DataCouchbaseAutoConfiguration.java │ │ │ │ ├── DataCouchbaseConfiguration.java │ │ │ │ ├── DataCouchbaseProperties.java │ │ │ │ ├── DataCouchbaseReactiveAutoConfiguration.java │ │ │ │ ├── DataCouchbaseReactiveConfiguration.java │ │ │ │ ├── DataCouchbaseReactiveRepositoriesAutoConfiguration.java │ │ │ │ ├── DataCouchbaseReactiveRepositoriesRegistrar.java │ │ │ │ ├── DataCouchbaseRepositoriesAutoConfiguration.java │ │ │ │ ├── DataCouchbaseRepositoriesRegistrar.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── couchbase/ │ │ ├── autoconfigure/ │ │ │ ├── CouchbaseMockConfiguration.java │ │ │ ├── DataCouchbaseAutoConfigurationTests.java │ │ │ ├── DataCouchbasePropertiesTests.java │ │ │ ├── DataCouchbaseReactiveAndImperativeRepositoriesAutoConfigurationTests.java │ │ │ ├── DataCouchbaseReactiveAutoConfigurationTests.java │ │ │ ├── DataCouchbaseReactiveRepositoriesAutoConfigurationTests.java │ │ │ └── DataCouchbaseRepositoriesAutoConfigurationTests.java │ │ └── domain/ │ │ ├── city/ │ │ │ ├── City.java │ │ │ ├── CityRepository.java │ │ │ ├── ReactiveCityRepository.java │ │ │ └── package-info.java │ │ └── empty/ │ │ ├── EmptyDataPackage.java │ │ └── package-info.java │ ├── spring-boot-data-couchbase-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── couchbase/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── DataCouchbaseTestIntegrationTests.java │ │ │ ├── DataCouchbaseTestReactiveIntegrationTests.java │ │ │ ├── DataCouchbaseTestWithIncludeFilterIntegrationTests.java │ │ │ ├── ExampleDocument.java │ │ │ ├── ExampleReactiveRepository.java │ │ │ ├── ExampleRepository.java │ │ │ └── ExampleService.java │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── couchbase/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureDataCouchbase.java │ │ │ │ ├── DataCouchbaseTest.java │ │ │ │ ├── DataCouchbaseTestContextBootstrapper.java │ │ │ │ ├── DataCouchbaseTypeExcludeFilter.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.data.couchbase.test.autoconfigure.AutoConfigureDataCouchbase.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── couchbase/ │ │ └── test/ │ │ └── autoconfigure/ │ │ ├── DataCouchbaseTestApplication.java │ │ └── DataCouchbaseTestPropertiesIntegrationTests.java │ ├── spring-boot-data-elasticsearch/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── elasticsearch/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── DataElasticsearchAutoConfigurationIntegrationTests.java │ │ │ │ ├── DataElasticsearchReactiveRepositoriesAutoConfigurationTests.java │ │ │ │ └── DataElasticsearchRepositoriesAutoConfigurationTests.java │ │ │ └── domain/ │ │ │ └── empty/ │ │ │ ├── EmptyDataPackage.java │ │ │ └── package-info.java │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── elasticsearch/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── DataElasticsearchAutoConfiguration.java │ │ │ │ │ ├── DataElasticsearchConfiguration.java │ │ │ │ │ ├── DataElasticsearchReactiveRepositoriesAutoConfiguration.java │ │ │ │ │ ├── DataElasticsearchReactiveRepositoriesRegistrar.java │ │ │ │ │ ├── DataElasticsearchRepositoriesAutoConfiguration.java │ │ │ │ │ ├── DataElasticsearchRepositoriesRegistrar.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── DataElasticsearchReactiveHealthContributorAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ └── health/ │ │ │ │ ├── DataElasticsearchReactiveHealthIndicator.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── elasticsearch/ │ │ ├── autoconfigure/ │ │ │ ├── DataElasticsearchAutoConfigurationTests.java │ │ │ └── health/ │ │ │ └── DataElasticsearchReactiveHealthContributorAutoConfigurationTests.java │ │ ├── domain/ │ │ │ └── city/ │ │ │ ├── City.java │ │ │ ├── CityRepository.java │ │ │ ├── ReactiveCityRepository.java │ │ │ └── package-info.java │ │ └── health/ │ │ └── DataElasticsearchReactiveHealthIndicatorTests.java │ ├── spring-boot-data-elasticsearch-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── elasticsearch/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── DataElasticsearchTestIntegrationTests.java │ │ │ ├── DataElasticsearchTestPropertiesIntegrationTests.java │ │ │ ├── DataElasticsearchTestReactiveIntegrationTests.java │ │ │ ├── DataElasticsearchTestWithIncludeFilterIntegrationTests.java │ │ │ ├── ExampleDocument.java │ │ │ ├── ExampleElasticsearchApplication.java │ │ │ ├── ExampleReactiveRepository.java │ │ │ ├── ExampleRepository.java │ │ │ └── ExampleService.java │ │ └── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── elasticsearch/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── AutoConfigureDataElasticsearch.java │ │ │ ├── DataElasticsearchTest.java │ │ │ ├── DataElasticsearchTestContextBootstrapper.java │ │ │ ├── DataElasticsearchTypeExcludeFilter.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring/ │ │ └── org.springframework.boot.data.elasticsearch.test.autoconfigure.AutoConfigureDataElasticsearch.imports │ ├── spring-boot-data-jdbc/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── jdbc/ │ │ │ └── autoconfigure/ │ │ │ └── DataJdbcRepositoriesAutoConfigurationDialectResolutionTests.java │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── jdbc/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── DataJdbcDatabaseDialect.java │ │ │ │ ├── DataJdbcProperties.java │ │ │ │ ├── DataJdbcRepositoriesAutoConfiguration.java │ │ │ │ ├── DataJdbcRepositoriesRegistrar.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── jdbc/ │ │ ├── autoconfigure/ │ │ │ └── DataJdbcRepositoriesAutoConfigurationTests.java │ │ └── domain/ │ │ ├── city/ │ │ │ ├── City.java │ │ │ ├── CityRepository.java │ │ │ └── package-info.java │ │ └── empty/ │ │ ├── EmptyDataPackage.java │ │ └── package-info.java │ ├── spring-boot-data-jdbc-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── jdbc/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureDataJdbc.java │ │ │ │ ├── DataJdbcTest.java │ │ │ │ ├── DataJdbcTestContextBootstrapper.java │ │ │ │ ├── DataJdbcTypeExcludeFilter.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.data.jdbc.test.autoconfigure.AutoConfigureDataJdbc.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── jdbc/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── DataJdbcTestIntegrationTests.java │ │ │ ├── DataJdbcTestPropertiesIntegrationTests.java │ │ │ ├── DataJdbcTypeExcludeFilterTests.java │ │ │ ├── ExampleComponent.java │ │ │ ├── ExampleDataJdbcApplication.java │ │ │ ├── ExampleEntity.java │ │ │ └── ExampleRepository.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── jdbc/ │ │ └── test/ │ │ └── autoconfigure/ │ │ └── schema.sql │ ├── spring-boot-data-jpa/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── jpa/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── DataJpaRepositoriesAutoConfiguration.java │ │ │ │ ├── DataJpaRepositoriesRegistrar.java │ │ │ │ ├── EnversRevisionRepositoriesRegistrar.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── jpa/ │ │ └── autoconfigure/ │ │ ├── AbstractDataJpaRepositoriesAutoConfigurationTests.java │ │ ├── DataJpaRepositoriesAutoConfigurationTests.java │ │ ├── DataJpaRepositoriesWithEnversRevisionAutoConfigurationTests.java │ │ ├── DataJpaRepositoriesWithSpringDataWebAutoConfigurationTests.java │ │ └── domain/ │ │ ├── city/ │ │ │ ├── City.java │ │ │ ├── CityRepository.java │ │ │ └── package-info.java │ │ └── country/ │ │ ├── Country.java │ │ ├── CountryRepository.java │ │ └── package-info.java │ ├── spring-boot-data-jpa-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── jpa/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureDataJpa.java │ │ │ │ ├── DataJpaTest.java │ │ │ │ ├── DataJpaTestContextBootstrapper.java │ │ │ │ ├── DataJpaTypeExcludeFilter.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.data.jpa.test.autoconfigure.AutoConfigureDataJpa.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── jpa/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── DataJpaTestAttributesIntegrationTests.java │ │ │ ├── DataJpaTestIntegrationTests.java │ │ │ ├── DataJpaTestPropertiesIntegrationTests.java │ │ │ ├── DataJpaTestSchemaCredentialsIntegrationTests.java │ │ │ ├── ExampleComponent.java │ │ │ ├── ExampleDataJpaApplication.java │ │ │ ├── ExampleEntity.java │ │ │ └── ExampleRepository.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── jpa/ │ │ └── test/ │ │ └── autoconfigure/ │ │ └── schema.sql │ ├── spring-boot-data-ldap/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── ldap/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── DataLdapRepositoriesAutoConfiguration.java │ │ │ │ ├── DataLdapRepositoriesRegistrar.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── ldap/ │ │ └── autoconfigure/ │ │ ├── DataLdapRepositoriesAutoConfigurationTests.java │ │ └── domain/ │ │ ├── empty/ │ │ │ ├── EmptyDataPackage.java │ │ │ └── package-info.java │ │ └── person/ │ │ ├── Person.java │ │ ├── PersonRepository.java │ │ └── package-info.java │ ├── spring-boot-data-ldap-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── ldap/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ └── DataLdapTestDockerTests.java │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── ldap/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureDataLdap.java │ │ │ │ ├── DataLdapTest.java │ │ │ │ ├── DataLdapTestContextBootstrapper.java │ │ │ │ ├── DataLdapTypeExcludeFilter.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.data.ldap.test.autoconfigure.AutoConfigureDataLdap.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── ldap/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── DataLdapTestIntegrationTests.java │ │ │ ├── DataLdapTestPropertiesIntegrationTests.java │ │ │ ├── DataLdapTestWithIncludeFilterIntegrationTests.java │ │ │ ├── ExampleEntry.java │ │ │ ├── ExampleLdapApplication.java │ │ │ ├── ExampleRepository.java │ │ │ └── ExampleService.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── ldap/ │ │ └── test/ │ │ └── autoconfigure/ │ │ └── schema.ldif │ ├── spring-boot-data-mongodb/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── mongodb/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── DataMongoAutoConfiguration.java │ │ │ │ ├── DataMongoConfiguration.java │ │ │ │ ├── DataMongoProperties.java │ │ │ │ ├── DataMongoReactiveAutoConfiguration.java │ │ │ │ ├── DataMongoReactiveRepositoriesAutoConfiguration.java │ │ │ │ ├── DataMongoReactiveRepositoriesRegistrar.java │ │ │ │ ├── DataMongoRepositoriesAutoConfiguration.java │ │ │ │ ├── DataMongoRepositoriesRegistrar.java │ │ │ │ ├── MongoDatabaseFactoryConfiguration.java │ │ │ │ ├── MongoDatabaseFactoryDependentConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── mongodb/ │ │ ├── alt/ │ │ │ ├── CityMongoDbRepository.java │ │ │ ├── ReactiveCityMongoDbRepository.java │ │ │ └── package-info.java │ │ └── autoconfigure/ │ │ ├── DataMongoAutoConfigurationTests.java │ │ ├── DataMongoPropertiesTests.java │ │ ├── DataMongoReactiveAndBlockingRepositoriesAutoConfigurationTests.java │ │ ├── DataMongoReactiveAutoConfigurationTests.java │ │ ├── DataMongoReactiveRepositoriesAutoConfigurationTests.java │ │ ├── DataMongoRepositoriesAutoConfigurationTests.java │ │ ├── domain/ │ │ │ ├── city/ │ │ │ │ ├── City.java │ │ │ │ ├── CityRepository.java │ │ │ │ ├── PersistentEntity.java │ │ │ │ ├── ReactiveCityRepository.java │ │ │ │ └── package-info.java │ │ │ └── country/ │ │ │ ├── Country.java │ │ │ ├── CountryRepository.java │ │ │ └── package-info.java │ │ └── empty/ │ │ ├── EmptyDataPackage.java │ │ └── package-info.java │ ├── spring-boot-data-mongodb-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── mongodb/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── DataMongoTestIntegrationTests.java │ │ │ ├── DataMongoTestReactiveIntegrationTests.java │ │ │ ├── DataMongoTestWithIncludeFilterIntegrationTests.java │ │ │ ├── ExampleDocument.java │ │ │ ├── ExampleReactiveRepository.java │ │ │ ├── ExampleRepository.java │ │ │ ├── ExampleService.java │ │ │ └── TransactionalDataMongoTestIntegrationTests.java │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── mongodb/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureDataMongo.java │ │ │ │ ├── DataMongoTest.java │ │ │ │ ├── DataMongoTestContextBootstrapper.java │ │ │ │ ├── DataMongoTypeExcludeFilter.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.data.mongodb.test.autoconfigure.AutoConfigureDataMongo.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── mongodb/ │ │ └── test/ │ │ └── autoconfigure/ │ │ ├── DataMongoTestApplication.java │ │ └── DataMongoTestPropertiesIntegrationTests.java │ ├── spring-boot-data-neo4j/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── neo4j/ │ │ │ └── autoconfigure/ │ │ │ └── Neo4jRepositoriesAutoConfigurationIntegrationTests.java │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── neo4j/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── DataNeo4jAutoConfiguration.java │ │ │ │ ├── DataNeo4jProperties.java │ │ │ │ ├── DataNeo4jReactiveAutoConfiguration.java │ │ │ │ ├── DataNeo4jReactiveRepositoriesAutoConfiguration.java │ │ │ │ ├── DataNeo4jReactiveRepositoriesRegistrar.java │ │ │ │ ├── DataNeo4jRepositoriesAutoConfiguration.java │ │ │ │ ├── DataNeo4jRepositoriesRegistrar.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── neo4j/ │ │ ├── autoconfigure/ │ │ │ ├── DataNeo4jAutoConfigurationTests.java │ │ │ ├── DataNeo4jReactiveAutoConfigurationTests.java │ │ │ ├── DataNeo4jReactiveRepositoriesAutoConfigurationTests.java │ │ │ ├── DataNeo4jRepositoriesAutoConfigurationTests.java │ │ │ └── MockedDriverConfiguration.java │ │ └── domain/ │ │ ├── city/ │ │ │ ├── City.java │ │ │ ├── CityRepository.java │ │ │ ├── ReactiveCityRepository.java │ │ │ └── package-info.java │ │ ├── country/ │ │ │ ├── Country.java │ │ │ ├── CountryRepository.java │ │ │ ├── ReactiveCountryRepository.java │ │ │ └── package-info.java │ │ ├── empty/ │ │ │ ├── EmptyPackage.java │ │ │ └── package-info.java │ │ └── scan/ │ │ ├── TestNode.java │ │ ├── TestNonAnnotated.java │ │ ├── TestPersistent.java │ │ ├── TestRelationshipProperties.java │ │ └── package-info.java │ ├── spring-boot-data-neo4j-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── neo4j/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── DataNeo4jTestIntegrationTests.java │ │ │ ├── DataNeo4jTestPropertiesIntegrationTests.java │ │ │ ├── DataNeo4jTestReactiveIntegrationTests.java │ │ │ ├── DataNeo4jTestWithIncludeFilterIntegrationTests.java │ │ │ ├── ExampleGraph.java │ │ │ ├── ExampleNeo4jApplication.java │ │ │ ├── ExampleReactiveRepository.java │ │ │ ├── ExampleRepository.java │ │ │ └── ExampleService.java │ │ └── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── neo4j/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── AutoConfigureDataNeo4j.java │ │ │ ├── DataNeo4jTest.java │ │ │ ├── DataNeo4jTestContextBootstrapper.java │ │ │ ├── DataNeo4jTypeExcludeFilter.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring/ │ │ └── org.springframework.boot.data.neo4j.test.autoconfigure.AutoConfigureDataNeo4j.imports │ ├── spring-boot-data-r2dbc/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── r2dbc/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── DataR2dbcAutoConfiguration.java │ │ │ │ ├── DataR2dbcRepositoriesAutoConfiguration.java │ │ │ │ ├── DataR2dbcRepositoriesAutoConfigureRegistrar.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── r2dbc/ │ │ ├── autoconfigure/ │ │ │ ├── DataR2dbcAutoConfigurationTests.java │ │ │ └── DataR2dbcRepositoriesAutoConfigurationTests.java │ │ └── domain/ │ │ ├── city/ │ │ │ ├── City.java │ │ │ ├── CityRepository.java │ │ │ └── package-info.java │ │ └── empty/ │ │ ├── EmptyDataPackage.java │ │ └── package-info.java │ ├── spring-boot-data-r2dbc-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── r2dbc/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureDataR2dbc.java │ │ │ │ ├── DataR2dbcTest.java │ │ │ │ ├── DataR2dbcTestContextBootstrapper.java │ │ │ │ ├── DataR2dbcTypeExcludeFilter.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.data.r2dbc.test.autoconfigure.AutoConfigureDataR2dbc.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── r2dbc/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── DataR2dbcTestIntegrationTests.java │ │ │ ├── DataR2dbcTestPropertiesIntegrationTests.java │ │ │ ├── Example.java │ │ │ ├── ExampleR2dbcApplication.java │ │ │ └── ExampleRepository.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── r2dbc/ │ │ └── test/ │ │ └── autoconfigure/ │ │ └── schema.sql │ ├── spring-boot-data-redis/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── redis/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ └── DataRedisRepositoriesAutoConfigurationTests.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── DataRedisDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── CustomRedisContainerConnectionDetailsFactoryTests.java │ │ │ │ ├── RedisContainerConnectionDetailsFactoryTests.java │ │ │ │ ├── RedisStackContainerConnectionDetailsFactoryTests.java │ │ │ │ └── RedisStackServerContainerConnectionDetailsFactoryTests.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ ├── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── redis/ │ │ │ │ └── docker/ │ │ │ │ └── compose/ │ │ │ │ ├── ca.crt │ │ │ │ ├── client.crt │ │ │ │ ├── client.key │ │ │ │ ├── redis-compose.yaml │ │ │ │ ├── redis-ssl-compose.yaml │ │ │ │ ├── server.crt │ │ │ │ └── server.key │ │ │ └── spring.properties │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── redis/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ClientResourcesBuilderCustomizer.java │ │ │ │ │ ├── DataRedisAutoConfiguration.java │ │ │ │ │ ├── DataRedisConnectionConfiguration.java │ │ │ │ │ ├── DataRedisConnectionDetails.java │ │ │ │ │ ├── DataRedisProperties.java │ │ │ │ │ ├── DataRedisReactiveAutoConfiguration.java │ │ │ │ │ ├── DataRedisRepositoriesAutoConfiguration.java │ │ │ │ │ ├── DataRedisRepositoriesRegistrar.java │ │ │ │ │ ├── DataRedisUrl.java │ │ │ │ │ ├── DataRedisUrlSyntaxException.java │ │ │ │ │ ├── DataRedisUrlSyntaxFailureAnalyzer.java │ │ │ │ │ ├── JedisClientConfigurationBuilderCustomizer.java │ │ │ │ │ ├── JedisConnectionConfiguration.java │ │ │ │ │ ├── LettuceClientConfigurationBuilderCustomizer.java │ │ │ │ │ ├── LettuceClientOptionsBuilderCustomizer.java │ │ │ │ │ ├── LettuceConnectionConfiguration.java │ │ │ │ │ ├── PropertiesDataRedisConnectionDetails.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── DataRedisHealthContributorAutoConfiguration.java │ │ │ │ │ │ ├── DataRedisReactiveHealthContributorAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── observation/ │ │ │ │ │ │ ├── LettuceObservationAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── RedisDockerComposeConnectionDetailsFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── DataRedisHealth.java │ │ │ │ │ ├── DataRedisHealthIndicator.java │ │ │ │ │ ├── DataRedisReactiveHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── RedisContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── redis/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── DataRedisAutoConfigurationJedisTests.java │ │ │ │ ├── DataRedisAutoConfigurationLettuceWithoutCommonsPool2Tests.java │ │ │ │ ├── DataRedisAutoConfigurationTests.java │ │ │ │ ├── DataRedisReactiveAutoConfigurationTests.java │ │ │ │ ├── PropertiesRedisConnectionDetailsTests.java │ │ │ │ ├── RedisPropertiesTests.java │ │ │ │ ├── RedisUrlSyntaxFailureAnalyzerTests.java │ │ │ │ ├── health/ │ │ │ │ │ ├── DataRedisHealthContributorAutoConfigurationTests.java │ │ │ │ │ └── DataRedisReactiveHealthContributorAutoConfigurationTests.java │ │ │ │ └── observation/ │ │ │ │ └── LettuceObservationAutoConfigurationTests.java │ │ │ ├── domain/ │ │ │ │ ├── city/ │ │ │ │ │ ├── City.java │ │ │ │ │ ├── CityRepository.java │ │ │ │ │ └── package-info.java │ │ │ │ └── empty/ │ │ │ │ ├── EmptyPackage.java │ │ │ │ └── package-info.java │ │ │ └── health/ │ │ │ ├── DataRedisReactiveHealthIndicatorTests.java │ │ │ └── RedisHealthIndicatorTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── redis/ │ │ └── autoconfigure/ │ │ └── test.jks │ ├── spring-boot-data-redis-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── redis/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── DataRedisTestIntegrationTests.java │ │ │ ├── DataRedisTestPropertiesIntegrationTests.java │ │ │ ├── DataRedisTestReactiveIntegrationTests.java │ │ │ ├── DataRedisTestWithIncludeFilterIntegrationTests.java │ │ │ ├── ExampleRedisApplication.java │ │ │ ├── ExampleRepository.java │ │ │ ├── ExampleService.java │ │ │ └── PersonHash.java │ │ └── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── data/ │ │ │ └── redis/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── AutoConfigureDataRedis.java │ │ │ ├── DataRedisTest.java │ │ │ ├── DataRedisTestContextBootstrapper.java │ │ │ ├── DataRedisTypeExcludeFilter.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring/ │ │ └── org.springframework.boot.data.redis.test.autoconfigure.AutoConfigureDataRedis.imports │ ├── spring-boot-data-rest/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── data/ │ │ │ │ └── rest/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── DataRestAutoConfiguration.java │ │ │ │ ├── DataRestProperties.java │ │ │ │ ├── SpringBootRepositoryRestConfigurer.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── data/ │ │ └── rest/ │ │ ├── autoconfigure/ │ │ │ └── DataRestAutoConfigurationTests.java │ │ └── domain/ │ │ └── city/ │ │ ├── City.java │ │ ├── CityRepository.java │ │ └── package-info.java │ ├── spring-boot-devtools/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── intTest/ │ │ │ └── java/ │ │ │ ├── com/ │ │ │ │ └── example/ │ │ │ │ ├── ControllerOne.java │ │ │ │ ├── DevToolsTestApplication.java │ │ │ │ └── package-info.java │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── devtools/ │ │ │ └── tests/ │ │ │ ├── AbstractApplicationLauncher.java │ │ │ ├── AbstractDevToolsIntegrationTests.java │ │ │ ├── ApplicationLauncher.java │ │ │ ├── ApplicationState.java │ │ │ ├── DevToolsIntegrationTests.java │ │ │ ├── DevToolsWithLazyInitializationIntegrationTests.java │ │ │ ├── Directories.java │ │ │ ├── ExplodedRemoteApplicationLauncher.java │ │ │ ├── FileContents.java │ │ │ ├── JarFileRemoteApplicationLauncher.java │ │ │ ├── JvmLauncher.java │ │ │ ├── LaunchedApplication.java │ │ │ ├── LocalApplicationLauncher.java │ │ │ ├── RemoteApplicationLauncher.java │ │ │ └── package-info.java │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── devtools/ │ │ │ │ ├── RemoteSpringApplication.java │ │ │ │ ├── RemoteUrlPropertyExtractor.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ConditionEvaluationDeltaLoggingListener.java │ │ │ │ │ ├── ConditionalOnEnabledDevTools.java │ │ │ │ │ ├── DevToolsDataSourceAutoConfiguration.java │ │ │ │ │ ├── DevToolsProperties.java │ │ │ │ │ ├── DevToolsR2dbcAutoConfiguration.java │ │ │ │ │ ├── FileWatchingFailureHandler.java │ │ │ │ │ ├── LocalDevToolsAutoConfiguration.java │ │ │ │ │ ├── OnEnabledDevToolsCondition.java │ │ │ │ │ ├── OptionalLiveReloadServer.java │ │ │ │ │ ├── RemoteDevToolsAutoConfiguration.java │ │ │ │ │ ├── RemoteDevToolsProperties.java │ │ │ │ │ ├── RemoteDevtoolsSecurityConfiguration.java │ │ │ │ │ ├── TriggerFileFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── classpath/ │ │ │ │ │ ├── ClassPathChangedEvent.java │ │ │ │ │ ├── ClassPathDirectories.java │ │ │ │ │ ├── ClassPathFileChangeListener.java │ │ │ │ │ ├── ClassPathFileSystemWatcher.java │ │ │ │ │ ├── ClassPathRestartStrategy.java │ │ │ │ │ ├── PatternClassPathRestartStrategy.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── env/ │ │ │ │ │ ├── DevToolsHomePropertiesPostProcessor.java │ │ │ │ │ ├── DevToolsPropertyDefaultsPostProcessor.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── filewatch/ │ │ │ │ │ ├── ChangedFile.java │ │ │ │ │ ├── ChangedFiles.java │ │ │ │ │ ├── DirectorySnapshot.java │ │ │ │ │ ├── FileChangeListener.java │ │ │ │ │ ├── FileSnapshot.java │ │ │ │ │ ├── FileSystemWatcher.java │ │ │ │ │ ├── FileSystemWatcherFactory.java │ │ │ │ │ ├── SnapshotStateRepository.java │ │ │ │ │ ├── StaticSnapshotStateRepository.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── livereload/ │ │ │ │ │ ├── Connection.java │ │ │ │ │ ├── ConnectionClosedException.java │ │ │ │ │ ├── ConnectionInputStream.java │ │ │ │ │ ├── ConnectionOutputStream.java │ │ │ │ │ ├── Frame.java │ │ │ │ │ ├── LiveReloadServer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── logger/ │ │ │ │ │ ├── DevToolsLogFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── remote/ │ │ │ │ │ ├── client/ │ │ │ │ │ │ ├── ClassPathChangeUploader.java │ │ │ │ │ │ ├── DelayedLiveReloadTrigger.java │ │ │ │ │ │ ├── HttpHeaderInterceptor.java │ │ │ │ │ │ ├── RemoteClientConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── server/ │ │ │ │ │ ├── AccessManager.java │ │ │ │ │ ├── Dispatcher.java │ │ │ │ │ ├── DispatcherFilter.java │ │ │ │ │ ├── Handler.java │ │ │ │ │ ├── HandlerMapper.java │ │ │ │ │ ├── HttpHeaderAccessManager.java │ │ │ │ │ ├── HttpStatusHandler.java │ │ │ │ │ ├── UrlHandlerMapper.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── restart/ │ │ │ │ │ ├── AgentReloader.java │ │ │ │ │ ├── ChangeableUrls.java │ │ │ │ │ ├── ClassLoaderFilesResourcePatternResolver.java │ │ │ │ │ ├── ConditionalOnInitializedRestarter.java │ │ │ │ │ ├── DefaultRestartInitializer.java │ │ │ │ │ ├── FailureHandler.java │ │ │ │ │ ├── MainMethod.java │ │ │ │ │ ├── OnInitializedRestarterCondition.java │ │ │ │ │ ├── RestartApplicationListener.java │ │ │ │ │ ├── RestartInitializer.java │ │ │ │ │ ├── RestartLauncher.java │ │ │ │ │ ├── RestartListener.java │ │ │ │ │ ├── RestartScope.java │ │ │ │ │ ├── RestartScopeInitializer.java │ │ │ │ │ ├── Restarter.java │ │ │ │ │ ├── SilentExitExceptionHandler.java │ │ │ │ │ ├── classloader/ │ │ │ │ │ │ ├── ClassLoaderFile.java │ │ │ │ │ │ ├── ClassLoaderFileRepository.java │ │ │ │ │ │ ├── ClassLoaderFileURLStreamHandler.java │ │ │ │ │ │ ├── ClassLoaderFiles.java │ │ │ │ │ │ ├── RestartClassLoader.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── server/ │ │ │ │ │ ├── DefaultSourceDirectoryUrlFilter.java │ │ │ │ │ ├── HttpRestartServer.java │ │ │ │ │ ├── HttpRestartServerHandler.java │ │ │ │ │ ├── RestartServer.java │ │ │ │ │ ├── SourceDirectoryUrlFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── settings/ │ │ │ │ │ ├── DevToolsSettings.java │ │ │ │ │ └── package-info.java │ │ │ │ └── system/ │ │ │ │ ├── DevToolsEnablementDeducer.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ │ ├── spring/ │ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ ├── spring-devtools.properties │ │ │ │ └── spring.factories │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── devtools/ │ │ │ ├── livereload/ │ │ │ │ └── livereload.js │ │ │ └── remote-banner.txt │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ ├── devtools/ │ │ │ │ ├── RemoteUrlPropertyExtractorTests.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── AbstractDevToolsDataSourceAutoConfigurationTests.java │ │ │ │ │ ├── DevToolsEmbeddedDataSourceAutoConfigurationTests.java │ │ │ │ │ ├── DevToolsPooledDataSourceAutoConfigurationTests.java │ │ │ │ │ ├── DevToolsPropertiesTests.java │ │ │ │ │ ├── DevToolsR2dbcAutoConfigurationTests.java │ │ │ │ │ ├── LocalDevToolsAutoConfigurationTests.java │ │ │ │ │ ├── OnEnabledDevToolsConditionTests.java │ │ │ │ │ ├── OptionalLiveReloadServerTests.java │ │ │ │ │ ├── RemoteDevToolsAutoConfigurationTests.java │ │ │ │ │ └── TriggerFileFilterTests.java │ │ │ │ ├── classpath/ │ │ │ │ │ ├── ClassPathChangedEventTests.java │ │ │ │ │ ├── ClassPathFileChangeListenerTests.java │ │ │ │ │ ├── ClassPathFileSystemWatcherTests.java │ │ │ │ │ └── PatternClassPathRestartStrategyTests.java │ │ │ │ ├── env/ │ │ │ │ │ ├── DevToolPropertiesIntegrationTests.java │ │ │ │ │ └── DevToolsHomePropertiesPostProcessorTests.java │ │ │ │ ├── filewatch/ │ │ │ │ │ ├── ChangedFileTests.java │ │ │ │ │ ├── DirectorySnapshotTests.java │ │ │ │ │ ├── FileSnapshotTests.java │ │ │ │ │ └── FileSystemWatcherTests.java │ │ │ │ ├── livereload/ │ │ │ │ │ ├── ConnectionInputStreamTests.java │ │ │ │ │ ├── ConnectionOutputStreamTests.java │ │ │ │ │ ├── FrameTests.java │ │ │ │ │ └── LiveReloadServerTests.java │ │ │ │ ├── remote/ │ │ │ │ │ ├── client/ │ │ │ │ │ │ ├── ClassPathChangeUploaderTests.java │ │ │ │ │ │ ├── DelayedLiveReloadTriggerTests.java │ │ │ │ │ │ ├── HttpHeaderInterceptorTests.java │ │ │ │ │ │ └── RemoteClientConfigurationTests.java │ │ │ │ │ └── server/ │ │ │ │ │ ├── DispatcherFilterTests.java │ │ │ │ │ ├── DispatcherTests.java │ │ │ │ │ ├── HttpHeaderAccessManagerTests.java │ │ │ │ │ ├── HttpStatusHandlerTests.java │ │ │ │ │ └── UrlHandlerMapperTests.java │ │ │ │ ├── restart/ │ │ │ │ │ ├── ChangeableUrlsTests.java │ │ │ │ │ ├── ClassLoaderFilesResourcePatternResolverTests.java │ │ │ │ │ ├── DefaultRestartInitializerTests.java │ │ │ │ │ ├── MainMethodTests.java │ │ │ │ │ ├── MockRestartInitializer.java │ │ │ │ │ ├── MockRestarter.java │ │ │ │ │ ├── OnInitializedRestarterConditionTests.java │ │ │ │ │ ├── RestartApplicationListenerTests.java │ │ │ │ │ ├── RestartScopeInitializerTests.java │ │ │ │ │ ├── RestarterTests.java │ │ │ │ │ ├── SilentExitExceptionHandlerTests.java │ │ │ │ │ ├── classloader/ │ │ │ │ │ │ ├── ClassLoaderFileTests.java │ │ │ │ │ │ ├── ClassLoaderFilesTests.java │ │ │ │ │ │ ├── RestartClassLoaderTests.java │ │ │ │ │ │ ├── Sample.java │ │ │ │ │ │ └── SampleParent.java │ │ │ │ │ └── server/ │ │ │ │ │ ├── DefaultSourceDirectoryUrlFilterTests.java │ │ │ │ │ ├── HttpRestartServerHandlerTests.java │ │ │ │ │ ├── HttpRestartServerTests.java │ │ │ │ │ └── RestartServerTests.java │ │ │ │ ├── settings/ │ │ │ │ │ └── DevToolsSettingsTests.java │ │ │ │ └── test/ │ │ │ │ ├── MockClientHttpRequestFactory.java │ │ │ │ └── package-info.java │ │ │ └── loader/ │ │ │ └── launch/ │ │ │ ├── FakeJarLauncher.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── devtools/ │ │ │ ├── env/ │ │ │ │ └── spring-devtools.yaml │ │ │ ├── restart/ │ │ │ │ └── classloader/ │ │ │ │ └── Parent.txt │ │ │ └── settings/ │ │ │ ├── spring-devtools-defaults.properties │ │ │ ├── spring-devtools-exclude.properties │ │ │ └── spring-devtools-include.properties │ │ └── user-home/ │ │ └── .spring-boot-devtools.properties │ ├── spring-boot-elasticsearch/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── elasticsearch/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ElasticsearchClientAutoConfigurationIntegrationTests.java │ │ │ │ │ └── ElasticsearchRestClientAutoConfigurationIntegrationTests.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── ElasticsearchDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ └── ElasticsearchContainerConnectionDetailsFactoryTests.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ ├── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── elasticsearch/ │ │ │ │ └── docker/ │ │ │ │ └── compose/ │ │ │ │ ├── ca.crt │ │ │ │ ├── client.crt │ │ │ │ ├── client.key │ │ │ │ ├── elasticsearch-compose.yaml │ │ │ │ ├── elasticsearch-ssl-compose.yaml │ │ │ │ ├── server.crt │ │ │ │ └── server.key │ │ │ └── spring.properties │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── elasticsearch/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ElasticsearchClientAutoConfiguration.java │ │ │ │ │ ├── ElasticsearchClientConfigurations.java │ │ │ │ │ ├── ElasticsearchConnectionDetails.java │ │ │ │ │ ├── ElasticsearchProperties.java │ │ │ │ │ ├── ElasticsearchRestClientAutoConfiguration.java │ │ │ │ │ ├── ElasticsearchRestClientConfigurations.java │ │ │ │ │ ├── Rest5ClientBuilderCustomizer.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── ElasticsearchRestHealthContributorAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── ElasticsearchDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── ElasticsearchEnvironment.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── ElasticsearchRestClientHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── ElasticsearchContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── elasticsearch/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── ElasticsearchClientAutoConfigurationTests.java │ │ │ │ ├── ElasticsearchRestClientAutoConfigurationTests.java │ │ │ │ └── health/ │ │ │ │ └── ElasticsearchRestHealthContributorAutoConfigurationTests.java │ │ │ ├── docker/ │ │ │ │ └── compose/ │ │ │ │ └── ElasticsearchEnvironmentTests.java │ │ │ └── health/ │ │ │ └── ElasticsearchRestClientHealthIndicatorTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── elasticsearch/ │ │ └── autoconfigure/ │ │ └── test.jks │ ├── spring-boot-flyway/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── flyway/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── JdbcAdaptingFlywayConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ └── FlywayContainerConnectionDetailsFactoryTests.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ ├── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── flyway/ │ │ │ │ └── docker/ │ │ │ │ └── compose/ │ │ │ │ └── flyway-compose.yaml │ │ │ └── spring.properties │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── flyway/ │ │ │ │ ├── FlywayDatabaseInitializerDetector.java │ │ │ │ ├── actuate/ │ │ │ │ │ └── endpoint/ │ │ │ │ │ ├── FlywayEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── FlywayAutoConfiguration.java │ │ │ │ │ ├── FlywayConfigurationCustomizer.java │ │ │ │ │ ├── FlywayConnectionDetails.java │ │ │ │ │ ├── FlywayDataSource.java │ │ │ │ │ ├── FlywayEndpointAutoConfiguration.java │ │ │ │ │ ├── FlywayMigrationInitializer.java │ │ │ │ │ ├── FlywayMigrationInitializerDatabaseInitializerDetector.java │ │ │ │ │ ├── FlywayMigrationStrategy.java │ │ │ │ │ ├── FlywayProperties.java │ │ │ │ │ ├── FlywaySchemaManagementProvider.java │ │ │ │ │ ├── NativeImageResourceProvider.java │ │ │ │ │ ├── NativeImageResourceProviderCustomizer.java │ │ │ │ │ ├── ResourceProviderCustomizer.java │ │ │ │ │ ├── ResourceProviderCustomizerBeanRegistrationAotProcessor.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── JdbcAdaptingFlywayConnectionDetailsFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── FlywayContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ │ ├── aot.factories │ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ └── org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureDataSourceInitialization.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── flyway/ │ │ ├── actuate/ │ │ │ └── endpoint/ │ │ │ └── FlywayEndpointTests.java │ │ └── autoconfigure/ │ │ ├── Flyway110AutoConfigurationTests.java │ │ ├── FlywayAutoConfigurationTests.java │ │ ├── FlywayAutoConfigureDataSourceInitializationTests.java │ │ ├── FlywayEndpointAutoConfigurationTests.java │ │ ├── FlywayPropertiesTests.java │ │ ├── NativeImageResourceProviderCustomizerTests.java │ │ └── ResourceProviderCustomizerBeanRegistrationAotProcessorTests.java │ ├── spring-boot-freemarker/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── freemarker/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AbstractFreeMarkerConfiguration.java │ │ │ │ ├── FreeMarkerAutoConfiguration.java │ │ │ │ ├── FreeMarkerNonWebConfiguration.java │ │ │ │ ├── FreeMarkerProperties.java │ │ │ │ ├── FreeMarkerReactiveWebConfiguration.java │ │ │ │ ├── FreeMarkerServletWebConfiguration.java │ │ │ │ ├── FreeMarkerTemplateAvailabilityProvider.java │ │ │ │ ├── FreeMarkerVariablesCustomizer.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── aot.factories │ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ ├── org.springframework.boot.webflux.test.autoconfigure.AutoConfigureWebFlux.imports │ │ │ │ └── org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureWebMvc.imports │ │ │ ├── spring-devtools.properties │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── freemarker/ │ │ └── autoconfigure/ │ │ ├── FreeMarkerAutoConfigurationReactiveIntegrationTests.java │ │ ├── FreeMarkerAutoConfigurationServletIntegrationTests.java │ │ ├── FreeMarkerAutoConfigurationTests.java │ │ ├── FreeMarkerPropertiesTests.java │ │ ├── FreeMarkerTemplateAvailabilityProviderTests.java │ │ ├── FreeMarkerWebFluxTestIntegrationTests.java │ │ └── FreeMarkerWebMvcTestIntegrationTests.java │ ├── spring-boot-graphql/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── graphql/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── ConditionalOnGraphQlSchema.java │ │ │ │ ├── DefaultGraphQlSchemaCondition.java │ │ │ │ ├── GraphQlAutoConfiguration.java │ │ │ │ ├── GraphQlCorsProperties.java │ │ │ │ ├── GraphQlProperties.java │ │ │ │ ├── GraphQlSourceBuilderCustomizer.java │ │ │ │ ├── data/ │ │ │ │ │ ├── GraphQlQueryByExampleAutoConfiguration.java │ │ │ │ │ ├── GraphQlQuerydslAutoConfiguration.java │ │ │ │ │ ├── GraphQlReactiveQueryByExampleAutoConfiguration.java │ │ │ │ │ ├── GraphQlReactiveQuerydslAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── observation/ │ │ │ │ │ ├── GraphQlObservationAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── GraphQlWebFluxAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── rsocket/ │ │ │ │ │ ├── GraphQlRSocketAutoConfiguration.java │ │ │ │ │ ├── GraphQlRSocketController.java │ │ │ │ │ ├── RSocketGraphQlClientAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── security/ │ │ │ │ │ ├── GraphQlWebFluxSecurityAutoConfiguration.java │ │ │ │ │ ├── GraphQlWebMvcSecurityAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ └── servlet/ │ │ │ │ ├── GraphQlWebMvcAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring-devtools.properties │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── graphql/ │ │ └── autoconfigure/ │ │ ├── Book.java │ │ ├── DefaultGraphQlSchemaConditionTests.java │ │ ├── GraphQlAutoConfigurationTests.java │ │ ├── GraphQlTestDataFetchers.java │ │ ├── QBook.java │ │ ├── data/ │ │ │ ├── GraphQlQueryByExampleAutoConfigurationTests.java │ │ │ ├── GraphQlQuerydslAutoConfigurationTests.java │ │ │ ├── GraphQlReactiveQueryByExampleAutoConfigurationTests.java │ │ │ └── GraphQlReactiveQuerydslAutoConfigurationTests.java │ │ ├── observation/ │ │ │ └── GraphQlObservationAutoConfigurationTests.java │ │ ├── reactive/ │ │ │ └── GraphQlWebFluxAutoConfigurationTests.java │ │ ├── rsocket/ │ │ │ ├── GraphQlRSocketAutoConfigurationTests.java │ │ │ └── RSocketGraphQlClientAutoConfigurationTests.java │ │ ├── security/ │ │ │ ├── GraphQlWebFluxSecurityAutoConfigurationTests.java │ │ │ └── GraphQlWebMvcSecurityAutoConfigurationTests.java │ │ └── servlet/ │ │ └── GraphQlWebMvcAutoConfigurationTests.java │ ├── spring-boot-graphql-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── graphql/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureGraphQl.java │ │ │ │ ├── GraphQlTest.java │ │ │ │ ├── GraphQlTestContextBootstrapper.java │ │ │ │ ├── GraphQlTypeExcludeFilter.java │ │ │ │ ├── package-info.java │ │ │ │ └── tester/ │ │ │ │ ├── AutoConfigureGraphQlTester.java │ │ │ │ ├── AutoConfigureHttpGraphQlTester.java │ │ │ │ ├── GraphQlTesterAutoConfiguration.java │ │ │ │ ├── HttpGraphQlTesterAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.graphql.test.autoconfigure.AutoConfigureGraphQl.imports │ │ │ ├── org.springframework.boot.graphql.test.autoconfigure.tester.AutoConfigureGraphQlTester.imports │ │ │ └── org.springframework.boot.graphql.test.autoconfigure.tester.AutoConfigureHttpGraphQlTester.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── graphql/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── Book.java │ │ │ ├── BookController.java │ │ │ ├── ExampleGraphQlApplication.java │ │ │ ├── GraphQlTestIntegrationTests.java │ │ │ ├── GraphQlTestPropertiesIntegrationTests.java │ │ │ ├── GraphQlTypeExcludeFilterTests.java │ │ │ └── tester/ │ │ │ ├── GraphQlTesterAutoConfigurationTests.java │ │ │ └── HttpGraphQlTesterAutoConfigurationTests.java │ │ └── resources/ │ │ └── graphql/ │ │ └── schema.graphqls │ ├── spring-boot-groovy-templates/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── groovy/ │ │ │ │ └── template/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── GroovyTemplateAutoConfiguration.java │ │ │ │ ├── GroovyTemplateAvailabilityProvider.java │ │ │ │ ├── GroovyTemplateProperties.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── aot.factories │ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ └── org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureWebMvc.imports │ │ │ ├── spring-devtools.properties │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── groovy/ │ │ └── template/ │ │ └── autoconfigure/ │ │ ├── GroovyTemplateAutoConfigurationTests.java │ │ ├── GroovyTemplateAvailabilityProviderTests.java │ │ ├── GroovyTemplatePropertiesTests.java │ │ └── GroovyTemplateWebMvcTestIntegrationTests.java │ ├── spring-boot-grpc-client/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── grpc/ │ │ │ │ └── client/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── CompositeChannelFactoryAutoConfiguration.java │ │ │ │ ├── ConditionalOnGrpcClientChannelFactoryEnabled.java │ │ │ │ ├── GrpcChannelBuilderCustomizers.java │ │ │ │ ├── GrpcChannelFactoryCustomizer.java │ │ │ │ ├── GrpcClientAutoConfiguration.java │ │ │ │ ├── GrpcClientCodecConfiguration.java │ │ │ │ ├── GrpcClientDefaultServiceConfigCustomizer.java │ │ │ │ ├── GrpcClientObservationAutoConfiguration.java │ │ │ │ ├── GrpcClientProperties.java │ │ │ │ ├── InProcessGrpcClientConfiguration.java │ │ │ │ ├── NettyGrpcClientConfiguration.java │ │ │ │ ├── PropertiesChannelCredentialsProvider.java │ │ │ │ ├── PropertiesGrpcChannelBuilderCustomizer.java │ │ │ │ ├── PropertiesGrpcClientDefaultServiceConfigCustomizer.java │ │ │ │ ├── PropertiesVirtualTargets.java │ │ │ │ ├── ServiceConfig.java │ │ │ │ ├── ShadedNettyGrpcClientConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── grpc/ │ │ └── client/ │ │ └── autoconfigure/ │ │ ├── CompositeChannelFactoryAutoConfigurationTests.java │ │ ├── GrpcChannelBuilderCustomizersTests.java │ │ ├── GrpcClientAutoConfigurationTests.java │ │ ├── GrpcClientObservationAutoConfigurationTests.java │ │ ├── GrpcClientPropertiesTests.java │ │ ├── GrpcCodecConfigurationTests.java │ │ ├── PropertiesChannelCredentialsProviderTests.java │ │ ├── PropertiesGrpcChannelBuilderCustomizerTests.java │ │ ├── PropertiesVirtualTargetsTests.java │ │ ├── ServiceConfigTests.java │ │ └── test/ │ │ └── scan/ │ │ ├── DummyBlockingGrpc.java │ │ ├── DummyBlockingV2Grpc.java │ │ └── package-info.java │ ├── spring-boot-grpc-server/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── grpc/ │ │ │ │ └── server/ │ │ │ │ ├── GrpcServletRegistration.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ConditionalOnGrpcServerFactoryEnabled.java │ │ │ │ │ ├── ConditionalOnMissingNetworkGrpcServer.java │ │ │ │ │ ├── GrpcServerAutoConfiguration.java │ │ │ │ │ ├── GrpcServerBuilderCustomizers.java │ │ │ │ │ ├── GrpcServerCodecConfiguration.java │ │ │ │ │ ├── GrpcServerExecutorProvider.java │ │ │ │ │ ├── GrpcServerFactoryCustomizer.java │ │ │ │ │ ├── GrpcServerObservationAutoConfiguration.java │ │ │ │ │ ├── GrpcServerProperties.java │ │ │ │ │ ├── GrpcServerServicesAutoConfiguration.java │ │ │ │ │ ├── InProcessGrpcServerConfiguration.java │ │ │ │ │ ├── MissingNetworkGrpcServerCondition.java │ │ │ │ │ ├── NettyAddress.java │ │ │ │ │ ├── NettyGrpcServerConfiguration.java │ │ │ │ │ ├── PropertiesServerBuilderCustomizer.java │ │ │ │ │ ├── ServerCredentials.java │ │ │ │ │ ├── ServletGrpcServerConfiguration.java │ │ │ │ │ ├── ShadedNettyGrpcServerConfiguration.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── AutoConfiguredHealthCheckedGrpcComponent.java │ │ │ │ │ │ ├── AutoConfiguredHealthCheckedGrpcComponents.java │ │ │ │ │ │ ├── GrpcServerHealthAutoConfiguration.java │ │ │ │ │ │ ├── GrpcServerHealthProperties.java │ │ │ │ │ │ ├── GrpcServerHealthScheduler.java │ │ │ │ │ │ ├── GrpcServerHealthSchedulerAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── security/ │ │ │ │ │ ├── GrpcDisableCsrfHttpConfigurer.java │ │ │ │ │ ├── GrpcServerOAuth2ResourceServerAutoConfiguration.java │ │ │ │ │ ├── GrpcServerSecurityAutoConfiguration.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── reactive/ │ │ │ │ │ │ ├── GrpcRequest.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── servlet/ │ │ │ │ │ ├── GrpcRequest.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── GrpcServerHealth.java │ │ │ │ │ ├── HealthCheckedGrpcComponent.java │ │ │ │ │ ├── HealthCheckedGrpcComponents.java │ │ │ │ │ ├── SimpleServingStatusMapper.java │ │ │ │ │ ├── SimpleStatusAggregator.java │ │ │ │ │ ├── StatusAggregator.java │ │ │ │ │ ├── StatusMapper.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── grpc/ │ │ │ └── server/ │ │ │ ├── GrpcServletRegistrationTests.java │ │ │ ├── autoconfigure/ │ │ │ │ ├── GrpcServerAutoConfigurationTests.java │ │ │ │ ├── GrpcServerBuilderCustomizersTests.java │ │ │ │ ├── GrpcServerCodecConfigurationTests.java │ │ │ │ ├── GrpcServerObservationAutoConfigurationTests.java │ │ │ │ ├── GrpcServerPropertiesTests.java │ │ │ │ ├── GrpcServerServicesAutoConfigurationTests.java │ │ │ │ ├── NettyAddressTests.java │ │ │ │ ├── PropertiesServerBuilderCustomizerTests.java │ │ │ │ ├── ServerCredentialsTests.java │ │ │ │ ├── health/ │ │ │ │ │ ├── AutoConfiguredHealthCheckedGrpcComponentTests.java │ │ │ │ │ ├── AutoConfiguredHealthCheckedGrpcComponentsTests.java │ │ │ │ │ ├── GrpcServerHealthAutoConfigurationTests.java │ │ │ │ │ └── GrpcServerHealthSchedulerTests.java │ │ │ │ └── security/ │ │ │ │ ├── GrpcDisableCsrfHttpConfigurerTests.java │ │ │ │ ├── GrpcServerOAuth2ResourceServerAutoConfigurationTests.java │ │ │ │ ├── GrpcServerSecurityAutoConfigurationTests.java │ │ │ │ └── web/ │ │ │ │ ├── reactive/ │ │ │ │ │ └── GrpcRequestTests.java │ │ │ │ └── servlet/ │ │ │ │ └── GrpcRequestTests.java │ │ │ └── health/ │ │ │ ├── GrpcServerHealthTests.java │ │ │ ├── StatusAggregatorTests.java │ │ │ └── StatusMapperTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── grpc/ │ │ └── server/ │ │ └── autoconfigure/ │ │ └── test.jks │ ├── spring-boot-grpc-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── grpc/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureTestGrpcTransport.java │ │ │ │ ├── GrpcPortInfoApplicationContextInitializer.java │ │ │ │ ├── LocalGrpcServerPort.java │ │ │ │ ├── TestGrpcChannelFactory.java │ │ │ │ ├── TestGrpcServerFactory.java │ │ │ │ ├── TestGrpcTransportAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.grpc.test.autoconfigure.AutoConfigureTestGrpcTransport.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── grpc/ │ │ └── test/ │ │ └── autoconfigure/ │ │ ├── AutoConfigureTestGrpcTransportOverrideTests.java │ │ ├── AutoConfigureTestGrpcTransportTests.java │ │ ├── GrpcPortInfoApplicationContextInitializerTests.java │ │ ├── TestGrpcChannelFactoryTests.java │ │ ├── TestGrpcServerFactoryTests.java │ │ └── TestGrpcTransportAutoConfigurationTests.java │ ├── spring-boot-gson/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── gson/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── GsonAutoConfiguration.java │ │ │ │ ├── GsonBuilderCustomizer.java │ │ │ │ ├── GsonProperties.java │ │ │ │ ├── GsonTesterTestAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ ├── org.springframework.boot.test.autoconfigure.json.AutoConfigureJson.imports │ │ │ └── org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── gson/ │ │ │ └── autoconfigure/ │ │ │ ├── Gson210AutoConfigurationTests.java │ │ │ ├── GsonAutoConfigurationTests.java │ │ │ ├── GsonPropertiesTests.java │ │ │ ├── GsonTesterAutoConfigurationTests.java │ │ │ └── jsontest/ │ │ │ ├── GsonAutoConfigureJsonIntegrationTests.java │ │ │ ├── JsonTestApplication.java │ │ │ ├── JsonTestIntegrationTests.java │ │ │ ├── JsonTestWithAutoConfigureJsonTestersTests.java │ │ │ ├── SpringBootTestWithAutoConfigureJsonTestersTests.java │ │ │ ├── app/ │ │ │ │ ├── ExampleBasicObject.java │ │ │ │ ├── ExampleJsonApplication.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── gson/ │ │ └── autoconfigure/ │ │ └── jsontest/ │ │ └── example.json │ ├── spring-boot-h2console/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── h2console/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── H2ConsoleAutoConfiguration.java │ │ │ │ ├── H2ConsoleProperties.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring-devtools.properties │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── h2console/ │ │ └── autoconfigure/ │ │ ├── H2ConsoleAutoConfigurationTests.java │ │ └── H2ConsolePropertiesTests.java │ ├── spring-boot-hateoas/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── hateoas/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── HateoasProperties.java │ │ │ │ ├── HypermediaAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureWebMvc.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── hateoas/ │ │ └── autoconfigure/ │ │ ├── HypermediaAutoConfigurationTests.java │ │ ├── HypermediaAutoConfigurationWithoutJacksonTests.java │ │ └── HypermediaWebMvcTestIntegrationTests.java │ ├── spring-boot-hazelcast/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── hazelcast/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── CustomClusterNameHazelcastContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── HazelcastContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ ├── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── hazelcast/ │ │ │ │ └── docker/ │ │ │ │ └── compose/ │ │ │ │ ├── hazelcast-cluster-name-compose.yaml │ │ │ │ └── hazelcast-compose.yaml │ │ │ └── spring.properties │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── hazelcast/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── HazelcastAutoConfiguration.java │ │ │ │ │ ├── HazelcastClientConfigAvailableCondition.java │ │ │ │ │ ├── HazelcastClientConfiguration.java │ │ │ │ │ ├── HazelcastClientInstanceConfiguration.java │ │ │ │ │ ├── HazelcastConfigCustomizer.java │ │ │ │ │ ├── HazelcastConfigResourceCondition.java │ │ │ │ │ ├── HazelcastConnectionDetails.java │ │ │ │ │ ├── HazelcastConnectionDetailsConfiguration.java │ │ │ │ │ ├── HazelcastJpaDependencyAutoConfiguration.java │ │ │ │ │ ├── HazelcastProperties.java │ │ │ │ │ ├── HazelcastServerConfiguration.java │ │ │ │ │ ├── PropertiesHazelcastConnectionDetails.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── HazelcastHealthContributorAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── HazelcastDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── HazelcastEnvironment.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── HazelcastHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── HazelcastContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── hazelcast/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── HazelcastAutoConfigurationClientTests.java │ │ │ │ ├── HazelcastAutoConfigurationServerTests.java │ │ │ │ ├── HazelcastAutoConfigurationTests.java │ │ │ │ ├── HazelcastClientConfigAvailableConditionTests.java │ │ │ │ ├── HazelcastJpaDependencyAutoConfigurationTests.java │ │ │ │ └── health/ │ │ │ │ ├── HazelcastHealthContributorAutoConfigurationIntegrationTests.java │ │ │ │ └── HazelcastHealthContributorAutoConfigurationTests.java │ │ │ ├── docker/ │ │ │ │ └── compose/ │ │ │ │ └── HazelcastEnvironmentTests.java │ │ │ ├── health/ │ │ │ │ └── HazelcastHealthIndicatorTests.java │ │ │ └── testcontainers/ │ │ │ └── HazelcastContainerConnectionDetailsFactoryTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── hazelcast/ │ │ └── autoconfigure/ │ │ ├── hazelcast-client-instance.xml │ │ ├── hazelcast-client-specific.xml │ │ ├── hazelcast-client-specific.yaml │ │ ├── hazelcast-client-specific.yml │ │ ├── hazelcast-specific.xml │ │ ├── hazelcast-specific.yaml │ │ └── hazelcast-specific.yml │ ├── spring-boot-health/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── health/ │ │ │ │ ├── actuate/ │ │ │ │ │ └── endpoint/ │ │ │ │ │ ├── AdditionalHealthEndpointPath.java │ │ │ │ │ ├── CompositeHealthDescriptor.java │ │ │ │ │ ├── Contributor.java │ │ │ │ │ ├── HealthDescriptor.java │ │ │ │ │ ├── HealthEndpoint.java │ │ │ │ │ ├── HealthEndpointGroup.java │ │ │ │ │ ├── HealthEndpointGroups.java │ │ │ │ │ ├── HealthEndpointGroupsPostProcessor.java │ │ │ │ │ ├── HealthEndpointSupport.java │ │ │ │ │ ├── HealthEndpointWebExtension.java │ │ │ │ │ ├── HealthEndpointWebExtensionRuntimeHints.java │ │ │ │ │ ├── HttpCodeStatusMapper.java │ │ │ │ │ ├── IndicatedHealthDescriptor.java │ │ │ │ │ ├── ReactiveHealthEndpointWebExtension.java │ │ │ │ │ ├── SimpleHttpCodeStatusMapper.java │ │ │ │ │ ├── SimpleStatusAggregator.java │ │ │ │ │ ├── StatusAggregator.java │ │ │ │ │ ├── SystemHealthDescriptor.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── application/ │ │ │ │ │ ├── AvailabilityStateHealthIndicator.java │ │ │ │ │ ├── DiskSpaceHealthIndicator.java │ │ │ │ │ ├── LivenessStateHealthIndicator.java │ │ │ │ │ ├── ReadinessStateHealthIndicator.java │ │ │ │ │ ├── SslHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── actuate/ │ │ │ │ │ │ └── endpoint/ │ │ │ │ │ │ ├── AutoConfiguredHealthEndpointGroup.java │ │ │ │ │ │ ├── AutoConfiguredHealthEndpointGroups.java │ │ │ │ │ │ ├── AvailabilityProbesAutoConfiguration.java │ │ │ │ │ │ ├── AvailabilityProbesHealthEndpointGroup.java │ │ │ │ │ │ ├── AvailabilityProbesHealthEndpointGroups.java │ │ │ │ │ │ ├── AvailabilityProbesHealthEndpointGroupsPostProcessor.java │ │ │ │ │ │ ├── DelegatingAvailabilityProbesHealthEndpointGroup.java │ │ │ │ │ │ ├── GroupsHealthContributorNameValidator.java │ │ │ │ │ │ ├── HealthEndpointAutoConfiguration.java │ │ │ │ │ │ ├── HealthEndpointConfiguration.java │ │ │ │ │ │ ├── HealthEndpointProperties.java │ │ │ │ │ │ ├── HealthEndpointReactiveWebExtensionConfiguration.java │ │ │ │ │ │ ├── HealthEndpointWebExtensionConfiguration.java │ │ │ │ │ │ ├── HealthProperties.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── application/ │ │ │ │ │ │ ├── AvailabilityHealthContributorAutoConfiguration.java │ │ │ │ │ │ ├── DiskSpaceHealthContributorAutoConfiguration.java │ │ │ │ │ │ ├── DiskSpaceHealthIndicatorProperties.java │ │ │ │ │ │ ├── SslHealthContributorAutoConfiguration.java │ │ │ │ │ │ ├── SslHealthIndicatorProperties.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── contributor/ │ │ │ │ │ │ ├── AbstractCompositeHealthContributorConfiguration.java │ │ │ │ │ │ ├── CompositeHealthContributorConfiguration.java │ │ │ │ │ │ ├── CompositeReactiveHealthContributorConfiguration.java │ │ │ │ │ │ ├── ConditionalOnEnabledHealthIndicator.java │ │ │ │ │ │ ├── HealthContributorAutoConfiguration.java │ │ │ │ │ │ ├── HealthContributorMembership.java │ │ │ │ │ │ ├── HealthContributorMembershipValidator.java │ │ │ │ │ │ ├── IncludeExcludeHealthContributorMembership.java │ │ │ │ │ │ ├── OnEnabledHealthIndicatorCondition.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── registry/ │ │ │ │ │ ├── HealthContributorNameGenerator.java │ │ │ │ │ ├── HealthContributorRegistryAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── contributor/ │ │ │ │ │ ├── AbstractHealthIndicator.java │ │ │ │ │ ├── AbstractReactiveHealthIndicator.java │ │ │ │ │ ├── CompositeHealthContributor.java │ │ │ │ │ ├── CompositeHealthContributorAdapter.java │ │ │ │ │ ├── CompositeHealthContributors.java │ │ │ │ │ ├── CompositeReactiveHealthContributor.java │ │ │ │ │ ├── CompositeReactiveHealthContributorAdapter.java │ │ │ │ │ ├── CompositeReactiveHealthContributors.java │ │ │ │ │ ├── Health.java │ │ │ │ │ ├── HealthContributor.java │ │ │ │ │ ├── HealthContributors.java │ │ │ │ │ ├── HealthContributorsAdapter.java │ │ │ │ │ ├── HealthIndicator.java │ │ │ │ │ ├── HealthIndicatorAdapter.java │ │ │ │ │ ├── MapCompositeHealthContributor.java │ │ │ │ │ ├── MapCompositeReactiveHealthContributor.java │ │ │ │ │ ├── PingHealthIndicator.java │ │ │ │ │ ├── ReactiveHealthContributor.java │ │ │ │ │ ├── ReactiveHealthContributors.java │ │ │ │ │ ├── ReactiveHealthContributorsAdapter.java │ │ │ │ │ ├── ReactiveHealthIndicator.java │ │ │ │ │ ├── ReactiveHealthIndicatorAdapter.java │ │ │ │ │ ├── Status.java │ │ │ │ │ └── package-info.java │ │ │ │ └── registry/ │ │ │ │ ├── AbstractRegistry.java │ │ │ │ ├── DefaultHealthContributorRegistry.java │ │ │ │ ├── DefaultReactiveHealthContributorRegistry.java │ │ │ │ ├── HealthContributorNameValidator.java │ │ │ │ ├── HealthContributorRegistry.java │ │ │ │ ├── ReactiveHealthContributorRegistry.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── health/ │ │ │ ├── actuate/ │ │ │ │ └── endpoint/ │ │ │ │ ├── AdditionalHealthEndpointPathTests.java │ │ │ │ ├── CompositeHealthDescriptorTests.java │ │ │ │ ├── HealthEndpointGroupsTests.java │ │ │ │ ├── HealthEndpointSupportTests.java │ │ │ │ ├── HealthEndpointTests.java │ │ │ │ ├── HealthEndpointWebExtensionRuntimeHintsTests.java │ │ │ │ ├── HealthEndpointWebExtensionTests.java │ │ │ │ ├── HttpCodeStatusMapperTests.java │ │ │ │ ├── IndicatedHealthDescriptorTests.java │ │ │ │ ├── ReactiveHealthEndpointWebExtensionTests.java │ │ │ │ ├── ReactiveHealthIndicatorImplementationTests.java │ │ │ │ ├── SimpleHttpCodeStatusMapperTests.java │ │ │ │ ├── SimpleStatusAggregatorTests.java │ │ │ │ ├── StatusAggregatorTests.java │ │ │ │ ├── SystemHealthDescriptorTests.java │ │ │ │ └── TestHealthEndpointGroup.java │ │ │ ├── application/ │ │ │ │ ├── AvailabilityStateHealthIndicatorTests.java │ │ │ │ ├── DiskSpaceHealthIndicatorTests.java │ │ │ │ ├── LivenessStateHealthIndicatorTests.java │ │ │ │ ├── ReadinessStateHealthIndicatorTests.java │ │ │ │ └── SslHealthIndicatorTests.java │ │ │ ├── autoconfigure/ │ │ │ │ ├── actuate/ │ │ │ │ │ └── endpoint/ │ │ │ │ │ ├── AutoConfiguredHealthEndpointGroupTests.java │ │ │ │ │ ├── AutoConfiguredHealthEndpointGroupsTests.java │ │ │ │ │ ├── AvailabilityProbesAutoConfigurationTests.java │ │ │ │ │ ├── AvailabilityProbesHealthEndpointGroupTests.java │ │ │ │ │ ├── AvailabilityProbesHealthEndpointGroupsPostProcessorTests.java │ │ │ │ │ ├── AvailabilityProbesHealthEndpointGroupsTests.java │ │ │ │ │ ├── DelegatingAvailabilityProbesHealthEndpointGroupTests.java │ │ │ │ │ └── HealthEndpointAutoConfigurationTests.java │ │ │ │ ├── application/ │ │ │ │ │ ├── AvailabilityHealthContributorAutoConfigurationTests.java │ │ │ │ │ ├── DiskSpaceHealthContributorAutoConfigurationTests.java │ │ │ │ │ └── SslHealthContributorAutoConfigurationTests.java │ │ │ │ ├── contributor/ │ │ │ │ │ ├── AbstractCompositeHealthContributorConfigurationTests.java │ │ │ │ │ ├── CompositeHealthContributorConfigurationTests.java │ │ │ │ │ ├── CompositeReactiveHealthContributorConfigurationTests.java │ │ │ │ │ ├── ConditionalOnEnabledHealthIndicatorTests.java │ │ │ │ │ ├── HealthContributorAutoConfigurationTests.java │ │ │ │ │ ├── HealthContributorMembershipTests.java │ │ │ │ │ └── HealthContributorMembershipValidatorTests.java │ │ │ │ └── registry/ │ │ │ │ ├── HealthContributorNameGeneratorTests.java │ │ │ │ └── HealthContributorRegistryAutoConfigurationTests.java │ │ │ ├── contributor/ │ │ │ │ ├── AbstractHealthIndicatorTests.java │ │ │ │ ├── AbstractReactiveHealthIndicatorTests.java │ │ │ │ ├── CompositeHealthContributorAdapterTests.java │ │ │ │ ├── CompositeHealthContributorTests.java │ │ │ │ ├── CompositeHealthContributorsTests.java │ │ │ │ ├── CompositeReactiveHealthContributorAdapterTests.java │ │ │ │ ├── CompositeReactiveHealthContributorTests.java │ │ │ │ ├── CompositeReactiveHealthContributorsTests.java │ │ │ │ ├── HealthContributorsAdapterTests.java │ │ │ │ ├── HealthContributorsTests.java │ │ │ │ ├── HealthIndicatorAdapterTests.java │ │ │ │ ├── HealthIndicatorTests.java │ │ │ │ ├── HealthTests.java │ │ │ │ ├── MapCompositeHealthContributorTests.java │ │ │ │ ├── MapCompositeReactiveHealthContributorTests.java │ │ │ │ ├── MapCompositeTests.java │ │ │ │ ├── PingHealthIndicatorTests.java │ │ │ │ ├── ReactiveHealthContributorTests.java │ │ │ │ ├── ReactiveHealthContributorsAdapterTests.java │ │ │ │ ├── ReactiveHealthContributorsTests.java │ │ │ │ ├── ReactiveHealthIndicatorAdapterTests.java │ │ │ │ ├── ReactiveHealthIndicatorTests.java │ │ │ │ └── StatusTests.java │ │ │ └── registry/ │ │ │ ├── AbstractHealthContributorRegistryTests.java │ │ │ ├── DefaultHealthContributorRegistryTests.java │ │ │ └── DefaultReactiveHealthContributorRegistryTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── health/ │ │ └── autoconfigure/ │ │ └── application/ │ │ └── test.jks │ ├── spring-boot-hibernate/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── hibernate/ │ │ │ │ ├── SpringImplicitNamingStrategy.java │ │ │ │ ├── SpringJtaPlatform.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── HibernateDefaultDdlAutoProvider.java │ │ │ │ │ ├── HibernateJpaAutoConfiguration.java │ │ │ │ │ ├── HibernateJpaConfiguration.java │ │ │ │ │ ├── HibernateProperties.java │ │ │ │ │ ├── HibernatePropertiesCustomizer.java │ │ │ │ │ ├── HibernateSettings.java │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── HibernateMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── hibernate/ │ │ └── autoconfigure/ │ │ ├── CustomHibernateJpaAutoConfigurationTests.java │ │ ├── Hibernate2ndLevelCacheIntegrationTests.java │ │ ├── HibernateDefaultDdlAutoProviderTests.java │ │ ├── HibernateJpaAutoConfigurationTests.java │ │ ├── HibernatePropertiesTests.java │ │ ├── mapping/ │ │ │ ├── NonAnnotatedEntity.java │ │ │ └── package-info.java │ │ ├── metrics/ │ │ │ └── HibernateMetricsAutoConfigurationTests.java │ │ └── test/ │ │ ├── city/ │ │ │ ├── City.java │ │ │ ├── CityListener.java │ │ │ └── package-info.java │ │ └── country/ │ │ ├── Country.java │ │ └── package-info.java │ ├── spring-boot-http-client/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── http/ │ │ │ │ └── client/ │ │ │ │ ├── AbstractClientHttpRequestFactoryBuilder.java │ │ │ │ ├── ClientHttpRequestFactoryBuilder.java │ │ │ │ ├── ClientHttpRequestFactoryRuntimeHints.java │ │ │ │ ├── Empty.java │ │ │ │ ├── HttpClientSettings.java │ │ │ │ ├── HttpComponentsClientHttpRequestFactoryBuilder.java │ │ │ │ ├── HttpComponentsHttpAsyncClientBuilder.java │ │ │ │ ├── HttpComponentsHttpClientBuilder.java │ │ │ │ ├── HttpComponentsRedirectStrategy.java │ │ │ │ ├── HttpComponentsSslBundleTlsStrategy.java │ │ │ │ ├── HttpCookieHandling.java │ │ │ │ ├── HttpRedirects.java │ │ │ │ ├── JdkClientHttpRequestFactoryBuilder.java │ │ │ │ ├── JdkHttpClientBuilder.java │ │ │ │ ├── JettyClientHttpRequestFactoryBuilder.java │ │ │ │ ├── JettyHttpClientBuilder.java │ │ │ │ ├── ReactorClientHttpRequestFactoryBuilder.java │ │ │ │ ├── ReactorHttpClientBuilder.java │ │ │ │ ├── ReflectiveComponentsClientHttpRequestFactoryBuilder.java │ │ │ │ ├── SimpleClientHttpRequestFactoryBuilder.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ApiversionProperties.java │ │ │ │ │ ├── ClientHttpRequestFactoryBuilderCustomizer.java │ │ │ │ │ ├── HttpClientAutoConfiguration.java │ │ │ │ │ ├── HttpClientProperties.java │ │ │ │ │ ├── HttpClientSettingsProperties.java │ │ │ │ │ ├── HttpClientSettingsPropertyMapper.java │ │ │ │ │ ├── HttpClientsProperties.java │ │ │ │ │ ├── PropertiesApiVersionInserter.java │ │ │ │ │ ├── imperative/ │ │ │ │ │ │ ├── ImperativeHttpClientAutoConfiguration.java │ │ │ │ │ │ ├── ImperativeHttpClientsProperties.java │ │ │ │ │ │ ├── NotReactiveWebApplicationCondition.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── HttpClientMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── reactive/ │ │ │ │ │ │ ├── ClientHttpConnectorBuilderCustomizer.java │ │ │ │ │ │ ├── ConditionalOnClientHttpConnectorBuilderDetection.java │ │ │ │ │ │ ├── ReactiveHttpClientAutoConfiguration.java │ │ │ │ │ │ ├── ReactiveHttpClientsProperties.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── service/ │ │ │ │ │ ├── HttpServiceClientProperties.java │ │ │ │ │ ├── HttpServiceClientPropertiesAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── reactive/ │ │ │ │ ├── AbstractClientHttpConnectorBuilder.java │ │ │ │ ├── ClientHttpConnectorBuilder.java │ │ │ │ ├── HttpComponentsClientHttpConnectorBuilder.java │ │ │ │ ├── JdkClientHttpConnectorBuilder.java │ │ │ │ ├── JettyClientHttpConnectorBuilder.java │ │ │ │ ├── ReactorClientHttpConnectorBuilder.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ ├── aot.factories │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── http/ │ │ │ └── client/ │ │ │ ├── AbstractClientHttpRequestFactoryBuilderTests.java │ │ │ ├── ClientHttpRequestFactoryBuilderTests.java │ │ │ ├── ClientHttpRequestFactoryRuntimeHintsTests.java │ │ │ ├── HttpClientSettingsTests.java │ │ │ ├── HttpComponentsClientHttpRequestFactoryBuilderTests.java │ │ │ ├── JdkClientHttpRequestFactoryBuilderTests.java │ │ │ ├── JettyClientHttpRequestFactoryBuilderTests.java │ │ │ ├── ReactorClientHttpRequestFactoryBuilderTests.java │ │ │ ├── ReflectiveComponentsClientHttpRequestFactoryBuilderTests.java │ │ │ ├── SimpleClientHttpRequestFactoryBuilderTests.java │ │ │ ├── TestCustomizer.java │ │ │ ├── autoconfigure/ │ │ │ │ ├── HttpClientAutoConfigurationTests.java │ │ │ │ ├── HttpClientSettingsPropertyMapperTests.java │ │ │ │ ├── PropertiesApiVersionInserterTests.java │ │ │ │ ├── imperative/ │ │ │ │ │ ├── ImperativeHttpClientAutoConfigurationTests.java │ │ │ │ │ └── ImperativeHttpClientsPropertiesTests.java │ │ │ │ ├── metrics/ │ │ │ │ │ └── HttpClientMetricsAutoConfigurationTests.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── ReactiveHttpClientAutoConfigurationTests.java │ │ │ │ │ └── ReactiveHttpClientsPropertiesTests.java │ │ │ │ └── service/ │ │ │ │ └── HttpServiceClientPropertiesTests.java │ │ │ └── reactive/ │ │ │ ├── AbstractClientHttpConnectorBuilderTests.java │ │ │ ├── HttpComponentsClientHttpConnectorBuilderTests.java │ │ │ ├── JdkClientHttpConnectorBuilderTests.java │ │ │ ├── JettyClientHttpConnectorBuilderTests.java │ │ │ ├── ReactorClientHttpConnectorBuilderTests.java │ │ │ └── TestCustomizer.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── http/ │ │ └── client/ │ │ ├── reactive/ │ │ │ └── test.jks │ │ └── test.jks │ ├── spring-boot-http-codec/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── http/ │ │ │ │ └── codec/ │ │ │ │ ├── CodecCustomizer.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── CodecsAutoConfiguration.java │ │ │ │ │ ├── HttpCodecsProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── http/ │ │ └── codec/ │ │ └── autoconfigure/ │ │ └── CodecsAutoConfigurationTests.java │ ├── spring-boot-http-converter/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── http/ │ │ │ │ └── converter/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── ClientHttpMessageConvertersCustomizer.java │ │ │ │ ├── DefaultClientHttpMessageConvertersCustomizer.java │ │ │ │ ├── DefaultServerHttpMessageConvertersCustomizer.java │ │ │ │ ├── GsonHttpMessageConvertersConfiguration.java │ │ │ │ ├── HttpMessageConverters.java │ │ │ │ ├── HttpMessageConvertersAutoConfiguration.java │ │ │ │ ├── HttpMessageConvertersProperties.java │ │ │ │ ├── Jackson2HttpMessageConvertersConfiguration.java │ │ │ │ ├── JacksonHttpMessageConvertersConfiguration.java │ │ │ │ ├── JsonbHttpMessageConvertersConfiguration.java │ │ │ │ ├── KotlinSerializationHttpMessageConvertersConfiguration.java │ │ │ │ ├── MessageConverterBackgroundPreinitializer.java │ │ │ │ ├── ServerHttpMessageConvertersCustomizer.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── http/ │ │ └── converter/ │ │ └── autoconfigure/ │ │ ├── HttpMessageConvertersAutoConfigurationTests.java │ │ ├── HttpMessageConvertersAutoConfigurationWithoutJacksonTests.java │ │ └── HttpMessageConvertersTests.java │ ├── spring-boot-integration/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── integration/ │ │ │ │ ├── actuate/ │ │ │ │ │ └── endpoint/ │ │ │ │ │ ├── IntegrationGraphEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ └── autoconfigure/ │ │ │ │ ├── IntegrationAutoConfiguration.java │ │ │ │ ├── IntegrationAutoConfigurationScanRegistrar.java │ │ │ │ ├── IntegrationDataSourceScriptDatabaseInitializer.java │ │ │ │ ├── IntegrationGraphEndpointAutoConfiguration.java │ │ │ │ ├── IntegrationJdbcProperties.java │ │ │ │ ├── IntegrationProperties.java │ │ │ │ ├── IntegrationPropertiesEnvironmentPostProcessor.java │ │ │ │ ├── PollerMetadataCustomizer.java │ │ │ │ ├── metrics/ │ │ │ │ │ ├── IntegrationMetricsAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── integration/ │ │ │ ├── actuate/ │ │ │ │ └── endpoint/ │ │ │ │ ├── IntegrationGraphEndpointTests.java │ │ │ │ └── IntegrationGraphEndpointWebIntegrationTests.java │ │ │ └── autoconfigure/ │ │ │ ├── IntegrationAutoConfigurationTests.java │ │ │ ├── IntegrationDataSourceScriptDatabaseInitializerTests.java │ │ │ ├── IntegrationGraphEndpointAutoConfigurationTests.java │ │ │ ├── IntegrationPropertiesEnvironmentPostProcessorTests.java │ │ │ └── metrics/ │ │ │ └── IntegrationMetricsAutoConfigurationTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── integration/ │ │ └── autoconfigure/ │ │ └── spring.integration.properties │ ├── spring-boot-jackson/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jackson/ │ │ │ │ ├── JacksonComponent.java │ │ │ │ ├── JacksonComponentModule.java │ │ │ │ ├── JacksonMixin.java │ │ │ │ ├── JacksonMixinModule.java │ │ │ │ ├── JacksonMixinModuleEntries.java │ │ │ │ ├── JacksonMixinModuleEntriesBeanRegistrationAotProcessor.java │ │ │ │ ├── ObjectValueDeserializer.java │ │ │ │ ├── ObjectValueSerializer.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── CborFactoryBuilderCustomizer.java │ │ │ │ │ ├── CborMapperBuilderCustomizer.java │ │ │ │ │ ├── JacksonAutoConfiguration.java │ │ │ │ │ ├── JacksonBackgroundPreinitializer.java │ │ │ │ │ ├── JacksonCborProperties.java │ │ │ │ │ ├── JacksonProperties.java │ │ │ │ │ ├── JacksonTesterTestAutoConfiguration.java │ │ │ │ │ ├── JacksonXmlProperties.java │ │ │ │ │ ├── JsonFactoryBuilderCustomizer.java │ │ │ │ │ ├── JsonMapperBuilderCustomizer.java │ │ │ │ │ ├── SpringBeanHandlerInstantiator.java │ │ │ │ │ ├── XmlFactoryBuilderCustomizer.java │ │ │ │ │ ├── XmlMapperBuilderCustomizer.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── aot.factories │ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ ├── org.springframework.boot.test.autoconfigure.json.AutoConfigureJson.imports │ │ │ │ ├── org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports │ │ │ │ └── org.springframework.boot.test.autoconfigure.json.JsonTest.includes │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── jackson/ │ │ │ ├── JacksonComponentModuleTests.java │ │ │ ├── JacksonMixinModuleEntriesBeanRegistrationAotProcessorTests.java │ │ │ ├── JacksonMixinModuleTests.java │ │ │ ├── NameAndAgeJacksonComponent.java │ │ │ ├── NameAndAgeJacksonKeyComponent.java │ │ │ ├── NameAndCareerJacksonComponent.java │ │ │ ├── ObjectValueDeserializerTests.java │ │ │ ├── ObjectValueSerializerTests.java │ │ │ ├── autoconfigure/ │ │ │ │ ├── JacksonAutoConfigurationTests.java │ │ │ │ ├── JacksonTesterAutoConfigurationTests.java │ │ │ │ ├── SpringBeanHandlerInstantiatorTests.java │ │ │ │ └── jsontest/ │ │ │ │ ├── JacksonAutoConfigureJsonIntegrationTests.java │ │ │ │ ├── JsonTestIntegrationTests.java │ │ │ │ ├── JsonTestWithAutoConfigureJsonTestersTests.java │ │ │ │ ├── SpringBootTestWithAutoConfigureJsonTestersTests.java │ │ │ │ ├── app/ │ │ │ │ │ ├── ExampleBasicObject.java │ │ │ │ │ ├── ExampleCustomObject.java │ │ │ │ │ ├── ExampleJacksonComponent.java │ │ │ │ │ ├── ExampleJsonApplication.java │ │ │ │ │ ├── ExampleJsonObjectWithView.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── scan/ │ │ │ │ ├── a/ │ │ │ │ │ ├── RenameMixInClass.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── b/ │ │ │ │ │ ├── RenameMixInAbstractClass.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── c/ │ │ │ │ │ ├── RenameMixInInterface.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── d/ │ │ │ │ │ ├── EmptyMixInClass.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── e/ │ │ │ │ │ ├── PrivateMixInClass.java │ │ │ │ │ └── package-info.java │ │ │ │ └── f/ │ │ │ │ ├── EmptyMixIn.java │ │ │ │ └── package-info.java │ │ │ └── types/ │ │ │ ├── Name.java │ │ │ ├── NameAndAge.java │ │ │ ├── NameAndCareer.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── jackson/ │ │ └── autoconfigure/ │ │ └── jsontest/ │ │ └── example.json │ ├── spring-boot-jackson2/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jackson2/ │ │ │ │ ├── JsonComponent.java │ │ │ │ ├── JsonComponentModule.java │ │ │ │ ├── JsonMixin.java │ │ │ │ ├── JsonMixinModule.java │ │ │ │ ├── JsonMixinModuleEntries.java │ │ │ │ ├── JsonMixinModuleEntriesBeanRegistrationAotProcessor.java │ │ │ │ ├── JsonObjectDeserializer.java │ │ │ │ ├── JsonObjectSerializer.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── Jackson2AutoConfiguration.java │ │ │ │ │ ├── Jackson2BackgroundPreinitializer.java │ │ │ │ │ ├── Jackson2ObjectMapperBuilderCustomizer.java │ │ │ │ │ ├── Jackson2Properties.java │ │ │ │ │ ├── Jackson2TesterTestAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── aot.factories │ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ ├── org.springframework.boot.test.autoconfigure.json.AutoConfigureJson.imports │ │ │ │ ├── org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports │ │ │ │ └── org.springframework.boot.test.autoconfigure.json.JsonTest.includes │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── jackson2/ │ │ ├── AutoConfigureJsonTests.java │ │ ├── JsonComponentModuleTests.java │ │ ├── JsonMixinModuleEntriesBeanRegistrationAotProcessorTests.java │ │ ├── JsonMixinModuleTests.java │ │ ├── JsonObjectDeserializerTests.java │ │ ├── JsonObjectSerializerTests.java │ │ ├── NameAndAgeJsonComponent.java │ │ ├── NameAndAgeJsonKeyComponent.java │ │ ├── NameAndCareerJsonComponent.java │ │ ├── autoconfigure/ │ │ │ └── Jackson2AutoConfigurationTests.java │ │ ├── scan/ │ │ │ ├── a/ │ │ │ │ ├── RenameMixInClass.java │ │ │ │ └── package-info.java │ │ │ ├── b/ │ │ │ │ ├── RenameMixInAbstractClass.java │ │ │ │ └── package-info.java │ │ │ ├── c/ │ │ │ │ ├── RenameMixInInterface.java │ │ │ │ └── package-info.java │ │ │ ├── d/ │ │ │ │ ├── EmptyMixInClass.java │ │ │ │ └── package-info.java │ │ │ ├── e/ │ │ │ │ ├── PrivateMixInClass.java │ │ │ │ └── package-info.java │ │ │ └── f/ │ │ │ ├── EmptyMixIn.java │ │ │ └── package-info.java │ │ └── types/ │ │ ├── Name.java │ │ ├── NameAndAge.java │ │ ├── NameAndCareer.java │ │ └── package-info.java │ ├── spring-boot-jdbc/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jdbc/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── ClickHouseJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ ├── MariaDbJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ ├── MySqlJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ ├── OracleFreeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ ├── OracleXeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ ├── PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ └── SqlServerJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ └── JdbcContainerConnectionDetailsFactoryTests.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ ├── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jdbc/ │ │ │ │ └── docker/ │ │ │ │ └── compose/ │ │ │ │ ├── clickhouse-compose.yaml │ │ │ │ ├── mariadb-compose.yaml │ │ │ │ ├── mssqlserver-compose.yaml │ │ │ │ ├── mssqlserver-with-jdbc-parameters-compose.yaml │ │ │ │ ├── mysql-compose.yaml │ │ │ │ ├── oracle-compose.yaml │ │ │ │ ├── otlp-compose.yaml │ │ │ │ ├── postgres-application-name-compose.yaml │ │ │ │ ├── postgres-compose.yaml │ │ │ │ └── postgres-with-trust-host-auth-method-compose.yaml │ │ │ └── spring.properties │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jdbc/ │ │ │ │ ├── DataSourceBuilder.java │ │ │ │ ├── DataSourceBuilderRuntimeHints.java │ │ │ │ ├── DataSourceUnwrapper.java │ │ │ │ ├── DatabaseDriver.java │ │ │ │ ├── EmbeddedDatabaseConnection.java │ │ │ │ ├── HikariCheckpointRestoreLifecycle.java │ │ │ │ ├── SchemaManagement.java │ │ │ │ ├── SchemaManagementProvider.java │ │ │ │ ├── SpringJdbcDependsOnDatabaseInitializationDetector.java │ │ │ │ ├── UnsupportedDataSourcePropertyException.java │ │ │ │ ├── XADataSourceWrapper.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ApplicationDataSourceScriptDatabaseInitializer.java │ │ │ │ │ ├── DataSourceAutoConfiguration.java │ │ │ │ │ ├── DataSourceBeanCreationFailureAnalyzer.java │ │ │ │ │ ├── DataSourceCheckpointRestoreConfiguration.java │ │ │ │ │ ├── DataSourceConfiguration.java │ │ │ │ │ ├── DataSourceInitializationAutoConfiguration.java │ │ │ │ │ ├── DataSourceJmxConfiguration.java │ │ │ │ │ ├── DataSourcePoolMetadataProvidersConfiguration.java │ │ │ │ │ ├── DataSourceProperties.java │ │ │ │ │ ├── DataSourceTransactionManagerAutoConfiguration.java │ │ │ │ │ ├── Dbcp2JdbcConnectionDetailsBeanPostProcessor.java │ │ │ │ │ ├── EmbeddedDataSourceConfiguration.java │ │ │ │ │ ├── HikariDriverConfigurationFailureAnalyzer.java │ │ │ │ │ ├── HikariJdbcConnectionDetailsBeanPostProcessor.java │ │ │ │ │ ├── JdbcClientAutoConfiguration.java │ │ │ │ │ ├── JdbcConnectionDetails.java │ │ │ │ │ ├── JdbcConnectionDetailsBeanPostProcessor.java │ │ │ │ │ ├── JdbcProperties.java │ │ │ │ │ ├── JdbcTemplateAutoConfiguration.java │ │ │ │ │ ├── JdbcTemplateConfiguration.java │ │ │ │ │ ├── JndiDataSourceAutoConfiguration.java │ │ │ │ │ ├── NamedParameterJdbcTemplateConfiguration.java │ │ │ │ │ ├── OracleUcpJdbcConnectionDetailsBeanPostProcessor.java │ │ │ │ │ ├── PropertiesJdbcConnectionDetails.java │ │ │ │ │ ├── TomcatJdbcConnectionDetailsBeanPostProcessor.java │ │ │ │ │ ├── XADataSourceAutoConfiguration.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── DataSourceHealthContributorAutoConfiguration.java │ │ │ │ │ │ ├── DataSourceHealthIndicatorProperties.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── DataSourcePoolMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── ClickHouseEnvironment.java │ │ │ │ │ ├── ClickHouseJdbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── JdbcUrlBuilder.java │ │ │ │ │ ├── MariaDbEnvironment.java │ │ │ │ │ ├── MariaDbJdbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── MySqlEnvironment.java │ │ │ │ │ ├── MySqlJdbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── OracleContainer.java │ │ │ │ │ ├── OracleEnvironment.java │ │ │ │ │ ├── OracleFreeJdbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── OracleJdbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── OracleXeJdbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── PostgresEnvironment.java │ │ │ │ │ ├── PostgresJdbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── SqlServerEnvironment.java │ │ │ │ │ ├── SqlServerJdbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── DataSourceHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── init/ │ │ │ │ │ ├── DataSourceScriptDatabaseInitializer.java │ │ │ │ │ ├── DataSourceScriptDatabaseInitializerDetector.java │ │ │ │ │ ├── DatabaseInitializationProperties.java │ │ │ │ │ ├── PlatformPlaceholderDatabaseDriverResolver.java │ │ │ │ │ ├── PropertiesBasedDataSourceScriptDatabaseInitializer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── metadata/ │ │ │ │ │ ├── AbstractDataSourcePoolMetadata.java │ │ │ │ │ ├── CommonsDbcp2DataSourcePoolMetadata.java │ │ │ │ │ ├── CompositeDataSourcePoolMetadataProvider.java │ │ │ │ │ ├── DataSourcePoolMetadata.java │ │ │ │ │ ├── DataSourcePoolMetadataProvider.java │ │ │ │ │ ├── HikariDataSourcePoolMetadata.java │ │ │ │ │ ├── OracleUcpDataSourcePoolMetadata.java │ │ │ │ │ ├── TomcatDataSourcePoolMetadata.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── metrics/ │ │ │ │ │ ├── DataSourcePoolMetrics.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── JdbcContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── aot.factories │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── jdbc/ │ │ ├── DataSourceBuilderNoHikariTests.java │ │ ├── DataSourceBuilderRuntimeHintsTests.java │ │ ├── DataSourceBuilderTests.java │ │ ├── DataSourceUnwrapperNoSpringJdbcTests.java │ │ ├── DataSourceUnwrapperTests.java │ │ ├── DatabaseDriverClassNameTests.java │ │ ├── DatabaseDriverTests.java │ │ ├── EmbeddedDatabaseConnectionTests.java │ │ ├── HikariCheckpointRestoreLifecycleTests.java │ │ ├── autoconfigure/ │ │ │ ├── DataSourceAutoConfigurationTests.java │ │ │ ├── DataSourceAutoConfigurationWithoutSpringJdbcTests.java │ │ │ ├── DataSourceBeanCreationFailureAnalyzerTests.java │ │ │ ├── DataSourceInitializationAutoConfigurationTests.java │ │ │ ├── DataSourceJmxConfigurationTests.java │ │ │ ├── DataSourceJsonSerializationTests.java │ │ │ ├── DataSourcePropertiesTests.java │ │ │ ├── DataSourceTransactionManagerAutoConfigurationTests.java │ │ │ ├── Dbcp2JdbcConnectionDetailsBeanPostProcessorTests.java │ │ │ ├── EmbeddedDataSourceConfigurationTests.java │ │ │ ├── HikariDataSourceConfigurationTests.java │ │ │ ├── HikariDataSourcePoolMetadataRuntimeHintsTests.java │ │ │ ├── HikariDriverConfigurationFailureAnalyzerTests.java │ │ │ ├── HikariJdbcConnectionDetailsBeanPostProcessorTests.java │ │ │ ├── JdbcClientAutoConfigurationTests.java │ │ │ ├── JdbcTemplateAutoConfigurationTests.java │ │ │ ├── JndiDataSourceAutoConfigurationTests.java │ │ │ ├── MultiDataSourceConfiguration.java │ │ │ ├── MultiDataSourceUsingPrimaryConfiguration.java │ │ │ ├── OracleUcpDataSourceConfigurationTests.java │ │ │ ├── OracleUcpJdbcConnectionDetailsBeanPostProcessorTests.java │ │ │ ├── TestDataSource.java │ │ │ ├── TestJdbcConnectionDetails.java │ │ │ ├── TomcatDataSourceConfigurationTests.java │ │ │ ├── TomcatJdbcConnectionDetailsBeanPostProcessorTests.java │ │ │ ├── XADataSourceAutoConfigurationTests.java │ │ │ ├── health/ │ │ │ │ └── DataSourceHealthContributorAutoConfigurationTests.java │ │ │ └── metrics/ │ │ │ └── DataSourcePoolMetricsAutoConfigurationTests.java │ │ ├── docker/ │ │ │ └── compose/ │ │ │ ├── ClickHouseEnvironmentTests.java │ │ │ ├── JdbcUrlBuilderTests.java │ │ │ ├── MariaDbEnvironmentTests.java │ │ │ ├── MySqlEnvironmentTests.java │ │ │ ├── OracleEnvironmentTests.java │ │ │ ├── PostgresEnvironmentTests.java │ │ │ ├── PostgresJdbcDockerComposeConnectionDetailsFactoryConnectionDetailsTests.java │ │ │ └── SqlServerEnvironmentTests.java │ │ ├── health/ │ │ │ └── DataSourceHealthIndicatorTests.java │ │ ├── init/ │ │ │ ├── DataSourceScriptDatabaseInitializerTests.java │ │ │ └── PlatformPlaceholderDatabaseDriverResolverTests.java │ │ ├── metadata/ │ │ │ ├── AbstractDataSourcePoolMetadataTests.java │ │ │ ├── CommonsDbcp2DataSourcePoolMetadataTests.java │ │ │ ├── CompositeDataSourcePoolMetadataProviderTests.java │ │ │ ├── HikariDataSourcePoolMetadataTests.java │ │ │ ├── OracleUcpDataSourcePoolMetadataTests.java │ │ │ └── TomcatDataSourcePoolMetadataTests.java │ │ └── metrics/ │ │ └── DataSourcePoolMetricsTests.java │ ├── spring-boot-jdbc-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jdbc/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureTestDatabaseDockerComposeIntegrationTests.java │ │ │ │ ├── AutoConfigureTestDatabaseDynamicPropertySourceIntegrationTests.java │ │ │ │ ├── AutoConfigureTestDatabaseNonTestDatabaseIntegrationTests.java │ │ │ │ ├── AutoConfigureTestDatabaseServiceConnectionIntegrationTests.java │ │ │ │ └── AutoConfigureTestDatabaseTestcontainersJdbcUrlIntegrationTests.java │ │ │ └── resources/ │ │ │ └── postgres-compose.yaml │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jdbc/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureJdbc.java │ │ │ │ ├── AutoConfigureTestDatabase.java │ │ │ │ ├── JdbcTest.java │ │ │ │ ├── JdbcTestContextBootstrapper.java │ │ │ │ ├── JdbcTypeExcludeFilter.java │ │ │ │ ├── TestDatabaseAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureJdbc.imports │ │ │ ├── org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase.imports │ │ │ └── org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureDataSourceInitialization.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── jdbc/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── AutoConfigureTestDatabaseWithMultipleDatasourcesIntegrationTests.java │ │ │ ├── AutoConfigureTestDatabaseWithNoDatabaseIntegrationTests.java │ │ │ ├── ExampleEntity.java │ │ │ ├── ExampleEntityRowMapper.java │ │ │ ├── ExampleJdbcApplication.java │ │ │ ├── ExampleJdbcClientRepository.java │ │ │ ├── ExampleRepository.java │ │ │ ├── JdbcTestIntegrationTests.java │ │ │ ├── JdbcTestPropertiesIntegrationTests.java │ │ │ ├── JdbcTestWithAutoConfigureTestDatabaseReplaceAutoConfiguredIntegrationTests.java │ │ │ ├── JdbcTestWithAutoConfigureTestDatabaseReplaceAutoConfiguredWithoutOverrideIntegrationTests.java │ │ │ ├── JdbcTestWithAutoConfigureTestDatabaseReplaceExplicitIntegrationTests.java │ │ │ ├── JdbcTestWithAutoConfigureTestDatabaseReplaceNoneIntegrationTests.java │ │ │ ├── JdbcTestWithAutoConfigureTestDatabaseReplacePropertyAnyIntegrationTests.java │ │ │ ├── JdbcTestWithAutoConfigureTestDatabaseReplacePropertyAutoConfiguredIntegrationTests.java │ │ │ ├── JdbcTestWithAutoConfigureTestDatabaseReplacePropertyNoneIntegrationTests.java │ │ │ ├── JdbcTestWithIncludeFilterIntegrationTests.java │ │ │ ├── TestDatabaseAutoConfigurationNoEmbeddedTests.java │ │ │ └── TestDatabaseAutoConfigurationTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── jdbc/ │ │ └── test/ │ │ └── autoconfigure/ │ │ └── schema.sql │ ├── spring-boot-jersey/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jersey/ │ │ │ │ ├── actuate/ │ │ │ │ │ └── endpoint/ │ │ │ │ │ └── web/ │ │ │ │ │ ├── JerseyEndpointResourceFactory.java │ │ │ │ │ ├── JerseyHealthEndpointAdditionalPathResourceFactory.java │ │ │ │ │ ├── JerseyRemainingPathSegmentProvider.java │ │ │ │ │ └── package-info.java │ │ │ │ └── autoconfigure/ │ │ │ │ ├── DefaultJerseyApplicationPath.java │ │ │ │ ├── JerseyApplicationPath.java │ │ │ │ ├── JerseyAutoConfiguration.java │ │ │ │ ├── JerseyProperties.java │ │ │ │ ├── ResourceConfigCustomizer.java │ │ │ │ ├── actuate/ │ │ │ │ │ ├── endpoint/ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ ├── HealthEndpointJerseyExtensionAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── JerseyChildManagementContextConfiguration.java │ │ │ │ │ ├── JerseyEndpointManagementContextConfiguration.java │ │ │ │ │ ├── JerseyManagementContextConfiguration.java │ │ │ │ │ ├── JerseySameManagementContextConfiguration.java │ │ │ │ │ ├── JerseyWebEndpointManagementContextConfiguration.java │ │ │ │ │ ├── ManagementContextResourceConfigCustomizer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── metrics/ │ │ │ │ │ ├── JerseyServerMetricsAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ ├── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── jersey/ │ │ │ └── autoconfigure/ │ │ │ ├── JerseyApplicationPathTests.java │ │ │ ├── JerseyAutoConfigurationCustomApplicationTests.java │ │ │ ├── JerseyAutoConfigurationCustomFilterContextPathTests.java │ │ │ ├── JerseyAutoConfigurationCustomFilterPathTests.java │ │ │ ├── JerseyAutoConfigurationCustomLoadOnStartupTests.java │ │ │ ├── JerseyAutoConfigurationCustomObjectMapperProviderTests.java │ │ │ ├── JerseyAutoConfigurationCustomServletContextPathTests.java │ │ │ ├── JerseyAutoConfigurationCustomServletPathTests.java │ │ │ ├── JerseyAutoConfigurationDefaultFilterPathTests.java │ │ │ ├── JerseyAutoConfigurationDefaultServletPathTests.java │ │ │ ├── JerseyAutoConfigurationObjectMapperProviderTests.java │ │ │ ├── JerseyAutoConfigurationServletContainerTests.java │ │ │ ├── JerseyAutoConfigurationTests.java │ │ │ ├── JerseyAutoConfigurationWithoutApplicationPathTests.java │ │ │ ├── actuate/ │ │ │ │ ├── endpoint/ │ │ │ │ │ └── web/ │ │ │ │ │ ├── HealthEndpointJerseyExtensionAutoConfigurationTests.java │ │ │ │ │ ├── JerseyEndpointAccessIntegrationTests.java │ │ │ │ │ └── JerseyEndpointIntegrationTests.java │ │ │ │ └── web/ │ │ │ │ ├── JerseyChildManagementContextConfigurationTests.java │ │ │ │ ├── JerseyEndpointManagementContextConfigurationTests.java │ │ │ │ ├── JerseyHealthEndpointAdditionalPathIntegrationTests.java │ │ │ │ ├── JerseySameManagementContextConfigurationTests.java │ │ │ │ ├── JerseyWebEndpointIntegrationTests.java │ │ │ │ └── JerseyWebEndpointManagementContextConfigurationTests.java │ │ │ └── metrics/ │ │ │ └── JerseyServerMetricsAutoConfigurationTests.java │ │ └── testFixtures/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── jersey/ │ │ │ └── actuate/ │ │ │ └── endpoint/ │ │ │ └── web/ │ │ │ └── test/ │ │ │ ├── JerseyEndpointConfiguration.java │ │ │ └── JerseyWebEndpointInfrastructureProvider.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring.factories │ ├── spring-boot-jetty/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jetty/ │ │ │ │ ├── ConfigurableJettyWebServerFactory.java │ │ │ │ ├── ForwardHeadersCustomizer.java │ │ │ │ ├── GracefulShutdown.java │ │ │ │ ├── JettyHandlerWrappers.java │ │ │ │ ├── JettyServerCustomizer.java │ │ │ │ ├── JettyWebServer.java │ │ │ │ ├── JettyWebServerFactory.java │ │ │ │ ├── SslServerCustomizer.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── JettyServerProperties.java │ │ │ │ │ ├── JettyThreadPool.java │ │ │ │ │ ├── JettyVirtualThreadsWebServerFactoryCustomizer.java │ │ │ │ │ ├── JettyWebServerConfiguration.java │ │ │ │ │ ├── JettyWebServerFactoryCustomizer.java │ │ │ │ │ ├── actuate/ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ └── server/ │ │ │ │ │ │ ├── JettyAccessLogCustomizer.java │ │ │ │ │ │ ├── JettyManagementServerProperties.java │ │ │ │ │ │ ├── JettyReactiveManagementChildContextConfiguration.java │ │ │ │ │ │ ├── JettyReactiveManagementContextAutoConfiguration.java │ │ │ │ │ │ ├── JettyServletManagementChildContextConfiguration.java │ │ │ │ │ │ ├── JettyServletManagementContextAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── JettyMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── reactive/ │ │ │ │ │ │ ├── JettyReactiveWebServerAutoConfiguration.java │ │ │ │ │ │ ├── WebSocketJettyReactiveWebServerFactoryCustomizer.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── servlet/ │ │ │ │ │ ├── JettyServletWebServerAutoConfiguration.java │ │ │ │ │ ├── WebSocketJettyServletWebServerFactoryCustomizer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── metrics/ │ │ │ │ │ ├── AbstractJettyMetricsBinder.java │ │ │ │ │ ├── JettyConnectionMetricsBinder.java │ │ │ │ │ ├── JettyServerThreadPoolMetricsBinder.java │ │ │ │ │ ├── JettySslHandshakeMetricsBinder.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── JettyReactiveWebServerFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ └── servlet/ │ │ │ │ ├── JasperInitializer.java │ │ │ │ ├── JettyEmbeddedErrorHandler.java │ │ │ │ ├── JettyEmbeddedWebAppContext.java │ │ │ │ ├── JettyServletWebServer.java │ │ │ │ ├── JettyServletWebServerFactory.java │ │ │ │ ├── LoaderHidingResource.java │ │ │ │ ├── ServletContextInitializerConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── org.springframework.boot.web.server.test.AutoConfigureWebServer.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── jetty/ │ │ │ ├── JettyAccess.java │ │ │ ├── SslServerCustomizerTests.java │ │ │ ├── autoconfigure/ │ │ │ │ ├── AutoConfigureWebServerJettyReactiveTests.java │ │ │ │ ├── AutoConfigureWebServerJettyServletTests.java │ │ │ │ ├── JettyServerPropertiesTests.java │ │ │ │ ├── JettyVirtualThreadsWebServerFactoryCustomizerTests.java │ │ │ │ ├── JettyWebServerFactoryCustomizerTests.java │ │ │ │ ├── actuate/ │ │ │ │ │ └── web/ │ │ │ │ │ └── server/ │ │ │ │ │ └── JettyManagementServerPropertiesTests.java │ │ │ │ ├── metrics/ │ │ │ │ │ └── JettyMetricsAutoConfigurationTests.java │ │ │ │ ├── reactive/ │ │ │ │ │ └── JettyReactiveWebServerAutoConfigurationTests.java │ │ │ │ └── servlet/ │ │ │ │ ├── JettyServletWebServerAutoConfigurationTests.java │ │ │ │ └── JettyServletWebServerServletContextListenerTests.java │ │ │ ├── reactive/ │ │ │ │ └── JettyReactiveWebServerFactoryTests.java │ │ │ └── servlet/ │ │ │ ├── JettyServletWebServerFactoryTests.java │ │ │ ├── JettyServletWebServerMvcIntegrationTests.java │ │ │ └── LoaderHidingResourceTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── jetty/ │ │ ├── autoconfigure/ │ │ │ ├── metrics/ │ │ │ │ └── test.jks │ │ │ └── test.jks │ │ ├── servlet/ │ │ │ └── test.jks │ │ └── test.jks │ ├── spring-boot-jms/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jms/ │ │ │ │ ├── ConnectionFactoryUnwrapper.java │ │ │ │ ├── XAConnectionFactoryWrapper.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── AbstractJmsListenerContainerFactoryConfigurer.java │ │ │ │ │ ├── AcknowledgeMode.java │ │ │ │ │ ├── DefaultJmsListenerContainerFactoryConfigurer.java │ │ │ │ │ ├── JmsAnnotationDrivenConfiguration.java │ │ │ │ │ ├── JmsAutoConfiguration.java │ │ │ │ │ ├── JmsClientConfigurations.java │ │ │ │ │ ├── JmsPoolConnectionFactoryFactory.java │ │ │ │ │ ├── JmsPoolConnectionFactoryProperties.java │ │ │ │ │ ├── JmsProperties.java │ │ │ │ │ ├── JndiConnectionFactoryAutoConfiguration.java │ │ │ │ │ ├── SimpleJmsListenerContainerFactoryConfigurer.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── JmsHealthContributorAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── JmsHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── jms/ │ │ ├── ConnectionFactoryUnwrapperTests.java │ │ ├── autoconfigure/ │ │ │ ├── AcknowledgeModeTests.java │ │ │ ├── JmsAutoConfigurationTests.java │ │ │ ├── JmsPropertiesTests.java │ │ │ ├── JndiConnectionFactoryAutoConfigurationTests.java │ │ │ └── health/ │ │ │ └── JmsHealthContributorAutoConfigurationTests.java │ │ └── health/ │ │ └── JmsHealthIndicatorTests.java │ ├── spring-boot-jooq/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jooq/ │ │ │ │ ├── JooqDependsOnDatabaseInitializationDetector.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── DefaultConfigurationCustomizer.java │ │ │ │ │ ├── DefaultExceptionTranslatorExecuteListener.java │ │ │ │ │ ├── ExceptionTranslatorExecuteListener.java │ │ │ │ │ ├── JaxbNotAvailableException.java │ │ │ │ │ ├── JaxbNotAvailableExceptionFailureAnalyzer.java │ │ │ │ │ ├── JooqAutoConfiguration.java │ │ │ │ │ ├── JooqProperties.java │ │ │ │ │ ├── NoDslContextBeanFailureAnalyzer.java │ │ │ │ │ ├── SpringTransaction.java │ │ │ │ │ ├── SpringTransactionProvider.java │ │ │ │ │ ├── SqlDialectLookup.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── jooq/ │ │ │ └── autoconfigure/ │ │ │ ├── DefaultExceptionTranslatorExecuteListenerTests.java │ │ │ ├── JooqAutoConfigurationTests.java │ │ │ ├── JooqFlywayDatabaseInitializationTests.java │ │ │ ├── JooqPropertiesTests.java │ │ │ ├── NoDslContextBeanFailureAnalyzerTests.java │ │ │ └── SqlDialectLookupTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── jooq/ │ │ └── autoconfigure/ │ │ └── settings.xml │ ├── spring-boot-jooq-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jooq/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureJooq.java │ │ │ │ ├── JooqTest.java │ │ │ │ ├── JooqTestContextBootstrapper.java │ │ │ │ ├── JooqTypeExcludeFilter.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.jooq.test.autoconfigure.AutoConfigureJooq.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── jooq/ │ │ └── test/ │ │ └── autoconfigure/ │ │ ├── ExampleComponent.java │ │ ├── ExampleJooqApplication.java │ │ ├── JooqTestIntegrationTests.java │ │ ├── JooqTestPropertiesIntegrationTests.java │ │ └── JooqTestWithAutoConfigureTestDatabaseIntegrationTests.java │ ├── spring-boot-jpa/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jpa/ │ │ │ │ ├── EntityManagerFactoryBuilder.java │ │ │ │ ├── JpaDatabaseInitializerDetector.java │ │ │ │ ├── JpaDependsOnDatabaseInitializationDetector.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── BootstrapExecutorRequiredException.java │ │ │ │ │ ├── BootstrapExecutorRequiredFailureAnalyzer.java │ │ │ │ │ ├── EntityManagerFactoryBuilderCustomizer.java │ │ │ │ │ ├── EntityManagerFactoryDependsOnPostProcessor.java │ │ │ │ │ ├── JpaBaseConfiguration.java │ │ │ │ │ ├── JpaProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── jpa/ │ │ ├── EntityManagerFactoryBuilderTests.java │ │ └── autoconfigure/ │ │ └── BootstrapExecutorRequiredFailureAnalyzerTests.java │ ├── spring-boot-jpa-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jpa/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureTestEntityManager.java │ │ │ │ ├── TestEntityManager.java │ │ │ │ ├── TestEntityManagerAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ ├── kotlin/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jpa/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ └── TestEntityManagerExtensions.kt │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.jpa.test.autoconfigure.AutoConfigureTestEntityManager.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── jpa/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ └── TestEntityManagerTests.java │ │ └── kotlin/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── jpa/ │ │ └── test/ │ │ └── autoconfigure/ │ │ └── TestEntityManagerExtensionsTests.kt │ ├── spring-boot-jsonb/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── jsonb/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── JsonbAutoConfiguration.java │ │ │ │ ├── JsonbTesterTestAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ ├── org.springframework.boot.test.autoconfigure.json.AutoConfigureJson.imports │ │ │ └── org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── jsonb/ │ │ │ └── autoconfigure/ │ │ │ ├── JsonTestApplication.java │ │ │ ├── JsonbAutoConfigurationTests.java │ │ │ ├── JsonbAutoConfigurationWithNoProviderTests.java │ │ │ ├── JsonbTesterAutoConfigurationTests.java │ │ │ └── jsontest/ │ │ │ ├── JsonTestApplication.java │ │ │ ├── JsonTestIntegrationTests.java │ │ │ ├── JsonTestWithAutoConfigureJsonTestersTests.java │ │ │ ├── JsonbAutoConfigureJsonIntegrationTests.java │ │ │ ├── SpringBootTestWithAutoConfigureJsonTestersTests.java │ │ │ ├── app/ │ │ │ │ ├── ExampleBasicObject.java │ │ │ │ ├── ExampleJsonApplication.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── jsonb/ │ │ └── autoconfigure/ │ │ └── jsontest/ │ │ └── example.json │ ├── spring-boot-kafka/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── kafka/ │ │ │ │ └── testcontainers/ │ │ │ │ ├── ApacheKafkaContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ ├── ConfluentKafkaContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── RedpandaContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ └── spring.properties │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── kafka/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ConcurrentKafkaListenerContainerFactoryConfigurer.java │ │ │ │ │ ├── DefaultKafkaConsumerFactoryCustomizer.java │ │ │ │ │ ├── DefaultKafkaProducerFactoryCustomizer.java │ │ │ │ │ ├── KafkaAnnotationDrivenConfiguration.java │ │ │ │ │ ├── KafkaAutoConfiguration.java │ │ │ │ │ ├── KafkaConnectionDetails.java │ │ │ │ │ ├── KafkaProperties.java │ │ │ │ │ ├── KafkaStreamsAnnotationDrivenConfiguration.java │ │ │ │ │ ├── PropertiesKafkaConnectionDetails.java │ │ │ │ │ ├── SslBundleSslEngineFactory.java │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── KafkaMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── ApacheKafkaContainerConnectionDetailsFactory.java │ │ │ │ ├── ConfluentKafkaContainerConnectionDetailsFactory.java │ │ │ │ ├── RedpandaContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── kafka/ │ │ └── autoconfigure/ │ │ ├── ConcurrentKafkaListenerContainerFactoryConfigurerTests.java │ │ ├── KafkaAutoConfigurationIntegrationTests.java │ │ ├── KafkaAutoConfigurationTests.java │ │ ├── KafkaPropertiesTests.java │ │ └── metrics/ │ │ └── KafkaMetricsAutoConfigurationTests.java │ ├── spring-boot-kotlinx-serialization-json/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── kotlinx/ │ │ │ │ └── serialization/ │ │ │ │ └── json/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── KotlinxSerializationJsonAutoConfiguration.java │ │ │ │ ├── KotlinxSerializationJsonBuilderCustomizer.java │ │ │ │ ├── KotlinxSerializationJsonProperties.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── org.springframework.boot.test.autoconfigure.json.AutoConfigureJson.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── kotlinx/ │ │ │ └── serialization/ │ │ │ └── json/ │ │ │ └── autoconfigure/ │ │ │ └── KotlinxSerializationJsonPropertiesTests.java │ │ └── kotlin/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── kotlinx/ │ │ └── serialization/ │ │ └── json/ │ │ └── autoconfigure/ │ │ └── KotlinxSerializationJsonAutoConfigurationTests.kt │ ├── spring-boot-ldap/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── ldap/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── LLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ └── OpenLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── LLdapContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── OpenLdapContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ ├── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── ldap/ │ │ │ │ └── docker/ │ │ │ │ └── compose/ │ │ │ │ ├── ldap-compose.yaml │ │ │ │ └── lldap-compose.yaml │ │ │ └── spring.properties │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── ldap/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── LdapAutoConfiguration.java │ │ │ │ │ ├── LdapConnectionDetails.java │ │ │ │ │ ├── LdapProperties.java │ │ │ │ │ ├── PropertiesLdapConnectionDetails.java │ │ │ │ │ ├── embedded/ │ │ │ │ │ │ ├── EmbeddedLdapAutoConfiguration.java │ │ │ │ │ │ ├── EmbeddedLdapProperties.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── LdapHealthContributorAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── LLdapDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── OpenLdapDockerComposeConnectionDetailsFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── LdapHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── LLdapContainerConnectionDetailsFactory.java │ │ │ │ ├── OpenLdapContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── ldap/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── LdapAutoConfigurationTests.java │ │ │ │ ├── LdapPropertiesTests.java │ │ │ │ ├── embedded/ │ │ │ │ │ └── EmbeddedLdapAutoConfigurationTests.java │ │ │ │ └── health/ │ │ │ │ └── LdapHealthContributorAutoConfigurationTests.java │ │ │ └── health/ │ │ │ └── LdapHealthIndicatorTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── ldap/ │ │ └── autoconfigure/ │ │ └── embedded/ │ │ └── test.jks │ ├── spring-boot-liquibase/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── liquibase/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── JdbcAdaptingLiquibaseConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ └── LiquibaseContainerConnectionDetailsFactoryTests.java │ │ │ └── resources/ │ │ │ ├── db/ │ │ │ │ └── changelog/ │ │ │ │ └── db.changelog-master.yaml │ │ │ ├── logback-test.xml │ │ │ ├── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── liquibase/ │ │ │ │ └── docker/ │ │ │ │ └── compose/ │ │ │ │ └── liquibase-compose.yaml │ │ │ └── spring.properties │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── liquibase/ │ │ │ │ ├── LiquibaseChangelogMissingFailureAnalyzer.java │ │ │ │ ├── LiquibaseDatabaseInitializerDetector.java │ │ │ │ ├── actuate/ │ │ │ │ │ └── endpoint/ │ │ │ │ │ ├── LiquibaseEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── DataSourceClosingSpringLiquibase.java │ │ │ │ │ ├── LiquibaseAutoConfiguration.java │ │ │ │ │ ├── LiquibaseConnectionDetails.java │ │ │ │ │ ├── LiquibaseDataSource.java │ │ │ │ │ ├── LiquibaseEndpointAutoConfiguration.java │ │ │ │ │ ├── LiquibaseProperties.java │ │ │ │ │ ├── LiquibaseSchemaManagementProvider.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── JdbcAdaptingLiquibaseConnectionDetailsFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── LiquibaseContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ └── org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureDataSourceInitialization.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── liquibase/ │ │ ├── LiquibaseChangelogMissingFailureAnalyzerTests.java │ │ ├── actuate/ │ │ │ └── endpoint/ │ │ │ └── LiquibaseEndpointTests.java │ │ └── autoconfigure/ │ │ ├── Liquibase423AutoConfigurationTests.java │ │ ├── LiquibaseAutoConfigurationTests.java │ │ ├── LiquibaseAutoConfigureDataSourceInitializationTests.java │ │ ├── LiquibaseEndpointAutoConfigurationTests.java │ │ └── LiquibasePropertiesTests.java │ ├── spring-boot-mail/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── mail/ │ │ │ │ └── autoconfigure/ │ │ │ │ └── MailSenderAutoConfigurationIntegrationTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── mail/ │ │ │ └── autoconfigure/ │ │ │ └── ssl/ │ │ │ ├── test-ca.crt │ │ │ ├── test-ca.key │ │ │ ├── test-client.crt │ │ │ ├── test-client.key │ │ │ ├── test-server.crt │ │ │ └── test-server.key │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── mail/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── MailHealthContributorAutoConfiguration.java │ │ │ │ │ ├── MailProperties.java │ │ │ │ │ ├── MailSenderAutoConfiguration.java │ │ │ │ │ ├── MailSenderJndiConfiguration.java │ │ │ │ │ ├── MailSenderPropertiesConfiguration.java │ │ │ │ │ ├── MailSenderValidatorAutoConfiguration.java │ │ │ │ │ ├── NoSuchMailSenderBeanFailureAnalyzer.java │ │ │ │ │ └── package-info.java │ │ │ │ └── health/ │ │ │ │ ├── MailHealthIndicator.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── mail/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── MailHealthContributorAutoConfigurationTests.java │ │ │ │ ├── MailSenderAutoConfigurationTests.java │ │ │ │ └── NoSuchMailSenderBeanFailureAnalyzerTests.java │ │ │ └── health/ │ │ │ └── MailHealthIndicatorTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── mail/ │ │ └── autoconfigure/ │ │ └── test.jks │ ├── spring-boot-micrometer-metrics/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── micrometer/ │ │ │ │ └── metrics/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── otlp/ │ │ │ │ │ ├── GrafanaOpenTelemetryMetricsDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ └── OpenTelemetryMetricsDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ └── otlp/ │ │ │ │ ├── GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── OpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ └── resources/ │ │ │ ├── collector-config.yml │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── micrometer/ │ │ │ └── metrics/ │ │ │ └── docker/ │ │ │ └── compose/ │ │ │ └── otlp/ │ │ │ ├── ca.crt │ │ │ ├── otlp-compose.yaml │ │ │ └── otlp-ssl-compose.yaml │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── micrometer/ │ │ │ │ └── metrics/ │ │ │ │ ├── MaximumAllowableTagsMeterFilter.java │ │ │ │ ├── ValidationFailureAnalyzer.java │ │ │ │ ├── actuate/ │ │ │ │ │ └── endpoint/ │ │ │ │ │ ├── MetricsEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── AutoConfiguredCompositeMeterRegistry.java │ │ │ │ │ ├── CompositeMeterRegistryAutoConfiguration.java │ │ │ │ │ ├── CompositeMeterRegistryConfiguration.java │ │ │ │ │ ├── MeterRegistryCustomizer.java │ │ │ │ │ ├── MeterRegistryPostProcessor.java │ │ │ │ │ ├── MeterValue.java │ │ │ │ │ ├── MetricsAspectsAutoConfiguration.java │ │ │ │ │ ├── MetricsAutoConfiguration.java │ │ │ │ │ ├── MetricsEndpointAutoConfiguration.java │ │ │ │ │ ├── MetricsProperties.java │ │ │ │ │ ├── NoOpMeterRegistryConfiguration.java │ │ │ │ │ ├── PropertiesMeterFilter.java │ │ │ │ │ ├── ServiceLevelObjectiveBoundary.java │ │ │ │ │ ├── export/ │ │ │ │ │ │ ├── ConditionalOnEnabledMetricsExport.java │ │ │ │ │ │ ├── OnMetricsExportEnabledCondition.java │ │ │ │ │ │ ├── appoptics/ │ │ │ │ │ │ │ ├── AppOpticsMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── AppOpticsProperties.java │ │ │ │ │ │ │ ├── AppOpticsPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── atlas/ │ │ │ │ │ │ │ ├── AtlasMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── AtlasProperties.java │ │ │ │ │ │ │ ├── AtlasPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── datadog/ │ │ │ │ │ │ │ ├── DatadogMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── DatadogProperties.java │ │ │ │ │ │ │ ├── DatadogPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── dynatrace/ │ │ │ │ │ │ │ ├── DynatraceMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── DynatraceProperties.java │ │ │ │ │ │ │ ├── DynatracePropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── elastic/ │ │ │ │ │ │ │ ├── ElasticMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── ElasticProperties.java │ │ │ │ │ │ │ ├── ElasticPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── ganglia/ │ │ │ │ │ │ │ ├── GangliaMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── GangliaProperties.java │ │ │ │ │ │ │ ├── GangliaPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── graphite/ │ │ │ │ │ │ │ ├── GraphiteMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── GraphiteProperties.java │ │ │ │ │ │ │ ├── GraphitePropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── humio/ │ │ │ │ │ │ │ ├── HumioMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── HumioProperties.java │ │ │ │ │ │ │ ├── HumioPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── influx/ │ │ │ │ │ │ │ ├── InfluxMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── InfluxProperties.java │ │ │ │ │ │ │ ├── InfluxPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── jmx/ │ │ │ │ │ │ │ ├── JmxMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── JmxProperties.java │ │ │ │ │ │ │ ├── JmxPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── kairos/ │ │ │ │ │ │ │ ├── KairosMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── KairosProperties.java │ │ │ │ │ │ │ ├── KairosPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── newrelic/ │ │ │ │ │ │ │ ├── NewRelicMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── NewRelicProperties.java │ │ │ │ │ │ │ ├── NewRelicPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── otlp/ │ │ │ │ │ │ │ ├── JdkClientHttpSender.java │ │ │ │ │ │ │ ├── OtlpMetricsConnectionDetails.java │ │ │ │ │ │ │ ├── OtlpMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── OtlpMetricsProperties.java │ │ │ │ │ │ │ ├── OtlpMetricsPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── package-info.java │ │ │ │ │ │ ├── prometheus/ │ │ │ │ │ │ │ ├── PrometheusMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── PrometheusOutputFormat.java │ │ │ │ │ │ │ ├── PrometheusProperties.java │ │ │ │ │ │ │ ├── PrometheusPropertiesConfigAdapter.java │ │ │ │ │ │ │ ├── PrometheusScrapeEndpoint.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── properties/ │ │ │ │ │ │ │ ├── PropertiesConfigAdapter.java │ │ │ │ │ │ │ ├── PushRegistryProperties.java │ │ │ │ │ │ │ ├── PushRegistryPropertiesConfigAdapter.java │ │ │ │ │ │ │ ├── StepRegistryProperties.java │ │ │ │ │ │ │ ├── StepRegistryPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── simple/ │ │ │ │ │ │ │ ├── SimpleMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── SimpleProperties.java │ │ │ │ │ │ │ ├── SimplePropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── stackdriver/ │ │ │ │ │ │ │ ├── StackdriverMetricsExportAutoConfiguration.java │ │ │ │ │ │ │ ├── StackdriverProperties.java │ │ │ │ │ │ │ ├── StackdriverPropertiesConfigAdapter.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── statsd/ │ │ │ │ │ │ ├── StatsdMetricsExportAutoConfiguration.java │ │ │ │ │ │ ├── StatsdProperties.java │ │ │ │ │ │ ├── StatsdPropertiesConfigAdapter.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── jvm/ │ │ │ │ │ │ ├── JvmMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── logging/ │ │ │ │ │ │ ├── log4j2/ │ │ │ │ │ │ │ ├── Log4J2MetricsAutoConfiguration.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── logback/ │ │ │ │ │ │ ├── LogbackMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── ssl/ │ │ │ │ │ │ ├── SslMeterBinder.java │ │ │ │ │ │ ├── SslMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── startup/ │ │ │ │ │ │ ├── StartupTimeMetricsListenerAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── system/ │ │ │ │ │ │ ├── SystemMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── task/ │ │ │ │ │ ├── TaskExecutorMetricsAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── otlp/ │ │ │ │ │ ├── OpenTelemetryMetricsDockerComposeConnectionDetailsFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── export/ │ │ │ │ │ └── prometheus/ │ │ │ │ │ ├── PrometheusPushGatewayManager.java │ │ │ │ │ ├── endpoint/ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── startup/ │ │ │ │ │ ├── StartupTimeMetricsListener.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── system/ │ │ │ │ │ ├── DiskSpaceMetricsBinder.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ └── otlp/ │ │ │ │ ├── GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactory.java │ │ │ │ ├── OpenTelemetryMetricsContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── aot.factories │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ ├── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── micrometer/ │ │ │ │ └── metrics/ │ │ │ │ ├── MaximumAllowableTagsMeterFilterTests.java │ │ │ │ ├── ValidationFailureAnalyzerTests.java │ │ │ │ ├── actuate/ │ │ │ │ │ └── endpoint/ │ │ │ │ │ └── MetricsEndpointTests.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── CompositeMeterRegistryAutoConfigurationTests.java │ │ │ │ │ ├── MeterRegistryCustomizerTests.java │ │ │ │ │ ├── MeterRegistryPostProcessorTests.java │ │ │ │ │ ├── MeterValueTests.java │ │ │ │ │ ├── MetricsAspectsAutoConfigurationTests.java │ │ │ │ │ ├── MetricsAutoConfigurationIntegrationTests.java │ │ │ │ │ ├── MetricsAutoConfigurationMeterRegistryPostProcessorIntegrationTests.java │ │ │ │ │ ├── MetricsAutoConfigurationTests.java │ │ │ │ │ ├── PropertiesMeterFilterTests.java │ │ │ │ │ ├── ServiceLevelObjectiveBoundaryTests.java │ │ │ │ │ ├── export/ │ │ │ │ │ │ ├── ConditionalOnEnabledMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ ├── appoptics/ │ │ │ │ │ │ │ ├── AppOpticsMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── AppOpticsPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── AppOpticsPropertiesTests.java │ │ │ │ │ │ ├── atlas/ │ │ │ │ │ │ │ ├── AtlasMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── AtlasPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── AtlasPropertiesTests.java │ │ │ │ │ │ ├── datadog/ │ │ │ │ │ │ │ ├── DatadogMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── DatadogPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── DatadogPropertiesTests.java │ │ │ │ │ │ ├── dynatrace/ │ │ │ │ │ │ │ ├── DynatraceMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── DynatracePropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── DynatracePropertiesTests.java │ │ │ │ │ │ ├── elastic/ │ │ │ │ │ │ │ ├── ElasticMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── ElasticPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── ElasticPropertiesTests.java │ │ │ │ │ │ ├── ganglia/ │ │ │ │ │ │ │ ├── GangliaMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── GangliaPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── GangliaPropertiesTests.java │ │ │ │ │ │ ├── graphite/ │ │ │ │ │ │ │ ├── GraphiteMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── GraphitePropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── GraphitePropertiesTests.java │ │ │ │ │ │ ├── humio/ │ │ │ │ │ │ │ ├── HumioMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── HumioPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── HumioPropertiesTests.java │ │ │ │ │ │ ├── influx/ │ │ │ │ │ │ │ ├── InfluxMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── InfluxPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── InfluxPropertiesTests.java │ │ │ │ │ │ ├── jmx/ │ │ │ │ │ │ │ ├── JmxMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── JmxPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── JmxPropertiesTests.java │ │ │ │ │ │ ├── kairos/ │ │ │ │ │ │ │ ├── KairosMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── KairosPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── KairosPropertiesTests.java │ │ │ │ │ │ ├── newrelic/ │ │ │ │ │ │ │ ├── NewRelicMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── NewRelicPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── NewRelicPropertiesTests.java │ │ │ │ │ │ ├── otlp/ │ │ │ │ │ │ │ ├── JdkClientHttpSenderTests.java │ │ │ │ │ │ │ ├── OtlpMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── OtlpMetricsPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── OtlpMetricsPropertiesTests.java │ │ │ │ │ │ ├── prometheus/ │ │ │ │ │ │ │ ├── PrometheusMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── PrometheusPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── PrometheusPropertiesTests.java │ │ │ │ │ │ ├── properties/ │ │ │ │ │ │ │ ├── PushRegistryPropertiesTests.java │ │ │ │ │ │ │ ├── StepRegistryPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── StepRegistryPropertiesTests.java │ │ │ │ │ │ ├── simple/ │ │ │ │ │ │ │ ├── SimpleMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── SimplePropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── SimplePropertiesTests.java │ │ │ │ │ │ ├── stackdriver/ │ │ │ │ │ │ │ ├── StackdriverMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ │ ├── StackdriverPropertiesConfigAdapterTests.java │ │ │ │ │ │ │ └── StackdriverPropertiesTests.java │ │ │ │ │ │ └── statsd/ │ │ │ │ │ │ ├── StatsdMetricsExportAutoConfigurationTests.java │ │ │ │ │ │ ├── StatsdPropertiesConfigAdapterTests.java │ │ │ │ │ │ └── StatsdPropertiesTests.java │ │ │ │ │ ├── jvm/ │ │ │ │ │ │ └── JvmMetricsAutoConfigurationTests.java │ │ │ │ │ ├── logging/ │ │ │ │ │ │ ├── log4j2/ │ │ │ │ │ │ │ ├── Log4J2MetricsWithLog4jLoggerContextAutoConfigurationTests.java │ │ │ │ │ │ │ └── Log4J2MetricsWithSlf4jLoggerContextAutoConfigurationTests.java │ │ │ │ │ │ └── logback/ │ │ │ │ │ │ ├── LogbackMetricsAutoConfigurationTests.java │ │ │ │ │ │ └── LogbackMetricsAutoConfigurationWithLog4j2AndLogbackTests.java │ │ │ │ │ ├── ssl/ │ │ │ │ │ │ ├── SslMeterBinderTests.java │ │ │ │ │ │ └── SslMetricsAutoConfigurationTests.java │ │ │ │ │ ├── startup/ │ │ │ │ │ │ └── StartupTimeMetricsListenerAutoConfigurationTests.java │ │ │ │ │ ├── system/ │ │ │ │ │ │ └── SystemMetricsAutoConfigurationTests.java │ │ │ │ │ └── task/ │ │ │ │ │ └── TaskExecutorMetricsAutoConfigurationTests.java │ │ │ │ ├── export/ │ │ │ │ │ └── prometheus/ │ │ │ │ │ └── PrometheusPushGatewayManagerTests.java │ │ │ │ ├── startup/ │ │ │ │ │ └── StartupTimeMetricsListenerTests.java │ │ │ │ └── system/ │ │ │ │ └── DiskSpaceMetricsBinderTests.java │ │ │ └── resources/ │ │ │ ├── certificates/ │ │ │ │ ├── chains.p12 │ │ │ │ └── chains2.p12 │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── micrometer/ │ │ │ └── metrics/ │ │ │ └── autoconfigure/ │ │ │ └── export/ │ │ │ └── otlp/ │ │ │ └── test.jks │ │ └── testFixtures/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── actuate/ │ │ └── autoconfigure/ │ │ └── metrics/ │ │ └── export/ │ │ └── properties/ │ │ ├── AbstractPropertiesConfigAdapterTests.java │ │ └── PushRegistryPropertiesConfigAdapterTests.java │ ├── spring-boot-micrometer-metrics-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── micrometer/ │ │ │ │ └── metrics/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureMetrics.java │ │ │ │ ├── MetricsContextCustomizerFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.micrometer.metrics.test.autoconfigure.AutoConfigureMetrics.imports │ │ │ ├── spring-configuration-metadata.json │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── micrometer/ │ │ └── metrics/ │ │ └── test/ │ │ └── autoconfigure/ │ │ ├── AutoConfigureMetricsMissingIntegrationTests.java │ │ ├── AutoConfigureMetricsPresentIntegrationTests.java │ │ ├── AutoConfigureMetricsSlicedIntegrationTests.java │ │ ├── AutoConfigureMetricsSpringBootApplication.java │ │ └── MetricsContextCustomizerFactoryTests.java │ ├── spring-boot-micrometer-observation/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── micrometer/ │ │ │ │ └── observation/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── ObservationAutoConfiguration.java │ │ │ │ ├── ObservationHandlerGroup.java │ │ │ │ ├── ObservationHandlerGroups.java │ │ │ │ ├── ObservationProperties.java │ │ │ │ ├── ObservationRegistryConfigurer.java │ │ │ │ ├── ObservationRegistryCustomizer.java │ │ │ │ ├── ObservationRegistryPostProcessor.java │ │ │ │ ├── PropertiesObservationFilterPredicate.java │ │ │ │ ├── ScheduledTasksObservationAutoConfiguration.java │ │ │ │ ├── SpelValueExpressionResolver.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── micrometer/ │ │ └── observation/ │ │ └── autoconfigure/ │ │ ├── ObservationAutoConfigurationTests.java │ │ ├── ObservationHandlerGroupTests.java │ │ ├── ObservationHandlerGroupsTests.java │ │ ├── ObservationRegistryConfigurerIntegrationTests.java │ │ ├── PropertiesObservationFilterPredicateTests.java │ │ ├── ScheduledTasksObservationAutoConfigurationTests.java │ │ └── SpelValueExpressionResolverTests.java │ ├── spring-boot-micrometer-tracing/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── micrometer/ │ │ │ │ └── tracing/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── ConditionalOnEnabledTracingExport.java │ │ │ │ ├── LogCorrelationEnvironmentPostProcessor.java │ │ │ │ ├── MicrometerTracingAutoConfiguration.java │ │ │ │ ├── NoopTracerAutoConfiguration.java │ │ │ │ ├── OnEnabledTracingExportCondition.java │ │ │ │ ├── TracingAndMeterObservationHandlerGroup.java │ │ │ │ ├── TracingProperties.java │ │ │ │ ├── otlp/ │ │ │ │ │ ├── OtlpExemplarsAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── prometheus/ │ │ │ │ ├── PrometheusExemplarsAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ ├── spring-devtools.properties │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── micrometer/ │ │ └── tracing/ │ │ └── autoconfigure/ │ │ ├── LogCorrelationEnvironmentPostProcessorTests.java │ │ ├── MicrometerTracingAutoConfigurationTests.java │ │ ├── NoopTracerAutoConfigurationTests.java │ │ ├── OnEnabledTracingExportConditionTests.java │ │ ├── TracingAndMeterObservationHandlerGroupTests.java │ │ ├── otlp/ │ │ │ ├── LazyTracingExemplarContextProviderTests.java │ │ │ └── OtlpExemplarsAutoConfigurationTests.java │ │ └── prometheus/ │ │ ├── LazyTracingSpanContextTests.java │ │ └── PrometheusExemplarsAutoConfigurationTests.java │ ├── spring-boot-micrometer-tracing-brave/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── micrometer/ │ │ │ │ └── tracing/ │ │ │ │ └── brave/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── BraveAutoConfiguration.java │ │ │ │ ├── BravePropagationConfigurations.java │ │ │ │ ├── BraveTracingProperties.java │ │ │ │ ├── CompositePropagationFactory.java │ │ │ │ ├── LocalBaggageFields.java │ │ │ │ ├── package-info.java │ │ │ │ └── zipkin/ │ │ │ │ ├── ZipkinWithBraveTracingAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── micrometer/ │ │ └── tracing/ │ │ └── brave/ │ │ └── autoconfigure/ │ │ ├── BraveAutoConfigurationTests.java │ │ ├── BraveBaggagePropagationIntegrationTests.java │ │ ├── CompositePropagationFactoryTests.java │ │ ├── LocalBaggageFieldsTests.java │ │ ├── OtlpExemplarsAutoConfigurationTests.java │ │ ├── PrometheusExemplarsAutoConfigurationTests.java │ │ └── zipkin/ │ │ ├── DefaultEncodingConfiguration.java │ │ ├── NoopSender.java │ │ └── ZipkinWithBraveTracingAutoConfigurationTests.java │ ├── spring-boot-micrometer-tracing-opentelemetry/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── micrometer/ │ │ │ │ └── tracing/ │ │ │ │ └── opentelemetry/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── otlp/ │ │ │ │ │ ├── GrafanaOpenTelemetryTracingDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ └── OpenTelemetryTracingDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ └── otlp/ │ │ │ │ ├── GrafanaOpenTelemetryTracingContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── OpenTelemetryTracingContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── micrometer/ │ │ │ └── tracing/ │ │ │ └── opentelemetry/ │ │ │ └── docker/ │ │ │ └── compose/ │ │ │ └── otlp/ │ │ │ ├── ca.crt │ │ │ ├── otlp-compose.yaml │ │ │ └── otlp-ssl-compose.yaml │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── micrometer/ │ │ │ │ └── tracing/ │ │ │ │ └── opentelemetry/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── CompositeTextMapPropagator.java │ │ │ │ │ ├── OpenTelemetryEventPublisherBeansApplicationListener.java │ │ │ │ │ ├── OpenTelemetryEventPublisherBeansTestExecutionListener.java │ │ │ │ │ ├── OpenTelemetryPropagationConfigurations.java │ │ │ │ │ ├── OpenTelemetryTracingAutoConfiguration.java │ │ │ │ │ ├── OpenTelemetryTracingProperties.java │ │ │ │ │ ├── SdkTracerProviderBuilderCustomizer.java │ │ │ │ │ ├── SpanExporters.java │ │ │ │ │ ├── SpanProcessors.java │ │ │ │ │ ├── otlp/ │ │ │ │ │ │ ├── OtlpGrpcSpanExporterBuilderCustomizer.java │ │ │ │ │ │ ├── OtlpHttpSpanExporterBuilderCustomizer.java │ │ │ │ │ │ ├── OtlpTracingAutoConfiguration.java │ │ │ │ │ │ ├── OtlpTracingConfigurations.java │ │ │ │ │ │ ├── OtlpTracingConnectionDetails.java │ │ │ │ │ │ ├── OtlpTracingProperties.java │ │ │ │ │ │ ├── Transport.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── zipkin/ │ │ │ │ │ ├── ZipkinWithOpenTelemetryTracingAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── otlp/ │ │ │ │ │ ├── OpenTelemetryTracingDockerComposeConnectionDetailsFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ └── otlp/ │ │ │ │ ├── GrafanaOpenTelemetryTracingContainerConnectionDetailsFactory.java │ │ │ │ ├── OpenTelemetryTracingContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── services/ │ │ │ │ └── org.junit.platform.launcher.TestExecutionListener │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── micrometer/ │ │ │ └── tracing/ │ │ │ └── opentelemetry/ │ │ │ └── autoconfigure/ │ │ │ ├── CompositeTextMapPropagatorTests.java │ │ │ ├── OpenTelemetryBaggagePropagationIntegrationTests.java │ │ │ ├── OpenTelemetryEventPublishingContextWrapperBeansTestExecutionListenerIntegrationTests.java │ │ │ ├── OpenTelemetryTracingAutoConfigurationTests.java │ │ │ ├── OpenTelemetryTracingPropertiesTests.java │ │ │ ├── SpanExportersTests.java │ │ │ ├── SpanProcessorsTests.java │ │ │ ├── otlp/ │ │ │ │ ├── OtlpTracingAutoConfigurationIntegrationTests.java │ │ │ │ └── OtlpTracingAutoConfigurationTests.java │ │ │ └── zipkin/ │ │ │ ├── DefaultEncodingConfiguration.java │ │ │ ├── NoopSender.java │ │ │ └── ZipkinWithOpenTelemetryTracingAutoConfigurationTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── micrometer/ │ │ └── tracing/ │ │ └── opentelemetry/ │ │ └── autoconfigure/ │ │ └── otlp/ │ │ └── test.jks │ ├── spring-boot-micrometer-tracing-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── micrometer/ │ │ │ │ └── tracing/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureTracing.java │ │ │ │ ├── TracingContextCustomizerFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.micrometer.tracing.test.autoconfigure.AutoConfigureTracing.imports │ │ │ ├── spring-configuration-metadata.json │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── micrometer/ │ │ └── tracing/ │ │ └── test/ │ │ └── autoconfigure/ │ │ ├── AutoConfigureTracingMissingIntegrationTests.java │ │ ├── AutoConfigureTracingPresentIntegrationTests.java │ │ ├── AutoConfigureTracingSlicedIntegrationTests.java │ │ ├── AutoConfigureTracingSpringBootApplication.java │ │ └── TracingContextCustomizerFactoryTests.java │ ├── spring-boot-mongodb/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── mongodb/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── MongoDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ ├── health/ │ │ │ │ │ ├── MongoHealthIndicatorIntegrationTests.java │ │ │ │ │ └── MongoReactiveHealthIndicatorIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── DeprecatedMongoDbContainerConnectionDetailsFactoryTests.java │ │ │ │ └── MongoDbAtlasLocalContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── mongodb/ │ │ │ └── docker/ │ │ │ └── compose/ │ │ │ ├── ca.crt │ │ │ ├── client.crt │ │ │ ├── client.key │ │ │ ├── mongo-compose.yaml │ │ │ ├── mongo-ssl-compose.yaml │ │ │ └── mongo.pem │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── mongodb/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── MongoAutoConfiguration.java │ │ │ │ │ ├── MongoClientFactory.java │ │ │ │ │ ├── MongoClientFactorySupport.java │ │ │ │ │ ├── MongoClientSettingsBuilderCustomizer.java │ │ │ │ │ ├── MongoConnectionDetails.java │ │ │ │ │ ├── MongoProperties.java │ │ │ │ │ ├── MongoReactiveAutoConfiguration.java │ │ │ │ │ ├── PropertiesMongoConnectionDetails.java │ │ │ │ │ ├── ReactiveMongoClientFactory.java │ │ │ │ │ ├── StandardMongoClientSettingsBuilderCustomizer.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── MongoHealthContributorAutoConfiguration.java │ │ │ │ │ │ ├── MongoReactiveHealthContributorAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── MongoMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── MongoDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── MongoEnvironment.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── MongoHealthIndicator.java │ │ │ │ │ ├── MongoReactiveHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── AbstractMongoContainerConnectionDetailsFactory.java │ │ │ │ ├── DeprecatedMongoDbContainerConnectionDetailsFactory.java │ │ │ │ ├── MongoDbAtlasLocalContainerConnectionDetailsFactory.java │ │ │ │ ├── MongoDbContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── mongodb/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── MongoAutoConfigurationTests.java │ │ │ │ ├── MongoClientFactorySupportTests.java │ │ │ │ ├── MongoClientFactoryTests.java │ │ │ │ ├── MongoPropertiesTests.java │ │ │ │ ├── MongoReactiveAutoConfigurationTests.java │ │ │ │ ├── PropertiesMongoConnectionDetailsTests.java │ │ │ │ ├── ReactiveMongoClientFactoryTests.java │ │ │ │ ├── health/ │ │ │ │ │ ├── MongoHealthContributorAutoConfigurationTests.java │ │ │ │ │ └── MongoReactiveHealthContributorAutoConfigurationTests.java │ │ │ │ └── metrics/ │ │ │ │ └── MongoMetricsAutoConfigurationTests.java │ │ │ ├── docker/ │ │ │ │ └── compose/ │ │ │ │ └── MongoEnvironmentTests.java │ │ │ ├── health/ │ │ │ │ ├── MongoHealthIndicatorTests.java │ │ │ │ └── MongoReactiveHealthIndicatorTests.java │ │ │ └── testcontainers/ │ │ │ └── AbstractMongoContainerConnectionDetailsFactoryTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── mongodb/ │ │ └── autoconfigure/ │ │ └── test.jks │ ├── spring-boot-mustache/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── mustache/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── MustacheAutoConfiguration.java │ │ │ │ │ ├── MustacheProperties.java │ │ │ │ │ ├── MustacheReactiveWebConfiguration.java │ │ │ │ │ ├── MustacheResourceTemplateLoader.java │ │ │ │ │ ├── MustacheServletWebConfiguration.java │ │ │ │ │ ├── MustacheTemplateAvailabilityProvider.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── reactive/ │ │ │ │ │ └── view/ │ │ │ │ │ ├── MustacheView.java │ │ │ │ │ ├── MustacheViewResolver.java │ │ │ │ │ └── package-info.java │ │ │ │ └── servlet/ │ │ │ │ └── view/ │ │ │ │ ├── MustacheView.java │ │ │ │ ├── MustacheViewResolver.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ ├── org.springframework.boot.webflux.test.autoconfigure.AutoConfigureWebFlux.imports │ │ │ │ └── org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureWebMvc.imports │ │ │ ├── spring-devtools.properties │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── mustache/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── MustacheAutoConfigurationReactiveIntegrationTests.java │ │ │ │ ├── MustacheAutoConfigurationServletIntegrationTests.java │ │ │ │ ├── MustacheAutoConfigurationTests.java │ │ │ │ ├── MustacheAutoConfigurationWithoutWebMvcTests.java │ │ │ │ ├── MustacheStandaloneIntegrationTests.java │ │ │ │ ├── MustacheWebFluxTestIntegrationTests.java │ │ │ │ └── MustacheWebMvcTestIntegrationTests.java │ │ │ ├── reactive/ │ │ │ │ └── view/ │ │ │ │ ├── MustacheViewResolverTests.java │ │ │ │ └── MustacheViewTests.java │ │ │ └── servlet/ │ │ │ └── view/ │ │ │ ├── MustacheViewResolverTests.java │ │ │ └── MustacheViewTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── mustache/ │ │ └── autoconfigure/ │ │ ├── content.html │ │ ├── foo.html │ │ ├── foo_de.html │ │ ├── home.html │ │ ├── layout.html │ │ └── partial.html │ ├── spring-boot-neo4j/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── neo4j/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ └── Neo4jAutoConfigurationIntegrationTests.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── Neo4jDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ ├── health/ │ │ │ │ │ └── Neo4jReactiveHealthIndicatorIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ └── DeprecatedNeo4jContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── neo4j/ │ │ │ └── docker/ │ │ │ └── compose/ │ │ │ └── neo4j-compose.yaml │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── neo4j/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ConfigBuilderCustomizer.java │ │ │ │ │ ├── Neo4jAutoConfiguration.java │ │ │ │ │ ├── Neo4jConnectionDetails.java │ │ │ │ │ ├── Neo4jProperties.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── Neo4jHealthContributorAutoConfiguration.java │ │ │ │ │ │ ├── Neo4jHealthContributorConfigurations.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── observation/ │ │ │ │ │ │ ├── Neo4jObservationAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── Neo4jDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── Neo4jEnvironment.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── Neo4jHealthDetails.java │ │ │ │ │ ├── Neo4jHealthDetailsHandler.java │ │ │ │ │ ├── Neo4jHealthIndicator.java │ │ │ │ │ ├── Neo4jReactiveHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── DeprecatedNeo4jContainerConnectionDetailsFactory.java │ │ │ │ ├── Neo4jContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── neo4j/ │ │ ├── autoconfigure/ │ │ │ ├── Neo4jAutoConfigurationTests.java │ │ │ ├── Neo4jPropertiesTests.java │ │ │ ├── health/ │ │ │ │ └── Neo4jHealthContributorAutoConfigurationTests.java │ │ │ └── observation/ │ │ │ └── Neo4jObservationAutoConfigurationTests.java │ │ ├── docker/ │ │ │ └── compose/ │ │ │ └── Neo4jEnvironmentTests.java │ │ ├── health/ │ │ │ ├── Neo4jHealthIndicatorTests.java │ │ │ ├── Neo4jReactiveHealthIndicatorTests.java │ │ │ └── ResultSummaryMock.java │ │ └── testcontainers/ │ │ └── Neo4jContainerConnectionDetailsFactoryTests.java │ ├── spring-boot-netty/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── netty/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── NettyAutoConfiguration.java │ │ │ │ ├── NettyProperties.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── netty/ │ │ └── autoconfigure/ │ │ ├── NettyAutoConfigurationTests.java │ │ └── NettyPropertiesTests.java │ ├── spring-boot-opentelemetry/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── opentelemetry/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── GrafanaOtlpLoggingDockerComposeConnectionDetailsFactoryTests.java │ │ │ │ │ └── OtelCollectorOtlpLoggingDockerComposeConnectionDetailsFactoryTests.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── GrafanaOtlpLoggingContainerConnectionDetailsFactoryTests.java │ │ │ │ └── OtelCollectorOltpLoggingContainerConnectionDetailsFactoryTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── opentelemetry/ │ │ │ └── docker/ │ │ │ └── compose/ │ │ │ ├── ca.crt │ │ │ ├── otlp-compose.yaml │ │ │ └── otlp-ssl-compose.yaml │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── opentelemetry/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ConditionalOnEnabledOpenTelemetry.java │ │ │ │ │ ├── OpenTelemetryEnvironmentVariableEnvironmentPostProcessor.java │ │ │ │ │ ├── OpenTelemetryEnvironmentVariables.java │ │ │ │ │ ├── OpenTelemetryProperties.java │ │ │ │ │ ├── OpenTelemetryResourceAttributes.java │ │ │ │ │ ├── OpenTelemetrySdkAutoConfiguration.java │ │ │ │ │ ├── W3CHeaderParser.java │ │ │ │ │ ├── logging/ │ │ │ │ │ │ ├── ConditionalOnEnabledLoggingExport.java │ │ │ │ │ │ ├── OnEnabledLoggingExportCondition.java │ │ │ │ │ │ ├── OpenTelemetryLoggingAutoConfiguration.java │ │ │ │ │ │ ├── OpenTelemetryLoggingProperties.java │ │ │ │ │ │ ├── SdkLoggerProviderBuilderCustomizer.java │ │ │ │ │ │ ├── otlp/ │ │ │ │ │ │ │ ├── OtlpGrpcLogRecordExporterBuilderCustomizer.java │ │ │ │ │ │ │ ├── OtlpHttpLogRecordExporterBuilderCustomizer.java │ │ │ │ │ │ │ ├── OtlpLoggingAutoConfiguration.java │ │ │ │ │ │ │ ├── OtlpLoggingConfigurations.java │ │ │ │ │ │ │ ├── OtlpLoggingConnectionDetails.java │ │ │ │ │ │ │ ├── OtlpLoggingProperties.java │ │ │ │ │ │ │ ├── Transport.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── OtlpLoggingDockerComposeConnectionDetailsFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── GrafanaOtlpLoggingContainerConnectionDetailsFactory.java │ │ │ │ ├── OtelCollectorOltpLoggingContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── opentelemetry/ │ │ │ └── autoconfigure/ │ │ │ ├── OpenTelemetryEnvironmentVariableEnvironmentPostProcessorTests.java │ │ │ ├── OpenTelemetryEnvironmentVariablesTests.java │ │ │ ├── OpenTelemetryPropertiesTests.java │ │ │ ├── OpenTelemetryResourceAttributesTests.java │ │ │ ├── OpenTelemetrySdkAutoConfigurationTests.java │ │ │ ├── W3CHeaderParserTests.java │ │ │ └── logging/ │ │ │ ├── OnEnabledLoggingExportConditionTests.java │ │ │ ├── OpenTelemetryLoggingAutoConfigurationTests.java │ │ │ ├── OpenTelemetryLoggingPropertiesTests.java │ │ │ └── otlp/ │ │ │ ├── OtlpLoggingAutoConfigurationIntegrationTests.java │ │ │ └── OtlpLoggingAutoConfigurationTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── opentelemetry/ │ │ └── autoconfigure/ │ │ └── logging/ │ │ └── otlp/ │ │ └── test.jks │ ├── spring-boot-persistence/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── persistence/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── EntityScan.java │ │ │ │ ├── EntityScanPackages.java │ │ │ │ ├── EntityScanner.java │ │ │ │ ├── PersistenceExceptionTranslationAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── persistence/ │ │ └── autoconfigure/ │ │ ├── EntityScanPackagesTests.java │ │ ├── EntityScannerTests.java │ │ ├── PersistenceExceptionTranslationAutoConfigurationTests.java │ │ └── scan/ │ │ ├── a/ │ │ │ ├── EmbeddableA.java │ │ │ ├── EntityA.java │ │ │ └── package-info.java │ │ ├── b/ │ │ │ ├── EmbeddableB.java │ │ │ ├── EntityB.java │ │ │ └── package-info.java │ │ └── c/ │ │ ├── EmbeddableC.java │ │ ├── EntityC.java │ │ └── package-info.java │ ├── spring-boot-pulsar/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── pulsar/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ └── PulsarAutoConfigurationIntegrationTests.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ └── PulsarDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── DeprecatedPulsarContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── PulsarContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ ├── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── pulsar/ │ │ │ │ └── docker/ │ │ │ │ └── compose/ │ │ │ │ └── pulsar-compose.yaml │ │ │ └── spring.properties │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── pulsar/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── DeadLetterPolicyMapper.java │ │ │ │ │ ├── PropertiesPulsarConnectionDetails.java │ │ │ │ │ ├── PulsarAutoConfiguration.java │ │ │ │ │ ├── PulsarConnectionDetails.java │ │ │ │ │ ├── PulsarContainerFactoryCustomizer.java │ │ │ │ │ ├── PulsarContainerFactoryCustomizers.java │ │ │ │ │ ├── PulsarProperties.java │ │ │ │ │ ├── PulsarPropertiesMapper.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── PulsarDockerComposeConnectionDetailsFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── DeprecatedPulsarContainerConnectionDetailsFactory.java │ │ │ │ ├── PulsarContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── pulsar/ │ │ └── autoconfigure/ │ │ ├── Customizers.java │ │ ├── DeadLetterPolicyMapperTests.java │ │ ├── MockAuthentication.java │ │ ├── PropertiesPulsarConnectionDetailsTests.java │ │ ├── PulsarAutoConfigurationTests.java │ │ ├── PulsarContainerFactoryCustomizersTests.java │ │ ├── PulsarPropertiesMapperTests.java │ │ └── PulsarPropertiesTests.java │ ├── spring-boot-quartz/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── quartz/ │ │ │ │ ├── actuate/ │ │ │ │ │ └── endpoint/ │ │ │ │ │ ├── QuartzEndpoint.java │ │ │ │ │ ├── QuartzEndpointWebExtension.java │ │ │ │ │ └── package-info.java │ │ │ │ └── autoconfigure/ │ │ │ │ ├── JobStoreType.java │ │ │ │ ├── QuartzAutoConfiguration.java │ │ │ │ ├── QuartzDataSource.java │ │ │ │ ├── QuartzDataSourceScriptDatabaseInitializer.java │ │ │ │ ├── QuartzEndpointAutoConfiguration.java │ │ │ │ ├── QuartzEndpointProperties.java │ │ │ │ ├── QuartzJdbcProperties.java │ │ │ │ ├── QuartzProperties.java │ │ │ │ ├── QuartzTransactionManager.java │ │ │ │ ├── SchedulerDependsOnDatabaseInitializationDetector.java │ │ │ │ ├── SchedulerFactoryBeanCustomizer.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── quartz/ │ │ │ ├── actuate/ │ │ │ │ └── endpoint/ │ │ │ │ ├── QuartzEndpointTests.java │ │ │ │ ├── QuartzEndpointWebExtensionTests.java │ │ │ │ └── QuartzEndpointWebIntegrationTests.java │ │ │ └── autoconfigure/ │ │ │ ├── QuartzAutoConfigurationTests.java │ │ │ ├── QuartzDataSourceScriptDatabaseInitializerTests.java │ │ │ └── QuartzEndpointAutoConfigurationTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── quartz/ │ │ └── autoconfigure/ │ │ ├── tables_#_comments.sql │ │ ├── tables_--_comments.sql │ │ └── tables_custom_comment_prefix.sql │ ├── spring-boot-r2dbc/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── r2dbc/ │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── ClickHouseR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ ├── MariaDbR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ ├── MySqlR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ ├── OracleFreeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ ├── OracleXeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ ├── PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ │ └── SqlServerR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── OracleFreeR2dbcContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ │ └── OracleXeR2dbcContainerConnectionDetailsFactoryIntegrationTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── r2dbc/ │ │ │ └── docker/ │ │ │ └── compose/ │ │ │ ├── clickhouse-compose.yaml │ │ │ ├── mariadb-compose.yaml │ │ │ ├── mssqlserver-compose.yaml │ │ │ ├── mssqlserver-with-jdbc-parameters-compose.yaml │ │ │ ├── mysql-compose.yaml │ │ │ ├── oracle-compose.yaml │ │ │ ├── otlp-compose.yaml │ │ │ ├── postgres-application-name-compose.yaml │ │ │ ├── postgres-compose.yaml │ │ │ └── postgres-with-trust-host-auth-method-compose.yaml │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── r2dbc/ │ │ │ │ ├── ConnectionFactoryBuilder.java │ │ │ │ ├── ConnectionFactoryDecorator.java │ │ │ │ ├── EmbeddedDatabaseConnection.java │ │ │ │ ├── OptionsCapableConnectionFactory.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ApplicationR2dbcScriptDatabaseInitializer.java │ │ │ │ │ ├── ConnectionFactoryBeanCreationFailureAnalyzer.java │ │ │ │ │ ├── ConnectionFactoryConfigurations.java │ │ │ │ │ ├── ConnectionFactoryDependentConfiguration.java │ │ │ │ │ ├── ConnectionFactoryOptionsBuilderCustomizer.java │ │ │ │ │ ├── ConnectionFactoryOptionsInitializer.java │ │ │ │ │ ├── MissingR2dbcPoolDependencyException.java │ │ │ │ │ ├── MissingR2dbcPoolDependencyFailureAnalyzer.java │ │ │ │ │ ├── MultipleConnectionPoolConfigurationsException.java │ │ │ │ │ ├── MultipleConnectionPoolConfigurationsFailureAnalyzer.java │ │ │ │ │ ├── NoConnectionFactoryBeanFailureAnalyzer.java │ │ │ │ │ ├── ProxyConnectionFactoryCustomizer.java │ │ │ │ │ ├── R2dbcAutoConfiguration.java │ │ │ │ │ ├── R2dbcConnectionDetails.java │ │ │ │ │ ├── R2dbcInitializationAutoConfiguration.java │ │ │ │ │ ├── R2dbcProperties.java │ │ │ │ │ ├── R2dbcProxyAutoConfiguration.java │ │ │ │ │ ├── R2dbcTransactionManagerAutoConfiguration.java │ │ │ │ │ ├── health/ │ │ │ │ │ │ ├── ConnectionFactoryHealthContributorAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── ConnectionPoolMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── observation/ │ │ │ │ │ │ ├── R2dbcObservationAutoConfiguration.java │ │ │ │ │ │ ├── R2dbcObservationProperties.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── docker/ │ │ │ │ │ └── compose/ │ │ │ │ │ ├── ClickHouseEnvironment.java │ │ │ │ │ ├── ClickHouseR2dbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── ConnectionFactoryOptionsBuilder.java │ │ │ │ │ ├── MariaDbEnvironment.java │ │ │ │ │ ├── MariaDbR2dbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── MySqlEnvironment.java │ │ │ │ │ ├── MySqlR2dbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── OracleContainer.java │ │ │ │ │ ├── OracleEnvironment.java │ │ │ │ │ ├── OracleFreeR2dbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── OracleR2dbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── OracleXeR2dbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── PostgresEnvironment.java │ │ │ │ │ ├── PostgresR2dbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ ├── SqlServerEnvironment.java │ │ │ │ │ ├── SqlServerR2dbcDockerComposeConnectionDetailsFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── health/ │ │ │ │ │ ├── ConnectionFactoryHealthIndicator.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── init/ │ │ │ │ │ ├── R2dbcScriptDatabaseInitializer.java │ │ │ │ │ ├── R2dbcScriptDatabaseInitializerDetector.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── metrics/ │ │ │ │ │ ├── ConnectionPoolMetrics.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── testcontainers/ │ │ │ │ ├── ClickHouseR2dbcContainerConnectionDetailsFactory.java │ │ │ │ ├── DeprecatedMariaDbR2dbcContainerConnectionDetailsFactory.java │ │ │ │ ├── DeprecatedMySqlR2dbcContainerConnectionDetailsFactory.java │ │ │ │ ├── DeprecatedPostgresR2dbcContainerConnectionDetailsFactory.java │ │ │ │ ├── DeprecatedSqlServerR2dbcContainerConnectionDetailsFactory.java │ │ │ │ ├── MariaDbR2dbcContainerConnectionDetailsFactory.java │ │ │ │ ├── MySqlR2dbcContainerConnectionDetailsFactory.java │ │ │ │ ├── OracleFreeR2dbcContainerConnectionDetailsFactory.java │ │ │ │ ├── OracleXeR2dbcContainerConnectionDetailsFactory.java │ │ │ │ ├── PostgresR2dbcContainerConnectionDetailsFactory.java │ │ │ │ ├── SqlServerR2dbcContainerConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ ├── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── r2dbc/ │ │ │ ├── ConnectionFactoryBuilderTests.java │ │ │ ├── EmbeddedDatabaseConnectionTests.java │ │ │ ├── autoconfigure/ │ │ │ │ ├── ConnectionFactoryBeanCreationFailureAnalyzerTests.java │ │ │ │ ├── MissingR2dbcPoolDependencyFailureAnalyzerTests.java │ │ │ │ ├── MultipleConnectionPoolConfigurationsFailureAnalyzerTests.java │ │ │ │ ├── NoConnectionFactoryBeanFailureAnalyzerTests.java │ │ │ │ ├── R2dbcAutoConfigurationTests.java │ │ │ │ ├── R2dbcAutoConfigurationWithoutConnectionPoolTests.java │ │ │ │ ├── R2dbcInitializationAutoConfigurationTests.java │ │ │ │ ├── R2dbcProxyAutoConfigurationTests.java │ │ │ │ ├── R2dbcTransactionManagerAutoConfigurationTests.java │ │ │ │ ├── health/ │ │ │ │ │ └── ConnectionFactoryHealthContributorAutoConfigurationTests.java │ │ │ │ ├── metrics/ │ │ │ │ │ └── ConnectionPoolMetricsAutoConfigurationTests.java │ │ │ │ └── observation/ │ │ │ │ └── R2dbcObservationAutoConfigurationTests.java │ │ │ ├── docker/ │ │ │ │ └── compose/ │ │ │ │ ├── ClickHouseEnvironmentTests.java │ │ │ │ ├── ConnectionFactoryOptionsBuilderTests.java │ │ │ │ ├── MariaDbEnvironmentTests.java │ │ │ │ ├── MySqlEnvironmentTests.java │ │ │ │ ├── OracleEnvironmentTests.java │ │ │ │ ├── PostgresEnvironmentTests.java │ │ │ │ ├── PostgresR2dbcDockerComposeConnectionDetailsFactoryConnectionDetailsTests.java │ │ │ │ └── SqlServerEnvironmentTests.java │ │ │ ├── health/ │ │ │ │ └── ConnectionFactoryHealthIndicatorTests.java │ │ │ ├── init/ │ │ │ │ └── R2dbcScriptDatabaseInitializerTests.java │ │ │ ├── metrics/ │ │ │ │ └── ConnectionPoolMetricsTests.java │ │ │ └── testcontainers/ │ │ │ ├── ClickHouseR2dbcContainerConnectionDetailsFactoryTests.java │ │ │ ├── MariaDbR2dbcContainerConnectionDetailsFactoryTests.java │ │ │ ├── MySqlR2dbcContainerConnectionDetailsFactoryTests.java │ │ │ ├── OracleFreeR2dbcContainerConnectionDetailsFactoryTests.java │ │ │ ├── OracleXeR2dbcContainerConnectionDetailsFactoryTests.java │ │ │ ├── PostgresR2dbcContainerConnectionDetailsFactoryTests.java │ │ │ └── SqlServerR2dbcContainerConnectionDetailsFactoryTests.java │ │ └── testFixtures/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── r2dbc/ │ │ │ ├── SimpleBindMarkerFactoryProvider.java │ │ │ └── SimpleConnectionFactoryProvider.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── services/ │ │ │ └── io.r2dbc.spi.ConnectionFactoryProvider │ │ └── spring.factories │ ├── spring-boot-reactor/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── reactor/ │ │ │ │ ├── ReactorEnvironmentPostProcessor.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ReactorAutoConfiguration.java │ │ │ │ │ ├── ReactorProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── reactor/ │ │ ├── InstrumentedFluxProvider.java │ │ ├── ReactorEnvironmentPostProcessorTests.java │ │ └── autoconfigure/ │ │ └── ReactorAutoConfigurationTests.java │ ├── spring-boot-reactor-netty/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── reactor/ │ │ │ │ └── netty/ │ │ │ │ ├── CompressionCustomizer.java │ │ │ │ ├── GracefulShutdown.java │ │ │ │ ├── NettyReactiveWebServerFactory.java │ │ │ │ ├── NettyRouteProvider.java │ │ │ │ ├── NettyServerCustomizer.java │ │ │ │ ├── NettyWebServer.java │ │ │ │ ├── SslServerCustomizer.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── NettyReactiveWebServerAutoConfiguration.java │ │ │ │ │ ├── NettyReactiveWebServerFactoryCustomizer.java │ │ │ │ │ ├── NettyServerProperties.java │ │ │ │ │ ├── NettyWebServerTestAutoConfiguration.java │ │ │ │ │ ├── ReactorNettyConfigurations.java │ │ │ │ │ ├── ReactorNettyProperties.java │ │ │ │ │ ├── actuate/ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ └── server/ │ │ │ │ │ │ ├── NettyReactiveManagementChildContextConfiguration.java │ │ │ │ │ │ ├── NettyReactiveManagementContextAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports │ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ └── org.springframework.boot.web.server.test.AutoConfigureWebServer.imports │ │ │ └── spring-devtools.properties │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── reactor/ │ │ │ └── netty/ │ │ │ ├── NettyReactiveWebServerFactoryTests.java │ │ │ └── autoconfigure/ │ │ │ ├── AutoConfigureWebServerReactorNettyTests.java │ │ │ ├── NettyReactiveWebServerAutoConfigurationTests.java │ │ │ ├── NettyReactiveWebServerFactoryCustomizerTests.java │ │ │ └── NettyServerPropertiesTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── reactor/ │ │ └── netty/ │ │ ├── 1.crt │ │ ├── 1.key │ │ ├── 2.crt │ │ ├── 2.key │ │ └── test.jks │ ├── spring-boot-restclient/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── restclient/ │ │ │ │ ├── BasicAuthentication.java │ │ │ │ ├── RestClientCustomizer.java │ │ │ │ ├── RestTemplateBuilder.java │ │ │ │ ├── RestTemplateBuilderClientHttpRequestInitializer.java │ │ │ │ ├── RestTemplateCustomizer.java │ │ │ │ ├── RestTemplateRequestCustomizer.java │ │ │ │ ├── RootUriBuilderFactory.java │ │ │ │ ├── RootUriTemplateHandler.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── AutoConfiguredRestClientSsl.java │ │ │ │ │ ├── HttpMessageConvertersRestClientCustomizer.java │ │ │ │ │ ├── NotReactiveWebApplicationCondition.java │ │ │ │ │ ├── RestClientAutoConfiguration.java │ │ │ │ │ ├── RestClientBuilderConfigurer.java │ │ │ │ │ ├── RestClientObservationAutoConfiguration.java │ │ │ │ │ ├── RestClientSsl.java │ │ │ │ │ ├── RestTemplateAutoConfiguration.java │ │ │ │ │ ├── RestTemplateBuilderConfigurer.java │ │ │ │ │ ├── RestTemplateObservationAutoConfiguration.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── service/ │ │ │ │ │ ├── HttpServiceClientAutoConfiguration.java │ │ │ │ │ ├── PropertiesRestClientHttpServiceGroupConfigurer.java │ │ │ │ │ ├── RestClientCustomizerHttpServiceGroupConfigurer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── observation/ │ │ │ │ │ ├── ObservationRestClientCustomizer.java │ │ │ │ │ ├── ObservationRestTemplateCustomizer.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── restclient/ │ │ ├── RestClientWithRestTemplateBuilderTests.java │ │ ├── RestClientWithRestTemplateTests.java │ │ ├── RestTemplateBuilderClientHttpRequestInitializerTests.java │ │ ├── RestTemplateBuilderTests.java │ │ ├── RootUriBuilderFactoryTests.java │ │ ├── RootUriTemplateHandlerTests.java │ │ ├── autoconfigure/ │ │ │ ├── AutoConfiguredRestClientSslTests.java │ │ │ ├── HttpMessageConvertersRestClientCustomizerTests.java │ │ │ ├── RestClientAutoConfigurationTests.java │ │ │ ├── RestClientObservationAutoConfigurationTests.java │ │ │ ├── RestClientObservationAutoConfigurationWithoutMetricsTests.java │ │ │ ├── RestTemplateAutoConfigurationTests.java │ │ │ ├── RestTemplateObservationAutoConfigurationTests.java │ │ │ ├── RestTemplateObservationAutoConfigurationWithoutMetricsTests.java │ │ │ └── service/ │ │ │ └── HttpServiceClientAutoConfigurationTests.java │ │ └── observation/ │ │ ├── ObservationRestClientCustomizerTests.java │ │ └── ObservationRestTemplateCustomizerTests.java │ ├── spring-boot-restclient-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── restclient/ │ │ │ │ └── test/ │ │ │ │ ├── MockServerRestClientCustomizer.java │ │ │ │ ├── MockServerRestTemplateCustomizer.java │ │ │ │ ├── RootUriRequestExpectationManager.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── AutoConfigureMockRestServiceServer.java │ │ │ │ │ ├── AutoConfigureRestClient.java │ │ │ │ │ ├── MockRestServiceServerAutoConfiguration.java │ │ │ │ │ ├── MockRestServiceServerResetTestExecutionListener.java │ │ │ │ │ ├── RestClientTest.java │ │ │ │ │ ├── RestClientTestContextBootstrapper.java │ │ │ │ │ ├── RestClientTypeExcludeFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ ├── org.springframework.boot.restclient.test.autoconfigure.AutoConfigureMockRestServiceServer.imports │ │ │ │ └── org.springframework.boot.restclient.test.autoconfigure.AutoConfigureRestClient.imports │ │ │ ├── spring-configuration-metadata.json │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── restclient/ │ │ └── test/ │ │ ├── MockServerRestClientCustomizerTests.java │ │ ├── MockServerRestTemplateCustomizerTests.java │ │ ├── RootUriRequestExpectationManagerTests.java │ │ └── autoconfigure/ │ │ ├── AnotherExampleRestClientService.java │ │ ├── AnotherExampleRestTemplateService.java │ │ ├── AutoConfigureMockRestServiceServerEnabledFalseIntegrationTests.java │ │ ├── AutoConfigureMockRestServiceServerWithRestClientIntegrationTests.java │ │ ├── AutoConfigureMockRestServiceServerWithRestTemplateRootUriIntegrationTests.java │ │ ├── ExampleProperties.java │ │ ├── ExampleRestClientService.java │ │ ├── ExampleRestTemplateService.java │ │ ├── ExampleWebClientApplication.java │ │ ├── MockRestServiceServerAutoConfigurationTests.java │ │ ├── RestClientTestNoComponentIntegrationTests.java │ │ ├── RestClientTestPropertiesIntegrationTests.java │ │ ├── RestClientTestRestClientIntegrationTests.java │ │ ├── RestClientTestRestClientTwoComponentsIntegrationTests.java │ │ ├── RestClientTestRestTemplateAndRestClientTogetherIntegrationTests.java │ │ ├── RestClientTestRestTemplateIntegrationTests.java │ │ ├── RestClientTestRestTemplateTwoComponentsIntegrationTests.java │ │ ├── RestClientTestWithConfigurationPropertiesIntegrationTests.java │ │ ├── RestClientTestWithRestClientComponentIntegrationTests.java │ │ ├── RestClientTestWithRestTemplateComponentIntegrationTests.java │ │ └── RestClientTestWithoutJacksonIntegrationTests.java │ ├── spring-boot-restdocs/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── restdocs/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureRestDocs.java │ │ │ │ ├── RestDocsAutoConfiguration.java │ │ │ │ ├── RestDocsMockMvcBuilderCustomizer.java │ │ │ │ ├── RestDocsMockMvcConfigurationCustomizer.java │ │ │ │ ├── RestDocsProperties.java │ │ │ │ ├── RestDocsTestExecutionListener.java │ │ │ │ ├── RestDocsWebTestClientBuilderCustomizer.java │ │ │ │ ├── RestDocsWebTestClientConfigurationCustomizer.java │ │ │ │ ├── RestDocumentationContextProviderRegistrar.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.restdocs.test.autoconfigure.AutoConfigureRestDocs.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── restdocs/ │ │ └── test/ │ │ └── autoconfigure/ │ │ ├── MockMvcRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java │ │ ├── MockMvcRestDocsAutoConfigurationIntegrationTests.java │ │ ├── RestDocsTestApplication.java │ │ ├── RestDocsTestController.java │ │ ├── WebTestClientRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java │ │ └── WebTestClientRestDocsAutoConfigurationIntegrationTests.java │ ├── spring-boot-resttestclient/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── resttestclient/ │ │ │ │ ├── TestRestTemplate.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── AutoConfigureRestTestClient.java │ │ │ │ │ ├── AutoConfigureTestRestTemplate.java │ │ │ │ │ ├── RestTestClientBuilderCustomizer.java │ │ │ │ │ ├── RestTestClientTestAutoConfiguration.java │ │ │ │ │ ├── SpringBootRestTestClientBuilderCustomizer.java │ │ │ │ │ ├── TestRestTemplateTestAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── kotlin/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── resttestclient/ │ │ │ │ └── TestRestTemplateExtensions.kt │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ ├── org.springframework.boot.resttestclient.autoconfigure.AutoConfigureRestTestClient.imports │ │ │ └── org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── resttestclient/ │ │ ├── TestRestTemplateTests.java │ │ └── autoconfigure/ │ │ ├── RestTestClientTestAutoConfigurationTests.java │ │ └── TestRestTemplateTestAutoConfigurationTests.java │ ├── spring-boot-rsocket/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── rsocket/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── RSocketMessageHandlerCustomizer.java │ │ │ │ │ ├── RSocketMessagingAutoConfiguration.java │ │ │ │ │ ├── RSocketProperties.java │ │ │ │ │ ├── RSocketRequesterAutoConfiguration.java │ │ │ │ │ ├── RSocketServerAutoConfiguration.java │ │ │ │ │ ├── RSocketStrategiesAutoConfiguration.java │ │ │ │ │ ├── RSocketWebSocketNettyRouteProvider.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── context/ │ │ │ │ │ ├── RSocketPortInfoApplicationContextInitializer.java │ │ │ │ │ ├── RSocketServerBootstrap.java │ │ │ │ │ ├── RSocketServerInitializedEvent.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── messaging/ │ │ │ │ │ ├── RSocketStrategiesCustomizer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── netty/ │ │ │ │ │ ├── NettyRSocketServer.java │ │ │ │ │ ├── NettyRSocketServerFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ └── server/ │ │ │ │ ├── ConfigurableRSocketServerFactory.java │ │ │ │ ├── RSocketServer.java │ │ │ │ ├── RSocketServerCustomizer.java │ │ │ │ ├── RSocketServerException.java │ │ │ │ ├── RSocketServerFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── rsocket/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── RSocketMessagingAutoConfigurationTests.java │ │ │ │ ├── RSocketPropertiesTests.java │ │ │ │ ├── RSocketRequesterAutoConfigurationTests.java │ │ │ │ ├── RSocketServerAutoConfigurationTests.java │ │ │ │ ├── RSocketStrategiesAutoConfigurationTests.java │ │ │ │ └── RSocketWebSocketNettyRouteProviderTests.java │ │ │ ├── context/ │ │ │ │ └── RSocketPortInfoApplicationContextInitializerTests.java │ │ │ └── netty/ │ │ │ └── NettyRSocketServerFactoryTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── rsocket/ │ │ ├── autoconfigure/ │ │ │ └── test.jks │ │ └── netty/ │ │ ├── test-cert.pem │ │ ├── test-key.pem │ │ └── test.jks │ ├── spring-boot-rsocket-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── test/ │ │ │ └── rsocket/ │ │ │ └── server/ │ │ │ ├── LocalRSocketServerPort.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── test/ │ │ └── rsocket/ │ │ └── server/ │ │ └── LocalRSocketServerPortTests.java │ ├── spring-boot-security/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── security/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured.java │ │ │ │ │ ├── ReactiveUserDetailsServiceAutoConfiguration.java │ │ │ │ │ ├── SecurityAutoConfiguration.java │ │ │ │ │ ├── SecurityProperties.java │ │ │ │ │ ├── UserDetailsServiceAutoConfiguration.java │ │ │ │ │ ├── actuate/ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ ├── reactive/ │ │ │ │ │ │ │ ├── EndpointRequest.java │ │ │ │ │ │ │ ├── ReactiveManagementWebSecurityAutoConfiguration.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── servlet/ │ │ │ │ │ │ ├── EndpointRequest.java │ │ │ │ │ │ ├── ManagementWebSecurityAutoConfiguration.java │ │ │ │ │ │ ├── PathPatternRequestMatcherProvider.java │ │ │ │ │ │ ├── RequestMatcherProvider.java │ │ │ │ │ │ ├── SecurityRequestMatchersManagementContextConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── rsocket/ │ │ │ │ │ │ ├── RSocketSecurityAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── StaticResourceLocation.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── reactive/ │ │ │ │ │ │ ├── PathRequest.java │ │ │ │ │ │ ├── ReactiveWebSecurityAutoConfiguration.java │ │ │ │ │ │ ├── StaticResourceRequest.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── servlet/ │ │ │ │ │ ├── ConditionalOnDefaultWebSecurity.java │ │ │ │ │ ├── DefaultWebSecurityCondition.java │ │ │ │ │ ├── PathRequest.java │ │ │ │ │ ├── SecurityFilterAutoConfiguration.java │ │ │ │ │ ├── SecurityFilterProperties.java │ │ │ │ │ ├── ServletWebSecurityAutoConfiguration.java │ │ │ │ │ ├── StaticResourceRequest.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── reactive/ │ │ │ │ │ ├── ApplicationContextServerWebExchangeMatcher.java │ │ │ │ │ └── package-info.java │ │ │ │ └── servlet/ │ │ │ │ ├── ApplicationContextRequestMatcher.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── security/ │ │ ├── autoconfigure/ │ │ │ ├── ReactiveUserDetailsServiceAutoConfigurationTests.java │ │ │ ├── SecurityAutoConfigurationTests.java │ │ │ ├── SecurityPropertiesTests.java │ │ │ ├── UserDetailsServiceAutoConfigurationTests.java │ │ │ ├── actuate/ │ │ │ │ └── web/ │ │ │ │ ├── reactive/ │ │ │ │ │ ├── EndpointRequestIntegrationTests.java │ │ │ │ │ ├── EndpointRequestTests.java │ │ │ │ │ └── ReactiveManagementWebSecurityAutoConfigurationTests.java │ │ │ │ └── servlet/ │ │ │ │ ├── AbstractEndpointRequestIntegrationTests.java │ │ │ │ ├── EndpointRequestTests.java │ │ │ │ ├── JerseyEndpointRequestIntegrationTests.java │ │ │ │ ├── ManagementWebSecurityAutoConfigurationTests.java │ │ │ │ ├── MvcEndpointRequestIntegrationTests.java │ │ │ │ └── SecurityRequestMatchersManagementContextConfigurationTests.java │ │ │ ├── jpa/ │ │ │ │ ├── City.java │ │ │ │ ├── JpaUserDetailsTests.java │ │ │ │ └── package-info.java │ │ │ ├── rsocket/ │ │ │ │ └── RSocketSecurityAutoConfigurationTests.java │ │ │ └── web/ │ │ │ ├── reactive/ │ │ │ │ ├── PathRequestTests.java │ │ │ │ ├── ReactiveWebSecurityAutoConfigurationTests.java │ │ │ │ └── StaticResourceRequestTests.java │ │ │ └── servlet/ │ │ │ ├── PathRequestTests.java │ │ │ ├── SecurityFilterAutoConfigurationEarlyInitializationTests.java │ │ │ ├── SecurityFilterAutoConfigurationTests.java │ │ │ ├── SecurityFilterPropertiesTests.java │ │ │ ├── ServletWebSecurityAutoConfigurationTests.java │ │ │ ├── StaticResourceRequestTests.java │ │ │ └── TestWebApplicationContext.java │ │ └── web/ │ │ ├── reactive/ │ │ │ └── ApplicationContextServerWebExchangeMatcherTests.java │ │ └── servlet/ │ │ └── ApplicationContextRequestMatcherTests.java │ ├── spring-boot-security-oauth2-authorization-server/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── security/ │ │ │ │ └── oauth2/ │ │ │ │ └── server/ │ │ │ │ └── authorization/ │ │ │ │ └── autoconfigure/ │ │ │ │ └── servlet/ │ │ │ │ ├── OAuth2AuthorizationServerAutoConfiguration.java │ │ │ │ ├── OAuth2AuthorizationServerConfiguration.java │ │ │ │ ├── OAuth2AuthorizationServerJwtAutoConfiguration.java │ │ │ │ ├── OAuth2AuthorizationServerProperties.java │ │ │ │ ├── OAuth2AuthorizationServerPropertiesMapper.java │ │ │ │ ├── OAuth2AuthorizationServerWebSecurityConfiguration.java │ │ │ │ ├── RegisteredClientsConfiguredCondition.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── security/ │ │ └── oauth2/ │ │ └── server/ │ │ └── authorization/ │ │ └── autoconfigure/ │ │ └── servlet/ │ │ ├── OAuth2AuthorizationServerAutoConfigurationTests.java │ │ ├── OAuth2AuthorizationServerJwtAutoConfigurationTests.java │ │ ├── OAuth2AuthorizationServerPropertiesMapperTests.java │ │ ├── OAuth2AuthorizationServerPropertiesTests.java │ │ └── OAuth2AuthorizationServerWebSecurityConfigurationTests.java │ ├── spring-boot-security-oauth2-client/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── security/ │ │ │ │ └── oauth2/ │ │ │ │ └── client/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── ClientsConfiguredCondition.java │ │ │ │ ├── ConditionalOnOAuth2ClientRegistrationProperties.java │ │ │ │ ├── OAuth2ClientAutoConfiguration.java │ │ │ │ ├── OAuth2ClientConfigurations.java │ │ │ │ ├── OAuth2ClientProperties.java │ │ │ │ ├── OAuth2ClientPropertiesMapper.java │ │ │ │ ├── package-info.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── ReactiveOAuth2ClientAutoConfiguration.java │ │ │ │ │ ├── ReactiveOAuth2ClientConfigurations.java │ │ │ │ │ ├── ReactiveOAuth2ClientWebSecurityAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ └── servlet/ │ │ │ │ ├── OAuth2ClientWebSecurityAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ ├── org.springframework.boot.webflux.test.autoconfigure.WebFluxTest.imports │ │ │ ├── org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest.imports │ │ │ └── org.springframework.boot.webtestclient.autoconfigure.AutoConfigureWebTestClient.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── security/ │ │ └── oauth2/ │ │ └── client/ │ │ └── autoconfigure/ │ │ ├── OAuth2ClientAutoConfigurationTests.java │ │ ├── OAuth2ClientPropertiesMapperTests.java │ │ ├── OAuth2ClientPropertiesTests.java │ │ ├── OAuth2ClientWebMvcTestIntegrationTests.java │ │ ├── reactive/ │ │ │ ├── ReactiveOAuth2ClientAutoConfigurationTests.java │ │ │ ├── ReactiveOAuth2ClientWebFluxTestIntegrationTests.java │ │ │ └── ReactiveOAuth2ClientWebSecurityAutoConfigurationTests.java │ │ └── servlet/ │ │ ├── OAuth2ClientWebSecurityAutoConfigurationTests.java │ │ └── webmvc/ │ │ ├── ExampleController.java │ │ ├── OAuth2ClientWebMvcApplication.java │ │ ├── OAuth2ClientWebMvcTestIntegrationTests.java │ │ └── package-info.java │ ├── spring-boot-security-oauth2-resource-server/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── security/ │ │ │ │ └── oauth2/ │ │ │ │ └── server/ │ │ │ │ └── resource/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── ConditionalOnIssuerLocationJwtDecoder.java │ │ │ │ ├── ConditionalOnPublicKeyJwtDecoder.java │ │ │ │ ├── IssuerUriCondition.java │ │ │ │ ├── JwkSetUriJwtDecoderBuilderCustomizer.java │ │ │ │ ├── JwtConverterConfiguration.java │ │ │ │ ├── JwtDecoderConfiguration.java │ │ │ │ ├── KeyValueCondition.java │ │ │ │ ├── OAuth2ResourceServerAutoConfiguration.java │ │ │ │ ├── OAuth2ResourceServerProperties.java │ │ │ │ ├── OpaqueTokenIntrospectionConfiguration.java │ │ │ │ ├── SpringOpaqueTokenIntrospectorBuilderCustomizer.java │ │ │ │ ├── package-info.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── JwkSetUriReactiveJwtDecoderBuilderCustomizer.java │ │ │ │ │ ├── ReactiveJwtConverterConfiguration.java │ │ │ │ │ ├── ReactiveJwtDecoderConfiguration.java │ │ │ │ │ ├── ReactiveOAuth2ResourceServerAutoConfiguration.java │ │ │ │ │ ├── ReactiveOpaqueTokenIntrospectionClientConfiguration.java │ │ │ │ │ ├── SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── servlet/ │ │ │ │ │ ├── JwkSetUriJwtDecoderBuilderCustomizer.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── OAuth2ResourceServerWebSecurityAutoConfiguration.java │ │ │ │ ├── package-info.java │ │ │ │ └── reactive/ │ │ │ │ ├── ReactiveOAuth2ResourceServerWebSecurityAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ ├── org.springframework.boot.webflux.test.autoconfigure.WebFluxTest.imports │ │ │ ├── org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest.imports │ │ │ └── org.springframework.boot.webtestclient.autoconfigure.AutoConfigureWebTestClient.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── security/ │ │ └── oauth2/ │ │ └── server/ │ │ └── resource/ │ │ └── autoconfigure/ │ │ ├── JwtConverterCustomizationsArgumentsProvider.java │ │ ├── OAuth2ResourceServerAutoConfigurationTests.java │ │ ├── reactive/ │ │ │ └── ReactiveOAuth2ResourceServerAutoConfigurationTests.java │ │ └── web/ │ │ ├── OAuth2ResourceServerWebSecurityAutoConfigurationMvcIntegrationTests.java │ │ ├── OAuth2ResourceServerWebSecurityAutoConfigurationTests.java │ │ └── reactive/ │ │ ├── ReactiveOAuth2ResourceServerWebSecurityAutoConfigurationTests.java │ │ └── ReactiveOAuth2ResourceServerWebSecurityAutoConfigurationWebFluxIntegrationTests.java │ ├── spring-boot-security-saml2/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── security/ │ │ │ │ └── saml2/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── RegistrationConfiguredCondition.java │ │ │ │ ├── Saml2LoginConfiguration.java │ │ │ │ ├── Saml2RelyingPartyAutoConfiguration.java │ │ │ │ ├── Saml2RelyingPartyProperties.java │ │ │ │ ├── Saml2RelyingPartyRegistrationConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── security/ │ │ │ └── saml2/ │ │ │ └── autoconfigure/ │ │ │ ├── Saml2RelyingPartyAutoConfigurationTests.java │ │ │ ├── Saml2RelyingPartyPropertiesTests.java │ │ │ └── webmvc/ │ │ │ ├── ExampleController.java │ │ │ ├── Saml2RelyingPartyWebMvcApplication.java │ │ │ ├── Saml2RelyingPartyWebMvcTestIntegrationTests.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── security/ │ │ └── saml2/ │ │ └── autoconfigure/ │ │ ├── certificate-location │ │ ├── idp-metadata │ │ ├── idp-metadata-with-multiple-providers │ │ ├── private-key-location │ │ ├── rsa.crt │ │ └── rsa.key │ ├── spring-boot-security-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── security/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── webflux/ │ │ │ │ │ ├── SecurityWebTestClientAutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ └── webmvc/ │ │ │ │ ├── SecurityMockMvcAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc.imports │ │ │ ├── org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest.includes │ │ │ └── org.springframework.boot.webtestclient.autoconfigure.AutoConfigureWebTestClient.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── security/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── webflux/ │ │ │ │ └── WebTestClientSecurityIntegrationTests.java │ │ │ └── webmvc/ │ │ │ ├── AfterSecurityFilter.java │ │ │ ├── AutoConfigureMockMvcSecurityFilterOrderingIntegrationTests.java │ │ │ ├── ExampleFilter.java │ │ │ ├── ExampleWebSecurityConfigurer.java │ │ │ ├── ExampleWebSecurityCustomizer.java │ │ │ ├── MockMvcSecurityIntegrationTests.java │ │ │ ├── SecurityTestApplication.java │ │ │ └── SecurityWebMvcTestIntegrationTests.java │ │ └── resources/ │ │ └── application.properties │ ├── spring-boot-sendgrid/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── sendgrid/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── SendGridAutoConfiguration.java │ │ │ │ ├── SendGridProperties.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── sendgrid/ │ │ └── autoconfigure/ │ │ └── SendGridAutoConfigurationTests.java │ ├── spring-boot-servlet/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── servlet/ │ │ │ │ ├── MultipartConfigFactory.java │ │ │ │ ├── actuate/ │ │ │ │ │ └── web/ │ │ │ │ │ ├── exchanges/ │ │ │ │ │ │ ├── HttpExchangesFilter.java │ │ │ │ │ │ ├── RecordableServletHttpRequest.java │ │ │ │ │ │ ├── RecordableServletHttpResponse.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── mappings/ │ │ │ │ │ ├── FilterRegistrationMappingDescription.java │ │ │ │ │ ├── FiltersMappingDescriptionProvider.java │ │ │ │ │ ├── RegistrationMappingDescription.java │ │ │ │ │ ├── ServletRegistrationMappingDescription.java │ │ │ │ │ ├── ServletsMappingDescriptionProvider.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── HttpEncodingAutoConfiguration.java │ │ │ │ │ ├── MultipartAutoConfiguration.java │ │ │ │ │ ├── MultipartProperties.java │ │ │ │ │ ├── ServletEncodingProperties.java │ │ │ │ │ ├── actuate/ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ ├── ManagementServletContext.java │ │ │ │ │ │ ├── ServletEndpointManagementContextConfiguration.java │ │ │ │ │ │ ├── ServletManagementChildContextConfiguration.java │ │ │ │ │ │ ├── ServletManagementContextAutoConfiguration.java │ │ │ │ │ │ ├── ServletManagementWebServerFactoryCustomizer.java │ │ │ │ │ │ ├── exchanges/ │ │ │ │ │ │ │ ├── ServletHttpExchangesAutoConfiguration.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── mappings/ │ │ │ │ │ │ │ ├── ServletMappingsAutoConfiguration.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── filter/ │ │ │ │ │ ├── ApplicationContextHeaderFilter.java │ │ │ │ │ ├── OrderedCharacterEncodingFilter.java │ │ │ │ │ ├── OrderedFilter.java │ │ │ │ │ ├── OrderedFormContentFilter.java │ │ │ │ │ ├── OrderedHiddenHttpMethodFilter.java │ │ │ │ │ ├── OrderedRequestContextFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ ├── org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── servlet/ │ │ ├── MultipartConfigFactoryTests.java │ │ ├── actuate/ │ │ │ └── web/ │ │ │ ├── exchanges/ │ │ │ │ ├── HttpExchangesFilterTests.java │ │ │ │ └── RecordableServletHttpRequestTests.java │ │ │ └── mappings/ │ │ │ ├── FiltersMappingDescriptionProviderTests.java │ │ │ └── ServletsMappingDescriptionProviderTests.java │ │ ├── autoconfigure/ │ │ │ ├── HttpEncodingAutoConfigurationTests.java │ │ │ ├── MultipartAutoConfigurationTests.java │ │ │ └── actuate/ │ │ │ └── web/ │ │ │ ├── ServletManagementChildContextConfigurationTests.java │ │ │ ├── ServletManagementContextAutoConfigurationIntegrationTests.java │ │ │ ├── exchanges/ │ │ │ │ └── ServletHttpExchangesAutoConfigurationTests.java │ │ │ └── mappings/ │ │ │ └── ServletMappingsAutoConfigurationTests.java │ │ └── filter/ │ │ └── OrderedFilterOrderingTests.java │ ├── spring-boot-session/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── session/ │ │ │ │ ├── actuate/ │ │ │ │ │ └── endpoint/ │ │ │ │ │ ├── ReactiveSessionsEndpoint.java │ │ │ │ │ ├── SessionsDescriptor.java │ │ │ │ │ ├── SessionsEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ └── autoconfigure/ │ │ │ │ ├── DefaultCookieSerializerCustomizer.java │ │ │ │ ├── SessionAutoConfiguration.java │ │ │ │ ├── SessionProperties.java │ │ │ │ ├── SessionRepositoryFilterConfiguration.java │ │ │ │ ├── SessionTimeout.java │ │ │ │ ├── SessionsEndpointAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ ├── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── session/ │ │ │ ├── actuate/ │ │ │ │ └── endpoint/ │ │ │ │ ├── ReactiveSessionsEndpointTests.java │ │ │ │ ├── ReactiveSessionsEndpointWebIntegrationTests.java │ │ │ │ ├── SessionsEndpointTests.java │ │ │ │ └── SessionsEndpointWebIntegrationTests.java │ │ │ └── autoconfigure/ │ │ │ ├── SessionAutoConfigurationEarlyInitializationIntegrationTests.java │ │ │ ├── SessionAutoConfigurationTests.java │ │ │ ├── SessionAutoConfigurationWithoutSecurityTests.java │ │ │ ├── SessionPropertiesTests.java │ │ │ └── SessionsEndpointAutoConfigurationTests.java │ │ └── testFixtures/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── session/ │ │ └── autoconfigure/ │ │ ├── AbstractSessionAutoConfigurationTests.java │ │ └── AbstractSessionReactiveAutoConfigurationTests.java │ ├── spring-boot-session-data-redis/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── session/ │ │ │ └── data/ │ │ │ └── redis/ │ │ │ └── autoconfigure/ │ │ │ ├── SessionDataRedisAutoConfigurationTests.java │ │ │ └── SessionDataRedisReactiveAutoConfigurationTests.java │ │ └── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── session/ │ │ │ └── data/ │ │ │ └── redis/ │ │ │ └── autoconfigure/ │ │ │ ├── SessionDataRedisAutoConfiguration.java │ │ │ ├── SessionDataRedisProperties.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── additional-spring-configuration-metadata.json │ │ └── spring/ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ ├── spring-boot-session-jdbc/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── session/ │ │ │ │ └── jdbc/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector.java │ │ │ │ ├── JdbcSessionAutoConfiguration.java │ │ │ │ ├── JdbcSessionDataSourceScriptDatabaseInitializer.java │ │ │ │ ├── JdbcSessionProperties.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── session/ │ │ │ └── jdbc/ │ │ │ └── autoconfigure/ │ │ │ ├── JdbcSessionAutoConfigurationTests.java │ │ │ └── JdbcSessionDataSourceScriptDatabaseInitializerTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── session/ │ │ └── jdbc/ │ │ └── autoconfigure/ │ │ └── custom-schema-h2.sql │ ├── spring-boot-sql/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── sql/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ └── init/ │ │ │ │ │ ├── ApplicationScriptDatabaseInitializer.java │ │ │ │ │ ├── ConditionalOnSqlInitialization.java │ │ │ │ │ ├── OnDatabaseInitializationCondition.java │ │ │ │ │ ├── OnSqlInitializationCondition.java │ │ │ │ │ ├── SettingsCreator.java │ │ │ │ │ ├── SqlInitializationProperties.java │ │ │ │ │ ├── SqlInitializationScriptsRuntimeHints.java │ │ │ │ │ └── package-info.java │ │ │ │ └── init/ │ │ │ │ ├── AbstractScriptDatabaseInitializer.java │ │ │ │ ├── DatabaseInitializationMode.java │ │ │ │ ├── DatabaseInitializationSettings.java │ │ │ │ ├── dependency/ │ │ │ │ │ ├── AbstractBeansOfTypeDatabaseInitializerDetector.java │ │ │ │ │ ├── AbstractBeansOfTypeDependsOnDatabaseInitializationDetector.java │ │ │ │ │ ├── AnnotationDependsOnDatabaseInitializationDetector.java │ │ │ │ │ ├── BeansOfTypeDetector.java │ │ │ │ │ ├── DatabaseInitializationDependencyConfigurer.java │ │ │ │ │ ├── DatabaseInitializerDetector.java │ │ │ │ │ ├── DependsOnDatabaseInitialization.java │ │ │ │ │ ├── DependsOnDatabaseInitializationDetector.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring.factories │ │ ├── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── sql/ │ │ │ ├── autoconfigure/ │ │ │ │ └── init/ │ │ │ │ ├── OnDatabaseInitializationConditionTests.java │ │ │ │ └── SqlInitializationScriptsRuntimeHintsTests.java │ │ │ └── init/ │ │ │ └── dependency/ │ │ │ └── DatabaseInitializationDependencyConfigurerTests.java │ │ └── testFixtures/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── sql/ │ │ └── init/ │ │ ├── AbstractScriptDatabaseInitializerTests.java │ │ └── ScriptDatabaseInitializerSettings.java │ ├── spring-boot-test-classic-modules/ │ │ └── build.gradle │ ├── spring-boot-thymeleaf/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── thymeleaf/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── TemplateEngineConfigurations.java │ │ │ │ ├── ThymeleafAutoConfiguration.java │ │ │ │ ├── ThymeleafProperties.java │ │ │ │ ├── ThymeleafTemplateAvailabilityProvider.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ ├── org.springframework.boot.webflux.test.autoconfigure.AutoConfigureWebFlux.imports │ │ │ │ ├── org.springframework.boot.webflux.test.autoconfigure.WebFluxTest.includes │ │ │ │ ├── org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureWebMvc.imports │ │ │ │ └── org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest.includes │ │ │ ├── spring-devtools.properties │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── thymeleaf/ │ │ └── autoconfigure/ │ │ ├── ThymeleafReactiveAutoConfigurationTests.java │ │ ├── ThymeleafServletAutoConfigurationTests.java │ │ ├── ThymeleafTemplateAvailabilityProviderTests.java │ │ ├── ThymeleafWebMvcTestIntegrationTests.java │ │ ├── webfluxtest/ │ │ │ ├── ExampleDialect.java │ │ │ ├── ThymeleafWebFluxTestApplication.java │ │ │ ├── ThymeleafWebFluxTestIntegrationTests.java │ │ │ └── package-info.java │ │ └── webmvctest/ │ │ ├── ExampleDialect.java │ │ ├── ThymeleafWebMvcTestApplication.java │ │ ├── ThymeleafWebMvcTestIntegrationTests.java │ │ └── package-info.java │ ├── spring-boot-tomcat/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── tomcat/ │ │ │ │ ├── CompressionConnectorCustomizer.java │ │ │ │ ├── ConfigurableTomcatWebServerFactory.java │ │ │ │ ├── ConnectorStartFailedException.java │ │ │ │ ├── ConnectorStartFailureAnalyzer.java │ │ │ │ ├── DisableReferenceClearingContextCustomizer.java │ │ │ │ ├── GracefulShutdown.java │ │ │ │ ├── LazySessionIdGenerator.java │ │ │ │ ├── SslConnectorCustomizer.java │ │ │ │ ├── TomcatConnectorCustomizer.java │ │ │ │ ├── TomcatContextCustomizer.java │ │ │ │ ├── TomcatEmbeddedContext.java │ │ │ │ ├── TomcatEmbeddedWebappClassLoader.java │ │ │ │ ├── TomcatProtocolHandlerCustomizer.java │ │ │ │ ├── TomcatWebServer.java │ │ │ │ ├── TomcatWebServerFactory.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── TomcatBackgroundPreinitializer.java │ │ │ │ │ ├── TomcatServerProperties.java │ │ │ │ │ ├── TomcatVirtualThreadsWebServerFactoryCustomizer.java │ │ │ │ │ ├── TomcatWebServerConfiguration.java │ │ │ │ │ ├── TomcatWebServerFactoryCustomizer.java │ │ │ │ │ ├── WebSocketTomcatWebServerFactoryCustomizer.java │ │ │ │ │ ├── actuate/ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ └── server/ │ │ │ │ │ │ ├── TomcatAccessLogCustomizer.java │ │ │ │ │ │ ├── TomcatManagementServerProperties.java │ │ │ │ │ │ ├── TomcatReactiveManagementChildContextConfiguration.java │ │ │ │ │ │ ├── TomcatReactiveManagementContextAutoConfiguration.java │ │ │ │ │ │ ├── TomcatServletManagementChildContextConfiguration.java │ │ │ │ │ │ ├── TomcatServletManagementContextAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── TomcatMetricsAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── reactive/ │ │ │ │ │ │ ├── TomcatReactiveWebServerAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── servlet/ │ │ │ │ │ ├── TomcatServletWebServerAutoConfiguration.java │ │ │ │ │ ├── TomcatServletWebServerFactoryCustomizer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── metrics/ │ │ │ │ │ ├── TomcatMetricsBinder.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── TomcatReactiveWebServerFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ └── servlet/ │ │ │ │ ├── DeferredServletContainerInitializers.java │ │ │ │ ├── NestedJarResourceSet.java │ │ │ │ ├── TldPatterns.java │ │ │ │ ├── TomcatServletWebServerFactory.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports │ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ └── org.springframework.boot.web.server.test.AutoConfigureWebServer.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── tomcat/ │ │ │ ├── CompressionConnectorCustomizerTests.java │ │ │ ├── SslConnectorCustomizerTests.java │ │ │ ├── TomcatAccess.java │ │ │ ├── TomcatEmbeddedWebappClassLoaderTests.java │ │ │ ├── autoconfigure/ │ │ │ │ ├── AutoConfigureWebServerTomcatReactiveTests.java │ │ │ │ ├── AutoConfigureWebServerTomcatServletTests.java │ │ │ │ ├── TomcatServerPropertiesTests.java │ │ │ │ ├── TomcatVirtualThreadsWebServerFactoryCustomizerTests.java │ │ │ │ ├── TomcatWebServerFactoryCustomizerTests.java │ │ │ │ ├── actuate/ │ │ │ │ │ └── web/ │ │ │ │ │ └── server/ │ │ │ │ │ └── TomcatManagementServerPropertiesTests.java │ │ │ │ ├── metrics/ │ │ │ │ │ └── TomcatMetricsAutoConfigurationTests.java │ │ │ │ ├── reactive/ │ │ │ │ │ └── TomcatReactiveWebServerAutoConfigurationTests.java │ │ │ │ └── servlet/ │ │ │ │ ├── TomcatServletWebServerAutoConfigurationTests.java │ │ │ │ └── TomcatServletWebServerFactoryCustomizerTests.java │ │ │ ├── metrics/ │ │ │ │ └── TomcatMetricsBinderTests.java │ │ │ ├── reactive/ │ │ │ │ └── TomcatReactiveWebServerFactoryTests.java │ │ │ └── servlet/ │ │ │ ├── TldPatternsTests.java │ │ │ ├── TomcatServletWebServerFactoryTests.java │ │ │ ├── TomcatServletWebServerMvcIntegrationTests.java │ │ │ └── TomcatServletWebServerServletContextListenerTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── tomcat/ │ │ ├── servlet/ │ │ │ ├── 1.crt │ │ │ ├── 1.key │ │ │ ├── 2.crt │ │ │ ├── 2.key │ │ │ └── test.jks │ │ └── test.jks │ ├── spring-boot-transaction/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── transaction/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ExecutionListenersTransactionManagerCustomizer.java │ │ │ │ │ ├── TransactionAutoConfiguration.java │ │ │ │ │ ├── TransactionManagerCustomizationAutoConfiguration.java │ │ │ │ │ ├── TransactionManagerCustomizer.java │ │ │ │ │ ├── TransactionManagerCustomizers.java │ │ │ │ │ ├── TransactionProperties.java │ │ │ │ │ └── package-info.java │ │ │ │ └── jta/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── JndiJtaConfiguration.java │ │ │ │ ├── JtaAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── transaction/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── ExecutionListenersTransactionManagerCustomizerTests.java │ │ │ │ ├── TransactionAutoConfigurationTests.java │ │ │ │ ├── TransactionManagerCustomizationAutoConfigurationTests.java │ │ │ │ └── TransactionManagerCustomizersTests.java │ │ │ └── jta/ │ │ │ └── autoconfigure/ │ │ │ └── JtaAutoConfigurationTests.java │ │ └── resources/ │ │ ├── jndi.properties │ │ └── simple-jndi │ ├── spring-boot-validation/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── validation/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── JakartaValidationBackgroundPreinitializer.java │ │ │ │ ├── PrimaryDefaultValidatorPostProcessor.java │ │ │ │ ├── ValidationAutoConfiguration.java │ │ │ │ ├── ValidationConfigurationCustomizer.java │ │ │ │ ├── ValidatorAdapter.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── validation/ │ │ └── autoconfigure/ │ │ ├── ValidationAutoConfigurationTests.java │ │ ├── ValidationAutoConfigurationWithHibernateValidatorMissingElImplTests.java │ │ ├── ValidationAutoConfigurationWithoutValidatorTests.java │ │ └── ValidatorAdapterTests.java │ ├── spring-boot-web-server/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── web/ │ │ │ │ └── server/ │ │ │ │ ├── AbstractConfigurableWebServerFactory.java │ │ │ │ ├── Compression.java │ │ │ │ ├── ConfigurableWebServerFactory.java │ │ │ │ ├── Cookie.java │ │ │ │ ├── GracefulShutdownCallback.java │ │ │ │ ├── GracefulShutdownResult.java │ │ │ │ ├── Http2.java │ │ │ │ ├── MimeMappings.java │ │ │ │ ├── PortInUseException.java │ │ │ │ ├── PortInUseFailureAnalyzer.java │ │ │ │ ├── Shutdown.java │ │ │ │ ├── Ssl.java │ │ │ │ ├── WebServer.java │ │ │ │ ├── WebServerException.java │ │ │ │ ├── WebServerFactory.java │ │ │ │ ├── WebServerFactoryCustomizer.java │ │ │ │ ├── WebServerFactoryCustomizerBeanPostProcessor.java │ │ │ │ ├── WebServerSslBundle.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ServerProperties.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── reactive/ │ │ │ │ │ │ ├── ReactiveWebServerConfiguration.java │ │ │ │ │ │ ├── ReactiveWebServerFactoryCustomizer.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── servlet/ │ │ │ │ │ ├── ForwardedHeaderFilterCustomizer.java │ │ │ │ │ ├── ServletWebServerConfiguration.java │ │ │ │ │ ├── ServletWebServerFactoryCustomizer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── context/ │ │ │ │ │ ├── ConfigurableWebServerApplicationContext.java │ │ │ │ │ ├── MissingWebServerFactoryBeanException.java │ │ │ │ │ ├── MissingWebServerFactoryBeanFailureAnalyzer.java │ │ │ │ │ ├── ServerPortInfoApplicationContextInitializer.java │ │ │ │ │ ├── SpringBootTestRandomPortContextCustomizer.java │ │ │ │ │ ├── SpringBootTestRandomPortContextCustomizerFactory.java │ │ │ │ │ ├── WebServerApplicationContext.java │ │ │ │ │ ├── WebServerGracefulShutdownLifecycle.java │ │ │ │ │ ├── WebServerInitializedEvent.java │ │ │ │ │ ├── WebServerPortFileWriter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── AbstractReactiveWebServerFactory.java │ │ │ │ │ ├── ConfigurableReactiveWebServerFactory.java │ │ │ │ │ ├── ReactiveWebServerFactory.java │ │ │ │ │ ├── context/ │ │ │ │ │ │ ├── AnnotationConfigReactiveWebServerApplicationContext.java │ │ │ │ │ │ ├── ApplicationReactiveWebEnvironment.java │ │ │ │ │ │ ├── ReactiveWebServerApplicationContext.java │ │ │ │ │ │ ├── ReactiveWebServerApplicationContextFactory.java │ │ │ │ │ │ ├── ReactiveWebServerApplicationContextLocalTestWebServerProvider.java │ │ │ │ │ │ ├── ReactiveWebServerInitializedEvent.java │ │ │ │ │ │ ├── WebServerManager.java │ │ │ │ │ │ ├── WebServerStartStopLifecycle.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ └── servlet/ │ │ │ │ ├── ConfigurableServletWebServerFactory.java │ │ │ │ ├── ContextPath.java │ │ │ │ ├── CookieSameSiteSupplier.java │ │ │ │ ├── DocumentRoot.java │ │ │ │ ├── Jsp.java │ │ │ │ ├── ServletContextInitializers.java │ │ │ │ ├── ServletWebServerFactory.java │ │ │ │ ├── ServletWebServerSettings.java │ │ │ │ ├── Session.java │ │ │ │ ├── SessionStoreDirectory.java │ │ │ │ ├── StaticResourceJars.java │ │ │ │ ├── WebListenerRegistrar.java │ │ │ │ ├── WebListenerRegistry.java │ │ │ │ ├── context/ │ │ │ │ │ ├── AnnotationConfigServletWebServerApplicationContext.java │ │ │ │ │ ├── ServletComponentHandler.java │ │ │ │ │ ├── ServletComponentRegisteringPostProcessor.java │ │ │ │ │ ├── ServletComponentScan.java │ │ │ │ │ ├── ServletComponentScanRegistrar.java │ │ │ │ │ ├── ServletWebServerApplicationContext.java │ │ │ │ │ ├── ServletWebServerApplicationContextFactory.java │ │ │ │ │ ├── ServletWebServerApplicationContextLocalTestWebServerProvider.java │ │ │ │ │ ├── ServletWebServerInitializedEvent.java │ │ │ │ │ ├── WebApplicationContextServletContextAwareProcessor.java │ │ │ │ │ ├── WebFilterHandler.java │ │ │ │ │ ├── WebListenerHandler.java │ │ │ │ │ ├── WebServerStartStopLifecycle.java │ │ │ │ │ ├── WebServletHandler.java │ │ │ │ │ ├── XmlServletWebServerApplicationContext.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ │ ├── spring/ │ │ │ │ │ └── aot.factories │ │ │ │ ├── spring-devtools.properties │ │ │ │ └── spring.factories │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── web/ │ │ │ └── server/ │ │ │ └── mime-mappings.properties │ │ ├── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── web/ │ │ │ │ └── server/ │ │ │ │ ├── AbstractConfigurableWebServerFactoryTests.java │ │ │ │ ├── CompressionTests.java │ │ │ │ ├── MimeMappingsTests.java │ │ │ │ ├── SpringApplicationWebServerTests.java │ │ │ │ ├── WebServerFactoryCustomizerBeanPostProcessorTests.java │ │ │ │ ├── WebServerSslBundleTests.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── ServerPropertiesTests.java │ │ │ │ │ ├── reactive/ │ │ │ │ │ │ └── ReactiveWebServerFactoryCustomizerTests.java │ │ │ │ │ └── servlet/ │ │ │ │ │ └── ServletWebServerFactoryCustomizerTests.java │ │ │ │ ├── context/ │ │ │ │ │ ├── MissingWebServerFactoryBeanFailureAnalyzerTests.java │ │ │ │ │ ├── SpringBootTestRandomPortContextCustomizerTests.java │ │ │ │ │ ├── WebServerApplicationContextTests.java │ │ │ │ │ └── WebServerPortFileWriterTests.java │ │ │ │ ├── reactive/ │ │ │ │ │ └── context/ │ │ │ │ │ ├── AnnotationConfigReactiveWebServerApplicationContextTests.java │ │ │ │ │ ├── ApplicationReactiveWebEnvironmentTests.java │ │ │ │ │ ├── ReactiveWebServerApplicationContextTests.java │ │ │ │ │ └── config/ │ │ │ │ │ ├── ExampleReactiveWebServerApplicationConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ └── servlet/ │ │ │ │ ├── CookieSameSiteSupplierTests.java │ │ │ │ ├── DocumentRootTests.java │ │ │ │ ├── StaticResourceJarsTests.java │ │ │ │ └── context/ │ │ │ │ ├── AnnotationConfigServletWebServerApplicationContextTests.java │ │ │ │ ├── ApplicationServletEnvironmentTests.java │ │ │ │ ├── MockWebEnvironmentServletComponentScanIntegrationTests.java │ │ │ │ ├── ServletComponentScanIntegrationTests.java │ │ │ │ ├── ServletComponentScanRegistrarTests.java │ │ │ │ ├── ServletWebServerApplicationContextTests.java │ │ │ │ ├── WebFilterHandlerTests.java │ │ │ │ ├── WebListenerHandlerTests.java │ │ │ │ ├── WebServletHandlerTests.java │ │ │ │ ├── XmlServletWebServerApplicationContextTests.java │ │ │ │ ├── config/ │ │ │ │ │ ├── ExampleServletWebServerApplicationConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ └── testcomponents/ │ │ │ │ ├── filter/ │ │ │ │ │ ├── TestFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── listener/ │ │ │ │ │ ├── TestListener.java │ │ │ │ │ └── package-info.java │ │ │ │ └── servlet/ │ │ │ │ ├── TestMultipartServlet.java │ │ │ │ ├── TestServlet.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── web/ │ │ │ └── server/ │ │ │ ├── pkcs1/ │ │ │ │ ├── dsa.key │ │ │ │ ├── rsa-aes-256-cbc.key │ │ │ │ └── rsa.key │ │ │ ├── pkcs8/ │ │ │ │ ├── brainpoolP256r1.key │ │ │ │ ├── brainpoolP256t1.key │ │ │ │ ├── brainpoolP320r1.key │ │ │ │ ├── brainpoolP320t1.key │ │ │ │ ├── brainpoolP384r1.key │ │ │ │ ├── brainpoolP384t1.key │ │ │ │ ├── brainpoolP512r1.key │ │ │ │ ├── brainpoolP512t1.key │ │ │ │ ├── dsa-aes-128-cbc.key │ │ │ │ ├── dsa.key │ │ │ │ ├── ed25519-aes-256-cbc.key │ │ │ │ ├── ed25519.key │ │ │ │ ├── ed448.key │ │ │ │ ├── prime256v1-aes-256-cbc.key │ │ │ │ ├── prime256v1.key │ │ │ │ ├── rsa-aes-256-cbc.key │ │ │ │ ├── rsa-des-ede3-cbc.key │ │ │ │ ├── rsa-pss.key │ │ │ │ ├── rsa-scrypt.key │ │ │ │ ├── rsa.key │ │ │ │ ├── secp224r1.key │ │ │ │ ├── secp256k1.key │ │ │ │ ├── secp256r1.key │ │ │ │ ├── secp384r1.key │ │ │ │ ├── secp521r1.key │ │ │ │ ├── x25519.key │ │ │ │ ├── x448-aes-256-cbc.key │ │ │ │ └── x448.key │ │ │ ├── sec1/ │ │ │ │ ├── brainpoolP256r1.key │ │ │ │ ├── brainpoolP256t1.key │ │ │ │ ├── brainpoolP320r1.key │ │ │ │ ├── brainpoolP320t1.key │ │ │ │ ├── brainpoolP384r1.key │ │ │ │ ├── brainpoolP384t1.key │ │ │ │ ├── brainpoolP512r1.key │ │ │ │ ├── brainpoolP512t1.key │ │ │ │ ├── prime256v1-aes-128-cbc.key │ │ │ │ ├── prime256v1.key │ │ │ │ ├── secp224r1.key │ │ │ │ ├── secp256k1.key │ │ │ │ ├── secp256r1.key │ │ │ │ ├── secp384r1.key │ │ │ │ └── secp521r1.key │ │ │ ├── servlet/ │ │ │ │ └── context/ │ │ │ │ └── exampleEmbeddedWebApplicationConfiguration.xml │ │ │ ├── test-cert-chain.pem │ │ │ ├── test-cert.pem │ │ │ ├── test-key.pem │ │ │ ├── test.jks │ │ │ └── test.p12 │ │ └── testFixtures/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── web/ │ │ │ ├── server/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── reactive/ │ │ │ │ │ │ └── AbstractReactiveWebServerAutoConfigurationTests.java │ │ │ │ │ └── servlet/ │ │ │ │ │ └── AbstractServletWebServerAutoConfigurationTests.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── AbstractReactiveWebServerFactoryTests.java │ │ │ │ │ ├── MockReactiveWebServer.java │ │ │ │ │ └── MockReactiveWebServerFactory.java │ │ │ │ └── servlet/ │ │ │ │ ├── AbstractServletWebServerFactoryTests.java │ │ │ │ ├── AbstractServletWebServerServletContextListenerTests.java │ │ │ │ ├── MockServletWebServer.java │ │ │ │ └── MockServletWebServerFactory.java │ │ │ └── servlet/ │ │ │ └── context/ │ │ │ └── AbstractServletWebServerMvcIntegrationTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── web/ │ │ └── server/ │ │ ├── reactive/ │ │ │ ├── test-cert.pem │ │ │ ├── test-key.pem │ │ │ ├── test.jks │ │ │ └── test.p12 │ │ └── servlet/ │ │ ├── test-cert.pem │ │ ├── test-key.pem │ │ ├── test.jks │ │ └── test.p12 │ ├── spring-boot-web-server-test/ │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── web/ │ │ └── server/ │ │ └── test/ │ │ ├── AutoConfigureWebServer.java │ │ └── package-info.java │ ├── spring-boot-webclient/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── webclient/ │ │ │ │ ├── WebClientCustomizer.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── AutoConfiguredWebClientSsl.java │ │ │ │ │ ├── WebClientAutoConfiguration.java │ │ │ │ │ ├── WebClientCodecCustomizer.java │ │ │ │ │ ├── WebClientObservationAutoConfiguration.java │ │ │ │ │ ├── WebClientSsl.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── service/ │ │ │ │ │ ├── PropertiesWebClientHttpServiceGroupConfigurer.java │ │ │ │ │ ├── ReactiveHttpServiceClientAutoConfiguration.java │ │ │ │ │ ├── WebClientCustomizerHttpServiceGroupConfigurer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── observation/ │ │ │ │ │ ├── ObservationWebClientCustomizer.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── webclient/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── WebClientAutoConfigurationTests.java │ │ │ │ ├── WebClientObservationAutoConfigurationTests.java │ │ │ │ └── service/ │ │ │ │ └── ReactiveHttpServiceClientAutoConfigurationTests.java │ │ │ └── observation/ │ │ │ └── ObservationWebClientCustomizerTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── http/ │ │ └── client/ │ │ └── reactive/ │ │ ├── test.jks │ │ └── web/ │ │ └── autoconfigure/ │ │ └── test.jks │ ├── spring-boot-webclient-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── webclient/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureWebClient.java │ │ │ │ ├── WebClientTest.java │ │ │ │ ├── WebClientTestContextBootstrapper.java │ │ │ │ ├── WebClientTypeExcludeFilter.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.webclient.test.autoconfigure.AutoConfigureWebClient.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── webclient/ │ │ └── test/ │ │ └── autoconfigure/ │ │ ├── AnotherExampleWebClientService.java │ │ ├── ExampleProperties.java │ │ ├── ExampleWebClientApplication.java │ │ ├── ExampleWebClientService.java │ │ ├── MockWebServerConfiguration.java │ │ ├── WebClientTestIntegrationTests.java │ │ ├── WebClientTestNoComponentIntegrationTests.java │ │ ├── WebClientTestPropertiesIntegrationTests.java │ │ ├── WebClientTestWithConfigurationPropertiesIntegrationTests.java │ │ └── WebClientTestWithoutJacksonIntegrationTests.java │ ├── spring-boot-webflux/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── webflux/ │ │ │ │ ├── WebFluxWebApplicationTypeDeducer.java │ │ │ │ ├── actuate/ │ │ │ │ │ ├── endpoint/ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ ├── AbstractWebFluxEndpointHandlerMapping.java │ │ │ │ │ │ ├── AdditionalHealthEndpointPathsWebFluxHandlerMapping.java │ │ │ │ │ │ ├── ControllerEndpointHandlerMapping.java │ │ │ │ │ │ ├── WebFluxEndpointHandlerMapping.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── exchanges/ │ │ │ │ │ │ ├── HttpExchangesWebFilter.java │ │ │ │ │ │ ├── RecordableServerHttpRequest.java │ │ │ │ │ │ ├── RecordableServerHttpResponse.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── mappings/ │ │ │ │ │ ├── DispatcherHandlerMappingDescription.java │ │ │ │ │ ├── DispatcherHandlerMappingDetails.java │ │ │ │ │ ├── DispatcherHandlersMappingDescriptionProvider.java │ │ │ │ │ ├── HandlerFunctionDescription.java │ │ │ │ │ ├── RequestMappingConditionsDescription.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── HttpHandlerAutoConfiguration.java │ │ │ │ │ ├── ProblemDetailsExceptionHandler.java │ │ │ │ │ ├── ReactiveMultipartAutoConfiguration.java │ │ │ │ │ ├── ReactiveMultipartProperties.java │ │ │ │ │ ├── ResourceChainResourceHandlerRegistrationCustomizer.java │ │ │ │ │ ├── ResourceHandlerRegistrationCustomizer.java │ │ │ │ │ ├── WebFluxAutoConfiguration.java │ │ │ │ │ ├── WebFluxObservationAutoConfiguration.java │ │ │ │ │ ├── WebFluxProperties.java │ │ │ │ │ ├── WebFluxRegistrations.java │ │ │ │ │ ├── WebHttpHandlerBuilderCustomizer.java │ │ │ │ │ ├── WebSessionIdResolverAutoConfiguration.java │ │ │ │ │ ├── WelcomePageRouterFunctionFactory.java │ │ │ │ │ ├── actuate/ │ │ │ │ │ │ ├── endpoint/ │ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ │ ├── WebFluxHealthEndpointExtensionAutoConfiguration.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── web/ │ │ │ │ │ │ ├── WebFluxEndpointManagementContextConfiguration.java │ │ │ │ │ │ ├── WebFluxManagementChildContextConfiguration.java │ │ │ │ │ │ ├── exchanges/ │ │ │ │ │ │ │ ├── WebFluxHttpExchangesAutoConfiguration.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── mappings/ │ │ │ │ │ │ │ ├── WebFluxMappingsAutoConfiguration.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── error/ │ │ │ │ │ │ ├── AbstractErrorWebExceptionHandler.java │ │ │ │ │ │ ├── DefaultErrorWebExceptionHandler.java │ │ │ │ │ │ ├── ErrorWebFluxAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── error/ │ │ │ │ │ ├── DefaultErrorAttributes.java │ │ │ │ │ ├── ErrorAttributes.java │ │ │ │ │ ├── ErrorWebExceptionHandler.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── filter/ │ │ │ │ │ ├── OrderedHiddenHttpMethodFilter.java │ │ │ │ │ ├── OrderedWebFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── aot.factories │ │ │ │ ├── org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ ├── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── webflux/ │ │ │ ├── actuate/ │ │ │ │ ├── endpoint/ │ │ │ │ │ └── web/ │ │ │ │ │ ├── AbstractWebFluxEndpointHandlerMappingTests.java │ │ │ │ │ ├── ControllerEndpointHandlerMappingTests.java │ │ │ │ │ └── WebFluxEndpointHandlerMappingTests.java │ │ │ │ └── web/ │ │ │ │ ├── exchanges/ │ │ │ │ │ ├── HttpExchangesWebFilterIntegrationTests.java │ │ │ │ │ ├── HttpExchangesWebFilterTests.java │ │ │ │ │ └── RecordableServerHttpRequestTests.java │ │ │ │ └── mappings/ │ │ │ │ └── DispatcherHandlersMappingDescriptionProviderTests.java │ │ │ ├── autoconfigure/ │ │ │ │ ├── HttpHandlerAutoConfigurationTests.java │ │ │ │ ├── ReactiveMultipartAutoConfigurationTests.java │ │ │ │ ├── ReactiveMultipartPropertiesTests.java │ │ │ │ ├── WebFluxAutoConfigurationTests.java │ │ │ │ ├── WebFluxObservationAutoConfigurationTests.java │ │ │ │ ├── WebFluxPropertiesTests.java │ │ │ │ ├── WelcomePageRouterFunctionFactoryTests.java │ │ │ │ ├── actuate/ │ │ │ │ │ ├── endpoint/ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ ├── WebFluxHealthEndpointAdditionalPathIntegrationTests.java │ │ │ │ │ │ └── WebFluxHealthEndpointExtensionAutoConfigurationTests.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── ControllerEndpointWebFluxIntegrationTests.java │ │ │ │ │ ├── WebFluxEndpointAccessIntegrationTests.java │ │ │ │ │ ├── WebFluxEndpointCorsIntegrationTests.java │ │ │ │ │ ├── WebFluxEndpointIntegrationTests.java │ │ │ │ │ ├── WebFluxManagementChildContextConfigurationIntegrationTests.java │ │ │ │ │ ├── WebFluxManagementChildContextConfigurationTests.java │ │ │ │ │ ├── exchanges/ │ │ │ │ │ │ └── WebFluxHttpExchangesAutoConfigurationTests.java │ │ │ │ │ └── mappings/ │ │ │ │ │ └── WebFluxMappingsAutoConfigurationTests.java │ │ │ │ └── error/ │ │ │ │ ├── DefaultErrorWebExceptionHandlerIntegrationTests.java │ │ │ │ ├── DefaultErrorWebExceptionHandlerTests.java │ │ │ │ └── DummyBody.java │ │ │ └── error/ │ │ │ └── DefaultErrorAttributesTests.java │ │ └── testFixtures/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── webflux/ │ │ │ └── actuate/ │ │ │ └── endpoint/ │ │ │ └── web/ │ │ │ └── test/ │ │ │ ├── WebFluxEndpointConfiguration.java │ │ │ └── WebFluxWebEndpointInfrastructureProvider.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring.factories │ ├── spring-boot-webflux-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── webflux/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureWebFlux.java │ │ │ │ ├── WebFluxTest.java │ │ │ │ ├── WebFluxTestContextBootstrapper.java │ │ │ │ ├── WebFluxTypeExcludeFilter.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.webflux.test.autoconfigure.AutoConfigureWebFlux.imports │ │ │ └── spring-configuration-metadata.json │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── webflux/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── ExampleController1.java │ │ │ ├── ExampleController2.java │ │ │ ├── ExampleId.java │ │ │ ├── ExampleIdConverter.java │ │ │ ├── ExamplePojo.java │ │ │ ├── ExampleRealService.java │ │ │ ├── ExampleResult.java │ │ │ ├── ExampleResult2.java │ │ │ ├── ExampleResult2Serializer.java │ │ │ ├── ExampleResultSerializer.java │ │ │ ├── ExampleWebExceptionHandler.java │ │ │ ├── ExampleWebFluxApplication.java │ │ │ ├── JsonController.java │ │ │ ├── WebFluxTestAllControllersIntegrationTests.java │ │ │ ├── WebFluxTestAutoConfigurationIntegrationTests.java │ │ │ ├── WebFluxTestConverterIntegrationTests.java │ │ │ ├── WebFluxTestJacksonComponentIntegrationTests.java │ │ │ ├── WebFluxTestJsonComponentIntegrationTests.java │ │ │ ├── WebFluxTestMessageSourceIntegrationTests.java │ │ │ ├── WebFluxTestOneControllerIntegrationTests.java │ │ │ ├── WebFluxTestPropertiesIntegrationTests.java │ │ │ ├── WebFluxTestWebTestClientCodecCustomizationIntegrationTests.java │ │ │ └── WebFluxTypeExcludeFilterTests.java │ │ └── resources/ │ │ └── web-test-messages.properties │ ├── spring-boot-webmvc/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── webmvc/ │ │ │ │ ├── WebMvcWebApplicationTypeDeducer.java │ │ │ │ ├── actuate/ │ │ │ │ │ ├── endpoint/ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ ├── AbstractWebMvcEndpointHandlerMapping.java │ │ │ │ │ │ ├── AdditionalHealthEndpointPathsWebMvcHandlerMapping.java │ │ │ │ │ │ ├── ControllerEndpointHandlerMapping.java │ │ │ │ │ │ ├── WebMvcEndpointHandlerMapping.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── web/ │ │ │ │ │ └── mappings/ │ │ │ │ │ ├── DispatcherServletHandlerMappings.java │ │ │ │ │ ├── DispatcherServletMappingDescription.java │ │ │ │ │ ├── DispatcherServletMappingDetails.java │ │ │ │ │ ├── DispatcherServletsMappingDescriptionProvider.java │ │ │ │ │ ├── HandlerFunctionDescription.java │ │ │ │ │ ├── RequestMappingConditionsDescription.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── DispatcherServletAutoConfiguration.java │ │ │ │ │ ├── DispatcherServletPath.java │ │ │ │ │ ├── DispatcherServletRegistrationBean.java │ │ │ │ │ ├── JspTemplateAvailabilityProvider.java │ │ │ │ │ ├── ProblemDetailsExceptionHandler.java │ │ │ │ │ ├── WebMvcAutoConfiguration.java │ │ │ │ │ ├── WebMvcObservationAutoConfiguration.java │ │ │ │ │ ├── WebMvcProperties.java │ │ │ │ │ ├── WebMvcRegistrations.java │ │ │ │ │ ├── WelcomePage.java │ │ │ │ │ ├── WelcomePageHandlerMapping.java │ │ │ │ │ ├── WelcomePageNotAcceptableHandlerMapping.java │ │ │ │ │ ├── actuate/ │ │ │ │ │ │ ├── endpoint/ │ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ │ ├── WebMvcHealthEndpointExtensionAutoConfiguration.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── web/ │ │ │ │ │ │ ├── CompositeHandlerAdapter.java │ │ │ │ │ │ ├── CompositeHandlerExceptionResolver.java │ │ │ │ │ │ ├── CompositeHandlerMapping.java │ │ │ │ │ │ ├── ManagementErrorEndpoint.java │ │ │ │ │ │ ├── WebMvcEndpointChildContextConfiguration.java │ │ │ │ │ │ ├── WebMvcEndpointManagementContextConfiguration.java │ │ │ │ │ │ ├── mappings/ │ │ │ │ │ │ │ ├── WebMvcMappingsAutoConfiguration.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── error/ │ │ │ │ │ │ ├── AbstractErrorController.java │ │ │ │ │ │ ├── BasicErrorController.java │ │ │ │ │ │ ├── DefaultErrorViewResolver.java │ │ │ │ │ │ ├── ErrorMvcAutoConfiguration.java │ │ │ │ │ │ ├── ErrorViewResolver.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── error/ │ │ │ │ │ ├── DefaultErrorAttributes.java │ │ │ │ │ ├── ErrorAttributes.java │ │ │ │ │ ├── ErrorController.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── aot.factories │ │ │ │ ├── org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports │ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ └── org.springframework.boot.web.server.test.AutoConfigureWebServer.imports │ │ │ ├── spring-devtools.properties │ │ │ └── spring.factories │ │ ├── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── webmvc/ │ │ │ │ ├── actuate/ │ │ │ │ │ ├── endpoint/ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ ├── AbstractWebMvcEndpointHandlerMappingTests.java │ │ │ │ │ │ ├── ControllerEndpointHandlerMappingTests.java │ │ │ │ │ │ └── WebMvcEndpointHandlerMappingTests.java │ │ │ │ │ └── web/ │ │ │ │ │ └── mappings/ │ │ │ │ │ └── DispatcherServletsMappingDescriptionProviderTests.java │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── DispatcherServletAutoConfigurationTests.java │ │ │ │ │ ├── DispatcherServletPathTests.java │ │ │ │ │ ├── DispatcherServletRegistrationBeanTests.java │ │ │ │ │ ├── JspTemplateAvailabilityProviderTests.java │ │ │ │ │ ├── WebMvcAutoConfigurationTests.java │ │ │ │ │ ├── WebMvcObservationAutoConfigurationTests.java │ │ │ │ │ ├── WebMvcPropertiesTests.java │ │ │ │ │ ├── WelcomePageHandlerMappingTests.java │ │ │ │ │ ├── WelcomePageIntegrationTests.java │ │ │ │ │ ├── WelcomePageNotAcceptableHandlerMappingTests.java │ │ │ │ │ ├── actuate/ │ │ │ │ │ │ ├── endpoint/ │ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ │ ├── WebMvcHealthEndpointAdditionalPathIntegrationTests.java │ │ │ │ │ │ │ └── WebMvcHealthEndpointExtensionAutoConfigurationTests.java │ │ │ │ │ │ └── web/ │ │ │ │ │ │ ├── CompositeHandlerExceptionResolverTests.java │ │ │ │ │ │ ├── ControllerEndpointWebMvcIntegrationTests.java │ │ │ │ │ │ ├── ManagementErrorEndpointTests.java │ │ │ │ │ │ ├── WebMvcEndpointAccessIntegrationTests.java │ │ │ │ │ │ ├── WebMvcEndpointChildContextConfigurationIntegrationTests.java │ │ │ │ │ │ ├── WebMvcEndpointChildContextConfigurationTests.java │ │ │ │ │ │ ├── WebMvcEndpointCorsIntegrationTests.java │ │ │ │ │ │ ├── WebMvcEndpointExposureIntegrationTests.java │ │ │ │ │ │ ├── WebMvcEndpointIntegrationTests.java │ │ │ │ │ │ ├── WebMvcEndpointManagementContextConfigurationTests.java │ │ │ │ │ │ └── mappings/ │ │ │ │ │ │ └── WebMvcMappingsAutoConfigurationTests.java │ │ │ │ │ └── error/ │ │ │ │ │ ├── BasicErrorControllerDirectMockMvcTests.java │ │ │ │ │ ├── BasicErrorControllerIntegrationTests.java │ │ │ │ │ ├── BasicErrorControllerMockMvcTests.java │ │ │ │ │ ├── DefaultErrorViewIntegrationTests.java │ │ │ │ │ ├── DefaultErrorViewResolverTests.java │ │ │ │ │ ├── ErrorMvcAutoConfigurationTests.java │ │ │ │ │ └── RemappedErrorViewIntegrationTests.java │ │ │ │ └── error/ │ │ │ │ └── DefaultErrorAttributesTests.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── webmvc/ │ │ │ └── autoconfigure/ │ │ │ ├── error/ │ │ │ │ ├── 4xx/ │ │ │ │ │ └── error/ │ │ │ │ │ ├── 402.html │ │ │ │ │ └── 4xx.html │ │ │ │ ├── 5xx/ │ │ │ │ │ └── error/ │ │ │ │ │ ├── 4xx.html │ │ │ │ │ └── 5xx.html │ │ │ │ └── exact/ │ │ │ │ └── error/ │ │ │ │ ├── 404.html │ │ │ │ └── 4xx.html │ │ │ ├── index.html │ │ │ └── static/ │ │ │ └── custom.css │ │ └── testFixtures/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── webmvc/ │ │ │ └── actuate/ │ │ │ └── endpoint/ │ │ │ └── web/ │ │ │ └── test/ │ │ │ ├── WebMvcEndpointConfiguration.java │ │ │ └── WebMvcWebEndpointInfrastructureProvider.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring.factories │ ├── spring-boot-webmvc-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── webmvc/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureMockMvc.java │ │ │ │ ├── AutoConfigureWebMvc.java │ │ │ │ ├── MockMvcAutoConfiguration.java │ │ │ │ ├── MockMvcBuilderCustomizer.java │ │ │ │ ├── MockMvcConfiguration.java │ │ │ │ ├── MockMvcHtmlUnitDriverCustomizer.java │ │ │ │ ├── MockMvcPrint.java │ │ │ │ ├── MockMvcPrintOnlyOnFailureTestExecutionListener.java │ │ │ │ ├── MockMvcTesterConfiguration.java │ │ │ │ ├── MockMvcWebClientAutoConfiguration.java │ │ │ │ ├── MockMvcWebDriverAutoConfiguration.java │ │ │ │ ├── SpringBootMockMvcBuilderCustomizer.java │ │ │ │ ├── WebDriverContextCustomizer.java │ │ │ │ ├── WebDriverContextCustomizerFactory.java │ │ │ │ ├── WebDriverScope.java │ │ │ │ ├── WebDriverTestExecutionListener.java │ │ │ │ ├── WebMvcTest.java │ │ │ │ ├── WebMvcTestContextBootstrapper.java │ │ │ │ ├── WebMvcTypeExcludeFilter.java │ │ │ │ └── package-info.java │ │ │ ├── resources/ │ │ │ │ └── META-INF/ │ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ │ ├── spring/ │ │ │ │ │ ├── org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc.imports │ │ │ │ │ └── org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureWebMvc.imports │ │ │ │ └── spring.factories │ │ │ └── webapp/ │ │ │ └── inwebapp │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── webmvc/ │ │ │ └── test/ │ │ │ └── autoconfigure/ │ │ │ ├── ExampleWebMvcApplication.java │ │ │ ├── MockMvcAutoConfigurationTests.java │ │ │ ├── SpringBootMockMvcBuilderCustomizerTests.java │ │ │ ├── WebMvcTestAutoConfigurationIntegrationTests.java │ │ │ ├── WebMvcTestPropertiesIntegrationTests.java │ │ │ ├── WebMvcTypeExcludeFilterTests.java │ │ │ └── mockmvc/ │ │ │ ├── ExampleArgument.java │ │ │ ├── ExampleController1.java │ │ │ ├── ExampleController2.java │ │ │ ├── ExampleController3.java │ │ │ ├── ExampleControllerAdvice.java │ │ │ ├── ExampleException.java │ │ │ ├── ExampleFilter.java │ │ │ ├── ExampleId.java │ │ │ ├── ExampleIdConverter.java │ │ │ ├── ExampleMockableService.java │ │ │ ├── ExampleRealService.java │ │ │ ├── ExampleResult.java │ │ │ ├── ExampleResult2.java │ │ │ ├── ExampleResult2Serializer.java │ │ │ ├── ExampleResultSerializer.java │ │ │ ├── ExampleWebMvcConfigurer.java │ │ │ ├── MockMvcSpringBootTestIntegrationTests.java │ │ │ ├── MockMvcTesterSpringBootTestIntegrationTests.java │ │ │ ├── WebMvcTestAllControllersIntegrationTests.java │ │ │ ├── WebMvcTestConverterIntegrationTests.java │ │ │ ├── WebMvcTestCustomDispatcherServletIntegrationTests.java │ │ │ ├── WebMvcTestHtmlUnitWebClientIntegrationTests.java │ │ │ ├── WebMvcTestHtmlUnitWebDriverCustomScopeIntegrationTests.java │ │ │ ├── WebMvcTestHtmlUnitWebDriverIntegrationTests.java │ │ │ ├── WebMvcTestJacksonComponentIntegrationTests.java │ │ │ ├── WebMvcTestJsonComponentIntegrationTests.java │ │ │ ├── WebMvcTestMessageSourceIntegrationTests.java │ │ │ ├── WebMvcTestNestedIntegrationTests.java │ │ │ ├── WebMvcTestOneControllerIntegrationTests.java │ │ │ ├── WebMvcTestPrintAlwaysIntegrationTests.java │ │ │ ├── WebMvcTestPrintDefaultIntegrationTests.java │ │ │ ├── WebMvcTestPrintDefaultOverrideIntegrationTests.java │ │ │ ├── WebMvcTestPrintOverrideIntegrationTests.java │ │ │ ├── WebMvcTestServletContextResourceTests.java │ │ │ ├── WebMvcTestServletFilterIntegrationTests.java │ │ │ ├── WebMvcTestServletFilterRegistrationDisabledIntegrationTests.java │ │ │ ├── WebMvcTestServletFilterRegistrationIntegrationTests.java │ │ │ ├── WebMvcTestWithAutoConfigureMockMvcIntegrationTests.java │ │ │ ├── WebMvcTestWithWebAppConfigurationTests.java │ │ │ └── package-info.java │ │ ├── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── resources/ │ │ │ │ └── inmetainfresources │ │ │ ├── public/ │ │ │ │ └── inpublic │ │ │ ├── resources/ │ │ │ │ └── inresources │ │ │ ├── static/ │ │ │ │ └── instatic │ │ │ └── web-test-messages.properties │ │ └── webapp/ │ │ └── inwebapp │ ├── spring-boot-webservices/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── webservices/ │ │ │ │ ├── autoconfigure/ │ │ │ │ │ ├── OnWsdlLocationsCondition.java │ │ │ │ │ ├── WebServicesAutoConfiguration.java │ │ │ │ │ ├── WebServicesProperties.java │ │ │ │ │ ├── client/ │ │ │ │ │ │ ├── WebServiceTemplateAutoConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ └── client/ │ │ │ │ ├── WebServiceMessageSenderFactory.java │ │ │ │ ├── WebServiceTemplateBuilder.java │ │ │ │ ├── WebServiceTemplateCustomizer.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── webservices/ │ │ ├── autoconfigure/ │ │ │ ├── OnWsdlLocationsConditionTests.java │ │ │ ├── WebServicesAutoConfigurationTests.java │ │ │ ├── WebServicesPropertiesTests.java │ │ │ └── client/ │ │ │ └── WebServiceTemplateAutoConfigurationTests.java │ │ └── client/ │ │ ├── WebServiceMessageSenderFactoryTests.java │ │ └── WebServiceTemplateBuilderTests.java │ ├── spring-boot-webservices-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── webservices/ │ │ │ │ └── test/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── client/ │ │ │ │ │ ├── AutoConfigureMockWebServiceServer.java │ │ │ │ │ ├── AutoConfigureWebServiceClient.java │ │ │ │ │ ├── MockWebServiceServerAutoConfiguration.java │ │ │ │ │ ├── MockWebServiceServerTestExecutionListener.java │ │ │ │ │ ├── MockWebServiceServerWebServiceTemplateCustomizer.java │ │ │ │ │ ├── TestMockWebServiceServer.java │ │ │ │ │ ├── WebServiceClientExcludeFilter.java │ │ │ │ │ ├── WebServiceClientTemplateAutoConfiguration.java │ │ │ │ │ ├── WebServiceClientTest.java │ │ │ │ │ ├── WebServiceClientTestContextBootstrapper.java │ │ │ │ │ └── package-info.java │ │ │ │ └── server/ │ │ │ │ ├── AutoConfigureMockWebServiceClient.java │ │ │ │ ├── AutoConfigureWebServiceServer.java │ │ │ │ ├── MockWebServiceClientAutoConfiguration.java │ │ │ │ ├── WebServiceServerTest.java │ │ │ │ ├── WebServiceServerTestContextBootstrapper.java │ │ │ │ ├── WebServiceServerTypeExcludeFilter.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── spring/ │ │ │ │ ├── org.springframework.boot.webservices.test.autoconfigure.client.AutoConfigureMockWebServiceServer.imports │ │ │ │ ├── org.springframework.boot.webservices.test.autoconfigure.client.AutoConfigureWebServiceClient.imports │ │ │ │ ├── org.springframework.boot.webservices.test.autoconfigure.server.AutoConfigureMockWebServiceClient.imports │ │ │ │ └── org.springframework.boot.webservices.test.autoconfigure.server.AutoConfigureWebServiceServer.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── webservices/ │ │ └── test/ │ │ └── autoconfigure/ │ │ ├── client/ │ │ │ ├── AutoConfigureMockWebServiceServerEnabledIntegrationTests.java │ │ │ ├── AutoConfigureWebServiceClientWebServiceTemplateIntegrationTests.java │ │ │ ├── ExampleWebServiceClient.java │ │ │ ├── ExampleWebServiceClientApplication.java │ │ │ ├── Request.java │ │ │ ├── Response.java │ │ │ ├── WebServiceClientIntegrationTests.java │ │ │ ├── WebServiceClientNoComponentIntegrationTests.java │ │ │ ├── WebServiceClientPropertiesIntegrationTests.java │ │ │ └── WebServiceMarshallerConfiguration.java │ │ └── server/ │ │ ├── ExampleWebServiceEndpoint.java │ │ ├── ExampleWebServiceServerApplication.java │ │ ├── MockWebServiceClientAutoConfigurationTests.java │ │ ├── Request.java │ │ ├── Response.java │ │ ├── WebServiceServerIntegrationTests.java │ │ ├── WebServiceServerPropertiesIntegrationTests.java │ │ └── WebServiceServerTypeExcludeFilterTests.java │ ├── spring-boot-websocket/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── websocket/ │ │ │ │ └── autoconfigure/ │ │ │ │ └── servlet/ │ │ │ │ ├── WebSocketMessagingAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── websocket/ │ │ └── autoconfigure/ │ │ └── servlet/ │ │ └── WebSocketMessagingAutoConfigurationTests.java │ ├── spring-boot-webtestclient/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── boot/ │ │ │ │ └── webtestclient/ │ │ │ │ └── autoconfigure/ │ │ │ │ ├── AutoConfigureWebTestClient.java │ │ │ │ ├── SpringBootWebTestClientBuilderCustomizer.java │ │ │ │ ├── WebTestClientAutoConfiguration.java │ │ │ │ ├── WebTestClientBuilderCustomizer.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.webtestclient.autoconfigure.AutoConfigureWebTestClient.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── webtestclient/ │ │ └── autoconfigure/ │ │ ├── AutoConfigureWebTestClientIntegrationTests.java │ │ ├── ExampleController1.java │ │ ├── ExampleController2.java │ │ ├── ExampleId.java │ │ ├── ExampleRealService.java │ │ ├── ExampleWebTestClientApplication.java │ │ ├── WebTestClientAutoConfigurationTests.java │ │ ├── WebTestClientSpringBootTestIntegrationTests.java │ │ └── WebTestClientTimeoutSpringBootTestIntegrationTests.java │ └── spring-boot-zipkin/ │ ├── build.gradle │ └── src/ │ ├── dockerTest/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── zipkin/ │ │ │ ├── docker/ │ │ │ │ └── compose/ │ │ │ │ └── ZipkinDockerComposeConnectionDetailsFactoryIntegrationTests.java │ │ │ └── testcontainers/ │ │ │ └── ZipkinContainerConnectionDetailsFactoryIntegrationTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── zipkin/ │ │ └── docker/ │ │ └── compose/ │ │ └── zipkin-compose.yaml │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── boot/ │ │ │ └── zipkin/ │ │ │ ├── autoconfigure/ │ │ │ │ ├── HttpSender.java │ │ │ │ ├── PropertiesZipkinConnectionDetails.java │ │ │ │ ├── ZipkinAutoConfiguration.java │ │ │ │ ├── ZipkinConnectionDetails.java │ │ │ │ ├── ZipkinHttpClientBuilderCustomizer.java │ │ │ │ ├── ZipkinHttpClientSender.java │ │ │ │ ├── ZipkinProperties.java │ │ │ │ └── package-info.java │ │ │ ├── docker/ │ │ │ │ └── compose/ │ │ │ │ ├── ZipkinDockerComposeConnectionDetailsFactory.java │ │ │ │ └── package-info.java │ │ │ └── testcontainers/ │ │ │ ├── ZipkinContainerConnectionDetailsFactory.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── additional-spring-configuration-metadata.json │ │ ├── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── spring.factories │ └── test/ │ └── java/ │ └── org/ │ └── springframework/ │ └── boot/ │ └── zipkin/ │ ├── autoconfigure/ │ │ ├── TestHttpEndpointSupplier.java │ │ ├── ZipkinAutoConfigurationTests.java │ │ ├── ZipkinHttpClientSenderTests.java │ │ └── ZipkinHttpSenderTests.java │ └── testcontainers/ │ ├── ZipkinContainerConnectionDetailsFactoryTests.java │ └── ZipkinContainerConnectionDetailsFactoryWithoutActuatorTests.java ├── platform/ │ ├── spring-boot-dependencies/ │ │ └── build.gradle │ └── spring-boot-internal-dependencies/ │ └── build.gradle ├── settings.gradle ├── smoke-test/ │ ├── spring-boot-smoke-test-activemq/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── activemq/ │ │ │ └── SampleActiveMqTests.java │ │ └── main/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── activemq/ │ │ │ ├── Consumer.java │ │ │ ├── Producer.java │ │ │ ├── SampleActiveMQApplication.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── application.properties │ ├── spring-boot-smoke-test-activemq-embedded/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── activemq/ │ │ │ │ └── embedded/ │ │ │ │ ├── Consumer.java │ │ │ │ ├── Producer.java │ │ │ │ ├── SampleActiveMQApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── activemq/ │ │ └── embedded/ │ │ └── SampleActiveMQApplicationTests.java │ ├── spring-boot-smoke-test-actuator/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── actuator/ │ │ │ │ ├── ExampleHealthIndicator.java │ │ │ │ ├── ExampleInfoContributor.java │ │ │ │ ├── HelloWorldService.java │ │ │ │ ├── SampleActuatorApplication.java │ │ │ │ ├── SampleController.java │ │ │ │ ├── SampleLegacyEndpointWithDot.java │ │ │ │ ├── SampleLegacyEndpointWithHyphen.java │ │ │ │ ├── SampleRestControllerEndpointWithException.java │ │ │ │ ├── ServiceProperties.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── build-info.properties │ │ │ ├── application.properties │ │ │ └── logback.xml │ │ └── test/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── actuator/ │ │ │ ├── AbstractManagementPortAndPathSampleActuatorApplicationTests.java │ │ │ ├── ApplicationStartupSpringBootContextLoader.java │ │ │ ├── CorsSampleActuatorApplicationTests.java │ │ │ ├── EndpointsPropertiesSampleActuatorApplicationTests.java │ │ │ ├── ManagementAddressActuatorApplicationTests.java │ │ │ ├── ManagementDifferentPortAndEndpointWithExceptionHandlerSampleActuatorApplicationTests.java │ │ │ ├── ManagementDifferentPortSampleActuatorApplicationTests.java │ │ │ ├── ManagementEndpointConflictSmokeTests.java │ │ │ ├── ManagementPathSampleActuatorApplicationTests.java │ │ │ ├── ManagementPortAndPathWithAntPatcherSampleActuatorApplicationTests.java │ │ │ ├── ManagementPortAndPathWithPathMatcherSampleActuatorApplicationTests.java │ │ │ ├── ManagementPortSampleActuatorApplicationTests.java │ │ │ ├── ManagementPortWithLazyInitializationTests.java │ │ │ ├── ManagementRandomPortWithConfiguredPortSampleActuatorApplicationTests.java │ │ │ ├── NoManagementSampleActuatorApplicationTests.java │ │ │ ├── SampleActuatorApplicationIsolatedJsonMapperFalseTests.java │ │ │ ├── SampleActuatorApplicationIsolatedJsonMapperTrueTests.java │ │ │ ├── SampleActuatorApplicationTests.java │ │ │ ├── ServletPathSampleActuatorApplicationTests.java │ │ │ └── ShutdownSampleActuatorApplicationTests.java │ │ └── resources/ │ │ ├── application-cors.properties │ │ └── application-endpoints.properties │ ├── spring-boot-smoke-test-actuator-custom-security/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── actuator/ │ │ │ │ └── customsecurity/ │ │ │ │ ├── ExampleController.java │ │ │ │ ├── ExampleRestControllerEndpoint.java │ │ │ │ ├── SampleActuatorCustomSecurityApplication.java │ │ │ │ ├── SecurityConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── templates/ │ │ │ ├── error.ftlh │ │ │ └── home.ftlh │ │ └── test/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── actuator/ │ │ │ └── customsecurity/ │ │ │ ├── AbstractSampleActuatorCustomSecurityTests.java │ │ │ ├── CorsSampleActuatorApplicationTests.java │ │ │ ├── CustomServletPathSampleActuatorTests.java │ │ │ ├── ManagementServerWithCustomBasePathAndWebEndpointsBasePathSampleActuatorApplicationTests.java │ │ │ ├── ManagementServerWithCustomBasePathSampleActuatorApplicationTests.java │ │ │ ├── ManagementServerWithCustomServletPathSampleActuatorTests.java │ │ │ └── SampleActuatorCustomSecurityApplicationTests.java │ │ └── resources/ │ │ └── application-cors.properties │ ├── spring-boot-smoke-test-actuator-extension/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── actuator/ │ │ │ │ └── extension/ │ │ │ │ ├── MyExtensionConfiguration.java │ │ │ │ ├── MyExtensionEndpointExposureOutcomeContributor.java │ │ │ │ ├── MyExtensionEndpointFilter.java │ │ │ │ ├── MyExtensionSecurityInterceptor.java │ │ │ │ ├── MyExtensionWebMvcEndpointHandlerMapping.java │ │ │ │ ├── SampleActuatorExtensionApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── spring.factories │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── actuator/ │ │ └── extension/ │ │ └── SampleActuatorExtensionApplicationTests.java │ ├── spring-boot-smoke-test-actuator-log4j2/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── actuator/ │ │ │ │ └── log4j2/ │ │ │ │ ├── SampleActuatorLog4J2Application.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── log4j2.xml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── actuator/ │ │ └── log4j2/ │ │ └── SampleActuatorLog4J2ApplicationTests.java │ ├── spring-boot-smoke-test-actuator-noweb/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── actuator/ │ │ │ │ └── noweb/ │ │ │ │ ├── HelloWorldService.java │ │ │ │ ├── SampleActuatorNoWebApplication.java │ │ │ │ ├── ServiceProperties.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── banner.txt │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── actuator/ │ │ └── noweb/ │ │ └── SampleActuatorNoWebApplicationTests.java │ ├── spring-boot-smoke-test-actuator-ui/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── actuator/ │ │ │ │ └── ui/ │ │ │ │ ├── SampleActuatorUiApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── templates/ │ │ │ ├── error.ftlh │ │ │ └── home.ftlh │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── actuator/ │ │ └── ui/ │ │ ├── SampleActuatorUiApplicationPortTests.java │ │ └── SampleActuatorUiApplicationTests.java │ ├── spring-boot-smoke-test-amqp/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── amqp/ │ │ │ ├── SampleAmqpSimpleApplicationSslTests.java │ │ │ ├── SampleAmqpSimpleApplicationTests.java │ │ │ └── SecureRabbitMqContainer.java │ │ ├── main/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── amqp/ │ │ │ ├── SampleAmqpSimpleApplication.java │ │ │ ├── Sender.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── resources/ │ │ └── ssl/ │ │ ├── rabbitmq.conf │ │ ├── test-ca.crt │ │ ├── test-ca.key │ │ ├── test-client.crt │ │ ├── test-client.key │ │ ├── test-server.crt │ │ └── test-server.key │ ├── spring-boot-smoke-test-ant/ │ │ ├── build.gradle │ │ ├── build.xml │ │ ├── ivy.xml │ │ ├── ivysettings.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── ant/ │ │ │ └── SampleAntApplication.java │ │ └── test/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── ant/ │ │ │ └── SampleAntApplicationIT.java │ │ └── resources/ │ │ └── application.properties │ ├── spring-boot-smoke-test-artemis/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── artemis/ │ │ │ └── SampleArtemisTests.java │ │ └── main/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── artemis/ │ │ │ ├── Consumer.java │ │ │ ├── Producer.java │ │ │ ├── SampleArtemisApplication.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── application.properties │ ├── spring-boot-smoke-test-aspectj/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── aspectj/ │ │ │ │ ├── SampleAspectJApplication.java │ │ │ │ ├── monitor/ │ │ │ │ │ ├── ServiceMonitor.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── service/ │ │ │ │ ├── HelloWorldService.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── aspectj/ │ │ └── SampleAspectJApplicationTests.java │ ├── spring-boot-smoke-test-autoconfigure-classic/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── autoconfigureclassic/ │ │ │ │ ├── SampleAutoConfigureClassicApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── banner.txt │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── autoconfigureclassic/ │ │ └── SampleAutoConfigureClassicApplicationTests.java │ ├── spring-boot-smoke-test-batch/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── batch/ │ │ │ ├── SampleBatchApplication.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── batch/ │ │ └── SampleBatchApplicationTests.java │ ├── spring-boot-smoke-test-batch-data-mongodb/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── batch/ │ │ │ ├── SampleBatchApplication.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── batch/ │ │ └── SampleBatchApplicationTests.java │ ├── spring-boot-smoke-test-batch-jdbc/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── batch/ │ │ │ ├── SampleBatchApplication.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── batch/ │ │ └── SampleBatchApplicationTests.java │ ├── spring-boot-smoke-test-bootstrap-registry/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── bootstrapregistry/ │ │ │ │ ├── app/ │ │ │ │ │ ├── MySubversionClient.java │ │ │ │ │ ├── Printer.java │ │ │ │ │ ├── SampleBootstrapRegistryApplication.java │ │ │ │ │ └── package-info.java │ │ │ │ └── external/ │ │ │ │ └── svn/ │ │ │ │ ├── SubversionBootstrap.java │ │ │ │ ├── SubversionClient.java │ │ │ │ ├── SubversionConfigDataLoader.java │ │ │ │ ├── SubversionConfigDataLocationResolver.java │ │ │ │ ├── SubversionConfigDataResource.java │ │ │ │ ├── SubversionServerCertificate.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── spring.factories │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── bootstrapregistry/ │ │ └── app/ │ │ └── SampleBootstrapRegistryApplicationTests.java │ ├── spring-boot-smoke-test-cache/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── cache/ │ │ │ │ └── SampleCacheApplicationRedisTests.java │ │ │ └── resources/ │ │ │ └── logback-test.xml │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── cache/ │ │ │ │ ├── CacheManagerCheck.java │ │ │ │ ├── Country.java │ │ │ │ ├── CountryRepository.java │ │ │ │ ├── SampleCacheApplication.java │ │ │ │ ├── SampleClient.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ ├── ehcache3.xml │ │ │ └── infinispan.xml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── cache/ │ │ └── SampleCacheApplicationTests.java │ ├── spring-boot-smoke-test-config/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── config/ │ │ │ │ ├── FromEnvConfigurationProperties.java │ │ │ │ ├── SampleConfigApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── config/ │ │ └── FromEnvConfigurationPropertiesTests.java │ ├── spring-boot-smoke-test-data-cassandra/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── data/ │ │ │ │ └── cassandra/ │ │ │ │ ├── SampleDataCassandraApplicationReactiveSslTests.java │ │ │ │ ├── SampleDataCassandraApplicationSslTests.java │ │ │ │ └── SecureCassandraContainer.java │ │ │ └── resources/ │ │ │ └── ssl/ │ │ │ ├── cassandra.yaml │ │ │ ├── test-ca.p12 │ │ │ ├── test-client.p12 │ │ │ └── test-server.p12 │ │ └── main/ │ │ └── java/ │ │ └── smoketest/ │ │ └── data/ │ │ └── cassandra/ │ │ ├── SampleDataCassandraApplication.java │ │ ├── SampleEntity.java │ │ ├── SampleRepository.java │ │ ├── SampleService.java │ │ └── package-info.java │ ├── spring-boot-smoke-test-data-couchbase/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── data/ │ │ │ └── couchbase/ │ │ │ ├── SampleDataCouchbaseApplicationReactiveSslTests.java │ │ │ ├── SampleDataCouchbaseApplicationSslTests.java │ │ │ └── SecureCouchbaseContainer.java │ │ ├── main/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── data/ │ │ │ └── couchbase/ │ │ │ ├── SampleDataCouchbaseApplication.java │ │ │ ├── SampleDocument.java │ │ │ ├── SampleReactiveRepository.java │ │ │ ├── SampleRepository.java │ │ │ ├── SampleService.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── resources/ │ │ └── ssl/ │ │ ├── test-ca.crt │ │ ├── test-ca.key │ │ ├── test-client.crt │ │ ├── test-client.key │ │ ├── test-server.crt │ │ └── test-server.key │ ├── spring-boot-smoke-test-data-elasticsearch/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── data/ │ │ │ │ └── elasticsearch/ │ │ │ │ ├── SampleDataElasticsearchApplicationTests.java │ │ │ │ ├── SampleDataElasticsearchSslApplicationTests.java │ │ │ │ ├── SampleDataElasticsearchWithElasticsearch8ApplicationTests.java │ │ │ │ ├── SampleDataElasticsearchWithElasticsearch9ApplicationTests.java │ │ │ │ └── SecureElasticsearchContainer.java │ │ │ └── resources/ │ │ │ ├── ssl.crt │ │ │ └── ssl.key │ │ └── main/ │ │ └── java/ │ │ └── smoketest/ │ │ └── data/ │ │ └── elasticsearch/ │ │ ├── SampleDataElasticsearchApplication.java │ │ ├── SampleDocument.java │ │ ├── SampleRepository.java │ │ └── package-info.java │ ├── spring-boot-smoke-test-data-jdbc/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── data/ │ │ │ │ └── jdbc/ │ │ │ │ ├── Customer.java │ │ │ │ ├── CustomerRepository.java │ │ │ │ ├── SampleController.java │ │ │ │ ├── SampleDataJdbcApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── data.sql │ │ │ └── schema.sql │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── data/ │ │ └── jdbc/ │ │ ├── CustomerRepositoryIntegrationTests.java │ │ └── SampleDataJdbcApplicationTests.java │ ├── spring-boot-smoke-test-data-jpa/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── data/ │ │ │ │ └── jpa/ │ │ │ │ ├── SampleDataJpaApplication.java │ │ │ │ ├── domain/ │ │ │ │ │ ├── City.java │ │ │ │ │ ├── Hotel.java │ │ │ │ │ ├── HotelSummary.java │ │ │ │ │ ├── Rating.java │ │ │ │ │ ├── RatingCount.java │ │ │ │ │ ├── Review.java │ │ │ │ │ ├── ReviewDetails.java │ │ │ │ │ ├── TripType.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── service/ │ │ │ │ │ ├── CityRepository.java │ │ │ │ │ ├── CitySearchCriteria.java │ │ │ │ │ ├── CityService.java │ │ │ │ │ ├── CityServiceImpl.java │ │ │ │ │ ├── HotelRepository.java │ │ │ │ │ ├── HotelService.java │ │ │ │ │ ├── HotelServiceImpl.java │ │ │ │ │ ├── ReviewRepository.java │ │ │ │ │ ├── ReviewsSummary.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── SampleController.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── import.sql │ │ └── test/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── data/ │ │ │ └── jpa/ │ │ │ ├── SampleDataJpaApplicationTests.java │ │ │ └── service/ │ │ │ ├── CityRepositoryIntegrationTests.java │ │ │ └── HotelRepositoryIntegrationTests.java │ │ └── resources/ │ │ └── application-scratch.properties │ ├── spring-boot-smoke-test-data-ldap/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── data/ │ │ │ │ └── ldap/ │ │ │ │ ├── Person.java │ │ │ │ ├── PersonRepository.java │ │ │ │ ├── SampleDataLdapApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── schema.ldif │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── data/ │ │ └── ldap/ │ │ └── SampleDataLdapApplicationTests.java │ ├── spring-boot-smoke-test-data-mongodb/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── data/ │ │ │ └── mongodb/ │ │ │ ├── SampleDataMongoApplicationReactiveSslTests.java │ │ │ ├── SampleMongoApplicationSslTests.java │ │ │ └── SecureMongoContainer.java │ │ ├── main/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── data/ │ │ │ └── mongodb/ │ │ │ ├── SampleDataMongoApplication.java │ │ │ ├── SampleDocument.java │ │ │ ├── SampleReactiveRepository.java │ │ │ ├── SampleRepository.java │ │ │ ├── SampleService.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── resources/ │ │ └── ssl/ │ │ ├── test-ca.crt │ │ ├── test-ca.key │ │ ├── test-client.crt │ │ ├── test-client.key │ │ └── test-server.pem │ ├── spring-boot-smoke-test-data-r2dbc/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── data/ │ │ │ │ └── r2dbc/ │ │ │ │ ├── City.java │ │ │ │ ├── CityController.java │ │ │ │ ├── CityRepository.java │ │ │ │ ├── SampleR2dbcApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ ├── data.sql │ │ │ └── schema.sql │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── data/ │ │ └── r2dbc/ │ │ └── SampleR2dbcApplicationTests.java │ ├── spring-boot-smoke-test-data-r2dbc-flyway/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── data/ │ │ │ └── r2dbc/ │ │ │ └── CityRepositoryTests.java │ │ └── main/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── data/ │ │ │ └── r2dbc/ │ │ │ ├── City.java │ │ │ ├── CityRepository.java │ │ │ ├── SampleR2dbcFlywayApplication.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── application.properties │ │ └── db/ │ │ └── migration/ │ │ └── V1__init.sql │ ├── spring-boot-smoke-test-data-r2dbc-liquibase/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── data/ │ │ │ └── r2dbc/ │ │ │ └── CityRepositoryTests.java │ │ └── main/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── data/ │ │ │ └── r2dbc/ │ │ │ ├── City.java │ │ │ ├── CityRepository.java │ │ │ ├── SampleR2dbcLiquibaseApplication.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── application.properties │ │ └── db/ │ │ └── changelog/ │ │ └── db.changelog-master.yaml │ ├── spring-boot-smoke-test-data-redis/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── data/ │ │ │ └── redis/ │ │ │ ├── SampleRedisApplicationJedisSslTests.java │ │ │ ├── SampleRedisApplicationReactiveSslTests.java │ │ │ ├── SampleRedisApplicationSslTests.java │ │ │ └── SecureRedisContainer.java │ │ ├── main/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── data/ │ │ │ └── redis/ │ │ │ ├── PersonHash.java │ │ │ ├── SampleRedisApplication.java │ │ │ ├── SampleRepository.java │ │ │ ├── SampleService.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── resources/ │ │ └── ssl/ │ │ ├── test-ca.crt │ │ ├── test-ca.key │ │ ├── test-client.crt │ │ ├── test-client.key │ │ ├── test-server.crt │ │ └── test-server.key │ ├── spring-boot-smoke-test-data-rest/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── data/ │ │ │ │ └── rest/ │ │ │ │ ├── SampleDataRestApplication.java │ │ │ │ ├── domain/ │ │ │ │ │ ├── City.java │ │ │ │ │ ├── Hotel.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── service/ │ │ │ │ ├── CityRepository.java │ │ │ │ ├── CitySearchCriteria.java │ │ │ │ ├── HotelRepository.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── import.sql │ │ └── test/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── data/ │ │ │ └── rest/ │ │ │ ├── SampleDataRestApplicationTests.java │ │ │ └── service/ │ │ │ └── CityRepositoryIntegrationTests.java │ │ └── resources/ │ │ └── application-scratch.properties │ ├── spring-boot-smoke-test-devtools/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── devtools/ │ │ │ │ ├── Message.java │ │ │ │ ├── MyController.java │ │ │ │ ├── SampleDevToolsApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ ├── public/ │ │ │ │ └── public.txt │ │ │ └── static/ │ │ │ └── css/ │ │ │ └── application.css │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── devtools/ │ │ └── SampleDevToolsApplicationIntegrationTests.java │ ├── spring-boot-smoke-test-flyway/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── flyway/ │ │ │ │ ├── Person.java │ │ │ │ ├── PersonRepository.java │ │ │ │ ├── SampleFlywayApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ ├── data.sql │ │ │ └── db/ │ │ │ └── migration/ │ │ │ └── V1__init.sql │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── flyway/ │ │ └── SampleFlywayApplicationTests.java │ ├── spring-boot-smoke-test-graphql/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── graphql/ │ │ │ │ ├── GreetingController.java │ │ │ │ ├── GreetingService.java │ │ │ │ ├── Project.java │ │ │ │ ├── ProjectController.java │ │ │ │ ├── SampleGraphQlApplication.java │ │ │ │ ├── SecurityConfig.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── graphql/ │ │ │ └── schema.graphqls │ │ └── test/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── graphql/ │ │ │ ├── GreetingControllerTests.java │ │ │ ├── ProjectControllerTests.java │ │ │ └── SampleGraphQlApplicationTests.java │ │ └── resources/ │ │ └── graphql-test/ │ │ └── greeting.graphql │ ├── spring-boot-smoke-test-grpc-client/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── grpcclient/ │ │ │ │ ├── SampleGrpcClientApplication.java │ │ │ │ └── package-info.java │ │ │ ├── proto/ │ │ │ │ └── hello.proto │ │ │ └── resources/ │ │ │ └── application.yaml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── grpcclient/ │ │ └── SampleGrpcClientApplicationTests.java │ ├── spring-boot-smoke-test-grpc-client-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── grpcclienttest/ │ │ │ │ ├── SampleGrpcClientTestApplication.java │ │ │ │ └── package-info.java │ │ │ └── proto/ │ │ │ └── hello.proto │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── grpcclienttest/ │ │ └── SampleGrpcClientTestApplicationTests.java │ ├── spring-boot-smoke-test-grpc-server/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── grpcserver/ │ │ │ └── SampleGrpcServerApplicationTests.java │ │ └── main/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── grpcserver/ │ │ │ ├── HelloWorldService.java │ │ │ ├── SampleGrpcServerApplication.java │ │ │ └── package-info.java │ │ └── proto/ │ │ └── hello.proto │ ├── spring-boot-smoke-test-grpc-server-netty-shaded/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── grpcservernettyshaded/ │ │ │ └── SampleGrpcServerNettyShadedApplicationTests.java │ │ └── main/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── grpcservernettyshaded/ │ │ │ ├── HelloWorldService.java │ │ │ ├── SampleGrpcServerNettyShadedApplication.java │ │ │ └── package-info.java │ │ └── proto/ │ │ └── hello.proto │ ├── spring-boot-smoke-test-grpc-server-oauth/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── grpcserveroauth/ │ │ │ │ ├── HelloWorldService.java │ │ │ │ ├── SampleGrpcServerOAuthApplication.java │ │ │ │ ├── SecurityConfiguration.java │ │ │ │ └── package-info.java │ │ │ ├── proto/ │ │ │ │ └── hello.proto │ │ │ └── resources/ │ │ │ └── application.yaml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── grpcserveroauth/ │ │ └── SampleGrpcServerOAuthApplicationTests.java │ ├── spring-boot-smoke-test-grpc-server-secure/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── grpcserversecure/ │ │ │ │ ├── HelloWorldService.java │ │ │ │ ├── SampleGrpcServerSecureApplication.java │ │ │ │ ├── SecurityConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── proto/ │ │ │ └── hello.proto │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── grpcserversecure/ │ │ └── SampleGrpcServerSecureApplicationTests.java │ ├── spring-boot-smoke-test-grpc-server-servlet/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── grpcserverservlet/ │ │ │ └── SampleGrpcServerServletApplicationTests.java │ │ └── main/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── grpcserverservlet/ │ │ │ ├── HelloWorldService.java │ │ │ ├── SampleGrpcServerServletApplication.java │ │ │ └── package-info.java │ │ ├── proto/ │ │ │ └── hello.proto │ │ └── resources/ │ │ └── application.properties │ ├── spring-boot-smoke-test-grpc-server-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── grpcservertest/ │ │ │ │ ├── HelloWorldService.java │ │ │ │ ├── SampleGrpcServerTestApplication.java │ │ │ │ ├── StandardExceptionHandler.java │ │ │ │ └── package-info.java │ │ │ └── proto/ │ │ │ └── hello.proto │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── grpcservertest/ │ │ ├── SampleGrpcServerTestApplicationIntegrationTests.java │ │ └── SampleGrpcServerTestApplicationTests.java │ ├── spring-boot-smoke-test-hateoas/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── hateoas/ │ │ │ ├── SampleHateoasApplication.java │ │ │ ├── domain/ │ │ │ │ ├── Customer.java │ │ │ │ ├── CustomerRepository.java │ │ │ │ ├── InMemoryCustomerRepository.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── web/ │ │ │ ├── CustomerController.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── hateoas/ │ │ └── SampleHateoasApplicationTests.java │ ├── spring-boot-smoke-test-hibernate/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── jpa/ │ │ │ │ ├── SampleJpaApplication.java │ │ │ │ ├── domain/ │ │ │ │ │ ├── Note.java │ │ │ │ │ ├── Tag.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── repository/ │ │ │ │ │ ├── JpaNoteRepository.java │ │ │ │ │ ├── JpaTagRepository.java │ │ │ │ │ ├── NoteRepository.java │ │ │ │ │ ├── TagRepository.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── IndexController.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ ├── import.sql │ │ │ └── templates/ │ │ │ └── index.ftlh │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── jpa/ │ │ ├── SampleJpaApplicationTests.java │ │ └── repository/ │ │ ├── JpaNoteRepositoryIntegrationTests.java │ │ └── JpaTagRepositoryIntegrationTests.java │ ├── spring-boot-smoke-test-integration/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── integration/ │ │ │ │ ├── HelloWorldService.java │ │ │ │ ├── SampleApplicationRunner.java │ │ │ │ ├── SampleEndpoint.java │ │ │ │ ├── SampleIntegrationApplication.java │ │ │ │ ├── SampleMessageGateway.java │ │ │ │ ├── ServiceProperties.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── integration/ │ │ ├── consumer/ │ │ │ ├── SampleIntegrationApplicationTests.java │ │ │ └── package-info.java │ │ └── producer/ │ │ ├── ProducerApplication.java │ │ └── package-info.java │ ├── spring-boot-smoke-test-jackson2-mixed/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── jackson2/ │ │ │ │ └── mixed/ │ │ │ │ ├── Name.java │ │ │ │ ├── NamesEndpoint.java │ │ │ │ ├── SampleController.java │ │ │ │ ├── SampleJackson2MixedApplication.java │ │ │ │ ├── SampleJacksonComponent.java │ │ │ │ ├── SampleJsonComponent.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── jackson2/ │ │ └── mixed/ │ │ ├── MixedJacksonPresenceTests.java │ │ ├── SampleJackson2MixedApplicationJsonTests.java │ │ ├── SampleJackson2MixedApplicationTests.java │ │ └── SampleJackson2MixedApplicationWithJackson3PreferredTests.java │ ├── spring-boot-smoke-test-jackson2-only/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── jackson2/ │ │ │ │ └── only/ │ │ │ │ ├── SampleJackson2OnlyApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── jackson2/ │ │ └── only/ │ │ ├── JsonPojo.java │ │ ├── OnlyJackson2PresenceTests.java │ │ ├── SampleJackson2OnlyApplicationJsonTests.java │ │ ├── SampleJackson2OnlyApplicationTests.java │ │ └── SampleJackson2OnlyWithoutSpringWebApplicationTests.java │ ├── spring-boot-smoke-test-jersey/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── jersey/ │ │ │ │ ├── Endpoint.java │ │ │ │ ├── JerseyConfig.java │ │ │ │ ├── ReverseEndpoint.java │ │ │ │ ├── SampleJerseyApplication.java │ │ │ │ ├── Service.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── jersey/ │ │ ├── AbstractJerseyApplicationTests.java │ │ ├── AbstractJerseyManagementPortTests.java │ │ ├── ApplicationStartupSpringBootContextLoader.java │ │ ├── JerseyActuatorIsolatedObjectMapperFalseTests.java │ │ ├── JerseyActuatorIsolatedObjectMapperTrueTests.java │ │ ├── JerseyApplicationPathAndManagementPortTests.java │ │ ├── JerseyDifferentPortSampleActuatorApplicationTests.java │ │ ├── JerseyFilterApplicationTests.java │ │ ├── JerseyFilterManagementPortTests.java │ │ ├── JerseyServletApplicationTests.java │ │ └── JerseyServletManagementPortTests.java │ ├── spring-boot-smoke-test-jetty/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── jetty/ │ │ │ │ ├── ExampleServletContextListener.java │ │ │ │ ├── SampleJettyApplication.java │ │ │ │ ├── package-info.java │ │ │ │ ├── service/ │ │ │ │ │ ├── HelloWorldService.java │ │ │ │ │ ├── HttpHeaderService.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── util/ │ │ │ │ │ ├── StringUtil.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── SampleController.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── jetty/ │ │ └── SampleJettyApplicationTests.java │ ├── spring-boot-smoke-test-jetty-jsp/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── jetty/ │ │ │ │ └── jsp/ │ │ │ │ ├── MyException.java │ │ │ │ ├── MyRestResponse.java │ │ │ │ ├── SampleJettyJspApplication.java │ │ │ │ ├── WelcomeController.java │ │ │ │ └── package-info.java │ │ │ ├── resources/ │ │ │ │ └── application.properties │ │ │ └── webapp/ │ │ │ └── WEB-INF/ │ │ │ └── jsp/ │ │ │ └── welcome.jsp │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── jetty/ │ │ └── jsp/ │ │ └── SampleWebJspApplicationTests.java │ ├── spring-boot-smoke-test-jetty-ssl/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── jetty/ │ │ │ │ └── ssl/ │ │ │ │ ├── SampleJettySslApplication.java │ │ │ │ ├── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── SampleController.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── sample.jks │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── jetty/ │ │ └── ssl/ │ │ └── SampleJettySslApplicationTests.java │ ├── spring-boot-smoke-test-kafka/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── kafka/ │ │ │ │ └── ssl/ │ │ │ │ ├── SampleKafkaSslApplicationTests.java │ │ │ │ └── SecureKafkaContainer.java │ │ │ └── resources/ │ │ │ └── ssl/ │ │ │ ├── credentials │ │ │ ├── test-ca.p12 │ │ │ ├── test-client.p12 │ │ │ └── test-server.p12 │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── kafka/ │ │ │ │ ├── Consumer.java │ │ │ │ ├── Producer.java │ │ │ │ ├── SampleKafkaApplication.java │ │ │ │ ├── SampleMessage.java │ │ │ │ ├── package-info.java │ │ │ │ └── ssl/ │ │ │ │ ├── SampleKafkaSslApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── kafka/ │ │ └── SampleKafkaApplicationTests.java │ ├── spring-boot-smoke-test-liquibase/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── liquibase/ │ │ │ │ ├── SampleLiquibaseApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── db/ │ │ │ └── changelog/ │ │ │ └── db.changelog-master.yaml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── liquibase/ │ │ └── SampleLiquibaseApplicationTests.java │ ├── spring-boot-smoke-test-log4j2/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── log4j2/ │ │ │ │ ├── SampleLog4j2Application.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── log4j2-spring.xml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── log4j2/ │ │ └── SampleLog4j2ApplicationTests.java │ ├── spring-boot-smoke-test-logback/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── logback/ │ │ │ │ ├── SampleLogbackApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── logback-spring.xml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── logback/ │ │ └── SampleLogbackApplicationTests.java │ ├── spring-boot-smoke-test-oauth2-authorization-server/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── oauth2/ │ │ │ │ └── server/ │ │ │ │ ├── SampleOAuth2AuthorizationServerApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── oauth2/ │ │ └── server/ │ │ └── SampleOAuth2AuthorizationServerApplicationTests.java │ ├── spring-boot-smoke-test-oauth2-client/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── oauth2/ │ │ │ │ └── client/ │ │ │ │ ├── ExampleController.java │ │ │ │ ├── SampleOAuth2ClientApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── oauth2/ │ │ └── client/ │ │ └── SampleOAuth2ClientApplicationTests.java │ ├── spring-boot-smoke-test-oauth2-resource-server/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── oauth2/ │ │ │ │ └── resource/ │ │ │ │ ├── ExampleController.java │ │ │ │ ├── SampleOauth2ResourceServerApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── oauth2/ │ │ └── resource/ │ │ └── SampleOauth2ResourceServerApplicationTests.java │ ├── spring-boot-smoke-test-opentelemetry/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── opentelemetry/ │ │ │ │ ├── OtlpCollectorIntegrationTests.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── certs/ │ │ │ │ ├── ca.crt │ │ │ │ ├── ca.key │ │ │ │ ├── server.crt │ │ │ │ └── server.key │ │ │ └── otel-collector-config.yaml │ │ ├── main/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── opentelemetry/ │ │ │ ├── SampleController.java │ │ │ ├── SampleOpenTelemetryApplication.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── opentelemetry/ │ │ ├── DisabledMappingTests.java │ │ ├── HeadersAndCompressionTests.java │ │ ├── SampleOpenTelemetryApplicationTests.java │ │ ├── SignalSpecificPrecedenceTests.java │ │ └── package-info.java │ ├── spring-boot-smoke-test-parent-context/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── parent/ │ │ │ │ ├── HelloWorldService.java │ │ │ │ ├── SampleEndpoint.java │ │ │ │ ├── SampleParentContextApplication.java │ │ │ │ ├── ServiceProperties.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── parent/ │ │ ├── consumer/ │ │ │ ├── SampleIntegrationParentApplicationTests.java │ │ │ └── package-info.java │ │ └── producer/ │ │ ├── ProducerApplication.java │ │ └── package-info.java │ ├── spring-boot-smoke-test-profile/ │ │ ├── application.yml │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── profile/ │ │ │ │ ├── ActiveProfilesEnvironmentPostProcessor.java │ │ │ │ ├── SampleProfileApplication.java │ │ │ │ ├── package-info.java │ │ │ │ └── service/ │ │ │ │ ├── GenericService.java │ │ │ │ ├── GoodbyeWorldService.java │ │ │ │ ├── HelloWorldService.java │ │ │ │ ├── MessageService.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── spring.factories │ │ │ └── application.yml │ │ └── test/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── profile/ │ │ │ ├── ActiveProfilesTests.java │ │ │ ├── AttributeInjectionTests.java │ │ │ └── SampleProfileApplicationTests.java │ │ └── resources/ │ │ └── application.properties │ ├── spring-boot-smoke-test-prometheus/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── prometheus/ │ │ │ │ ├── SamplePrometheusApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── prometheus/ │ │ └── SamplePrometheusApplicationTests.java │ ├── spring-boot-smoke-test-property-validation/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── propertyvalidation/ │ │ │ │ ├── SampleProperties.java │ │ │ │ ├── SamplePropertiesValidator.java │ │ │ │ ├── SamplePropertyValidationApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── propertyvalidation/ │ │ └── SamplePropertyValidationApplicationTests.java │ ├── spring-boot-smoke-test-pulsar/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── pulsar/ │ │ │ └── SamplePulsarApplicationTests.java │ │ └── main/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── pulsar/ │ │ │ ├── SampleMessage.java │ │ │ ├── SamplePulsarApplication.java │ │ │ ├── SamplePulsarApplicationConfig.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── application.properties │ ├── spring-boot-smoke-test-quartz/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── quartz/ │ │ │ │ ├── SampleJob.java │ │ │ │ ├── SampleQuartzApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── quartz/ │ │ ├── SampleQuartzApplicationTests.java │ │ └── SampleQuartzApplicationWebTests.java │ ├── spring-boot-smoke-test-reactive-oauth2-client/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── oauth2/ │ │ │ │ └── client/ │ │ │ │ ├── ExampleController.java │ │ │ │ ├── SampleReactiveOAuth2ClientApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── oauth2/ │ │ └── client/ │ │ └── SampleReactiveOAuth2ClientApplicationTests.java │ ├── spring-boot-smoke-test-reactive-oauth2-resource-server/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── oauth2/ │ │ │ │ └── resource/ │ │ │ │ ├── ExampleController.java │ │ │ │ ├── SampleReactiveOAuth2ResourceServerApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── oauth2/ │ │ └── resource/ │ │ └── SampleReactiveOAuth2ResourceServerApplicationTests.java │ ├── spring-boot-smoke-test-restclient/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ ├── aaaa/ │ │ │ │ │ ├── Gh49223AutoConfiguration.java │ │ │ │ │ └── package-info.java │ │ │ │ └── smoketest/ │ │ │ │ └── restclient/ │ │ │ │ ├── SampleRestClientApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── restclient/ │ │ └── SampleRestClientApplicationGh49223Tests.java │ ├── spring-boot-smoke-test-rsocket/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── rsocket/ │ │ │ │ ├── Project.java │ │ │ │ ├── ProjectController.java │ │ │ │ ├── SampleRSocketApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── rsocket/ │ │ └── SampleRSocketApplicationTests.java │ ├── spring-boot-smoke-test-saml2-service-provider/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── saml2/ │ │ │ │ └── serviceprovider/ │ │ │ │ ├── ExampleController.java │ │ │ │ ├── SampleSaml2RelyingPartyApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.yml │ │ │ └── saml/ │ │ │ ├── certificate.txt │ │ │ └── privatekey.txt │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── saml2/ │ │ └── serviceprovider/ │ │ └── SampleSaml2RelyingPartyApplicationTests.java │ ├── spring-boot-smoke-test-secure/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── secure/ │ │ │ │ ├── SampleSecureApplication.java │ │ │ │ ├── SampleService.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── secure/ │ │ └── SampleSecureApplicationTests.java │ ├── spring-boot-smoke-test-secure-jersey/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── secure/ │ │ │ │ └── jersey/ │ │ │ │ ├── Endpoint.java │ │ │ │ ├── JerseyConfig.java │ │ │ │ ├── ReverseEndpoint.java │ │ │ │ ├── SampleSecureJerseyApplication.java │ │ │ │ ├── SecurityConfiguration.java │ │ │ │ ├── Service.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── secure/ │ │ └── jersey/ │ │ ├── AbstractJerseySecureTests.java │ │ ├── CustomApplicationPathActuatorTests.java │ │ ├── JerseySecureApplicationTests.java │ │ ├── ManagementPortAndPathJerseyApplicationTests.java │ │ └── ManagementPortCustomApplicationPathJerseyTests.java │ ├── spring-boot-smoke-test-secure-webflux/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── secure/ │ │ │ │ └── webflux/ │ │ │ │ ├── EchoHandler.java │ │ │ │ ├── SampleSecureWebFluxApplication.java │ │ │ │ ├── WelcomeController.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── secure/ │ │ │ └── webflux/ │ │ │ ├── CorsSampleActuatorApplicationTests.java │ │ │ ├── ManagementPortSampleSecureWebFluxTests.java │ │ │ ├── SampleSecureWebFluxApplicationTests.java │ │ │ └── SampleSecureWebFluxCustomSecurityTests.java │ │ └── resources/ │ │ └── application-cors.properties │ ├── spring-boot-smoke-test-servlet/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── servlet/ │ │ │ │ ├── SampleServletApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── servlet/ │ │ └── SampleServletApplicationTests.java │ ├── spring-boot-smoke-test-session-data-redis/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── session/ │ │ │ └── redis/ │ │ │ ├── SampleSessionRedisApplicationTests.java │ │ │ ├── TestPropertiesImportSampleSessionRedisApplication.java │ │ │ ├── TestPropertiesSampleSessionRedisApplication.java │ │ │ ├── TestServiceConnectionImportSampleSessionRedisApplication.java │ │ │ └── TestServiceConnectionSampleSessionRedisApplication.java │ │ └── main/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── session/ │ │ │ └── redis/ │ │ │ ├── SampleSessionRedisApplication.java │ │ │ ├── SecurityConfiguration.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── application.properties │ ├── spring-boot-smoke-test-session-data-redis-webflux/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── dockerTest/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── session/ │ │ │ └── SampleSessionWebFluxRedisApplicationTests.java │ │ └── main/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── session/ │ │ │ ├── HelloRestController.java │ │ │ ├── SampleSessionWebFluxRedisApplication.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── application.properties │ ├── spring-boot-smoke-test-session-jdbc/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── session/ │ │ │ │ ├── HelloRestController.java │ │ │ │ ├── SampleSessionJdbcApplication.java │ │ │ │ ├── SecurityConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── session/ │ │ └── SampleSessionJdbcApplicationTests.java │ ├── spring-boot-smoke-test-simple/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── simple/ │ │ │ │ ├── ExitException.java │ │ │ │ ├── SampleConfigurationProperties.java │ │ │ │ ├── SampleSimpleApplication.java │ │ │ │ ├── package-info.java │ │ │ │ └── service/ │ │ │ │ ├── HelloWorldService.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── banner.txt │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── simple/ │ │ ├── SampleSimpleApplicationTests.java │ │ └── SpringTestSampleSimpleApplicationTests.java │ ├── spring-boot-smoke-test-structured-logging/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── structuredlogging/ │ │ │ │ ├── CustomStructuredLogFormatter.java │ │ │ │ ├── DuplicateJsonMembersCustomizer.java │ │ │ │ ├── SampleJsonMembersCustomizer.java │ │ │ │ ├── SampleStructuredLoggingApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── custom-logback.xml │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── structuredlogging/ │ │ └── SampleStructuredLoggingApplicationTests.java │ ├── spring-boot-smoke-test-structured-logging-log4j2/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── structuredlogging/ │ │ │ │ └── log4j2/ │ │ │ │ ├── CustomStructuredLogFormatter.java │ │ │ │ ├── DuplicateJsonMembersCustomizer.java │ │ │ │ ├── SampleLog4j2StructuredLoggingApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── structuredlogging/ │ │ └── log4j2/ │ │ └── SampleLog4j2StructuredLoggingApplicationTests.java │ ├── spring-boot-smoke-test-test/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── test/ │ │ │ │ ├── SampleTestApplication.java │ │ │ │ ├── WelcomeCommandLineRunner.java │ │ │ │ ├── domain/ │ │ │ │ │ ├── User.java │ │ │ │ │ ├── UserRepository.java │ │ │ │ │ ├── VehicleIdentificationNumber.java │ │ │ │ │ ├── VehicleIdentificationNumberAttributeConverter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── service/ │ │ │ │ │ ├── RemoteVehicleDetailsService.java │ │ │ │ │ ├── ServiceProperties.java │ │ │ │ │ ├── VehicleDetails.java │ │ │ │ │ ├── VehicleDetailsService.java │ │ │ │ │ ├── VehicleIdentificationNumberNotFoundException.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── UserNameNotFoundException.java │ │ │ │ ├── UserVehicleController.java │ │ │ │ ├── UserVehicleService.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── test/ │ │ │ ├── SampleTestApplicationWebIntegrationTests.java │ │ │ ├── domain/ │ │ │ │ ├── UserEntityTests.java │ │ │ │ ├── UserRepositoryTests.java │ │ │ │ └── VehicleIdentificationNumberTests.java │ │ │ ├── service/ │ │ │ │ ├── RemoteVehicleDetailsServiceTests.java │ │ │ │ └── VehicleDetailsJsonTests.java │ │ │ └── web/ │ │ │ ├── UserVehicleControllerApplicationTests.java │ │ │ ├── UserVehicleControllerHtmlUnitTests.java │ │ │ ├── UserVehicleControllerSeleniumTests.java │ │ │ ├── UserVehicleControllerTests.java │ │ │ └── UserVehicleServiceTests.java │ │ └── resources/ │ │ ├── application.properties │ │ ├── data.sql │ │ └── smoketest/ │ │ └── test/ │ │ └── service/ │ │ └── vehicledetails.json │ ├── spring-boot-smoke-test-test-nomockito/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── smoketest/ │ │ │ └── testnomockito/ │ │ │ ├── SampleTestNoMockitoApplication.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── testnomockito/ │ │ └── SampleTestNoMockitoApplicationTests.java │ ├── spring-boot-smoke-test-testng/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── testng/ │ │ │ │ ├── SampleTestNGApplication.java │ │ │ │ ├── package-info.java │ │ │ │ ├── service/ │ │ │ │ │ ├── HelloWorldService.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── SampleController.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── public/ │ │ │ └── test.css │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── testng/ │ │ └── SampleTestNGApplicationTests.java │ ├── spring-boot-smoke-test-tomcat/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── tomcat/ │ │ │ │ ├── SampleTomcatApplication.java │ │ │ │ ├── package-info.java │ │ │ │ ├── service/ │ │ │ │ │ ├── HelloWorldService.java │ │ │ │ │ ├── HttpHeaderService.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── util/ │ │ │ │ │ ├── RandomStringUtil.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── SampleController.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── tomcat/ │ │ ├── NonAutoConfigurationSampleTomcatApplicationTests.java │ │ └── SampleTomcatApplicationTests.java │ ├── spring-boot-smoke-test-tomcat-jsp/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── tomcat/ │ │ │ │ └── jsp/ │ │ │ │ ├── MyException.java │ │ │ │ ├── MyRestResponse.java │ │ │ │ ├── SampleTomcatJspApplication.java │ │ │ │ ├── WelcomeController.java │ │ │ │ └── package-info.java │ │ │ ├── resources/ │ │ │ │ └── application.properties │ │ │ └── webapp/ │ │ │ └── WEB-INF/ │ │ │ └── jsp/ │ │ │ └── welcome.jsp │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── tomcat/ │ │ └── jsp/ │ │ └── SampleWebJspApplicationTests.java │ ├── spring-boot-smoke-test-tomcat-multi-connectors/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── tomcat/ │ │ │ │ └── multiconnector/ │ │ │ │ ├── SampleTomcatTwoConnectorsApplication.java │ │ │ │ ├── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── SampleController.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── sample.jks │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── tomcat/ │ │ └── multiconnector/ │ │ └── SampleTomcatTwoConnectorsApplicationTests.java │ ├── spring-boot-smoke-test-tomcat-ssl/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── tomcat/ │ │ │ │ └── ssl/ │ │ │ │ ├── SampleTomcatSslApplication.java │ │ │ │ ├── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── SampleController.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── sample.jks │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── tomcat/ │ │ └── ssl/ │ │ └── SampleTomcatSslApplicationTests.java │ ├── spring-boot-smoke-test-traditional/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── traditional/ │ │ │ │ ├── SampleTraditionalApplication.java │ │ │ │ ├── config/ │ │ │ │ │ ├── WebConfig.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── resources/ │ │ │ │ └── application.properties │ │ │ └── webapp/ │ │ │ ├── WEB-INF/ │ │ │ │ ├── views/ │ │ │ │ │ └── home.jsp │ │ │ │ └── web.xml │ │ │ └── index.html │ │ └── test/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── traditional/ │ │ │ └── SampleTraditionalApplicationTests.java │ │ └── resources/ │ │ └── log4j.properties │ ├── spring-boot-smoke-test-war/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── war/ │ │ │ │ ├── MyController.java │ │ │ │ ├── SampleWarApplication.java │ │ │ │ └── package-info.java │ │ │ └── webapp/ │ │ │ ├── WEB-INF/ │ │ │ │ └── custom.properties │ │ │ └── webapp.txt │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── war/ │ │ └── WarApplicationResourceTests.java │ ├── spring-boot-smoke-test-web-application-type/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── webapplicationtype/ │ │ │ │ ├── SampleWebApplicationTypeApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── webapplicationtype/ │ │ ├── OverriddenWebApplicationTypeApplicationTests.java │ │ ├── SampleWebApplicationTypeApplicationTests.java │ │ ├── WebApplicationTypeIntegrationTests.java │ │ └── WebEnvironmentNoneOverridesWebApplicationTypeTests.java │ ├── spring-boot-smoke-test-web-freemarker/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── freemarker/ │ │ │ │ ├── SampleWebFreeMarkerApplication.java │ │ │ │ ├── WelcomeController.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── templates/ │ │ │ ├── error/ │ │ │ │ └── 507.ftlh │ │ │ ├── error.ftlh │ │ │ └── welcome.ftlh │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── freemarker/ │ │ └── SampleWebFreeMarkerApplicationTests.java │ ├── spring-boot-smoke-test-web-groovy-templates/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── groovytemplates/ │ │ │ │ ├── InMemoryMessageRepository.java │ │ │ │ ├── Message.java │ │ │ │ ├── MessageRepository.java │ │ │ │ ├── SampleGroovyTemplateApplication.java │ │ │ │ ├── mvc/ │ │ │ │ │ ├── MessageController.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ ├── static/ │ │ │ │ └── js/ │ │ │ │ ├── jquery-1.7.2.js │ │ │ │ └── jquery.validate.js │ │ │ └── templates/ │ │ │ ├── layout.tpl │ │ │ └── messages/ │ │ │ ├── form.tpl │ │ │ ├── list.tpl │ │ │ └── view.tpl │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── groovytemplates/ │ │ ├── MessageControllerWebTests.java │ │ └── SampleGroovyTemplateApplicationTests.java │ ├── spring-boot-smoke-test-web-jsp/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── jsp/ │ │ │ │ ├── SampleWebJspApplication.java │ │ │ │ ├── WelcomeController.java │ │ │ │ └── package-info.java │ │ │ ├── resources/ │ │ │ │ └── application.properties │ │ │ └── webapp/ │ │ │ └── WEB-INF/ │ │ │ └── jsp/ │ │ │ ├── error.jsp │ │ │ └── welcome.jsp │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── jsp/ │ │ └── SampleWebJspApplicationTests.java │ ├── spring-boot-smoke-test-web-method-security/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── security/ │ │ │ │ └── method/ │ │ │ │ ├── SampleMethodSecurityApplication.java │ │ │ │ └── package-info.java │ │ │ ├── resources/ │ │ │ │ └── application.properties │ │ │ └── webapp/ │ │ │ └── templates/ │ │ │ ├── access.html │ │ │ ├── home.html │ │ │ └── login.html │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── security/ │ │ └── method/ │ │ └── SampleMethodSecurityApplicationTests.java │ ├── spring-boot-smoke-test-web-mustache/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── mustache/ │ │ │ │ ├── SampleWebMustacheApplication.java │ │ │ │ ├── WelcomeController.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ ├── public/ │ │ │ │ └── error/ │ │ │ │ ├── 503.html │ │ │ │ └── 5xx.html │ │ │ └── templates/ │ │ │ ├── error/ │ │ │ │ └── 507.mustache │ │ │ ├── error.mustache │ │ │ └── welcome.mustache │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── mustache/ │ │ └── SampleWebMustacheApplicationTests.java │ ├── spring-boot-smoke-test-web-secure/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── web/ │ │ │ │ └── secure/ │ │ │ │ ├── SampleWebSecureApplication.java │ │ │ │ └── package-info.java │ │ │ ├── resources/ │ │ │ │ └── application.properties │ │ │ └── webapp/ │ │ │ └── templates/ │ │ │ ├── home.html │ │ │ └── login.html │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── web/ │ │ └── secure/ │ │ ├── AbstractErrorPageTests.java │ │ ├── AbstractUnauthenticatedErrorPageTests.java │ │ ├── CustomContextPathErrorPageTests.java │ │ ├── CustomContextPathUnauthenticatedErrorPageTests.java │ │ ├── CustomServletPathErrorPageTests.java │ │ ├── CustomServletPathUnauthenticatedErrorPageTests.java │ │ ├── ErrorPageTests.java │ │ ├── NoSessionErrorPageTests.java │ │ ├── SampleWebSecureApplicationTests.java │ │ └── UnauthenticatedErrorPageTests.java │ ├── spring-boot-smoke-test-web-secure-custom/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── web/ │ │ │ │ └── secure/ │ │ │ │ └── custom/ │ │ │ │ ├── SampleWebSecureCustomApplication.java │ │ │ │ └── package-info.java │ │ │ ├── resources/ │ │ │ │ └── application.properties │ │ │ └── webapp/ │ │ │ └── templates/ │ │ │ ├── home.html │ │ │ └── login.html │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── web/ │ │ └── secure/ │ │ └── custom/ │ │ └── SampleWebSecureCustomApplicationTests.java │ ├── spring-boot-smoke-test-web-secure-jdbc/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── web/ │ │ │ │ └── secure/ │ │ │ │ └── jdbc/ │ │ │ │ ├── SampleWebSecureJdbcApplication.java │ │ │ │ └── package-info.java │ │ │ ├── resources/ │ │ │ │ ├── application.properties │ │ │ │ ├── data.sql │ │ │ │ └── schema.sql │ │ │ └── webapp/ │ │ │ └── templates/ │ │ │ ├── home.html │ │ │ └── login.html │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── web/ │ │ └── secure/ │ │ └── jdbc/ │ │ └── SampleWebSecureJdbcApplicationTests.java │ ├── spring-boot-smoke-test-web-static/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── web/ │ │ │ │ └── staticcontent/ │ │ │ │ ├── SampleWebStaticApplication.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── static/ │ │ │ └── index.html │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── web/ │ │ └── staticcontent/ │ │ └── SampleWebStaticApplicationTests.java │ ├── spring-boot-smoke-test-web-thymeleaf/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── web/ │ │ │ │ └── thymeleaf/ │ │ │ │ ├── InMemoryMessageRepository.java │ │ │ │ ├── Message.java │ │ │ │ ├── MessageRepository.java │ │ │ │ ├── SampleWebUiApplication.java │ │ │ │ ├── mvc/ │ │ │ │ │ ├── MessageController.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ ├── logback.xml │ │ │ ├── messages.properties │ │ │ └── templates/ │ │ │ ├── fragments.html │ │ │ └── messages/ │ │ │ ├── form.html │ │ │ ├── list.html │ │ │ └── view.html │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── web/ │ │ └── thymeleaf/ │ │ ├── MessageControllerWebTests.java │ │ └── SampleWebUiApplicationTests.java │ ├── spring-boot-smoke-test-webflux/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── webflux/ │ │ │ │ ├── EchoHandler.java │ │ │ │ ├── ExampleController.java │ │ │ │ ├── SampleWebFluxApplication.java │ │ │ │ ├── WelcomeController.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── templates/ │ │ │ └── error/ │ │ │ ├── 404.mustache │ │ │ ├── 4xx.mustache │ │ │ └── error.mustache │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── webflux/ │ │ ├── ApplicationStartupSpringBootContextLoader.java │ │ ├── SampleWebFluxApplicationActuatorDifferentPortTests.java │ │ ├── SampleWebFluxApplicationActuatorIsolatedJsonMapperFalseTests.java │ │ ├── SampleWebFluxApplicationActuatorIsolatedJsonMapperTrueTests.java │ │ ├── SampleWebFluxApplicationIntegrationTests.java │ │ └── SampleWebFluxApplicationTests.java │ ├── spring-boot-smoke-test-webflux-coroutines/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── kotlin/ │ │ │ └── smoketest/ │ │ │ └── coroutines/ │ │ │ ├── CoroutinesController.kt │ │ │ └── SampleCoroutinesApplication.kt │ │ └── test/ │ │ └── kotlin/ │ │ └── smoketest/ │ │ └── coroutines/ │ │ └── CoroutinesControllerTests.kt │ ├── spring-boot-smoke-test-webservices/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── webservices/ │ │ │ │ ├── SampleWebServicesApplication.java │ │ │ │ ├── WebServiceConfig.java │ │ │ │ ├── endpoint/ │ │ │ │ │ ├── HolidayEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── service/ │ │ │ │ ├── HumanResourceService.java │ │ │ │ ├── StubHumanResourceService.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── schemas/ │ │ │ │ └── hr.xsd │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── webservices/ │ │ ├── SampleWsApplicationTests.java │ │ └── WebServiceServerTestSampleWsApplicationTests.java │ ├── spring-boot-smoke-test-websocket-jetty/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── websocket/ │ │ │ │ └── jetty/ │ │ │ │ ├── SampleJettyWebSocketsApplication.java │ │ │ │ ├── client/ │ │ │ │ │ ├── GreetingService.java │ │ │ │ │ ├── SimpleClientWebSocketHandler.java │ │ │ │ │ ├── SimpleGreetingService.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── echo/ │ │ │ │ │ ├── DefaultEchoService.java │ │ │ │ │ ├── EchoService.java │ │ │ │ │ ├── EchoWebSocketHandler.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── reverse/ │ │ │ │ │ ├── ReverseWebSocketEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ └── snake/ │ │ │ │ ├── Direction.java │ │ │ │ ├── Location.java │ │ │ │ ├── Snake.java │ │ │ │ ├── SnakeTimer.java │ │ │ │ ├── SnakeUtils.java │ │ │ │ ├── SnakeWebSocketHandler.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── static/ │ │ │ ├── echo.html │ │ │ ├── index.html │ │ │ ├── reverse.html │ │ │ └── snake.html │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── websocket/ │ │ └── jetty/ │ │ ├── SampleWebSocketsApplicationTests.java │ │ ├── echo/ │ │ │ └── CustomContainerWebSocketsApplicationTests.java │ │ └── snake/ │ │ └── SnakeTimerTests.java │ ├── spring-boot-smoke-test-websocket-tomcat/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── smoketest/ │ │ │ │ └── websocket/ │ │ │ │ └── tomcat/ │ │ │ │ ├── SampleTomcatWebSocketApplication.java │ │ │ │ ├── client/ │ │ │ │ │ ├── GreetingService.java │ │ │ │ │ ├── SimpleClientWebSocketHandler.java │ │ │ │ │ ├── SimpleGreetingService.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── echo/ │ │ │ │ │ ├── DefaultEchoService.java │ │ │ │ │ ├── EchoService.java │ │ │ │ │ ├── EchoWebSocketHandler.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── reverse/ │ │ │ │ │ ├── ReverseWebSocketEndpoint.java │ │ │ │ │ └── package-info.java │ │ │ │ └── snake/ │ │ │ │ ├── Direction.java │ │ │ │ ├── Location.java │ │ │ │ ├── Snake.java │ │ │ │ ├── SnakeTimer.java │ │ │ │ ├── SnakeUtils.java │ │ │ │ ├── SnakeWebSocketHandler.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── static/ │ │ │ ├── echo.html │ │ │ ├── index.html │ │ │ ├── reverse.html │ │ │ └── snake.html │ │ └── test/ │ │ └── java/ │ │ └── smoketest/ │ │ └── websocket/ │ │ └── tomcat/ │ │ ├── SampleWebSocketsApplicationTests.java │ │ ├── echo/ │ │ │ └── CustomContainerWebSocketsApplicationTests.java │ │ └── snake/ │ │ └── SnakeTimerTests.java │ └── spring-boot-smoke-test-xml/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── smoketest/ │ │ │ └── xml/ │ │ │ ├── SampleSpringXmlApplication.java │ │ │ ├── package-info.java │ │ │ └── service/ │ │ │ ├── HelloWorldService.java │ │ │ ├── OtherService.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── application-context.xml │ └── test/ │ ├── java/ │ │ └── smoketest/ │ │ └── xml/ │ │ ├── SampleSpringXmlApplicationTests.java │ │ └── SampleSpringXmlPlaceholderBeanDefinitionTests.java │ └── resources/ │ ├── META-INF/ │ │ └── context.xml │ └── application.properties ├── starter/ │ ├── README.adoc │ ├── spring-boot-starter/ │ │ └── build.gradle │ ├── spring-boot-starter-activemq/ │ │ └── build.gradle │ ├── spring-boot-starter-activemq-test/ │ │ └── build.gradle │ ├── spring-boot-starter-actuator/ │ │ └── build.gradle │ ├── spring-boot-starter-actuator-test/ │ │ └── build.gradle │ ├── spring-boot-starter-amqp/ │ │ └── build.gradle │ ├── spring-boot-starter-amqp-test/ │ │ └── build.gradle │ ├── spring-boot-starter-artemis/ │ │ └── build.gradle │ ├── spring-boot-starter-artemis-test/ │ │ └── build.gradle │ ├── spring-boot-starter-aspectj/ │ │ └── build.gradle │ ├── spring-boot-starter-aspectj-test/ │ │ └── build.gradle │ ├── spring-boot-starter-batch/ │ │ └── build.gradle │ ├── spring-boot-starter-batch-data-mongodb/ │ │ └── build.gradle │ ├── spring-boot-starter-batch-data-mongodb-test/ │ │ └── build.gradle │ ├── spring-boot-starter-batch-jdbc/ │ │ └── build.gradle │ ├── spring-boot-starter-batch-jdbc-test/ │ │ └── build.gradle │ ├── spring-boot-starter-batch-test/ │ │ └── build.gradle │ ├── spring-boot-starter-cache/ │ │ └── build.gradle │ ├── spring-boot-starter-cache-test/ │ │ └── build.gradle │ ├── spring-boot-starter-cassandra/ │ │ └── build.gradle │ ├── spring-boot-starter-cassandra-test/ │ │ └── build.gradle │ ├── spring-boot-starter-classic/ │ │ └── build.gradle │ ├── spring-boot-starter-cloudfoundry/ │ │ └── build.gradle │ ├── spring-boot-starter-cloudfoundry-test/ │ │ └── build.gradle │ ├── spring-boot-starter-couchbase/ │ │ └── build.gradle │ ├── spring-boot-starter-couchbase-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-cassandra/ │ │ └── build.gradle │ ├── spring-boot-starter-data-cassandra-reactive/ │ │ └── build.gradle │ ├── spring-boot-starter-data-cassandra-reactive-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-cassandra-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-couchbase/ │ │ └── build.gradle │ ├── spring-boot-starter-data-couchbase-reactive/ │ │ └── build.gradle │ ├── spring-boot-starter-data-couchbase-reactive-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-couchbase-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-elasticsearch/ │ │ └── build.gradle │ ├── spring-boot-starter-data-elasticsearch-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-jdbc/ │ │ └── build.gradle │ ├── spring-boot-starter-data-jdbc-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-jpa/ │ │ └── build.gradle │ ├── spring-boot-starter-data-jpa-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-ldap/ │ │ └── build.gradle │ ├── spring-boot-starter-data-ldap-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-mongodb/ │ │ └── build.gradle │ ├── spring-boot-starter-data-mongodb-reactive/ │ │ └── build.gradle │ ├── spring-boot-starter-data-mongodb-reactive-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-mongodb-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-neo4j/ │ │ └── build.gradle │ ├── spring-boot-starter-data-neo4j-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-r2dbc/ │ │ └── build.gradle │ ├── spring-boot-starter-data-r2dbc-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-redis/ │ │ └── build.gradle │ ├── spring-boot-starter-data-redis-reactive/ │ │ └── build.gradle │ ├── spring-boot-starter-data-redis-reactive-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-redis-test/ │ │ └── build.gradle │ ├── spring-boot-starter-data-rest/ │ │ └── build.gradle │ ├── spring-boot-starter-data-rest-test/ │ │ └── build.gradle │ ├── spring-boot-starter-elasticsearch/ │ │ └── build.gradle │ ├── spring-boot-starter-elasticsearch-test/ │ │ └── build.gradle │ ├── spring-boot-starter-flyway/ │ │ └── build.gradle │ ├── spring-boot-starter-flyway-test/ │ │ └── build.gradle │ ├── spring-boot-starter-freemarker/ │ │ └── build.gradle │ ├── spring-boot-starter-freemarker-test/ │ │ └── build.gradle │ ├── spring-boot-starter-graphql/ │ │ └── build.gradle │ ├── spring-boot-starter-graphql-test/ │ │ └── build.gradle │ ├── spring-boot-starter-groovy-templates/ │ │ └── build.gradle │ ├── spring-boot-starter-groovy-templates-test/ │ │ └── build.gradle │ ├── spring-boot-starter-grpc-client/ │ │ └── build.gradle │ ├── spring-boot-starter-grpc-server/ │ │ └── build.gradle │ ├── spring-boot-starter-grpc-test/ │ │ └── build.gradle │ ├── spring-boot-starter-gson/ │ │ └── build.gradle │ ├── spring-boot-starter-gson-test/ │ │ └── build.gradle │ ├── spring-boot-starter-hateoas/ │ │ └── build.gradle │ ├── spring-boot-starter-hateoas-test/ │ │ └── build.gradle │ ├── spring-boot-starter-hazelcast/ │ │ └── build.gradle │ ├── spring-boot-starter-hazelcast-test/ │ │ └── build.gradle │ ├── spring-boot-starter-integration/ │ │ └── build.gradle │ ├── spring-boot-starter-integration-test/ │ │ └── build.gradle │ ├── spring-boot-starter-jackson/ │ │ └── build.gradle │ ├── spring-boot-starter-jackson-test/ │ │ └── build.gradle │ ├── spring-boot-starter-jdbc/ │ │ └── build.gradle │ ├── spring-boot-starter-jdbc-test/ │ │ └── build.gradle │ ├── spring-boot-starter-jersey/ │ │ └── build.gradle │ ├── spring-boot-starter-jersey-test/ │ │ └── build.gradle │ ├── spring-boot-starter-jetty/ │ │ └── build.gradle │ ├── spring-boot-starter-jetty-runtime/ │ │ └── build.gradle │ ├── spring-boot-starter-jms/ │ │ └── build.gradle │ ├── spring-boot-starter-jms-test/ │ │ └── build.gradle │ ├── spring-boot-starter-jooq/ │ │ └── build.gradle │ ├── spring-boot-starter-jooq-test/ │ │ └── build.gradle │ ├── spring-boot-starter-json/ │ │ └── build.gradle │ ├── spring-boot-starter-jsonb/ │ │ └── build.gradle │ ├── spring-boot-starter-jsonb-test/ │ │ └── build.gradle │ ├── spring-boot-starter-kafka/ │ │ └── build.gradle │ ├── spring-boot-starter-kafka-test/ │ │ └── build.gradle │ ├── spring-boot-starter-kotlinx-serialization-json/ │ │ └── build.gradle │ ├── spring-boot-starter-kotlinx-serialization-json-test/ │ │ └── build.gradle │ ├── spring-boot-starter-ldap/ │ │ └── build.gradle │ ├── spring-boot-starter-ldap-test/ │ │ └── build.gradle │ ├── spring-boot-starter-liquibase/ │ │ └── build.gradle │ ├── spring-boot-starter-liquibase-test/ │ │ └── build.gradle │ ├── spring-boot-starter-log4j2/ │ │ └── build.gradle │ ├── spring-boot-starter-logback/ │ │ └── build.gradle │ ├── spring-boot-starter-logging/ │ │ └── build.gradle │ ├── spring-boot-starter-mail/ │ │ └── build.gradle │ ├── spring-boot-starter-mail-test/ │ │ └── build.gradle │ ├── spring-boot-starter-micrometer-metrics/ │ │ └── build.gradle │ ├── spring-boot-starter-micrometer-metrics-test/ │ │ └── build.gradle │ ├── spring-boot-starter-mongodb/ │ │ └── build.gradle │ ├── spring-boot-starter-mongodb-test/ │ │ └── build.gradle │ ├── spring-boot-starter-mustache/ │ │ └── build.gradle │ ├── spring-boot-starter-mustache-test/ │ │ └── build.gradle │ ├── spring-boot-starter-neo4j/ │ │ └── build.gradle │ ├── spring-boot-starter-neo4j-test/ │ │ └── build.gradle │ ├── spring-boot-starter-oauth2-authorization-server/ │ │ └── build.gradle │ ├── spring-boot-starter-oauth2-client/ │ │ └── build.gradle │ ├── spring-boot-starter-oauth2-resource-server/ │ │ └── build.gradle │ ├── spring-boot-starter-opentelemetry/ │ │ └── build.gradle │ ├── spring-boot-starter-opentelemetry-test/ │ │ └── build.gradle │ ├── spring-boot-starter-parent/ │ │ └── build.gradle │ ├── spring-boot-starter-pulsar/ │ │ └── build.gradle │ ├── spring-boot-starter-pulsar-test/ │ │ └── build.gradle │ ├── spring-boot-starter-quartz/ │ │ └── build.gradle │ ├── spring-boot-starter-quartz-test/ │ │ └── build.gradle │ ├── spring-boot-starter-r2dbc/ │ │ └── build.gradle │ ├── spring-boot-starter-r2dbc-test/ │ │ └── build.gradle │ ├── spring-boot-starter-reactor-netty/ │ │ └── build.gradle │ ├── spring-boot-starter-restclient/ │ │ └── build.gradle │ ├── spring-boot-starter-restclient-test/ │ │ └── build.gradle │ ├── spring-boot-starter-restdocs/ │ │ └── build.gradle │ ├── spring-boot-starter-rsocket/ │ │ └── build.gradle │ ├── spring-boot-starter-rsocket-test/ │ │ └── build.gradle │ ├── spring-boot-starter-security/ │ │ └── build.gradle │ ├── spring-boot-starter-security-oauth2-authorization-server/ │ │ └── build.gradle │ ├── spring-boot-starter-security-oauth2-authorization-server-test/ │ │ └── build.gradle │ ├── spring-boot-starter-security-oauth2-client/ │ │ └── build.gradle │ ├── spring-boot-starter-security-oauth2-client-test/ │ │ └── build.gradle │ ├── spring-boot-starter-security-oauth2-resource-server/ │ │ └── build.gradle │ ├── spring-boot-starter-security-oauth2-resource-server-test/ │ │ └── build.gradle │ ├── spring-boot-starter-security-saml2/ │ │ └── build.gradle │ ├── spring-boot-starter-security-saml2-test/ │ │ └── build.gradle │ ├── spring-boot-starter-security-test/ │ │ └── build.gradle │ ├── spring-boot-starter-sendgrid/ │ │ └── build.gradle │ ├── spring-boot-starter-sendgrid-test/ │ │ └── build.gradle │ ├── spring-boot-starter-session-data-redis/ │ │ └── build.gradle │ ├── spring-boot-starter-session-data-redis-test/ │ │ └── build.gradle │ ├── spring-boot-starter-session-jdbc/ │ │ └── build.gradle │ ├── spring-boot-starter-session-jdbc-test/ │ │ └── build.gradle │ ├── spring-boot-starter-test/ │ │ └── build.gradle │ ├── spring-boot-starter-test-classic/ │ │ └── build.gradle │ ├── spring-boot-starter-thymeleaf/ │ │ └── build.gradle │ ├── spring-boot-starter-thymeleaf-test/ │ │ └── build.gradle │ ├── spring-boot-starter-tomcat/ │ │ └── build.gradle │ ├── spring-boot-starter-tomcat-runtime/ │ │ └── build.gradle │ ├── spring-boot-starter-validation/ │ │ └── build.gradle │ ├── spring-boot-starter-validation-test/ │ │ └── build.gradle │ ├── spring-boot-starter-web/ │ │ └── build.gradle │ ├── spring-boot-starter-web-server-test/ │ │ └── build.gradle │ ├── spring-boot-starter-web-services/ │ │ └── build.gradle │ ├── spring-boot-starter-webclient/ │ │ └── build.gradle │ ├── spring-boot-starter-webclient-test/ │ │ └── build.gradle │ ├── spring-boot-starter-webflux/ │ │ └── build.gradle │ ├── spring-boot-starter-webflux-test/ │ │ └── build.gradle │ ├── spring-boot-starter-webmvc/ │ │ └── build.gradle │ ├── spring-boot-starter-webmvc-test/ │ │ └── build.gradle │ ├── spring-boot-starter-webservices/ │ │ └── build.gradle │ ├── spring-boot-starter-webservices-test/ │ │ └── build.gradle │ ├── spring-boot-starter-websocket/ │ │ └── build.gradle │ ├── spring-boot-starter-websocket-test/ │ │ └── build.gradle │ ├── spring-boot-starter-zipkin/ │ │ └── build.gradle │ └── spring-boot-starter-zipkin-test/ │ └── build.gradle ├── system-test/ │ ├── spring-boot-deployment-system-tests/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── sample/ │ │ │ │ ├── app/ │ │ │ │ │ ├── DeploymentTestApplication.java │ │ │ │ │ ├── SampleController.java │ │ │ │ │ └── package-info.java │ │ │ │ └── autoconfig/ │ │ │ │ ├── ExampleAutoConfiguration.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── application.yml │ │ └── systemTest/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── deployment/ │ │ ├── AbstractDeploymentTests.java │ │ ├── TomcatDeploymentTests.java │ │ └── package-info.java │ └── spring-boot-image-system-tests/ │ ├── build.gradle │ └── src/ │ └── systemTest/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── boot/ │ │ └── image/ │ │ ├── assertions/ │ │ │ ├── ContainerConfigAssert.java │ │ │ ├── ImageAssert.java │ │ │ ├── ImageAssertions.java │ │ │ └── package-info.java │ │ ├── junit/ │ │ │ ├── GradleBuildInjectionExtension.java │ │ │ └── package-info.java │ │ └── paketo/ │ │ ├── LayersIndex.java │ │ ├── PaketoBuilderTests.java │ │ └── package-info.java │ └── resources/ │ └── org/ │ └── springframework/ │ └── boot/ │ └── image/ │ └── paketo/ │ ├── PaketoBuilderTests-bootDistZipJarApp.gradle │ ├── PaketoBuilderTests-classDataSharingApp.gradle │ ├── PaketoBuilderTests-executableWarApp.gradle │ ├── PaketoBuilderTests-nativeApp.gradle │ ├── PaketoBuilderTests-plainDistZipJarApp.gradle │ ├── PaketoBuilderTests-plainWarApp.gradle │ ├── PaketoBuilderTests.gradle │ └── settings.gradle └── test-support/ ├── spring-boot-docker-test-support/ │ ├── build.gradle │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── springframework/ │ └── boot/ │ └── testsupport/ │ └── container/ │ ├── DisabledIfDockerUnavailable.java │ ├── DisabledIfDockerUnavailableCondition.java │ ├── ElasticsearchContainer9.java │ ├── HazelcastContainer.java │ ├── MailpitContainer.java │ ├── OpenLdapContainer.java │ ├── RedisStackServerContainer.java │ ├── RegistryContainer.java │ ├── SymptomaActiveMQContainer.java │ ├── TestImage.java │ ├── ZipkinContainer.java │ └── package-info.java ├── spring-boot-gradle-test-support/ │ ├── build.gradle │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── springframework/ │ └── boot/ │ └── testsupport/ │ └── gradle/ │ └── testkit/ │ ├── Dsl.java │ ├── GradleBuild.java │ ├── GradleBuildExtension.java │ ├── GradleVersions.java │ └── package-info.java └── spring-boot-test-support/ ├── build.gradle └── src/ ├── main/ │ └── java/ │ └── org/ │ └── springframework/ │ └── boot/ │ └── testsupport/ │ ├── BuildOutput.java │ ├── FileUtils.java │ ├── assertj/ │ │ ├── ScheduledExecutorServiceAssert.java │ │ ├── SimpleAsyncTaskExecutorAssert.java │ │ └── package-info.java │ ├── classpath/ │ │ ├── ClassPathExclusions.java │ │ ├── ClassPathOverrides.java │ │ ├── ForkedClassPath.java │ │ ├── ModifiedClassPathClassLoader.java │ │ ├── ModifiedClassPathExtension.java │ │ ├── package-info.java │ │ └── resources/ │ │ ├── Resource.java │ │ ├── ResourceContent.java │ │ ├── ResourcePath.java │ │ ├── Resources.java │ │ ├── ResourcesClassLoader.java │ │ ├── ResourcesExtension.java │ │ ├── ResourcesRoot.java │ │ ├── WithPackageResources.java │ │ ├── WithResource.java │ │ ├── WithResourceDirectories.java │ │ ├── WithResourceDirectory.java │ │ ├── WithResources.java │ │ └── package-info.java │ ├── junit/ │ │ ├── BooleanArgumentsProvider.java │ │ ├── BooleanValueSource.java │ │ ├── DisabledOnOs.java │ │ ├── DisabledOnOsCondition.java │ │ ├── EnabledOnLocale.java │ │ ├── EnabledOnLocaleCondition.java │ │ └── package-info.java │ ├── logging/ │ │ ├── ConfigureClasspathToPreferLog4j2.java │ │ └── package-info.java │ ├── package-info.java │ ├── process/ │ │ ├── DisabledIfProcessUnavailable.java │ │ ├── DisabledIfProcessUnavailableCondition.java │ │ ├── DisabledIfProcessUnavailables.java │ │ └── package-info.java │ ├── ssl/ │ │ ├── MockKeyStoreSpi.java │ │ ├── MockPkcs11Security.java │ │ ├── MockPkcs11SecurityProvider.java │ │ ├── MockPkcs11SecurityProviderExtension.java │ │ └── package-info.java │ ├── system/ │ │ ├── CapturedOutput.java │ │ ├── OutputCapture.java │ │ ├── OutputCaptureExtension.java │ │ └── package-info.java │ └── web/ │ └── servlet/ │ ├── DirtiesUrlFactories.java │ ├── DirtiesUrlFactoriesExtension.java │ ├── ExampleFilter.java │ ├── ExampleServlet.java │ └── package-info.java └── test/ ├── java/ │ └── org/ │ └── springframework/ │ └── boot/ │ └── testsupport/ │ ├── FileUtilsTests.java │ ├── assertj/ │ │ └── SimpleAsyncTaskExecutorAssertTests.java │ ├── classpath/ │ │ ├── ModifiedClassPathExtensionExclusionsTests.java │ │ ├── ModifiedClassPathExtensionForkParameterizedTests.java │ │ ├── ModifiedClassPathExtensionForkTests.java │ │ ├── ModifiedClassPathExtensionOverridesParameterizedTests.java │ │ ├── ModifiedClassPathExtensionOverridesTests.java │ │ └── resources/ │ │ ├── OnClassWithPackageResourcesTests.java │ │ ├── OnClassWithResourceTests.java │ │ ├── OnSuperClassWithPackageResourcesTests.java │ │ ├── OnSuperClassWithResourceTests.java │ │ ├── ResourcesTests.java │ │ ├── WithPackageResourcesClass.java │ │ ├── WithPackageResourcesTests.java │ │ ├── WithResourceClass.java │ │ ├── WithResourceDirectoryTests.java │ │ └── WithResourceTests.java │ └── process/ │ └── DisabledIfProcessUnavailableTests.java └── resources/ └── org/ └── springframework/ └── boot/ └── testsupport/ └── classpath/ └── resources/ ├── resource-1.txt ├── resource-2.txt └── sub/ └── resource-3.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root=true [*.{groovy,java,kt,xml}] indent_style = tab indent_size = 4 continuation_indent_size = 8 ================================================ FILE: .git-blame-ignore-revs ================================================ # .git-blame-ignore-revs # Reformat code following spring-javaformat upgrade df5898a1464112f185d295d585740de696934a12 c4de86c244acdcff69ed0aecacd254399be79ce2 b07269a018a4a9d4c029aba7dd8a15fa66df681c ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Community Support url: https://stackoverflow.com/tags/spring-boot about: Please ask and answer questions on StackOverflow with the tag `spring-boot`. ================================================ FILE: .github/ISSUE_TEMPLATE/issue.md ================================================ --- name: General about: Bugs, enhancements, documentation, tasks. title: '' labels: '' assignees: '' --- ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ================================================ FILE: .github/actions/await-http-resource/action.yml ================================================ name: Await HTTP Resource description: 'Waits for an HTTP resource to be available (a HEAD request succeeds)' inputs: url: description: 'URL of the resource to await' required: true runs: using: composite steps: - name: Await HTTP resource shell: bash run: | url=${{ inputs.url }} echo "Waiting for $url" until curl --fail --head --silent ${{ inputs.url }} > /dev/null do echo "." sleep 60 done echo "$url is available" ================================================ FILE: .github/actions/create-github-release/action.yml ================================================ name: Create GitHub Release description: 'Create the release on GitHub with a changelog' inputs: commercial: description: 'Whether to generate the changelog for the commercial release' required: true milestone: description: 'Name of the GitHub milestone for which a release will be created' required: true pre-release: description: 'Whether the release is a pre-release (a milestone or release candidate)' required: false default: 'false' token: description: 'Token to use for authentication with GitHub' required: true runs: using: composite steps: - name: Generate Changelog uses: spring-io/github-changelog-generator@f7d7a87a3e7c627ecb8c26cf086c38ac5a939721 #v0.0.14 with: config-file: ${{ inputs.commercial && '.github/actions/create-github-release/changelog-generator-commercial.yml' || '.github/actions/create-github-release/changelog-generator-oss.yml' }} milestone: ${{ inputs.milestone }} token: ${{ inputs.token }} - name: Create GitHub Release shell: bash env: GITHUB_TOKEN: ${{ inputs.token }} run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md ${{ inputs.pre-release == 'true' && '--prerelease' || '' }} ================================================ FILE: .github/actions/create-github-release/changelog-generator-commercial.yml ================================================ changelog: sections: - title: ":warning: Attention Required" labels: - "for: upgrade-attention" summary: mode: "member-comment" config: prefix: "Attention required:" - title: ":star: New Features" labels: - "type: enhancement" - title: ":lady_beetle: Bug Fixes" labels: - "type: bug" - "type: regression" - title: ":notebook_with_decorative_cover: Documentation" labels: - "type: documentation" - title: ":hammer: Dependency Upgrades" sort: "title" labels: - "type: dependency-upgrade" issues: generate_links: false ports: - label: "status: forward-port" bodyExpression: 'Forward port of issue #(\d+).*' - label: "status: back-port" bodyExpression: 'Back port of issue #(\d+).*' ================================================ FILE: .github/actions/create-github-release/changelog-generator-oss.yml ================================================ changelog: sections: - title: ":warning: Attention Required" labels: - "for: upgrade-attention" summary: mode: "member-comment" config: prefix: "Attention required:" - title: ":star: New Features" labels: - "type: enhancement" - title: ":lady_beetle: Bug Fixes" labels: - "type: bug" - "type: regression" - title: ":notebook_with_decorative_cover: Documentation" labels: - "type: documentation" - title: ":hammer: Dependency Upgrades" sort: "title" labels: - "type: dependency-upgrade" summary: mode: "body-regex" config: expression: '(Upgrade to \[.*\]\(.*\)).*' issues: generate_links: true ports: - label: "status: forward-port" bodyExpression: 'Forward port of issue #(\d+).*' - label: "status: back-port" bodyExpression: 'Back port of issue #(\d+).*' ================================================ FILE: .github/actions/prepare-gradle-build/action.yml ================================================ name: Prepare Gradle Build description: 'Prepares a Gradle build. Sets up Java and Gradle and configures Gradle properties' inputs: cache-read-only: description: 'Whether Gradle''s cache should be read only' required: false default: 'true' develocity-access-key: description: 'Access key for authentication with ge.spring.io' required: false java-distribution: description: 'Java distribution to use' required: false default: 'liberica' java-early-access: description: 'Whether the Java version is in early access. When true, forces java-distribution to temurin' required: false default: 'false' java-toolchain: description: 'Whether a Java toolchain should be used' required: false default: 'false' java-version: description: 'Java version to use for the build' required: false default: '25' runs: using: composite steps: - name: Free Disk Space if: ${{ runner.os == 'Linux' }} uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 with: tool-cache: true docker-images: false - name: Set Up Java uses: actions/setup-java@v5 with: distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || (inputs.java-distribution || 'liberica') }} java-version: | ${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }} ${{ inputs.java-toolchain == 'true' && '25' || '' }} - name: Set Up Gradle With Read/Write Cache if: ${{ inputs.cache-read-only == 'false' }} uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: cache-read-only: false develocity-access-key: ${{ inputs.develocity-access-key }} - name: Set Up Gradle uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: develocity-access-key: ${{ inputs.develocity-access-key }} develocity-token-expiry: 4 - name: Configure Gradle Properties shell: bash run: | mkdir -p $HOME/.gradle echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties - name: Configure Toolchain Properties if: ${{ inputs.java-toolchain == 'true' }} shell: bash run: | echo toolchainVersion=${{ inputs.java-version }} >> $HOME/.gradle/gradle.properties echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', inputs.java-version) }} >> $HOME/.gradle/gradle.properties ================================================ FILE: .github/actions/print-jvm-thread-dumps/action.yml ================================================ name: Print JVM thread dumps description: 'Prints a thread dump for all running JVMs' runs: using: composite steps: - if: ${{ runner.os == 'Linux' }} shell: bash run: | for jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem); do jcmd $jvm_pid Thread.print done - if: ${{ runner.os == 'Windows' }} shell: powershell run: | foreach ($jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem)) { jcmd $jvm_pid Thread.print } ================================================ FILE: .github/actions/publish-gradle-plugin/action.yml ================================================ name: Publish Gradle Plugin description: 'Publishes Spring Boot''s Gradle plugin to the Plugin Portal' inputs: build-number: description: 'Build number to use when downloading plugin artifacts' required: false default: ${{ github.run_number }} gradle-plugin-publish-key: description: 'Gradle publishing key' required: true gradle-plugin-publish-secret: description: 'Gradle publishing secret' required: true jfrog-cli-config-token: description: 'Config token for the JFrog CLI' required: true plugin-version: description: 'Version of the plugin' required: true runs: using: composite steps: - name: Set Up JFrog CLI uses: jfrog/setup-jfrog-cli@1641575d87647fb969c0545f0b6a76873e328b7c # v5.0.0 env: JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }} - name: Download Artifacts shell: bash run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-boot-{0}', inputs.plugin-version) }};buildNumber=${{ inputs.build-number }}' - name: Set Up Java uses: actions/setup-java@v5 with: distribution: 'liberica' java-version: '17' - name: Publish shell: bash working-directory: ${{ github.action_path }} run: ${{ github.workspace }}/gradlew publishExisting -Pgradle.publish.key=${{ inputs.gradle-plugin-publish-key }} -Pgradle.publish.secret=${{ inputs.gradle-plugin-publish-secret }} -PbootVersion=${{ inputs.plugin-version }} -PrepositoryRoot=${{ github.workspace }}/repository ================================================ FILE: .github/actions/publish-gradle-plugin/artifacts.spec ================================================ { "files": [ { "aql": { "items.find": { "$and": [ { "@build.name": "${buildName}", "@build.number": "${buildNumber}", "path": { "$match": "org/springframework/boot/spring-boot-gradle-plugin/*" } } ] } }, "target": "repository/" } ] } ================================================ FILE: .github/actions/publish-gradle-plugin/build.gradle ================================================ /* * 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. */ plugins { id "com.gradle.plugin-publish" version "1.2.1" } tasks.register("publishExisting", com.gradle.publish.PublishExistingTask) { pluginId = "org.springframework.boot" fileRepositoryRoot = new File("${repositoryRoot}") pluginVersion = "${bootVersion}" pluginCoordinates = "org.springframework.boot:spring-boot-gradle-plugin:${bootVersion}" displayName = "Spring Boot Gradle Plugin" pluginDescription = "Spring Boot Gradle Plugin" website = "https://spring.io/projects/spring-boot" vcsUrl = "https://github.com/spring-projects/spring-boot" } ================================================ FILE: .github/actions/publish-gradle-plugin/settings.gradle ================================================ /* * 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. */ ================================================ FILE: .github/actions/publish-to-sdkman/action.yml ================================================ name: Publish to SDKMAN! description: 'Publishes the release as a new candidate version on SDKMAN!' inputs: make-default: description: 'Whether the release should be made the default version' required: false default: 'false' sdkman-consumer-key: description: 'Key for publishing to SDKMAN!' required: true sdkman-consumer-token: description: 'Token for publishing to SDKMAN!' required: true spring-boot-version: description: 'Version to publish' required: true runs: using: composite steps: - name: Publish Release shell: bash run: > curl -X POST \ -H "Consumer-Key: ${{ inputs.sdkman-consumer-key }}" \ -H "Consumer-Token: ${{ inputs.sdkman-consumer-token }}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"candidate": "springboot", "version": "${{ inputs.spring-boot-version }}", "url": "${{ format('https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-cli/{0}/spring-boot-cli-{0}-bin.zip', inputs.spring-boot-version) }}"}' \ https://vendors.sdkman.io/release - name: Flag Release as Default if: ${{ inputs.make-default == 'true' }} shell: bash run: > curl -X PUT \ -H "Consumer-Key: ${{ inputs.sdkman-consumer-key }}" \ -H "Consumer-Token: ${{ inputs.sdkman-consumer-token }}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"candidate": "springboot", "version": "${{ inputs.spring-boot-version }}"}' \ https://vendors.sdkman.io/default ================================================ FILE: .github/actions/send-notification/action.yml ================================================ name: Send Notification description: 'Sends a Google Chat message as a notification of the job''s outcome' inputs: build-scan-url: description: 'URL of the build scan to include in the notification' required: false run-name: description: 'Name of the run to include in the notification' required: false default: ${{ format('{0} {1}', github.ref_name, github.job) }} status: description: 'Status of the job' required: true webhook-url: description: 'Google Chat Webhook URL' required: true runs: using: composite steps: - name: Prepare Variables shell: bash run: | echo "BUILD_SCAN=${{ inputs.build-scan-url == '' && ' [build scan unavailable]' || format(' [<{0}|Build Scan>]', inputs.build-scan-url) }}" >> "$GITHUB_ENV" echo "RUN_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_ENV" - name: Success Notification if: ${{ inputs.status == 'success' }} shell: bash run: | curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was successful ${{ env.BUILD_SCAN }}"}' || true - name: Failure Notification if: ${{ inputs.status == 'failure' }} shell: bash run: | curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: " *<${{ env.RUN_URL }}|${{ inputs.run-name }}> failed* ${{ env.BUILD_SCAN }}"}' || true - name: Cancel Notification if: ${{ inputs.status == 'cancelled' }} shell: bash run: | curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was cancelled"}' || true ================================================ FILE: .github/actions/sync-to-maven-central/action.yml ================================================ name: Sync to Maven Central description: 'Syncs a release to Maven Central and waits for it to be available for use' inputs: central-token-password: description: 'Password for authentication with central.sonatype.com' required: true central-token-username: description: 'Username for authentication with central.sonatype.com' required: true jfrog-cli-config-token: description: 'Config token for the JFrog CLI' required: true spring-boot-version: description: 'Version of Spring Boot that is being synced to Central' required: true runs: using: composite steps: - name: Set Up JFrog CLI uses: jfrog/setup-jfrog-cli@1641575d87647fb969c0545f0b6a76873e328b7c # v5.0.0 env: JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }} - name: Download Release Artifacts shell: bash run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-boot-{0}', inputs.spring-boot-version) }};buildNumber=${{ github.run_number }}' - name: Sync uses: spring-io/central-publish-action@0c03960e9b16fdfe70e2443e1d5393cbc3a35622 # v0.3.0 with: token: ${{ inputs.central-token-password }} token-name: ${{ inputs.central-token-username }} ignore-already-exists-error: true timeout: "90m" - name: Await uses: ./.github/actions/await-http-resource with: url: ${{ format('https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot/{0}/spring-boot-{0}.jar', inputs.spring-boot-version) }} ================================================ FILE: .github/actions/sync-to-maven-central/artifacts.spec ================================================ { "files": [ { "aql": { "items.find": { "$and": [ { "@build.name": "${buildName}", "@build.number": "${buildNumber}", "path": { "$nmatch": "org/springframework/boot/spring-boot-docs/*" } } ] } }, "target": "nexus/" } ] } ================================================ FILE: .github/actions/update-homebrew-tap/action.yml ================================================ name: Update Homebrew Tap description: Updates the Homebrew Tap for the Spring Boot CLI inputs: spring-boot-version: description: 'The version to publish' required: true token: description: 'Token to use for GitHub authentication' required: true runs: using: composite steps: - name: Check Out Homebrew Tap Repo uses: actions/checkout@v6 with: path: updated-homebrew-tap-repo repository: spring-io/homebrew-tap token: ${{ inputs.token }} - name: Await Formula uses: ./.github/actions/await-http-resource with: url: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-cli/${{ inputs.spring-boot-version }}/spring-boot-cli-${{ inputs.spring-boot-version }}-homebrew.rb - name: Update Homebrew Tap shell: bash run: | pushd updated-homebrew-tap-repo > /dev/null curl https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-cli/${{ inputs.spring-boot-version }}/spring-boot-cli-${{ inputs.spring-boot-version }}-homebrew.rb --output spring-boot-cli-${{ inputs.spring-boot-version }}-homebrew.rb rm spring-boot.rb mv spring-boot-cli-*.rb spring-boot.rb git config user.name "Spring Builds" > /dev/null git config user.email "spring-builds@users.noreply.github.com" > /dev/null git add spring-boot.rb > /dev/null git commit -m "Upgrade to Spring Boot ${{ inputs.spring-boot-version }}" > /dev/null git push echo "DONE" popd > /dev/null ================================================ FILE: .github/dco.yml ================================================ require: members: false ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" labels: - "type: task" - "status: waiting-for-triage" - package-ecosystem: "npm" directory: "/antora" schedule: interval: "weekly" labels: - "type: task" - "status: waiting-for-triage" ================================================ FILE: .github/scripts/reclaim-docker-diskspace.sh ================================================ #!/bin/bash echo "Reclaiming Docker Disk Space" echo docker image ls --format "{{.Size}} {{.ID}} {{.Repository}} {{.Tag}}" | LANG=en_US sort -rh | while read line; do size=$( echo "$line" | cut -d' ' -f1 | sed -e 's/\.[0-9]*//' | sed -e 's/MB/000000/' | sed -e 's/GB/000000000/' ) image=$( echo "$line" | cut -d' ' -f2 ) repository=$( echo "$line" | cut -d' ' -f3 ) tag=$( echo "$line" | cut -d' ' -f4 ) echo "Considering $image $repository:$tag $size" if [[ "$tag" =~ ^[a-f0-9]{32}$ ]]; then echo "Ignoring GitHub action image $image $repository:$tag" elif [[ "$tag" == "" ]]; then echo "Ignoring untagged image $image $repository:$tag" elif [[ "$size" -lt 200000000 ]]; then echo "Ignoring small image $image $repository:$tag" else echo "Cleaning $image $repository:$tag" docker image rm $image fi done echo "Finished cleanup, leaving the following containers:" echo docker image ls --format "{{.Size}} {{.ID}} {{.Repository}}:{{.Tag}}" | LANG=en_US sort -rh echo df -h echo ================================================ FILE: .github/workflows/build-and-deploy-snapshot.yml ================================================ name: Build and Deploy Snapshot on: workflow_dispatch: push: branches: - 'main' permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: build-and-deploy-snapshot: name: Build and Deploy Snapshot if: ${{ github.repository == 'spring-projects/spring-boot' || github.repository == 'spring-projects/spring-boot-commercial' }} runs-on: ${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} steps: - name: Check Out Code uses: actions/checkout@v6 - name: Build and Publish id: build-and-publish uses: ./.github/actions/build with: commercial-release-repository-url: ${{ vars.COMMERCIAL_RELEASE_REPO_URL }} commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }} commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }} commercial-snapshot-repository-url: ${{ vars.COMMERCIAL_SNAPSHOT_REPO_URL }} develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} gradle-cache-read-only: false publish: true - name: Deploy uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4 with: build-name: ${{ vars.COMMERCIAL && format('spring-boot-commercial-{0}', '4.1.x') || format('spring-boot-{0}', '4.1.x') }} folder: 'deployment-repository' password: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_ARTIFACTORY_PASSWORD || secrets.ARTIFACTORY_PASSWORD }} project: ${{ vars.COMMERCIAL && 'spring' }} repository: ${{ vars.COMMERCIAL && 'spring-enterprise-maven-dev-local' || 'libs-snapshot-local' }} signing-key: ${{ secrets.GPG_PRIVATE_KEY }} signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} threads: 8 uri: ${{ vars.COMMERCIAL_DEPLOY_REPO_URL || 'https://repo.spring.io' }} username: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_ARTIFACTORY_USERNAME || secrets.ARTIFACTORY_USERNAME }} - name: Send Notification if: always() uses: ./.github/actions/send-notification with: build-scan-url: ${{ steps.build-and-publish.outputs.build-scan-url }} run-name: ${{ format('{0} | Linux | Java 25', github.ref_name) }} status: ${{ job.status }} webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }} outputs: version: ${{ steps.build-and-publish.outputs.version }} trigger-docs-build: name: Trigger Docs Build needs: build-and-deploy-snapshot permissions: actions: write runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Run Deploy Docs Workflow env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh workflow run deploy-docs.yml --repo ${{ github.repository }} -r docs-build -f build-sha=${{ github.sha }} -f build-refname=${{ github.ref_name }} -f build-version=${{ needs.build-and-deploy-snapshot.outputs.version }} verify: name: Verify needs: build-and-deploy-snapshot uses: ./.github/workflows/verify.yml secrets: commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }} commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }} google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }} opensource-repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }} opensource-repository-username: ${{ secrets.ARTIFACTORY_USERNAME }} token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} with: version: ${{ needs.build-and-deploy-snapshot.outputs.version }} ================================================ FILE: .github/workflows/build-pull-request.yml ================================================ name: Build Pull Request on: pull_request permissions: contents: read jobs: build: name: Build Pull Request if: ${{ github.repository == 'spring-projects/spring-boot' }} runs-on: ${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} steps: - name: Check Out Code uses: actions/checkout@v6 - name: Build id: build uses: ./.github/actions/build - name: Print JVM Thread Dumps When Cancelled if: cancelled() uses: ./.github/actions/print-jvm-thread-dumps - name: Upload Build Reports if: failure() uses: actions/upload-artifact@v7 with: name: build-reports path: '**/build/reports/' ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: - 'main' permissions: contents: read jobs: ci: name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}' if: ${{ github.repository == 'spring-projects/spring-boot' || github.repository == 'spring-projects/spring-boot-commercial' }} runs-on: ${{ matrix.os.id }} strategy: fail-fast: false matrix: os: - id: ${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name: Linux - id: windows-latest name: Windows java: - version: 17 toolchain: true - version: 21 toolchain: true - version: 25 toolchain: false - version: 26 toolchain: true exclude: - os: name: Linux java: version: 25 - os: name: ${{ github.repository == 'spring-projects/spring-boot-commercial' && 'Windows' }} steps: - name: Prepare Windows runner if: ${{ runner.os == 'Windows' }} run: | git config --global core.autocrlf true git config --global core.longPaths true Stop-Service -name Docker - name: Check Out Code uses: actions/checkout@v6 - name: Build id: build uses: ./.github/actions/build with: commercial-release-repository-url: ${{ vars.COMMERCIAL_RELEASE_REPO_URL }} commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }} commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }} commercial-snapshot-repository-url: ${{ vars.COMMERCIAL_SNAPSHOT_REPO_URL }} develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} gradle-cache-read-only: false java-early-access: ${{ matrix.java.early-access || 'false' }} java-distribution: ${{ matrix.java.distribution }} java-toolchain: ${{ matrix.java.toolchain }} java-version: ${{ matrix.java.version }} - name: Send Notification if: always() uses: ./.github/actions/send-notification with: build-scan-url: ${{ steps.build.outputs.build-scan-url }} run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }} status: ${{ job.status }} webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }} ================================================ FILE: .github/workflows/distribute.yml ================================================ name: Distribute on: workflow_dispatch: inputs: build-number: description: 'Number of the build to use to create the bundle' required: true type: string create-bundle: description: 'Whether to create the bundle. If unchecked, only the bundle distribution is executed' required: true type: boolean default: true version: description: 'Version to bundle and distribute' required: true type: string permissions: contents: read jobs: distribute-spring-enterprise-release-bundle: runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Create Bundle if: ${{ vars.COMMERCIAL && inputs.create-bundle }} shell: bash run: | curl -s -u "${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}:${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}" \ -X POST -H "X-JFrog-Signing-Key-Name: packagesKey" -H "Content-Type: application/json" \ "https://usw1.packages.broadcom.com/lifecycle/api/v2/release_bundle?project=spring" \ -d '{"release_bundle_name": "TNZ-spring-boot-commercial", "release_bundle_version": "${{ inputs.version }}", "skip_docker_manifest_resolution": true, "source_type": "builds", "source": {"builds": [ {"build_repository": "spring-build-info", "build_name": "spring-boot-commercial-${{ inputs.version }}", "build_number": "${{ inputs.build-number }}", "include_dependencies": false}]}}' | \ jq -e 'if has("repository_key") then . else halt_error end' - name: Sleep if: ${{ vars.COMMERCIAL && inputs.create-bundle }} shell: bash run: sleep 30 - name: Distribute Bundle if: ${{ vars.COMMERCIAL }} shell: bash run: | curl -s -u "${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}:${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}" \ -X POST -H "Content-Type: application/json" \ "https://usw1.packages.broadcom.com/lifecycle/api/v2/distribution/distribute/TNZ-spring-boot-commercial/${{ inputs.version }}?project=spring" \ -d '{"auto_create_missing_repositories": "false", "distribution_rules": [{"site_name": "JP-SaaS"}], "modifications": {"mappings": [{"input": "spring-enterprise-maven-prod-local/(.*)", "output": "spring-enterprise/$1"}]}}' | \ jq -e 'if has("id") then . else halt_error end' ================================================ FILE: .github/workflows/release-milestone.yml ================================================ name: Release Milestone on: push: tags: - v4.1.0-M[0-9] - v4.1.0-RC[0-9] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: build-and-stage-release: name: Build and Stage Release if: ${{ github.repository == 'spring-projects/spring-boot' }} runs-on: ${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} steps: - name: Check Out Code uses: actions/checkout@v6 - name: Build and Publish id: build-and-publish uses: ./.github/actions/build with: develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} gradle-cache-read-only: false publish: true - name: Stage Release uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4 with: build-name: ${{ format('spring-boot-{0}', steps.build-and-publish.outputs.version)}} folder: 'deployment-repository' password: ${{ secrets.ARTIFACTORY_PASSWORD }} repository: 'libs-staging-local' signing-key: ${{ secrets.GPG_PRIVATE_KEY }} signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} threads: 8 uri: 'https://repo.spring.io' username: ${{ secrets.ARTIFACTORY_USERNAME }} outputs: version: ${{ steps.build-and-publish.outputs.version }} verify: name: Verify needs: build-and-stage-release uses: ./.github/workflows/verify.yml secrets: commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_RO_PASSWORD }} commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_RO_USERNAME }} google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }} opensource-repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }} opensource-repository-username: ${{ secrets.ARTIFACTORY_USERNAME }} token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} with: staging: true version: ${{ needs.build-and-stage-release.outputs.version }} sync-to-maven-central: name: Sync to Maven Central if: ${{ !vars.COMMERCIAL }} needs: - build-and-stage-release - verify runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Check Out Code uses: actions/checkout@v6 - name: Sync to Maven Central uses: ./.github/actions/sync-to-maven-central with: central-token-password: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} central-token-username: ${{ secrets.CENTRAL_TOKEN_USERNAME }} jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }} spring-boot-version: ${{ needs.build-and-stage-release.outputs.version }} promote-release: name: Promote Release needs: - build-and-stage-release - sync-to-maven-central runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Set up JFrog CLI uses: jfrog/setup-jfrog-cli@1641575d87647fb969c0545f0b6a76873e328b7c # v5.0.0 env: JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - name: Promote build run: jfrog rt build-promote ${{ format('spring-boot-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-milestone-local publish-gradle-plugin: name: Publish Gradle Plugin if: ${{ !vars.COMMERCIAL }} needs: - build-and-stage-release - sync-to-maven-central runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Check Out Code uses: actions/checkout@v6 - name: Publish uses: ./.github/actions/publish-gradle-plugin with: gradle-plugin-publish-key: ${{ secrets.GRADLE_PLUGIN_PUBLISH_KEY }} gradle-plugin-publish-secret: ${{ secrets.GRADLE_PLUGIN_PUBLISH_SECRET }} jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }} plugin-version: ${{ needs.build-and-stage-release.outputs.version }} trigger-docs-build: name: Trigger Docs Build needs: - build-and-stage-release - promote-release permissions: actions: write runs-on: ubuntu-latest steps: - name: Run Deploy Docs Workflow env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh workflow run deploy-docs.yml --repo ${{ github.repository }} -r docs-build -f build-refname=${{ github.ref_name }} -f build-version=${{ needs.build-and-stage-release.outputs.version }} create-github-release: name: Create GitHub Release needs: - build-and-stage-release - promote-release - publish-gradle-plugin - trigger-docs-build runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Check Out Code uses: actions/checkout@v6 - name: Create GitHub Release uses: ./.github/actions/create-github-release with: commercial: ${{ vars.COMMERCIAL }} milestone: ${{ needs.build-and-stage-release.outputs.version }} pre-release: true token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: tags: - v4.1.[0-9]+ permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: build-and-stage-release: name: Build and Stage Release if: ${{ github.repository == 'spring-projects/spring-boot' || github.repository == 'spring-projects/spring-boot-commercial' }} runs-on: ${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} steps: - name: Check Out Code uses: actions/checkout@v6 - name: Build and Publish id: build-and-publish uses: ./.github/actions/build with: commercial-release-repository-url: ${{ vars.COMMERCIAL_RELEASE_REPO_URL }} commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }} commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }} commercial-snapshot-repository-url: ${{ vars.COMMERCIAL_SNAPSHOT_REPO_URL }} develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} gradle-cache-read-only: false publish: true - name: Stage Release uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4 with: build-name: ${{ vars.COMMERCIAL && format('spring-boot-commercial-{0}', steps.build-and-publish.outputs.version) || format('spring-boot-{0}', steps.build-and-publish.outputs.version) }} folder: 'deployment-repository' password: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_ARTIFACTORY_PASSWORD || secrets.ARTIFACTORY_PASSWORD }} project: ${{ vars.COMMERCIAL && 'spring' }} repository: ${{ vars.COMMERCIAL && 'spring-enterprise-maven-stage-local' || 'libs-staging-local' }} signing-key: ${{ secrets.GPG_PRIVATE_KEY }} signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} threads: 8 uri: ${{ vars.COMMERCIAL_DEPLOY_REPO_URL || 'https://repo.spring.io' }} username: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_ARTIFACTORY_USERNAME || secrets.ARTIFACTORY_USERNAME }} - name: Send Notification if: failure() uses: ./.github/actions/send-notification with: run-name: ${{ format('{0} | Release Staging | {1}', github.ref_name, inputs.version) }} status: ${{ job.status }} webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }} outputs: version: ${{ steps.build-and-publish.outputs.version }} verify: name: Verify needs: build-and-stage-release uses: ./.github/workflows/verify.yml secrets: commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }} commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }} google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }} opensource-repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }} opensource-repository-username: ${{ secrets.ARTIFACTORY_USERNAME }} token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} with: staging: true version: ${{ needs.build-and-stage-release.outputs.version }} sync-to-maven-central: name: Sync to Maven Central if: ${{ !vars.COMMERCIAL }} needs: - build-and-stage-release - verify runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Check Out Code uses: actions/checkout@v6 - name: Sync to Maven Central uses: ./.github/actions/sync-to-maven-central with: central-token-password: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} central-token-username: ${{ secrets.CENTRAL_TOKEN_USERNAME }} jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }} spring-boot-version: ${{ needs.build-and-stage-release.outputs.version }} promote-release: name: Promote Release needs: - build-and-stage-release - sync-to-maven-central runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Set up JFrog CLI uses: jfrog/setup-jfrog-cli@1641575d87647fb969c0545f0b6a76873e328b7c # v5.0.0 env: JF_ENV_SPRING: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_JF_ARTIFACTORY_SPRING || secrets.JF_ARTIFACTORY_SPRING }} - name: Promote open source build if: ${{ !vars.COMMERCIAL }} run: jfrog rt build-promote ${{ format('spring-boot-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-release-local - name: Promote commercial build if: ${{ vars.COMMERCIAL }} run: jfrog rt build-promote ${{ format('spring-boot-commercial-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} spring-enterprise-maven-prod-local --project spring publish-gradle-plugin: name: Publish Gradle Plugin if: ${{ !vars.COMMERCIAL }} needs: - build-and-stage-release - sync-to-maven-central runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Check Out Code uses: actions/checkout@v6 - name: Publish uses: ./.github/actions/publish-gradle-plugin with: gradle-plugin-publish-key: ${{ secrets.GRADLE_PLUGIN_PUBLISH_KEY }} gradle-plugin-publish-secret: ${{ secrets.GRADLE_PLUGIN_PUBLISH_SECRET }} jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }} plugin-version: ${{ needs.build-and-stage-release.outputs.version }} publish-to-sdkman: name: Publish to SDKMAN! if: ${{ !vars.COMMERCIAL }} needs: - build-and-stage-release - sync-to-maven-central runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Check Out Code uses: actions/checkout@v6 - name: Publish to SDKMAN! uses: ./.github/actions/publish-to-sdkman with: make-default: true sdkman-consumer-key: ${{ secrets.SDKMAN_CONSUMER_KEY }} sdkman-consumer-token: ${{ secrets.SDKMAN_CONSUMER_TOKEN }} spring-boot-version: ${{ needs.build-and-stage-release.outputs.version }} update-homebrew-tap: name: Update Homebrew Tap needs: - build-and-stage-release - sync-to-maven-central runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Check Out Code uses: actions/checkout@v6 - name: Update Homebrew Tap uses: ./.github/actions/update-homebrew-tap with: spring-boot-version: ${{ needs.build-and-stage-release.outputs.version }} token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} trigger-docs-build: name: Trigger Docs Build needs: - build-and-stage-release - promote-release permissions: actions: write runs-on: ubuntu-latest steps: - name: Run Deploy Docs Workflow env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh workflow run deploy-docs.yml --repo ${{ github.repository }} -r docs-build -f build-refname=${{ github.ref_name }} -f build-version=${{ needs.build-and-stage-release.outputs.version }} create-github-release: name: Create GitHub Release needs: - build-and-stage-release - promote-release - publish-gradle-plugin - publish-to-sdkman - trigger-docs-build - update-homebrew-tap runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Check Out Code uses: actions/checkout@v6 - name: Create GitHub Release uses: ./.github/actions/create-github-release with: commercial: ${{ vars.COMMERCIAL }} milestone: ${{ needs.build-and-stage-release.outputs.version }} token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} ================================================ FILE: .github/workflows/run-codeql-analysis.yml ================================================ name: "Run CodeQL Analysis" on: push: pull_request: workflow_dispatch: permissions: read-all jobs: run-analysis: permissions: actions: read contents: read security-events: write uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@6e66995f7d29de1e4ff76e4f0def7a10163fe910 ================================================ FILE: .github/workflows/run-system-tests.yml ================================================ name: Run System Tests on: push: branches: - "main" permissions: contents: read jobs: run-system-tests: name: "Java ${{ matrix.java.version}}" if: ${{ github.repository == 'spring-projects/spring-boot' }} runs-on: ${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} strategy: matrix: java: - version: 17 toolchain: true - version: 21 toolchain: true steps: - name: Switch Docker to Overlay2 shell: bash run: | echo '{"storage-driver":"overlay2"}' | sudo tee /etc/docker/daemon.json sudo systemctl restart docker - name: Check Out Code uses: actions/checkout@v6 - name: Prepare Gradle Build uses: ./.github/actions/prepare-gradle-build with: develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} java-toolchain: ${{ matrix.java.toolchain }} java-version: ${{ matrix.java.version }} - name: Run System Tests id: run-system-tests shell: bash run: ./gradlew systemTest - name: Show docker info if: always() shell: bash run: docker version ; docker info - name: List docker images if: always() shell: bash run: docker image ls --digests - name: Send Notification if: always() uses: ./.github/actions/send-notification with: build-scan-url: ${{ steps.run-system-tests.outputs.build-scan-url }} run-name: ${{ format('{0} | System Tests | Java {1}', github.ref_name, matrix.java.version) }} status: ${{ job.status }} webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }} ================================================ FILE: .github/workflows/trigger-docs-build.yml ================================================ name: Trigger Docs Build on: push: branches: 'main' paths: [ 'antora/*' ] workflow_dispatch: inputs: build-version: description: 'Version being build (e.g. 1.0.3-SNAPSHOT)' required: false build-sha: description: Enter the SHA to build (e.g. 82c97891569821a7f91a77ca074232e0b54ca7a5) required: false build-refname: description: 'Git refname to build (e.g., 1.0.x)' required: false permissions: contents: read jobs: trigger-docs-build: name: Trigger Docs Build if: github.repository_owner == 'spring-projects' runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} permissions: actions: write steps: - name: Check Out uses: actions/checkout@v6 with: ref: docs-build - name: Trigger Workflow env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh workflow run deploy-docs.yml -r docs-build -f build-refname=${{ github.event.inputs.build-refname }} -f build-sha=${{ github.event.inputs.build-sha }} -f build-version=${{ github.event.inputs.build-version }} ================================================ FILE: .github/workflows/verify.yml ================================================ name: Verify on: workflow_call: inputs: staging: description: 'Whether the release to verify is in the staging repository' required: false default: false type: boolean version: description: 'Version to verify' required: true type: string secrets: commercial-repository-password: description: 'Password for authentication with the commercial repository' required: false commercial-repository-username: description: 'Username for authentication with the commercial repository' required: false google-chat-webhook-url: description: 'Google Chat Webhook URL' required: true opensource-repository-password: description: 'Password for authentication with the open-source repository' required: false opensource-repository-username: description: 'Username for authentication with the open-source repository' required: false token: description: 'Token to use for authentication with GitHub' required: true permissions: contents: read jobs: verify: name: Verify runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Check Out Release Verification Tests uses: actions/checkout@v6 with: ref: 'v0.0.15' repository: spring-projects/spring-boot-release-verification token: ${{ secrets.token }} - name: Check Out Send Notification Action uses: actions/checkout@v6 with: path: send-notification sparse-checkout: .github/actions/send-notification - name: Set Up Java uses: actions/setup-java@v5 with: distribution: 'liberica' java-version: 17 - name: Set Up Homebrew if: ${{ !vars.COMMERCIAL }} uses: Homebrew/actions/setup-homebrew@7657c9512f50e1c35b640971116425935bab3eea with: stable: true - name: Set Up Gradle uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: cache-read-only: false - name: Configure Gradle Properties shell: bash run: | mkdir -p $HOME/.gradle echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties - name: Run Release Verification Tests env: RVT_COMMERCIAL_REPOSITORY_PASSWORD: ${{ secrets.commercial-repository-password }} RVT_COMMERCIAL_REPOSITORY_USERNAME: ${{ secrets.commercial-repository-username }} RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.opensource-repository-password }} RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.opensource-repository-username }} RVT_RELEASE_TYPE: ${{ vars.COMMERCIAL && 'commercial' || 'oss' }} RVT_STAGING: ${{ inputs.staging }} RVT_VERSION: ${{ inputs.version }} run: ./gradlew spring-boot-release-verification-tests:test - name: Upload Build Reports on Failure if: failure() uses: actions/upload-artifact@v7 with: name: build-reports path: '**/build/reports/' - name: Send Notification if: always() uses: ./send-notification/.github/actions/send-notification with: run-name: ${{ format('{0} | Verification | {1}', github.ref_name, inputs.version) }} status: ${{ job.status }} webhook-url: ${{ secrets.google-chat-webhook-url }} ================================================ FILE: .gitignore ================================================ *# *.iml *.ipr *.iws *.jar *.sw? *~ .#* .*.md.html .DS_Store .attach_pid* .classpath .factorypath .gradle .metadata .project .recommenders .settings .springBeans .vscode /code MANIFEST.MF _site/ activemq-data bin build !/**/src/**/bin !/**/src/**/build build.log dependency-reduced-pom.xml dump.rdb interpolated*.xml lib/ manifest.yml out overridedb.* target .flattened-pom.xml secrets.yml .gradletasknamecache .sts4-cache .git-hooks/ node_modules /.kotlin/ ================================================ FILE: .idea/.gitignore ================================================ # Project name .name *.xml /modules/ /shelf/ /workspace.xml # Editor-based HTTP Client requests /httpRequests/ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml ================================================ FILE: .sdkmanrc ================================================ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below java=25.0.2-librca ================================================ FILE: CONTRIBUTING.adoc ================================================ = Contributing to Spring Boot Spring Boot is released under the Apache 2.0 license. If you would like to contribute something, or want to hack on the code this document should help you get started. == Code of Conduct This project adheres to the Contributor Covenant https://github.com/spring-projects/spring-boot?tab=coc-ov-file#contributor-code-of-conduct[code of conduct]. By participating, you are expected to uphold this code. Please report unacceptable behavior to code-of-conduct@spring.io. == Using GitHub Issues We use GitHub issues to track bugs and enhancements. If you have a general usage question please ask on https://stackoverflow.com[Stack Overflow]. The Spring Boot team and the broader community monitor the https://stackoverflow.com/tags/spring-boot[`spring-boot`] tag. If you are reporting a bug, please help to speed up problem diagnosis by providing as much information as possible. Ideally, that would include a small sample project that reproduces the problem. == Reporting Security Vulnerabilities If you think you have found a security vulnerability in Spring Boot please *DO NOT* disclose it publicly until we've had a chance to fix it. Please don't report security vulnerabilities using GitHub issues, instead head over to https://spring.io/security-policy and learn how to disclose them responsibly. == Include a Signed Off By Trailer All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin. For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring]. == Code Conventions and Housekeeping None of these is essential for a pull request, but they will all help. They can also be added after the original pull request but before a merge. * We use the https://github.com/spring-io/spring-javaformat/[Spring JavaFormat] project to apply code formatting conventions. If you use Eclipse and you follow the https://github.com/spring-projects/spring-boot/wiki/Working-with-the-Code#importing-into-eclipse["Importing into Eclipse"] instructions you should get project-specific formatting automatically. You can also install the https://github.com/spring-io/spring-javaformat/#intellij-idea[Spring JavaFormat IntelliJ Plugin] or format the code from the Gradle build by running `./gradlew format`. Note that if you have format violations in `buildSrc`, you can fix them by running `./gradlew -p buildSrc format` from the project root directory. * The build includes Checkstyle rules for many of our code conventions. Run `./gradlew checkstyleMain checkstyleTest` if you want to check your changes are compliant. * Make sure all new `.java` files have a Javadoc class comment with at least an `@author` tag identifying you, and preferably at least a paragraph on what the class is for. * Add the ASF license header comment to all new `.java` files (copy from existing files in the project). * Add yourself as an `@author` to the `.java` files that you modify substantially (more than cosmetic changes). * Add some Javadocs. * A few unit tests would help a lot as well -- someone has to do it. * Verification tasks, including tests and Checkstyle, can be executed by running `./gradlew check` from the project root. Note that `SPRING_PROFILES_ACTIVE` environment variable might affect the result of tests, so in that case, you can prevent it by running `unset SPRING_PROFILES_ACTIVE` before running the task. * If no-one else is using your branch, please rebase it against the current main branch (or other target branch in the project). * When writing a commit message please follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions]. == Working with the Code For information on editing, building, and testing the code, see the https://github.com/spring-projects/spring-boot/wiki/Working-with-the-Code[Working with the Code] page on the project wiki. ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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. ================================================ FILE: README.adoc ================================================ = Spring Boot image:https://github.com/spring-projects/spring-boot/actions/workflows/build-and-deploy-snapshot.yml/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-boot/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3Amain"] image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?&search.rootProjectNames=Spring%20Boot%20Build&search.rootProjectNames=spring-boot-build"] :docs: https://docs.spring.io/spring-boot :github: https://github.com/spring-projects/spring-boot Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss. It takes an opinionated view of the Spring platform so that new and existing users can quickly get to the bits they need. You can use Spring Boot to create stand-alone Java applications that can be started using `java -jar` or more traditional WAR deployments. We also provide a command-line tool that runs Spring scripts. Our primary goals are: * Provide a radically faster and widely accessible getting started experience for all Spring development. * Be opinionated, but get out of the way quickly as requirements start to diverge from the defaults. * Provide a range of non-functional features common to large classes of projects (for example, embedded servers, security, metrics, health checks, externalized configuration). * Absolutely no code generation and no requirement for XML configuration. == Installation and Getting Started The {docs}[reference documentation] includes detailed {docs}/installing.html[installation instructions] as well as a comprehensive {docs}/tutorial/first-application/index.html[``getting started``] guide. Here is a quick teaser of a complete Spring Boot application in Java: [source,java] ---- import org.springframework.boot.*; import org.springframework.boot.autoconfigure.*; import org.springframework.web.bind.annotation.*; @RestController @SpringBootApplication public class Example { @RequestMapping("/") String home() { return "Hello World!"; } public static void main(String[] args) { SpringApplication.run(Example.class, args); } } ---- == Getting Help Are you having trouble with Spring Boot? We want to help! * Check the {docs}/[reference documentation], especially the {docs}/how-to/index.html[How-to's] -- they provide solutions to the most common questions. * Learn the Spring basics -- Spring Boot builds on many other Spring projects; check the https://spring.io[spring.io] website for a wealth of reference documentation. If you are new to Spring, try one of the https://spring.io/guides[guides]. * If you are upgrading, read the {github}/wiki[release notes] for upgrade instructions and "new and noteworthy" features. * Ask a question -- we monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-boot[`spring-boot`]. * Report bugs with Spring Boot at {github}/issues[github.com/spring-projects/spring-boot/issues]. == Contributing We welcome contributions of all kinds! Please read our link:CONTRIBUTING.adoc[contribution guidelines] before submitting a pull request. == Reporting Issues Spring Boot uses GitHub's integrated issue tracking system to record bugs and feature requests. If you want to raise an issue, please follow the recommendations below: * Before you log a bug, please search the {github}/issues[issue tracker] to see if someone has already reported the problem. * If the issue doesn't already exist, {github}/issues/new[create a new issue]. * Please provide as much information as possible with the issue report. We like to know the Spring Boot version, operating system, and JVM version you're using. * If you need to paste code or include a stack trace, use Markdown. +++```+++ escapes before and after your text. * If possible, try to create a test case or project that replicates the problem and attach it to the issue. == Building from Source You don't need to build from source to use Spring Boot. If you want to try out the latest and greatest, Spring Boot can be built and published to your local Maven cache using the https://docs.gradle.org/current/userguide/gradle_wrapper.html[Gradle wrapper]. You also need JDK 25. [source,shell] ---- $ ./gradlew publishToMavenLocal ---- This command builds all modules and publishes them to your local Maven cache. It won't run any of the tests. If you want to build everything, use the `build` task: [source,shell] ---- $ ./gradlew build ---- == Guides The https://spring.io/[spring.io] site contains several guides that show how to use Spring Boot step-by-step: * https://spring.io/guides/gs/spring-boot/[Building an Application with Spring Boot] is an introductory guide that shows you how to create an application, run it, and add some management services. * https://spring.io/guides/gs/actuator-service/[Building a RESTful Web Service with Spring Boot Actuator] is a guide to creating a REST web service and also shows how the server can be configured. == License Spring Boot is Open Source software released under the https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license]. ================================================ FILE: SUPPORT.adoc ================================================ = Getting support for Spring Boot == GitHub issues We choose not to use GitHub issues for general usage questions and support, preferring to use issues solely for the tracking of bugs and enhancements. If you have a general usage question please do not open a GitHub issue, but use one of the other channels described below. If you are reporting a bug, please help to speed up problem diagnosis by providing as much information as possible. Ideally, that would include a small sample project that reproduces the problem. == Stack Overflow The Spring Boot community monitors the https://stackoverflow.com/tags/spring-boot[`spring-boot`] tag on Stack Overflow. Before asking a question, please familiarize yourself with Stack Overflow's https://stackoverflow.com/help/how-to-ask[advice on how to ask a good question]. == VMware Tanzu Enterprise Support If you are interested in more dedicated support, VMware Tanzu provides https://enterprise.spring.io/[enterprise support] for Spring Boot. ================================================ FILE: antora/package.json ================================================ { "scripts": { "antora": "node npm/antora.js" }, "dependencies": { "@antora/cli": "3.2.0-alpha.11", "@antora/site-generator": "3.2.0-alpha.11", "@antora/atlas-extension": "1.0.0-alpha.5", "@springio/antora-extensions": "1.14.10", "@springio/antora-xref-extension": "1.0.0-alpha.5", "@springio/antora-zip-contents-collector-extension": "1.0.0-alpha.10", "@asciidoctor/tabs": "1.0.0-beta.6", "@springio/asciidoctor-extensions": "1.0.0-alpha.18" }, "config": { "ui-bundle-url": "https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.26/ui-bundle.zip" } } ================================================ FILE: build-plugin/spring-boot-antlib/build.gradle ================================================ /* * 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. */ plugins { id "java-library" id "org.springframework.boot.deployed" } description = "Spring Boot Antlib" ext { antVersion = "1.10.7" } configurations { antUnit antIvy } dependencies { antUnit "org.apache.ant:ant-antunit:1.3" antIvy "org.apache.ivy:ivy:2.5.0" compileOnly(project(":loader:spring-boot-loader")) compileOnly("org.apache.ant:ant:${antVersion}") implementation(project(":loader:spring-boot-loader-tools")) implementation("org.springframework:spring-core") } tasks.register("syncIntegrationTestSources", Sync) { destinationDir = file(layout.buildDirectory.dir("it")) from file("src/it") filter(springRepositoryTransformers.ant()) } processResources { def version = project.version eachFile { filter { it.replace('${spring-boot.version}', version) } } inputs.property "version", version } tasks.register("integrationTest") { dependsOn syncIntegrationTestSources, jar def resultsDir = file(layout.buildDirectory.dir("test-results/integrationTest")) inputs.dir(file("src/it")).withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("source") inputs.files(sourceSets.main.runtimeClasspath).withNormalizer(ClasspathNormalizer).withPropertyName("classpath") outputs.dirs resultsDir doLast { ant.with { taskdef(resource: "org/apache/ant/antunit/antlib.xml", classpath: configurations.antUnit.asPath) taskdef(resource: "org/apache/ivy/ant/antlib.xml", classpath: configurations.antIvy.asPath) taskdef(resource: "org/springframework/boot/ant/antlib.xml", classpath: sourceSets.main.runtimeClasspath.asPath, uri: "antlib:org.springframework.boot.ant") ant.property(name: "ivy.class.path", value: configurations.antIvy.asPath) ant.property(name: "antunit.class.path", value: configurations.antUnit.asPath) antunit { propertyset { ant.propertyref(name: "build.compiler") ant.propertyref(name: "antunit.class.path") ant.propertyref(name: "ivy.class.path") } plainlistener() xmllistener(toDir: resultsDir) fileset(dir: layout.buildDirectory.dir("it").get().asFile.toString(), includes: "**/build.xml") } } } } check { dependsOn integrationTest } ================================================ FILE: build-plugin/spring-boot-antlib/src/it/sample/build.xml ================================================ Checking @{jar} ================================================ FILE: build-plugin/spring-boot-antlib/src/it/sample/ivysettings.xml ================================================ ================================================ FILE: build-plugin/spring-boot-antlib/src/it/sample/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.joda.time.LocalDate; public class SampleApplication { public static void main(String[] args) { System.out.println(LocalDate.class.getSimpleName()); } } ================================================ FILE: build-plugin/spring-boot-antlib/src/it/sample/src/main/resources/foo ================================================ FOO ================================================ FILE: build-plugin/spring-boot-antlib/src/main/java/org/springframework/boot/ant/FindMainClass.java ================================================ /* * 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.ant; import java.io.File; import java.io.IOException; import java.util.jar.JarFile; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.jspecify.annotations.Nullable; import org.springframework.boot.loader.tools.MainClassFinder; import org.springframework.util.StringUtils; /** * Ant task to find a main class. * * @author Matt Benson * @since 1.3.0 */ public class FindMainClass extends Task { private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication"; private @Nullable String mainClass; private @Nullable File classesRoot; private @Nullable String property; public FindMainClass(Project project) { setProject(project); } @Override public void execute() throws BuildException { String mainClass = this.mainClass; if (!StringUtils.hasText(mainClass)) { mainClass = findMainClass(); if (!StringUtils.hasText(mainClass)) { throw new BuildException("Could not determine main class given @classesRoot " + this.classesRoot); } } handle(mainClass); } private @Nullable String findMainClass() { if (this.classesRoot == null) { throw new BuildException("one of @mainClass or @classesRoot must be specified"); } if (!this.classesRoot.exists()) { throw new BuildException("@classesRoot " + this.classesRoot + " does not exist"); } try { if (this.classesRoot.isDirectory()) { return MainClassFinder.findSingleMainClass(this.classesRoot, SPRING_BOOT_APPLICATION_CLASS_NAME); } return MainClassFinder.findSingleMainClass(new JarFile(this.classesRoot), "/", SPRING_BOOT_APPLICATION_CLASS_NAME); } catch (IOException ex) { throw new BuildException(ex); } } private void handle(String mainClass) { if (StringUtils.hasText(this.property)) { getProject().setProperty(this.property, mainClass); } else { log("Found main class " + mainClass); } } /** * Set the main class, which will cause the search to be bypassed. * @param mainClass the main class name */ public void setMainClass(String mainClass) { this.mainClass = mainClass; } /** * Set the root location of classes to be searched. * @param classesRoot the root location */ public void setClassesRoot(File classesRoot) { this.classesRoot = classesRoot; } /** * Set the ANT property to set (if left unset, result will be printed to the log). * @param property the ANT property to set */ public void setProperty(String property) { this.property = property; } } ================================================ FILE: build-plugin/spring-boot-antlib/src/main/java/org/springframework/boot/ant/ShareAntlibLoader.java ================================================ /* * 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.ant; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.jspecify.annotations.Nullable; import org.springframework.util.StringUtils; /** * Quiet task that establishes a reference to its loader. * * @author Matt Benson * @since 1.3.0 */ public class ShareAntlibLoader extends Task { private @Nullable String refid; public ShareAntlibLoader(Project project) { setProject(project); } @Override public void execute() throws BuildException { if (!StringUtils.hasText(this.refid)) { throw new BuildException("@refid has no text"); } getProject().addReference(this.refid, getClass().getClassLoader()); } public void setRefid(@Nullable String refid) { this.refid = refid; } } ================================================ FILE: build-plugin/spring-boot-antlib/src/main/java/org/springframework/boot/ant/package-info.java ================================================ /* * 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. */ /** * Support for building Spring Boot applications using Ant. */ @NullMarked package org.springframework.boot.ant; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-antlib/src/main/resources/org/springframework/boot/ant/antlib.xml ================================================ Using start class ${start-class} Using destination directory ${destdir} Extracting spring-boot-loader to ${destdir}/dependency Embedding spring-boot-loader v${spring-boot.version}... ================================================ FILE: build-plugin/spring-boot-gradle-plugin/.gitignore ================================================ /bin/ /build/ /out/ ================================================ FILE: build-plugin/spring-boot-gradle-plugin/build.gradle ================================================ /* * 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. */ import org.gradle.plugins.ide.eclipse.model.Classpath import org.gradle.plugins.ide.eclipse.model.Library plugins { id "java-gradle-plugin" id "maven-publish" id "org.springframework.boot.antora-contributor" id "org.springframework.boot.docker-test" id "org.springframework.boot.maven-repository" id "org.springframework.boot.optional-dependencies" } description = "Spring Boot Gradle Plugins" configurations { "testCompileClasspath" { // Downgrade SLF4J is required for tests to run in Eclipse resolutionStrategy.force("org.slf4j:slf4j-api:1.7.36") } } dependencies { dockerTestImplementation(project(":test-support:spring-boot-gradle-test-support")) dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) dockerTestImplementation(gradleTestKit()) dockerTestImplementation("org.assertj:assertj-core") dockerTestImplementation("org.junit.jupiter:junit-jupiter") dockerTestImplementation("org.testcontainers:testcontainers-junit-jupiter") implementation(project(":buildpack:spring-boot-buildpack-platform")) implementation(project(":loader:spring-boot-loader-tools")) implementation("io.spring.gradle:dependency-management-plugin") implementation("org.apache.commons:commons-compress") implementation("org.springframework:spring-core") optional("com.google.protobuf:protobuf-gradle-plugin") optional("org.graalvm.buildtools:native-gradle-plugin") optional("org.cyclonedx:cyclonedx-gradle-plugin") { exclude(group: "javax.annotation", module: "javax.annotation-api") exclude(group: "javax.inject", module: "javax.inject") } optional("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") testImplementation(project(":test-support:spring-boot-gradle-test-support")) testImplementation(project(":test-support:spring-boot-test-support")) testImplementation("com.tngtech.archunit:archunit-junit5:1.4.0") testImplementation("net.java.dev.jna:jna-platform") testImplementation("org.apache.commons:commons-compress") testImplementation("org.apache.httpcomponents.client5:httpclient5") testImplementation("org.graalvm.buildtools:native-gradle-plugin") testImplementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") testImplementation("org.jetbrains.kotlin:kotlin-compiler-runner:$kotlinVersion") testImplementation("org.jetbrains.kotlin:kotlin-daemon-client:$kotlinVersion") testImplementation("org.tomlj:tomlj:1.0.0") testImplementation("tools.jackson.core:jackson-databind") } repositories { gradlePluginPortal() { content { includeGroup("org.cyclonedx") } } } gradlePlugin { plugins { springBootPlugin { id = "org.springframework.boot" displayName = "Spring Boot Gradle Plugin" description = "Spring Boot Gradle Plugin" implementationClass = "org.springframework.boot.gradle.plugin.SpringBootPlugin" } springBootAotPlugin { id = "org.springframework.boot.aot" displayName = "Spring Boot AOT Gradle Plugin" description = "Spring Boot AOT Gradle Plugin" implementationClass = "org.springframework.boot.gradle.plugin.SpringBootAotPlugin" } } } tasks.register("preparePluginValidationClasses", Copy) { destinationDir = layout.buildDirectory.dir("classes/java/pluginValidation").get().asFile from(sourceSets.main.output.classesDirs) { exclude "**/CreateBootStartScripts.class" } } validatePlugins { classes.setFrom preparePluginValidationClasses enableStricterValidation = true } tasks.named('test') { inputs.dir('src/docs/antora/modules/gradle-plugin/examples').withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName('buildScripts') } javadoc { options { author = true docTitle = "Spring Boot Gradle Plugin ${project.version} API" encoding = "UTF-8" memberLevel = "protected" outputLevel = "quiet" splitIndex = true use = true windowTitle = "Spring Boot Gradle Plugin ${project.version} API" links "https://docs.gradle.org/$gradle.gradleVersion/javadoc" links "https://docs.oracle.com/en/java/javase/17/docs/api" } } antoraContributions { 'gradle-plugin' { catalogContent { from(javadoc) { into("api/java") } } localAggregateContent { from(tasks.named("generateAntoraYml")) { into "modules" } } source() } } tasks.named("generateAntoraPlaybook") { antoraExtensions.xref.stubs = ["appendix:.*", "api:.*", "reference:.*"] asciidocExtensions.excludeJavadocExtension = true } plugins.withType(EclipsePlugin) { eclipse { classpath.file { merger -> merger.whenMerged { content -> if (content instanceof Classpath) { content.entries.each { entry -> if (entry instanceof Library && (entry.path.contains("gradle-api-") || entry.path.contains("groovy-"))) { entry.entryAttributes.remove("test") } } } } } } } tasks.named("compileTestJava") { options.nullability.checking = "tests" } tasks.named("compileDockerTestJava") { options.nullability.checking = "tests" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.time.OffsetDateTime; import java.util.Random; import java.util.Set; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; import org.springframework.boot.buildpack.platform.docker.DockerApi; 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.transport.DockerEngineException; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.io.FilePermissions; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.util.FileSystemUtils; import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link BootBuildImage}. * * @author Andy Wilkinson * @author Scott Frederick * @author Rafael Ceccone */ @GradleCompatibility(configurationCache = true) @DisabledIfDockerUnavailable class BootBuildImageIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void buildsImageWithDefaultBuilder() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("Running detector"); assertThat(result.getOutput()).contains("Running builder"); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("Network status: HTTP/2 200"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); removeImages(projectName); } @TestTemplate void buildsImageWithTrustBuilder() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("Running creator"); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("Network status: HTTP/2 200"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); removeImages(projectName); } @TestTemplate void buildsImageWithWarPackaging() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage", "-PapplyWarPlugin"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs"); assertThat(buildLibs.listFiles()) .containsExactly(new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".war")); removeImages(projectName); } @TestTemplate void buildsImageWithWarPackagingAndJarConfiguration() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs"); assertThat(buildLibs.listFiles()) .containsExactly(new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".war")); removeImages(projectName); } @TestTemplate void buildsImageWithCustomName() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("example/test-image-name"); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); removeImages("example/test-image-name"); } @TestTemplate void buildsImageWithCustomBuilderAndRunImage() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("example/test-image-custom"); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); removeImages("example/test-image-custom"); } @TestTemplate void buildsImageWithCommandLineOptions() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT", "--imageName=example/test-image-cmd", "--builder=ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2", "--trustBuilder", "--runImage=paketobuildpacks/run-noble-tiny", "--createdDate=2020-07-01T12:34:56Z", "--applicationDirectory=/application"); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("example/test-image-cmd"); assertThat(result.getOutput()).contains("Running creator"); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); Image image = new DockerApi().image().inspect(ImageReference.of("example/test-image-cmd")); assertThat(image.getCreated()).isEqualTo("2020-07-01T12:34:56Z"); removeImages("example/test-image-cmd"); } @TestTemplate void buildsImageWithPullPolicy() throws IOException { writeMainClass(); writeLongNameResource(); String projectName = this.gradleBuild.getProjectDir().getName(); BuildResult result = this.gradleBuild.build("bootBuildImage"); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("Pulled builder image").contains("Pulled run image"); result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT"); task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).doesNotContain("Pulled builder image").doesNotContain("Pulled run image"); removeImages(projectName); } @TestTemplate void buildsImageWithBuildpackFromBuilder() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done"); removeImages(projectName); } @TestTemplate @DisabledOnOs(OS.WINDOWS) void buildsImageWithBuildpackFromDirectory() throws IOException { writeMainClass(); writeLongNameResource(); writeBuildpackContent(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Hello World buildpack"); removeImages(projectName); } @TestTemplate @DisabledOnOs(OS.WINDOWS) void buildsImageWithBuildpackFromTarGzip() throws IOException { writeMainClass(); writeLongNameResource(); writeBuildpackContent(); tarGzipBuildpackContent(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Hello World buildpack"); removeImages(projectName); } @TestTemplate void buildsImageWithBuildpacksFromImages() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done"); removeImages(projectName); } @TestTemplate void buildsImageWithBinding() throws IOException { writeMainClass(); writeLongNameResource(); writeCertificateBindingFiles(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("binding: certificates/type=ca-certificates"); assertThat(result.getOutput()).contains("binding: certificates/test1.crt=---certificate one---"); assertThat(result.getOutput()).contains("binding: certificates/test2.crt=---certificate two---"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); removeImages(projectName); } @TestTemplate void buildsImageWithTag() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); assertThat(result.getOutput()).contains("example.com/myapp:latest"); removeImages(projectName, "example.com/myapp:latest"); } @TestTemplate void buildsImageWithNetworkModeNone() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("Network status: curl failed"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); removeImages(projectName); } @TestTemplate void buildsImageWithVolumeCaches() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); removeImages(projectName); deleteVolumes("cache-" + projectName + ".build", "cache-" + projectName + ".launch"); } @TestTemplate @EnabledOnOs(value = OS.LINUX, disabledReason = "Works with Docker Engine on Linux but is not reliable with " + "Docker Desktop on other OSs") void buildsImageWithBindCaches() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); removeImages(projectName); String tempDir = System.getProperty("java.io.tmpdir"); Path buildCachePath = Paths.get(tempDir, "junit-image-cache-" + projectName + "-build"); Path launchCachePath = Paths.get(tempDir, "junit-image-cache-" + projectName + "-launch"); assertThat(buildCachePath).exists().isDirectory(); assertThat(launchCachePath).exists().isDirectory(); cleanupCache(buildCachePath); cleanupCache(launchCachePath); } private static void cleanupCache(Path cachePath) { try { FileSystemUtils.deleteRecursively(cachePath); } catch (Exception ex) { // ignore } } @TestTemplate void buildsImageWithCreatedDate() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); Image image = new DockerApi().image().inspect(ImageReference.of("docker.io/library/" + projectName)); assertThat(image.getCreated()).isEqualTo("2020-07-01T12:34:56Z"); removeImages(projectName); } @TestTemplate void buildsImageWithCurrentCreatedDate() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); Image image = new DockerApi().image().inspect(ImageReference.of("docker.io/library/" + projectName)); OffsetDateTime createdDateTime = OffsetDateTime.parse(image.getCreated()); OffsetDateTime current = OffsetDateTime.now().withOffsetSameInstant(createdDateTime.getOffset()); assertThat(createdDateTime.getYear()).isEqualTo(current.getYear()); assertThat(createdDateTime.getMonth()).isEqualTo(current.getMonth()); assertThat(createdDateTime.getDayOfMonth()).isEqualTo(current.getDayOfMonth()); removeImages(projectName); } @TestTemplate void buildsImageWithApplicationDirectory() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); removeImages(projectName); } @TestTemplate void buildsImageWithEmptySecurityOptions() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); removeImages(projectName); } @TestTemplate @EnabledOnOs(value = { OS.LINUX, OS.MAC }, architectures = "aarch64", disabledReason = "Lifecycle will only run on ARM architecture") void buildsImageOnLinuxArmWithImagePlatformLinuxArm() throws IOException { writeMainClass(); writeLongNameResource(); String builderImage = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2"; String runImage = "docker.io/paketobuildpacks/run-noble-tiny:latest"; String buildpackImage = "ghcr.io/spring-io/spring-boot-test-info:0.0.2"; removeImages(builderImage, runImage, buildpackImage); BuildResult result = this.gradleBuild.build("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()) .contains("Pulling builder image '" + builderImage + "' for platform 'linux/arm64'"); assertThat(result.getOutput()) .contains("Pulling builder image '" + builderImage + "' for platform 'linux/arm64'"); assertThat(result.getOutput()).contains("Pulling run image '" + runImage + "' for platform 'linux/arm64'"); assertThat(result.getOutput()) .contains("Pulling buildpack image '" + buildpackImage + "' for platform 'linux/arm64'"); assertThat(result.getOutput()).contains("Running detector"); assertThat(result.getOutput()).contains("Running builder"); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); removeImages(projectName, builderImage, runImage, buildpackImage); } @TestTemplate void buildsImageWithMultipleCommandLineEnvironments() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage", "--environment", "BP_LIVE_RELOAD_ENABLED=true", "--environment", "MY_CUSTOM_VAR=hello_world"); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("BP_LIVE_RELOAD_ENABLED=true"); assertThat(result.getOutput()).contains("MY_CUSTOM_VAR=hello_world"); removeImages(this.gradleBuild.getProjectDir().getName()); } @TestTemplate @EnabledOnOs(value = { OS.LINUX, OS.MAC }, architectures = "amd64", disabledReason = "The expected failure condition will not fail on ARM architectures") void failsWhenBuildingOnLinuxAmdWithImagePlatformLinuxArm() throws IOException { writeMainClass(); writeLongNameResource(); String builderImage = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2"; String runImage = "docker.io/paketobuildpacks/run-noble-tiny:latest"; String buildpackImage = "ghcr.io/spring-io/spring-boot-test-info:0.0.2"; removeImages(builderImage, runImage, buildpackImage); BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage"); String projectName = this.gradleBuild.getProjectDir().getName(); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.FAILED); assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()) .contains("Pulling builder image '" + builderImage + "' for platform 'linux/arm64'"); assertThat(result.getOutput()).contains("Pulling run image '" + runImage + "' for platform 'linux/arm64'"); assertThat(result.getOutput()) .contains("Pulling buildpack image '" + buildpackImage + "' for platform 'linux/arm64'"); assertThat(result.getOutput()).contains("exec format error"); removeImages(builderImage, runImage, buildpackImage); } @TestTemplate void failsWithInvalidCreatedDate() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage"); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.FAILED); assertThat(result.getOutput()).contains("Error parsing 'invalid date' as an image created date"); } @TestTemplate void failsWithBuilderError() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage"); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.FAILED); assertThat(result.getOutput()).contains("Forced builder failure"); assertThat(result.getOutput()).containsPattern("Builder lifecycle '.*' failed with status code"); } @TestTemplate void failsWithInvalidImageName() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage", "--imageName=example/Invalid-Image-Name"); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.FAILED); assertThat(result.getOutput()).containsPattern("must be an image reference") .containsPattern("example/Invalid-Image-Name"); } @TestTemplate void failsWithBuildpackNotInBuilder() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage"); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.FAILED); assertThat(result.getOutput()).contains("'urn:cnb:builder:example/does-not-exist:0.0.1' not found in builder"); } @TestTemplate void failsWithInvalidTag() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage"); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.FAILED); assertThat(result.getOutput()).containsPattern("must be an image reference") .containsPattern("example/Invalid-Tag-Name"); } @TestTemplate void failsWhenCachesAreConfiguredTwice() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage"); assertThat(result.getOutput()).containsPattern("Each image building cache can be configured only once"); } @TestTemplate void failsWithIncompatiblePlatform() throws IOException { writeMainClass(); BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage"); assertThat(result.getOutput()).containsAnyOf( "Image platform mismatch detected. The configured platform 'linux/arm64' is not supported by the image 'ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.3-amd64'. Requested platform 'linux/arm64' but got 'linux/amd64'", "image with reference ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.3-amd64 was found but its platform (linux/amd64) does not match the specified platform (linux/arm64)"); } private void writeMainClass() throws IOException { File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example"); examplePackage.mkdirs(); File main = new File(examplePackage, "Main.java"); try (PrintWriter writer = new PrintWriter(new FileWriter(main))) { writer.println("package example;"); writer.println(); writer.println("import java.io.IOException;"); writer.println(); writer.println("public class Main {"); writer.println(); writer.println(" public static void main(String[] args) throws Exception {"); writer.println(" System.out.println(\"Launched\");"); writer.println(" synchronized(args) {"); writer.println(" args.wait(); // Prevent exit"); writer.println(" }"); writer.println(" }"); writer.println(); writer.println("}"); } } private void writeLongNameResource() throws IOException { StringBuilder name = new StringBuilder(); new Random().ints('a', 'z' + 1).limit(128).forEach((i) -> name.append((char) i)); Path path = this.gradleBuild.getProjectDir() .toPath() .resolve(Paths.get("src", "main", "resources", name.toString())); Files.createDirectories(path.getParent()); Files.createFile(path); } private void writeBuildpackContent() throws IOException { FileAttribute> dirAttribute = PosixFilePermissions .asFileAttribute(PosixFilePermissions.fromString("rwxr-xr-x")); FileAttribute> execFileAttribute = PosixFilePermissions .asFileAttribute(PosixFilePermissions.fromString("rwxrwxrwx")); File buildpackDir = new File(this.gradleBuild.getProjectDir(), "buildpack/hello-world"); Files.createDirectories(buildpackDir.toPath(), dirAttribute); File binDir = new File(buildpackDir, "bin"); Files.createDirectories(binDir.toPath(), dirAttribute); File descriptor = new File(buildpackDir, "buildpack.toml"); try (PrintWriter writer = new PrintWriter(new FileWriter(descriptor))) { writer.println("api = \"0.10\""); writer.println("[buildpack]"); writer.println("id = \"example/hello-world\""); writer.println("version = \"0.0.1\""); writer.println("name = \"Hello World Buildpack\""); writer.println("homepage = \"https://github.com/buildpacks/samples/tree/main/buildpacks/hello-world\""); writer.println("[[stacks]]\n"); writer.println("id = \"*\""); } File detect = Files.createFile(Paths.get(binDir.getAbsolutePath(), "detect"), execFileAttribute).toFile(); try (PrintWriter writer = new PrintWriter(new FileWriter(detect))) { writer.println("#!/usr/bin/env bash"); writer.println("set -eo pipefail"); writer.println("exit 0"); } File build = Files.createFile(Paths.get(binDir.getAbsolutePath(), "build"), execFileAttribute).toFile(); try (PrintWriter writer = new PrintWriter(new FileWriter(build))) { writer.println("#!/usr/bin/env bash"); writer.println("set -eo pipefail"); writer.println("echo \"---> Hello World buildpack\""); writer.println("echo \"---> done\""); writer.println("exit 0"); } } private void tarGzipBuildpackContent() throws IOException { Path tarGzipPath = Paths.get(this.gradleBuild.getProjectDir().getAbsolutePath(), "hello-world.tgz"); try (TarArchiveOutputStream tar = new TarArchiveOutputStream( new GzipCompressorOutputStream(Files.newOutputStream(Files.createFile(tarGzipPath))))) { File buildpackDir = new File(this.gradleBuild.getProjectDir(), "buildpack/hello-world"); writeDirectoryToTar(tar, buildpackDir, buildpackDir.getAbsolutePath()); } } private void writeDirectoryToTar(TarArchiveOutputStream tar, File dir, String baseDirPath) throws IOException { for (File file : dir.listFiles()) { String name = file.getAbsolutePath().replace(baseDirPath, ""); int mode = FilePermissions.umaskForPath(file.toPath()); if (file.isDirectory()) { writeTarEntry(tar, name + "/", mode); writeDirectoryToTar(tar, file, baseDirPath); } else { writeTarEntry(tar, file, name, mode); } } } private void writeTarEntry(TarArchiveOutputStream tar, String name, int mode) throws IOException { TarArchiveEntry entry = new TarArchiveEntry(name); entry.setMode(mode); tar.putArchiveEntry(entry); tar.closeArchiveEntry(); } private void writeTarEntry(TarArchiveOutputStream tar, File file, String name, int mode) throws IOException { TarArchiveEntry entry = new TarArchiveEntry(file, name); entry.setMode(mode); tar.putArchiveEntry(entry); StreamUtils.copy(Files.newInputStream(file.toPath()), tar); tar.closeArchiveEntry(); } private void writeCertificateBindingFiles() throws IOException { File bindingDir = new File(this.gradleBuild.getProjectDir(), "bindings/ca-certificates"); bindingDir.mkdirs(); File type = new File(bindingDir, "type"); try (PrintWriter writer = new PrintWriter(new FileWriter(type))) { writer.print("ca-certificates"); } File cert1 = new File(bindingDir, "test1.crt"); try (PrintWriter writer = new PrintWriter(new FileWriter(cert1))) { writer.println("---certificate one---"); } File cert2 = new File(bindingDir, "test2.crt"); try (PrintWriter writer = new PrintWriter(new FileWriter(cert2))) { writer.println("---certificate two---"); } } private void removeImages(String... names) throws IOException { ImageApi imageApi = new DockerApi().image(); for (String name : names) { try { imageApi.remove(ImageReference.of(name), false); } catch (DockerEngineException ex) { // ignore image remove failures } } } private void deleteVolumes(String... names) throws IOException { VolumeApi volumeApi = new DockerApi().volume(); for (String name : names) { volumeApi.delete(VolumeName.of(name), false); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageRegistryIntegrationTests.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.TestTemplate; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.boot.buildpack.platform.docker.DockerApi; import org.springframework.boot.buildpack.platform.docker.UpdateListener; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.container.RegistryContainer; import org.springframework.boot.testsupport.container.TestImage; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link BootBuildImage} tasks requiring a Docker image registry. * * @author Scott Frederick */ @GradleCompatibility @Testcontainers(disabledWithoutDocker = true) @Disabled("Disabled until differences between running locally and in CI can be diagnosed") class BootBuildImageRegistryIntegrationTests { @Container static final RegistryContainer registry = TestImage.container(RegistryContainer.class); String registryAddress; @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @BeforeEach void setUp() { assertThat(registry.isRunning()).isTrue(); this.registryAddress = registry.getHost() + ":" + registry.getFirstMappedPort(); } @TestTemplate void buildsImageAndPublishesToRegistry() throws IOException { writeMainClass(); String repoName = "test-image"; String imageName = this.registryAddress + "/" + repoName; BuildResult result = this.gradleBuild.build("bootBuildImage", "--imageName=" + imageName); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("Building image") .contains("Successfully built image") .contains("Pushing image '" + imageName + ":latest" + "'") .contains("Pushed image '" + imageName + ":latest" + "'"); ImageReference imageReference = ImageReference.of(imageName); Image pulledImage = new DockerApi().image().pull(imageReference, null, UpdateListener.none()); assertThat(pulledImage).isNotNull(); new DockerApi().image().remove(imageReference, false); } private void writeMainClass() { File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example"); examplePackage.mkdirs(); File main = new File(examplePackage, "Main.java"); try (PrintWriter writer = new PrintWriter(new FileWriter(main))) { writer.println("package example;"); writer.println(); writer.println("import java.io.IOException;"); writer.println(); writer.println("public class Main {"); writer.println(); writer.println(" public static void main(String[] args) {"); writer.println(" }"); writer.println(); writer.println("}"); } catch (IOException ex) { throw new RuntimeException(ex); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageOnLinuxArmWithImagePlatformLinuxArm.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" runImage = "paketobuildpacks/run-noble-tiny" buildpacks = ["ghcr.io/spring-io/spring-boot-test-info:0.0.2"] imagePlatform = "linux/arm64" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithApplicationDirectory.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } if (project.hasProperty('applyWarPlugin')) { apply plugin: 'war' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" applicationDirectory = "/application" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBindCaches.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" buildWorkspace { bind { source = System.getProperty('java.io.tmpdir') + "/junit-image-pack-${rootProject.name}-work" } } buildCache { bind { source = System.getProperty('java.io.tmpdir') + "/junit-image-cache-${rootProject.name}-build" } } launchCache { bind { source = System.getProperty('java.io.tmpdir') + "/junit-image-cache-${rootProject.name}-launch" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBinding.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" bindings = [ "${projectDir}/bindings/ca-certificates:/platform/bindings/certificates" as String ] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromBuilder.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" buildpacks = [ "spring-boot/spring-boot-test-info" ] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromDirectory.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" buildpacks = [ "file://${projectDir}/buildpack/hello-world" as String ] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpackFromTarGzip.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" buildpacks = [ "file://${projectDir}/hello-world.tgz" as String ] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithBuildpacksFromImages.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" buildpacks = ["ghcr.io/spring-io/spring-boot-test-info:0.0.2"] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCommandLineOptions.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCreatedDate.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" createdDate = "2020-07-01T12:34:56Z" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCurrentCreatedDate.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" createdDate = "now" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilderAndRunImage.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { imageName = "example/test-image-custom" builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" runImage = "paketobuildpacks/run-noble-tiny" pullPolicy = "IF_NOT_PRESENT" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomName.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { imageName = "example/test-image-name" builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithEmptySecurityOptions.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" securityOptions = [] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithNetworkModeNone.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } if (project.hasProperty('applyWarPlugin')) { apply plugin: 'war' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" network = "none" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithPullPolicy.gradle ================================================ /* * 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. */ import org.springframework.boot.buildpack.platform.build.PullPolicy plugins { id 'java' id 'org.springframework.boot' version '{version}' } if (project.hasProperty('applyWarPlugin')) { apply plugin: 'war' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = PullPolicy.ALWAYS } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithTag.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" tags = [ "example.com/myapp:latest" ] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithTrustBuilder.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } if (project.hasProperty('applyWarPlugin')) { apply plugin: 'war' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" trustBuilder = true pullPolicy = "IF_NOT_PRESENT" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithVolumeCaches.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" buildWorkspace { volume { name = "pack-${rootProject.name}.work" } } buildCache { volume { name = "cache-${rootProject.name}.build" } } launchCache { volume { name = "cache-${rootProject.name}.launch" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithWarPackagingAndJarConfiguration.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" archiveFile = bootWar.archiveFile } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWhenBuildingOnLinuxAmdWithImagePlatformLinuxArm.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" runImage = "paketobuildpacks/run-noble-tiny" buildpacks = ["ghcr.io/spring-io/spring-boot-test-info:0.0.2"] imagePlatform = "linux/arm64" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWhenCachesAreConfiguredTwice.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" buildCache { volume { name = "build-cache-volume" } bind { name = "/tmp/build-cache-bind" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuilderError.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" environment = ["FORCE_FAILURE": "true"] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuildpackNotInBuilder.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" buildpacks = [ "urn:cnb:builder:example/does-not-exist:0.0.1" ] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithIncompatiblePlatform.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.3-amd64" imagePlatform = "linux/arm64" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithInvalidCreatedDate.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" createdDate = "invalid date" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithInvalidTag.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" tags = [ "example/Invalid-Tag-Name" ] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } if (project.hasProperty('applyWarPlugin')) { apply plugin: 'war' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" pullPolicy = "IF_NOT_PRESENT" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageRegistryIntegrationTests.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootBuildImage { builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2" publish = true } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/antora.yml ================================================ name: boot version: true ext: zip_contents_collector: include: - name: gradle-plugin classifier: catalog-content module: gradle-plugin destination: content-catalog ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/local-nav.adoc ================================================ include::gradle-plugin:partial$nav-gradle-plugin.adoc[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/aot/apply-aot-plugin.gradle ================================================ plugins { id 'org.springframework.boot' version '{version-spring-boot}' id 'java' } apply plugin: 'org.springframework.boot.aot' ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/aot/apply-aot-plugin.gradle.kts ================================================ plugins { id("org.springframework.boot") version "{version-spring-boot}" java } apply(plugin = "org.springframework.boot.aot") ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/aot/apply-native-image-plugin.gradle ================================================ plugins { id 'org.springframework.boot' version '{version-spring-boot}' id 'org.graalvm.buildtools.native' version '{version-native-build-tools}' id 'java' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/aot/apply-native-image-plugin.gradle.kts ================================================ plugins { id("org.springframework.boot") version "{version-spring-boot}" id("org.graalvm.buildtools.native") version "{version-native-build-tools}" java } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-commercial.gradle ================================================ plugins { id 'org.springframework.boot' version '{version-spring-boot}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-commercial.gradle.kts ================================================ plugins { id("org.springframework.boot") version "{version-spring-boot}" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-release.gradle ================================================ plugins { id 'org.springframework.boot' version '{version-spring-boot}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-release.gradle.kts ================================================ plugins { id("org.springframework.boot") version "{version-spring-boot}" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-snapshot.gradle ================================================ buildscript { repositories { maven { url = 'https://repo.spring.io/libs-snapshot' } } dependencies { classpath 'org.springframework.boot:spring-boot-gradle-plugin:{version-spring-boot}' } } apply plugin: 'org.springframework.boot' ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/milestone-settings.gradle ================================================ pluginManagement { repositories { maven { url = 'https://repo.spring.io/milestone' } gradlePluginPortal() } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/milestone-settings.gradle.kts ================================================ pluginManagement { repositories { maven { url = uri("https://repo.spring.io/milestone") } gradlePluginPortal() } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/snapshot-settings.gradle ================================================ pluginManagement { repositories { maven { url = 'https://repo.spring.io/milestone' } maven { url = 'https://repo.spring.io/snapshot' } gradlePluginPortal() } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/snapshot-settings.gradle.kts ================================================ pluginManagement { repositories { maven { url = uri("https://repo.spring.io/milestone") } maven { url = uri("https://repo.spring.io/snapshot") } gradlePluginPortal() } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/typical-plugins.gradle ================================================ // tag::apply[] plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } apply plugin: 'io.spring.dependency-management' // end::apply[] tasks.register("verify") { doLast { plugins.getPlugin(org.gradle.api.plugins.JavaPlugin.class) plugins.getPlugin(io.spring.gradle.dependencymanagement.DependencyManagementPlugin.class) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/typical-plugins.gradle.kts ================================================ // tag::apply[] plugins { java id("org.springframework.boot") version "{version-spring-boot}" } apply(plugin = "io.spring.dependency-management") // end::apply[] tasks.register("verify") { val plugins = project.plugins doLast { plugins.getPlugin(JavaPlugin::class) plugins.getPlugin(io.spring.gradle.dependencymanagement.DependencyManagementPlugin::class) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-additional.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::additional[] springBoot { buildInfo { properties { additional = [ 'a': 'alpha', 'b': 'bravo' ] } } } // end::additional[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-additional.gradle.kts ================================================ plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::additional[] springBoot { buildInfo { properties { additional.set(mapOf( "a" to "alpha", "b" to "bravo" )) } } } // end::additional[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-basic.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::build-info[] springBoot { buildInfo() } // end::build-info[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-basic.gradle.kts ================================================ plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::build-info[] springBoot { buildInfo() } // end::build-info[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-custom-values.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::custom-values[] springBoot { buildInfo { properties { artifact = 'example-app' version = '1.2.3' group = 'com.example' name = 'Example application' } } } // end::custom-values[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-custom-values.gradle.kts ================================================ plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::custom-values[] springBoot { buildInfo { properties { artifact.set("example-app") version.set("1.2.3") group.set("com.example") name.set("Example application") } } } // end::custom-values[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-exclude-time.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::exclude-time[] springBoot { buildInfo { excludes = ['time'] } } // end::exclude-time[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/integrating-with-actuator/build-info-exclude-time.gradle.kts ================================================ plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::exclude-time[] springBoot { buildInfo { excludes.set(setOf("time")) } } // end::exclude-time[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom-with-plugins.gradle.kts ================================================ import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension // tag::configure-bom[] plugins { java id("org.springframework.boot") version "{version-spring-boot}" apply false id("io.spring.dependency-management") version "{version-dependency-management-plugin}" } dependencyManagement { imports { mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) } } // end::configure-bom[] the().apply { resolutionStrategy { eachDependency { if (requested.group == "org.springframework.boot") { useVersion("TEST-SNAPSHOT") } } } } repositories { maven { url = uri("repository") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::configure-bom[] apply plugin: 'io.spring.dependency-management' dependencyManagement { imports { mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES } } // end::configure-bom[] dependencyManagement { resolutionStrategy { eachDependency { if (it.requested.group == 'org.springframework.boot') { it.useVersion 'TEST-SNAPSHOT' } } } } repositories { maven { url = 'repository' } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom.gradle.kts ================================================ import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::configure-bom[] apply(plugin = "io.spring.dependency-management") the().apply { imports { mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) } } // end::configure-bom[] the().apply { resolutionStrategy { eachDependency { if (requested.group == "org.springframework.boot") { useVersion("TEST-SNAPSHOT") } } } } repositories { maven { url = uri("repository") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-platform.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::configure-platform[] dependencies { implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) } // end::configure-platform[] dependencies { implementation "org.springframework.boot:spring-boot-starter" } repositories { maven { url = 'repository' } } configurations.all { resolutionStrategy { eachDependency { if (it.requested.group == 'org.springframework.boot') { it.useVersion 'TEST-SNAPSHOT' } } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/configure-platform.gradle.kts ================================================ plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::configure-platform[] dependencies { implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) } // end::configure-platform[] dependencies { implementation("org.springframework.boot:spring-boot-starter") } repositories { maven { url = uri("repository") } } configurations.all { resolutionStrategy { eachDependency { if (requested.group == "org.springframework.boot") { useVersion("TEST-SNAPSHOT") } } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version-with-platform.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } dependencies { implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) implementation "org.slf4j:slf4j-api" } repositories { maven { url = 'repository' } } configurations.all { resolutionStrategy { eachDependency { if (it.requested.group == 'org.springframework.boot') { it.useVersion 'TEST-SNAPSHOT' } } } } // tag::custom-version[] configurations.all { resolutionStrategy.eachDependency { DependencyResolveDetails details -> if (details.requested.group == 'org.slf4j') { details.useVersion '1.7.20' } } } // end::custom-version[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version-with-platform.gradle.kts ================================================ plugins { java id("org.springframework.boot") version "{version-spring-boot}" } dependencies { implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) implementation("org.slf4j:slf4j-api") } repositories { maven { url = uri("repository") } } configurations.all { resolutionStrategy { eachDependency { if (requested.group == "org.springframework.boot") { useVersion("TEST-SNAPSHOT") } } } } // tag::custom-version[] configurations.all { resolutionStrategy.eachDependency { if (requested.group == "org.slf4j") { useVersion("1.7.20") } } } // end::custom-version[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version.gradle ================================================ plugins { id 'org.springframework.boot' version '{version-spring-boot}' } apply plugin: 'io.spring.dependency-management' dependencyManagement { resolutionStrategy { eachDependency { if (it.requested.group == 'org.springframework.boot') { it.useVersion 'TEST-SNAPSHOT' } } } } // tag::custom-version[] ext['slf4j.version'] = '1.7.20' // end::custom-version[] repositories { maven { url = 'repository' } } tasks.register("slf4jVersion") { doLast { println dependencyManagement.managedVersions['org.slf4j:slf4j-api'] } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/custom-version.gradle.kts ================================================ import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension plugins { id("org.springframework.boot") version "{version-spring-boot}" } apply(plugin = "io.spring.dependency-management") // tag::custom-version[] extra["slf4j.version"] = "1.7.20" // end::custom-version[] repositories { maven { url = uri("repository") } } the().apply { resolutionStrategy { eachDependency { if (requested.group == "org.springframework.boot") { useVersion("TEST-SNAPSHOT") } } } } tasks.register("slf4jVersion") { val dependencyManagement = project.the() doLast { println(dependencyManagement.managedVersions["org.slf4j:slf4j-api"]) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-commercial.gradle ================================================ plugins { id 'org.springframework.boot' version '{version-spring-boot}' apply false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-commercial.gradle.kts ================================================ plugins { id("org.springframework.boot") version "{version-spring-boot}" apply false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-milestone.gradle ================================================ buildscript { repositories { maven { url = 'https://repo.spring.io/libs-milestone' } } dependencies { classpath 'org.springframework.boot:spring-boot-gradle-plugin:{version-spring-boot}' } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-release.gradle ================================================ plugins { id 'org.springframework.boot' version '{version-spring-boot}' apply false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-release.gradle.kts ================================================ plugins { id("org.springframework.boot") version "{version-spring-boot}" apply false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-snapshot.gradle ================================================ buildscript { repositories { maven { url = 'https://repo.spring.io/libs-snapshot' } } dependencies { classpath 'org.springframework.boot:spring-boot-gradle-plugin:{version-spring-boot}' } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/dependencies.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } apply plugin: 'io.spring.dependency-management' // tag::dependencies[] dependencies { implementation('org.springframework.boot:spring-boot-starter-web') implementation('org.springframework.boot:spring-boot-starter-data-jpa') } // end::dependencies[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/dependencies.gradle.kts ================================================ plugins { java id("org.springframework.boot") version "{version-spring-boot}" } apply(plugin = "io.spring.dependency-management") // tag::dependencies[] dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-jpa") } // end::dependencies[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/application-plugin-main-class.gradle ================================================ plugins { id 'java' id 'application' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::main-class[] application { mainClass = 'com.example.ExampleApplication' } // end::main-class[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/application-plugin-main-class.gradle.kts ================================================ plugins { java application id("org.springframework.boot") version "{version-spring-boot}" } // tag::main-class[] application { mainClass.set("com.example.ExampleApplication") } // end::main-class[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-bind-caches.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::caches[] tasks.named("bootBuildImage") { buildWorkspace { bind { source = "/tmp/cache-${rootProject.name}.work" } } buildCache { bind { source = "/tmp/cache-${rootProject.name}.build" } } launchCache { bind { source = "/tmp/cache-${rootProject.name}.launch" } } } // end::caches[] tasks.register("bootBuildImageCaches") { doFirst { bootBuildImage.buildWorkspace.asCache().with { print "buildWorkspace=$source" } bootBuildImage.buildCache.asCache().with { println "buildCache=$source" } bootBuildImage.launchCache.asCache().with { println "launchCache=$source" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-bind-caches.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::caches[] tasks.named("bootBuildImage") { buildWorkspace { bind { source.set("/tmp/cache-${rootProject.name}.work") } } buildCache { bind { source.set("/tmp/cache-${rootProject.name}.build") } } launchCache { bind { source.set("/tmp/cache-${rootProject.name}.launch") } } } // end::caches[] tasks.register("bootBuildImageCaches") { doFirst { println("buildWorkspace=" + tasks.getByName("bootBuildImage").buildWorkspace.asCache()?.bind?.source) println("buildCache=" + tasks.getByName("bootBuildImage").buildCache.asCache()?.bind?.source) println("launchCache=" + tasks.getByName("bootBuildImage").launchCache.asCache()?.bind?.source) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-builder.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::builder[] tasks.named("bootBuildImage") { builder = "mine/java-cnb-builder" runImage = "mine/java-cnb-run" } // end::builder[] tasks.register("bootBuildImageBuilder") { doFirst { println("builder=${tasks.bootBuildImage.builder.get()}") println("runImage=${tasks.bootBuildImage.runImage.get()}") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-builder.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { mainClass.set("com.example.ExampleApplication") } // tag::builder[] tasks.named("bootBuildImage") { builder.set("mine/java-cnb-builder") runImage.set("mine/java-cnb-run") } // end::builder[] tasks.register("bootBuildImageBuilder") { doFirst { println("builder=${tasks.getByName("bootBuildImage").builder.get()}") println("runImage=${tasks.getByName("bootBuildImage").runImage.get()}") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-buildpacks.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::buildpacks[] tasks.named("bootBuildImage") { buildpacks = ["file:///path/to/example-buildpack.tgz", "urn:cnb:builder:paketo-buildpacks/java"] } // end::buildpacks[] tasks.register("bootBuildImageBuildpacks") { doFirst { bootBuildImage.buildpacks.get().each { reference -> println "$reference" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-buildpacks.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::buildpacks[] tasks.named("bootBuildImage") { buildpacks.set(listOf("file:///path/to/example-buildpack.tgz", "urn:cnb:builder:paketo-buildpacks/java")) } // end::buildpacks[] tasks.register("bootBuildImageBuildpacks") { doFirst { for(reference in tasks.getByName("bootBuildImage").buildpacks.get()) { print(reference) } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-caches.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::caches[] tasks.named("bootBuildImage") { buildCache { volume { name = "cache-${rootProject.name}.build" } } launchCache { volume { name = "cache-${rootProject.name}.launch" } } } // end::caches[] tasks.register("bootBuildImageCaches") { doFirst { bootBuildImage.buildCache.asCache().with { println "buildCache=$name" } bootBuildImage.launchCache.asCache().with { println "launchCache=$name" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-caches.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::caches[] tasks.named("bootBuildImage") { buildCache { volume { name.set("cache-${rootProject.name}.build") } } launchCache { volume { name.set("cache-${rootProject.name}.launch") } } } // end::caches[] tasks.register("bootBuildImageCaches") { doFirst { println("buildCache=" + tasks.getByName("bootBuildImage").buildCache.asCache()?.volume?.name) println("launchCache=" + tasks.getByName("bootBuildImage").launchCache.asCache()?.volume?.name) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-token.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::docker-auth-token[] tasks.named("bootBuildImage") { docker { builderRegistry { token = "9cbaf023786cd7..." } } } // end::docker-auth-token[] tasks.register("bootBuildImageDocker") { doFirst { println("token=${tasks.bootBuildImage.docker.builderRegistry.token.get()}") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-token.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { mainClass.set("com.example.ExampleApplication") } // tag::docker-auth-token[] tasks.named("bootBuildImage") { docker { builderRegistry { token.set("9cbaf023786cd7...") } } } // end::docker-auth-token[] tasks.register("bootBuildImageDocker") { doFirst { println("token=${tasks.getByName("bootBuildImage").docker.builderRegistry.token.get()}") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-user.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::docker-auth-user[] tasks.named("bootBuildImage") { docker { builderRegistry { username = "user" password = "secret" url = "https://docker.example.com/v1/" email = "user@example.com" } } } // end::docker-auth-user[] tasks.register("bootBuildImageDocker") { doFirst { println("username=${tasks.bootBuildImage.docker.builderRegistry.username.get()}") println("password=${tasks.bootBuildImage.docker.builderRegistry.password.get()}") println("url=${tasks.bootBuildImage.docker.builderRegistry.url.get()}") println("email=${tasks.bootBuildImage.docker.builderRegistry.email.get()}") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-auth-user.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { mainClass.set("com.example.ExampleApplication") } // tag::docker-auth-user[] tasks.named("bootBuildImage") { docker { builderRegistry { username.set("user") password.set("secret") url.set("https://docker.example.com/v1/") email.set("user@example.com") } } } // end::docker-auth-user[] tasks.register("bootBuildImageDocker") { doFirst { println("username=${tasks.getByName("bootBuildImage").docker.builderRegistry.username.get()}") println("password=${tasks.getByName("bootBuildImage").docker.builderRegistry.password.get()}") println("url=${tasks.getByName("bootBuildImage").docker.builderRegistry.url.get()}") println("email=${tasks.getByName("bootBuildImage").docker.builderRegistry.email.get()}") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-colima.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::docker-host[] tasks.named("bootBuildImage") { docker { host = "unix://${System.properties['user.home']}/.colima/docker.sock" } } // end::docker-host[] tasks.register("bootBuildImageDocker") { doFirst { println("host=${tasks.bootBuildImage.docker.host.get()}") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-colima.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { mainClass.set("com.example.ExampleApplication") } // tag::docker-host[] tasks.named("bootBuildImage") { docker { host.set("unix://${System.getProperty("user.home")}/.colima/docker.sock") } } // end::docker-host[] tasks.register("bootBuildImageDocker") { doFirst { println("host=${tasks.getByName("bootBuildImage").docker.host.get()}") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-podman.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::docker-host[] tasks.named("bootBuildImage") { docker { host = "unix:///run/user/1000/podman/podman.sock" bindHostToBuilder = true } } // end::docker-host[] tasks.register("bootBuildImageDocker") { doFirst { println("host=${tasks.bootBuildImage.docker.host.get()}") println("bindHostToBuilder=${tasks.bootBuildImage.docker.bindHostToBuilder.get()}") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host-podman.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { mainClass.set("com.example.ExampleApplication") } // tag::docker-host[] tasks.named("bootBuildImage") { docker { host.set("unix:///run/user/1000/podman/podman.sock") bindHostToBuilder.set(true) } } // end::docker-host[] tasks.register("bootBuildImageDocker") { doFirst { println("host=${tasks.getByName("bootBuildImage").docker.host.get()}") println("bindHostToBuilder=${tasks.getByName("bootBuildImage").docker.bindHostToBuilder.get()}") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::docker-host[] tasks.named("bootBuildImage") { docker { host = "tcp://192.168.99.100:2376" tlsVerify = true certPath = "/home/user/.minikube/certs" } } // end::docker-host[] tasks.register("bootBuildImageDocker") { doFirst { println("host=${tasks.bootBuildImage.docker.host.get()}") println("tlsVerify=${tasks.bootBuildImage.docker.tlsVerify.get()}") println("certPath=${tasks.bootBuildImage.docker.certPath.get()}") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-docker-host.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { mainClass.set("com.example.ExampleApplication") } // tag::docker-host[] tasks.named("bootBuildImage") { docker { host.set("tcp://192.168.99.100:2376") tlsVerify.set(true) certPath.set("/home/user/.minikube/certs") } } // end::docker-host[] tasks.register("bootBuildImageDocker") { doFirst { println("host=${tasks.getByName("bootBuildImage").docker.host.get()}") println("tlsVerify=${tasks.getByName("bootBuildImage").docker.tlsVerify.get()}") println("certPath=${tasks.getByName("bootBuildImage").docker.certPath.get()}") } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-proxy.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::env[] tasks.named("bootBuildImage") { environment["HTTP_PROXY"] = "http://proxy.example.com" environment["HTTPS_PROXY"] = "https://proxy.example.com" } // end::env[] tasks.register("bootBuildImageEnvironment") { doFirst { bootBuildImage.environment.get().each { name, value -> println "$name=$value" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-proxy.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::env[] tasks.named("bootBuildImage") { environment.putAll(mapOf("HTTP_PROXY" to "http://proxy.example.com", "HTTPS_PROXY" to "https://proxy.example.com")) } // end::env[] tasks.register("bootBuildImageEnvironment") { doFirst { for((name, value) in tasks.getByName("bootBuildImage").environment.get()) { print(name + "=" + value) } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-runtime.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::env-runtime[] tasks.named("bootBuildImage") { environment["BPE_DELIM_JAVA_TOOL_OPTIONS"] = " " environment["BPE_APPEND_JAVA_TOOL_OPTIONS"] = "-XX:+HeapDumpOnOutOfMemoryError" } // end::env-runtime[] tasks.register("bootBuildImageEnvironment") { doFirst { bootBuildImage.environment.get().each { name, value -> println "$name=$value" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env-runtime.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::env-runtime[] tasks.named("bootBuildImage") { environment.putAll(mapOf( "BPE_DELIM_JAVA_TOOL_OPTIONS" to " ", "BPE_APPEND_JAVA_TOOL_OPTIONS" to "-XX:+HeapDumpOnOutOfMemoryError" )) } // end::env-runtime[] tasks.register("bootBuildImageEnvironment") { doFirst { for((name, value) in tasks.getByName("bootBuildImage").environment.get()) { print(name + "=" + value) } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::env[] tasks.named("bootBuildImage") { environment["BP_JVM_VERSION"] = "17" } // end::env[] tasks.register("bootBuildImageEnvironment") { doFirst { bootBuildImage.environment.get().each { name, value -> println "$name=$value" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::env[] tasks.named("bootBuildImage") { environment.put("BP_JVM_VERSION", "17") } // end::env[] tasks.register("bootBuildImageEnvironment") { doFirst { for((name, value) in tasks.getByName("bootBuildImage").environment.get()) { print(name + "=" + value) } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-name.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::image-name[] tasks.named("bootBuildImage") { imageName = "example.com/library/${project.name}" } // end::image-name[] tasks.register("bootBuildImageName") { doFirst { println(tasks.bootBuildImage.imageName.get()) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-name.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::image-name[] tasks.named("bootBuildImage") { imageName.set("example.com/library/${project.name}") } // end::image-name[] tasks.register("bootBuildImageName") { doFirst { println(tasks.getByName("bootBuildImage").imageName.get()) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-publish.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::publish[] tasks.named("bootBuildImage") { imageName.set("docker.example.com/library/${project.name}") publish = true docker { publishRegistry { username = "user" password = "secret" } } } // end::publish[] tasks.register("bootBuildImagePublish") { doFirst { println(tasks.bootBuildImage.publish.get()) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-build-image-publish.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { java id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { mainClass.set("com.example.ExampleApplication") } // tag::publish[] tasks.named("bootBuildImage") { imageName.set("docker.example.com/library/${project.name}") publish.set(true) docker { publishRegistry { username.set("user") password.set("secret") } } } // end::publish[] tasks.register("bootBuildImagePublish") { doFirst { println(tasks.getByName("bootBuildImage").publish.get()) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-and-jar-classifiers.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::classifiers[] tasks.named("bootJar") { archiveClassifier = 'boot' } tasks.named("jar") { archiveClassifier = '' } // end::classifiers[] tasks.named("bootJar") { mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-and-jar-classifiers.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::classifiers[] tasks.named("bootJar") { archiveClassifier.set("boot") } tasks.named("jar") { archiveClassifier.set("") } // end::classifiers[] tasks.named("bootJar") { mainClass.set("com.example.Application") } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-custom.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::layered[] tasks.named("bootJar") { layered { application { intoLayer("spring-boot-loader") { include "org/springframework/boot/loader/**" } intoLayer("application") } dependencies { intoLayer("application") { includeProjectDependencies() } intoLayer("snapshot-dependencies") { include "*:*:*SNAPSHOT" } intoLayer("dependencies") } layerOrder = ["dependencies", "spring-boot-loader", "snapshot-dependencies", "application"] } } // end::layered[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-custom.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { mainClass.set("com.example.ExampleApplication") } // tag::layered[] tasks.named("bootJar") { layered { application { intoLayer("spring-boot-loader") { include("org/springframework/boot/loader/**") } intoLayer("application") } dependencies { intoLayer("application") { includeProjectDependencies() } intoLayer("snapshot-dependencies") { include("*:*:*SNAPSHOT") } intoLayer("dependencies") } layerOrder.set(listOf("dependencies", "spring-boot-loader", "snapshot-dependencies", "application")) } } // end::layered[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-disabled.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::layered[] tasks.named("bootJar") { layered { enabled = false } } // end::layered[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-disabled.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { mainClass.set("com.example.ExampleApplication") } // tag::layered[] tasks.named("bootJar") { layered { enabled.set(false) } } // end::layered[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-exclude-tools.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::layered[] tasks.named("bootJar") { includeTools = false } // end::layered[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-layered-exclude-tools.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootJar") { mainClass.set("com.example.ExampleApplication") } // tag::layered[] tasks.named("bootJar") { includeTools.set(false) } // end::layered[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-main-class.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::main-class[] tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // end::main-class[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-main-class.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::main-class[] tasks.named("bootJar") { mainClass.set("com.example.ExampleApplication") } // end::main-class[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-manifest-main-class.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::main-class[] tasks.named("bootJar") { manifest { attributes 'Start-Class': 'com.example.ExampleApplication' } } // end::main-class[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-manifest-main-class.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::main-class[] tasks.named("bootJar") { manifest { attributes("Start-Class" to "com.example.ExampleApplication") } } // end::main-class[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-requires-unpack.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } repositories { mavenCentral() } dependencies { runtimeOnly('org.jruby:jruby-complete:1.7.25') } tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } // tag::requires-unpack[] tasks.named("bootJar") { requiresUnpack '**/jruby-complete-*.jar' } // end::requires-unpack[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-jar-requires-unpack.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java id("org.springframework.boot") version "{version-spring-boot}" } repositories { mavenCentral() } dependencies { runtimeOnly("org.jruby:jruby-complete:1.7.25") } tasks.named("bootJar") { mainClass.set("com.example.ExampleApplication") } // tag::requires-unpack[] tasks.named("bootJar") { requiresUnpack("**/jruby-complete-*.jar") } // end::requires-unpack[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-include-devtools.gradle ================================================ plugins { id 'war' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootWar") { mainClass = 'com.example.ExampleApplication' } dependencies { developmentOnly files("spring-boot-devtools-1.2.3.RELEASE.jar") } // tag::include-devtools[] tasks.named("bootWar") { classpath configurations.developmentOnly } // end::include-devtools[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-include-devtools.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootWar plugins { war id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootWar") { mainClass.set("com.example.ExampleApplication") } dependencies { "developmentOnly"(files("spring-boot-devtools-1.2.3.RELEASE.jar")) } // tag::include-devtools[] tasks.named("bootWar") { classpath(configurations["developmentOnly"]) } // end::include-devtools[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-properties-launcher.gradle ================================================ plugins { id 'war' id 'org.springframework.boot' version '{version-spring-boot}' } tasks.named("bootWar") { mainClass = 'com.example.ExampleApplication' } // tag::properties-launcher[] tasks.named("bootWar") { manifest { attributes 'Main-Class': 'org.springframework.boot.loader.launch.PropertiesLauncher' } } // end::properties-launcher[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-properties-launcher.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootWar plugins { war id("org.springframework.boot") version "{version-spring-boot}" } tasks.named("bootWar") { mainClass.set("com.example.ExampleApplication") } // tag::properties-launcher[] tasks.named("bootWar") { manifest { attributes("Main-Class" to "org.springframework.boot.loader.launch.PropertiesLauncher") } } // end::properties-launcher[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/only-boot-jar.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::disable-jar[] tasks.named("jar") { enabled = false } // end::disable-jar[] tasks.named("bootJar") { mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/only-boot-jar.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::disable-jar[] tasks.named("jar") { enabled = false } // end::disable-jar[] tasks.named("bootJar") { mainClass.set("com.example.Application") } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/spring-boot-dsl-main-class.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::main-class[] springBoot { mainClass = 'com.example.ExampleApplication' } // end::main-class[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/spring-boot-dsl-main-class.gradle.kts ================================================ plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::main-class[] springBoot { mainClass.set("com.example.ExampleApplication") } // end::main-class[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/war-container-dependency.gradle ================================================ plugins { id 'war' id 'org.springframework.boot' version '{version-spring-boot}' } apply plugin: 'io.spring.dependency-management' // tag::dependencies[] dependencies { implementation('org.springframework.boot:spring-boot-starter-webmvc') providedRuntime('org.springframework.boot:spring-boot-starter-tomcat-runtime') } // end::dependencies[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/war-container-dependency.gradle.kts ================================================ plugins { war id("org.springframework.boot") version "{version-spring-boot}" } apply(plugin = "io.spring.dependency-management") // tag::dependencies[] dependencies { implementation("org.springframework.boot:spring-boot-starter-web") providedRuntime("org.springframework.boot:spring-boot-starter-tomcat-runtime") } // end::dependencies[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/publishing/maven-publish.gradle ================================================ plugins { id 'java' id 'maven-publish' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::publishing[] publishing { publications { bootJava(MavenPublication) { artifact tasks.named("bootJar") } } repositories { maven { url = 'https://repo.example.com' } } } // end::publishing[] tasks.register("publishingConfiguration") { doLast { println publishing.publications.bootJava println publishing.repositories.maven.url } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/publishing/maven-publish.gradle.kts ================================================ plugins { java `maven-publish` id("org.springframework.boot") version "{version-spring-boot}" } // tag::publishing[] publishing { publications { create("bootJava") { artifact(tasks.named("bootJar")) } } repositories { maven { url = uri("https://repo.example.com") } } } // end::publishing[] tasks.register("publishingConfiguration") { doLast { println(publishing.publications["bootJava"]) println(publishing.repositories.getByName("maven").url) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/application-plugin-main-class-name.gradle ================================================ plugins { id 'java' id 'application' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::main-class[] application { mainClass = 'com.example.ExampleApplication' } // end::main-class[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/application-plugin-main-class-name.gradle.kts ================================================ plugins { java application id("org.springframework.boot") version "{version-spring-boot}" } // tag::main-class[] application { mainClass.set("com.example.ExampleApplication") } // end::main-class[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-disable-optimized-launch.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::launch[] tasks.named("bootRun") { optimizedLaunch = false } // end::launch[] tasks.register("optimizedLaunch") { doLast { println bootRun.optimizedLaunch.get() } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-disable-optimized-launch.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.run.BootRun plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::launch[] tasks.named("bootRun") { optimizedLaunch.set(false) } // end::launch[] tasks.register("optimizedLaunch") { doLast { println(tasks.getByName("bootRun").optimizedLaunch.get()) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-main.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::main[] tasks.named("bootRun") { mainClass = 'com.example.ExampleApplication' } // end::main[] tasks.register("configuredMainClass") { doLast { println bootRun.mainClass } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-main.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.run.BootRun plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::main[] tasks.named("bootRun") { mainClass.set("com.example.ExampleApplication") } // end::main[] tasks.register("configuredMainClass") { doLast { println(tasks.getByName("bootRun").mainClass.get()) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-source-resources.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::source-resources[] tasks.named("bootRun") { sourceResources sourceSets.main } // end::source-resources[] tasks.register("configuredClasspath") { doLast { println bootRun.classpath.files } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-source-resources.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.run.BootRun plugins { java id("org.springframework.boot") version "{version-spring-boot}" } // tag::source-resources[] tasks.named("bootRun") { sourceResources(sourceSets["main"]) } // end::source-resources[] tasks.register("configuredClasspath") { doLast { println(tasks.getByName("bootRun").classpath.files) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-system-property.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version}' } // tag::system-property[] tasks.named("bootRun") { systemProperty 'com.example.property', findProperty('example') ?: 'default' } // end::system-property[] tasks.register("configuredSystemProperties") { doLast { bootRun.systemProperties.each { k, v -> println "$k = $v" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/boot-run-system-property.gradle.kts ================================================ import org.springframework.boot.gradle.tasks.run.BootRun plugins { java id("org.springframework.boot") version "{version}" } // tag::system-property[] tasks.named("bootRun") { systemProperty("com.example.property", findProperty("example") ?: "default") } // end::system-property[] tasks.register("configuredSystemProperties") { doLast { tasks.getByName("bootRun").systemProperties.forEach { k, v -> println("$k = $v") } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/spring-boot-dsl-main-class-name.gradle ================================================ plugins { id 'java' id 'application' id 'org.springframework.boot' version '{version-spring-boot}' } // tag::main-class[] springBoot { mainClass = 'com.example.ExampleApplication' } // end::main-class[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/running/spring-boot-dsl-main-class-name.gradle.kts ================================================ plugins { java application id("org.springframework.boot") version "{version-spring-boot}" } // tag::main-class[] springBoot { mainClass.set("com.example.ExampleApplication") } // end::main-class[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/aot.adoc ================================================ [[aot]] = Ahead-of-Time Processing Spring AOT is a process that analyzes your code at build-time in order to generate an optimized version of it. It is most often used to help generate GraalVM native images. The `org.springframework.boot.aot` Gradle plugin provides tasks that can be used to perform AOT processing on both application and test code. The plugin is applied automatically when the {url-native-build-tools-docs-gradle-plugin}[GraalVM Native Image plugin] is applied: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$aot/apply-native-image-plugin.gradle[] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$aot/apply-native-image-plugin.gradle.kts[] ---- ====== If you want to xref:reference:packaging/aot.adoc[run an AOT-processed application on the JVM] rather than as a native image, apply the `org.springframework.boot.aot` plugin directly: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$aot/apply-aot-plugin.gradle[] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$aot/apply-aot-plugin.gradle.kts[] ---- ====== [[aot.processing-applications]] == Processing Applications Based on your `@SpringBootApplication`-annotated main class, the `processAot` task generates a persistent view of the beans that are going to be contributed at runtime in a way that bean instantiation is as straightforward as possible. Additional post-processing of the factory is possible using callbacks. For instance, these are used to generate the necessary reflection configuration that GraalVM needs to initialize the context in a native image. As the `BeanFactory` is fully prepared at build-time, conditions are also evaluated. This has an important difference compared to what a regular Spring Boot application does at runtime. For instance, if you want to opt-in or opt-out for certain features, you need to configure the environment used at build time to do so. To this end, the `processAot` task is a {url-gradle-dsl}/org.gradle.api.tasks.JavaExec.html[`JavaExec`] task and can be configured with environment variables, system properties, and arguments as needed. The `nativeCompile` task of the GraalVM Native Image plugin is automatically configured to use the output of the `processAot` task. [[aot.processing-tests]] == Processing Tests The AOT engine can be applied to JUnit 5 tests that use Spring's Test Context Framework. Suitable tests are processed by the `processTestAot` task to generate `ApplicationContextInitializer` code. As with application AOT processing, the `BeanFactory` is fully prepared at build-time. As with `processAot`, the `processTestAot` task is `JavaExec` subclass and can be configured as needed to influence this processing. The `nativeTest` task of the GraalVM Native Image plugin is automatically configured to use the output of the `processAot` and `processTestAot` tasks. ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/getting-started.adoc ================================================ [[getting-started]] = Getting Started To get started with the plugin it needs to be applied to your project. ifeval::["{build-type}" == "commercial"] The plugin is published to the Spring Commercial repository. You will have to configure your build to access this repository. This is usual done through a local artifact repository that mirrors the content of the Spring Commercial repository. Alternatively, while it is not recommended, the Spring Commercial repository can also be accessed directly. In either case, see https://docs.vmware.com/en/Tanzu-Spring-Runtime/Commercial/Tanzu-Spring-Runtime/spring-enterprise-subscription.html[the Tanzu Spring Runtime documentation] for further details. With access to the Spring Commercial repository configured in `settings.gradle` or `settings.gradle.kts`, the plugin can be applied using the `plugins` block: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/apply-plugin-commercial.gradle[] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/apply-plugin-commercial.gradle.kts[] ---- ====== endif::[] ifeval::["{build-and-artifact-release-type}" == "opensource-release"] The plugin is https://plugins.gradle.org/plugin/org.springframework.boot[published to Gradle's plugin portal] and can be applied using the `plugins` block: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/apply-plugin-release.gradle[] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/apply-plugin-release.gradle.kts[] ---- ====== endif::[] ifeval::["{build-and-artifact-release-type}" == "opensource-milestone"] The plugin is published to the Spring milestones repository. Gradle can be configured to use the milestones repository and the plugin can then be applied using the `plugins` block. To configure Gradle to use the milestones repository, add the following to your `settings.gradle` (Groovy) or `settings.gradle.kts` (Kotlin): [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/milestone-settings.gradle[] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/milestone-settings.gradle.kts[] ---- ====== The plugin can then be applied using the `plugins` block: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/apply-plugin-release.gradle[] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/apply-plugin-release.gradle.kts[] ---- ====== endif::[] ifeval::["{build-and-artifact-release-type}" == "opensource-snapshot"] The plugin is published to the Spring snapshots repository. Gradle can be configured to use the snapshots repository and the plugin can then be applied using the `plugins` block. To configure Gradle to use the snapshots repository, add the following to your `settings.gradle` (Groovy) or `settings.gradle.kts` (Kotlin): [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/snapshot-settings.gradle[] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/snapshot-settings.gradle.kts[] ---- ====== The plugin can then be applied using the `plugins` block: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/apply-plugin-release.gradle[] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/apply-plugin-release.gradle.kts[] ---- ====== endif::[] Applied in isolation the plugin makes few changes to a project. Instead, the plugin detects when certain other plugins are applied and reacts accordingly. For example, when the `java` plugin is applied a task for building an executable jar is automatically configured. A typical Spring Boot project will apply the {url-gradle-docs-groovy-plugin}[`groovy`], {url-gradle-docs-java-plugin}[`java`], or {url-kotlin-docs-kotlin-plugin}[`org.jetbrains.kotlin.jvm`] plugin as a minimum and also use the {url-dependency-management-plugin-site}[`io.spring.dependency-management`] plugin or Gradle's native bom support for dependency management. For example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/typical-plugins.gradle[tags=apply] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/typical-plugins.gradle.kts[tags=apply] ---- ====== To learn more about how the Spring Boot plugin behaves when other plugins are applied please see the section on xref:reacting.adoc[reacting to other plugins]. ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/index.adoc ================================================ [[gradle-plugin]] = Gradle Plugin The Spring Boot Gradle Plugin provides Spring Boot support in https://gradle.org[Gradle]. It allows you to package executable jar or war archives, run Spring Boot applications, and use the dependency management provided by `spring-boot-dependencies`. Spring Boot's Gradle plugin requires Gradle 8.x (8.14 or later) or 9.x and can be used with Gradle's {url-gradle-docs}/configuration_cache.html[configuration cache]. In addition to this user guide, xref:api/java/index.html[API documentation] is also available. ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/integrating-with-actuator.adoc ================================================ [[integrating-with-actuator]] = Integrating with Actuator [[integrating-with-actuator.build-info]] == Generating Build Information Spring Boot Actuator's `info` endpoint automatically publishes information about your build in the presence of a `META-INF/build-info.properties` file. A {apiref-gradle-plugin-boot-build-info}[`BuildInfo`] task is provided to generate this file. The easiest way to use the task is through the plugin's DSL: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$integrating-with-actuator/build-info-basic.gradle[tags=build-info] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$integrating-with-actuator/build-info-basic.gradle.kts[tags=build-info] ---- ====== This will configure a {apiref-gradle-plugin-boot-build-info}[`BuildInfo`] task named `bootBuildInfo`. The task's destination directory will be `build/bootBuildInfo` and its filename will be `META-INF/build-info.properties`. If the main source set exists, its resources are configured to use the output of the `bootBuildInfo` task as a src dir. By default, the generated build information is derived from the project: |=== | Property | Default value | `build.artifact` | The base name of the `bootJar` or `bootWar` task | `build.group` | The group of the project | `build.name` | The name of the project | `build.version` | The version of the project | `build.time` | The time at which the project is being built |=== The properties can be customized using the DSL: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$integrating-with-actuator/build-info-custom-values.gradle[tags=custom-values] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$integrating-with-actuator/build-info-custom-values.gradle.kts[tags=custom-values] ---- ====== To exclude any of the default properties from the generated build information, add its name to the excludes. For example, the `time` property can be excluded as follows: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$integrating-with-actuator/build-info-exclude-time.gradle[tags=exclude-time] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$integrating-with-actuator/build-info-exclude-time.gradle.kts[tags=exclude-time] ---- ====== The default value for `build.time` is the instant at which the project is being built. A side-effect of this is that the task will never be up-to-date. As a result, builds will take longer as more tasks, including the project's tests, will have to be executed. Another side-effect is that the task's output will always change and, therefore, the build will not be truly repeatable. If you value build performance or repeatability more highly than the accuracy of the `build.time` property, exclude the `time` property as shown in the preceding example. Additional properties can also be added to the build information: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$integrating-with-actuator/build-info-additional.gradle[tags=additional] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$integrating-with-actuator/build-info-additional.gradle.kts[tags=additional] ---- ====== An additional property's value can be computed lazily by using a `Provider`. ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/introduction.adoc ================================================ [[introduction]] = Introduction The Spring Boot Gradle Plugin provides Spring Boot support in https://gradle.org[Gradle]. It allows you to package executable jar or war archives, run Spring Boot applications, and use the dependency management provided by `spring-boot-dependencies`. Spring Boot's Gradle plugin requires Gradle 8.x (8.14 or later) or 9.x and can be used with Gradle's {url-gradle-docs}/configuration_cache.html[configuration cache]. In addition to this user guide, xref:api/java/index.html[API documentation] is also available. ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/managing-dependencies.adoc ================================================ [[managing-dependencies]] = Managing Dependencies To manage dependencies in your Spring Boot application, you can either apply the {url-dependency-management-plugin-site}[`io.spring.dependency-management`] plugin or use Gradle's native bom support. The primary benefit of the former is that it offers property-based customization of managed versions, while using the latter will likely result in faster builds. [[managing-dependencies.dependency-management-plugin]] == Managing Dependencies with the Dependency Management Plugin When you apply the {url-dependency-management-plugin-site}[`io.spring.dependency-management`] plugin, Spring Boot's plugin will automatically xref:reacting.adoc#reacting-to-other-plugins.dependency-management[import the `spring-boot-dependencies` bom] from the version of Spring Boot that you are using. This provides a similar dependency management experience to the one that's enjoyed by Maven users. For example, it allows you to omit version numbers when declaring dependencies that are managed in the bom. To make use of this functionality, declare dependencies in the usual way but omit the version number: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/dependencies.gradle[tags=dependencies] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/dependencies.gradle.kts[tags=dependencies] ---- ====== [[managing-dependencies.dependency-management-plugin.customizing]] === Customizing Managed Versions The `spring-boot-dependencies` bom that is automatically imported when the dependency management plugin is applied uses properties to control the versions of the dependencies that it manages. Browse the xref:appendix:dependency-versions/properties.adoc[Dependency Versions Properties] section in the Spring Boot reference for a complete list of these properties. To customize a managed version you set its corresponding property. For example, to customize the version of SLF4J which is controlled by the `slf4j.version` property: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/custom-version.gradle[tags=custom-version] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/custom-version.gradle.kts[tags=custom-version] ---- ====== 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. [[managing-dependencies.dependency-management-plugin.using-in-isolation]] === Using Spring Boot's Dependency Management in Isolation Spring Boot's dependency management can be used in a project without applying Spring Boot's plugin to that project. The `SpringBootPlugin` class provides a `BOM_COORDINATES` constant that can be used to import the bom without having to know its group ID, artifact ID, or version. First, configure the project to depend on the Spring Boot plugin but do not apply it: ifeval::["{build-type}" == "commercial"] [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/depend-on-plugin-commercial.gradle[] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/depend-on-plugin-commercial.gradle.kts[] ---- ====== endif::[] ifeval::["{build-and-artifact-release-type}" == "opensource-release"] [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/depend-on-plugin-release.gradle[] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/depend-on-plugin-release.gradle.kts[] ---- ====== endif::[] ifeval::["{build-and-artifact-release-type}" == "opensource-milestone"] [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/depend-on-plugin-milestone.gradle[] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/depend-on-plugin-release.gradle.kts[] ---- ====== endif::[] ifeval::["{build-and-artifact-release-type}" == "opensource-snapshot"] [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/depend-on-plugin-snapshot.gradle[] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/depend-on-plugin-release.gradle.kts[] ---- ====== endif::[] The Spring Boot plugin's dependency on the dependency management plugin means that you can use the dependency management plugin without having to declare a dependency on it. This also means that you will automatically use the same version of the dependency management plugin as Spring Boot uses. Apply the dependency management plugin and then configure it to import Spring Boot's bom: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/configure-bom.gradle[tags=configure-bom] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/configure-bom.gradle.kts[tags=configure-bom] ---- ====== The Kotlin code above is a bit awkward. That's because we're using the imperative way of applying the dependency management plugin. We can make the code less awkward by applying the plugin from the root parent project, or by using the `plugins` block as we're doing for the Spring Boot plugin. A downside of this method is that it forces us to specify the version of the dependency management plugin: [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/configure-bom-with-plugins.gradle.kts[tags=configure-bom] ---- [[managing-dependencies.dependency-management-plugin.learning-more]] === Learning More To learn more about the capabilities of the dependency management plugin, please refer to its {url-dependency-management-plugin-docs}[documentation]. [[managing-dependencies.gradle-bom-support]] == Managing Dependencies with Gradle's Bom Support Gradle allows a bom to be used to manage a project's versions by declaring it as a `platform` or `enforcedPlatform` dependency. A `platform` dependency treats the versions in the bom as recommendations and other versions and constraints in the dependency graph may cause a version of a dependency other than that declared in the bom to be used. An `enforcedPlatform` dependency treats the versions in the bom as requirements and they will override any other version found in the dependency graph. The `SpringBootPlugin` class provides a `BOM_COORDINATES` constant that can be used to declare a dependency upon Spring Boot's bom without having to know its group ID, artifact ID, or version, as shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/configure-platform.gradle[tags=configure-platform] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/configure-platform.gradle.kts[tags=configure-platform] ---- ====== A platform or enforced platform will only constrain the versions of the configuration in which it has been declared or that extend from the configuration in which it has been declared. As a result, in may be necessary to declare the same dependency in more than one configuration. [[managing-dependencies.gradle-bom-support.customizing]] === Customizing Managed Versions When using Gradle's bom support, you cannot use the properties from `spring-boot-dependencies` to control the versions of the dependencies that it manages. Instead, you must use one of the mechanisms that Gradle provides. One such mechanism is a resolution strategy. SLF4J's modules are all in the `org.slf4j` group so their version can be controlled by configuring every dependency in that group to use a particular version, as shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/custom-version-with-platform.gradle[tags=custom-version] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$managing-dependencies/custom-version-with-platform.gradle.kts[tags=custom-version] ---- ====== 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. ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging-oci-image.adoc ================================================ [[build-image]] = Packaging OCI Images The plugin can create an https://github.com/opencontainers/image-spec[OCI image] from a jar or war file using https://buildpacks.io[Cloud Native Buildpacks] (CNB). Images can be built using the `bootBuildImage` task. NOTE: For security reasons, images build and run as non-root users. See the {url-buildpacks-docs}/reference/spec/platform-api/#users[CNB specification] for more details. The task is automatically created when the `java` or `war` plugin is applied and is an instance of {apiref-gradle-plugin-boot-build-image}[`BootBuildImage`]. [[build-image.docker-daemon]] == Docker Daemon The `bootBuildImage` task requires access to a Docker daemon. The task will inspect local Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] to determine the current https://docs.docker.com/engine/context/working-with-contexts/[context] and use the context connection information to communicate with a Docker daemon. If the current context can not be determined or the context does not have connection information, then the task will use a default local connection. This works with https://docs.docker.com/install/[Docker Engine] on all supported platforms without configuration. Environment variables can be set to configure the `bootBuildImage` task to use an alternative local or remote connection. The following table shows the environment variables and their values: |=== | Environment variable | Description | DOCKER_CONFIG | Location of Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] used to determine the current context (defaults to `$HOME/.docker`) | DOCKER_CONTEXT | Name of a https://docs.docker.com/engine/context/working-with-contexts/[context] that should be used to retrieve host information from Docker CLI configuration files (overrides `DOCKER_HOST`) | DOCKER_HOST | URL containing the host and port for the Docker daemon - for example `tcp://192.168.99.100:2376` | DOCKER_TLS_VERIFY | Enable secure HTTPS protocol when set to `1` (optional) | DOCKER_CERT_PATH | Path to certificate and key files for HTTPS (required if `DOCKER_TLS_VERIFY=1`, ignored otherwise) |=== Docker daemon connection information can also be provided using `docker` properties in the plugin configuration. The following table summarizes the available properties: |=== | Property | Description | `context` | Name of a https://docs.docker.com/engine/context/working-with-contexts/[context] that should be used to retrieve host information from Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] | `host` | URL containing the host and port for the Docker daemon - for example `tcp://192.168.99.100:2376` | `tlsVerify` | Enable secure HTTPS protocol when set to `true` (optional) | `certPath` | Path to certificate and key files for HTTPS (required if `tlsVerify` is `true`, ignored otherwise) | `bindHostToBuilder` | When `true`, the value of the `host` property will be provided to the container that is created for the CNB builder (optional) |=== For more details, see also xref:packaging-oci-image.adoc#build-image.examples.docker[examples]. [[build-image.docker-registry]] == Docker Registry If the Docker images specified by the `builder` or `runImage` properties are stored in a private Docker image registry that requires authentication, the authentication credentials can be provided using `docker.builderRegistry` properties. If the generated Docker image is to be published to a Docker image registry, the authentication credentials can be provided using `docker.publishRegistry` properties. Properties are provided for user authentication or identity token authentication. Consult the documentation for the Docker registry being used to store images for further information on supported authentication methods. The following table summarizes the available properties for `docker.builderRegistry` and `docker.publishRegistry`: |=== | Property | Description | `username` | Username for the Docker image registry user. Required for user authentication. | `password` | Password for the Docker image registry user. Required for user authentication. | `url` | Address of the Docker image registry. Optional for user authentication. | `email` | E-mail address for the Docker image registry user. Optional for user authentication. | `token` | Identity token for the Docker image registry user. Required for token authentication. |=== For more details, see also xref:packaging-oci-image.adoc#build-image.examples.docker[examples]. [NOTE] ==== If credentials are not provided, the plugin reads the user's existing Docker configuration file (typically located at `$HOME/.docker/config.json`) to determine authentication methods. Using these methods, the plugin attempts to provide authentication credentials for the requested image. The plugin supports the following authentication methods: - *Credential Helpers*: External tools configured in the Docker configuration file to provide credentials for specific registries. For example, tools like `osxkeychain` or `ecr-login` handle authentication for certain registries. - *Credential Store*: A default fallback mechanism that securely stores and retrieves credentials (e.g., `desktop` for Docker Desktop). - *Static Credentials*: Credentials that are stored directly in the Docker configuration file under the `auths` section. ==== [[build-image.customization]] == Image Customizations The plugin invokes a {url-buildpacks-docs}/for-app-developers/concepts/builder/[builder] to orchestrate the generation of an image. The builder includes multiple {url-buildpacks-docs}/for-app-developers/concepts/buildpack/[buildpacks] that can inspect the application to influence the generated image. By default, the plugin chooses a builder image. The name of the generated image is deduced from project properties. Task properties can be used to configure how the builder should operate on the project. The following table summarizes the available properties and their default values: |=== | Property | Command-line option | Description | Default value | `builder` | `--builder` | Name of the builder image to use. | `paketobuildpacks/builder-noble-java-tiny:latest` | `trustBuilder` | `--trustBuilder` | Whether to treat the builder as {url-buildpacks-docs}/for-platform-operators/how-to/integrate-ci/pack/concepts/trusted_builders/#what-is-a-trusted-builder[trusted]. | `true` if the builder is one of `paketobuildpacks/builder-noble-java-tiny`, `paketobuildpacks/builder-jammy-java-tiny`, `paketobuildpacks/builder-jammy-tiny`, `paketobuildpacks/builder-jammy-base`, `paketobuildpacks/builder-jammy-full`, `paketobuildpacks/builder-jammy-buildpackless-tiny`, `paketobuildpacks/builder-jammy-buildpackless-base`, `paketobuildpacks/builder-jammy-buildpackless-full`, `gcr.io/buildpacks/builder`, `heroku/builder`; `false` otherwise. | `imagePlatform` | `--imagePlatform` a|The platform (operating system and architecture) of any builder, run, and buildpack images that are pulled. Must be in the form of `OS[/architecture[/variant]]`, such as `linux/amd64`, `linux/arm64`, or `linux/arm/v5`. Refer to documentation of the builder being used to determine the image OS and architecture options available. | No default value, indicating that the platform of the host machine should be used. | `runImage` | `--runImage` | Name of the run image to use. | No default value, indicating the run image specified in Builder metadata should be used. | `imageName` | `--imageName` | javadoc:org.springframework.boot.buildpack.platform.docker.type.ImageReference#of(java.lang.String)[Image name] for the generated image. | `docker.io/library/${project.name}:${project.version}` | `pullPolicy` | `--pullPolicy` | javadoc:org.springframework.boot.buildpack.platform.build.PullPolicy[Policy] used to determine when to pull the builder and run images from the registry. Acceptable values are `ALWAYS`, `NEVER`, and `IF_NOT_PRESENT`. | `ALWAYS` | `environment` | `--environment` | Environment variables that should be passed to the builder. | Empty. | `buildpacks` | a|Buildpacks that the builder should use when building the image. Only the specified buildpacks will be used, overriding the default buildpacks included in the builder. Buildpack references must be in one of the following forms: * Buildpack in the builder - `[urn:cnb:builder:][@]` * Buildpack in a directory on the file system - `[file://]` * Buildpack in a gzipped tar (.tgz) file on the file system - `[file://]/` * Buildpack in an OCI image - `[docker://]/[:][@]` | None, indicating the builder should use the buildpacks included in it. | `bindings` | a|https://docs.docker.com/storage/bind-mounts/[Volume bind mounts] that should be mounted to the builder container when building the image. The bindings will be passed unparsed and unvalidated to Docker when creating the builder container. Bindings must be in one of the following forms: * `:[:]` * `:[:]` Where `` can contain: * `ro` to mount the volume as read-only in the container * `rw` to mount the volume as readable and writable in the container * `volume-opt=key=value` to specify key-value pairs consisting of an option name and its value | | `network` | `--network` | The https://docs.docker.com/network/#network-drivers[network driver] the builder container will be configured to use. The value supplied will be passed unvalidated to Docker when creating the builder container. | | `cleanCache` | `--cleanCache` | Whether to clean the cache before building. | `false` | `verboseLogging` | | Enables verbose logging of builder operations. | `false` | `publish` | `--publishImage` | Whether to publish the generated image to a Docker registry. | `false` | `tags` | | A list of one or more additional tags to apply to the generated image. The values provided to the `tags` option should be *full* image references. See xref:packaging-oci-image.adoc#build-image.customization.tags[the tags section] for more details. | | `buildWorkspace` | | A temporary workspace that will be used by the builder and buildpacks to store files during image building. The value can be a named volume or a bind mount location. | A named volume in the Docker daemon, with a name derived from the image name. | `buildCache` | | A cache containing layers created by buildpacks and used by the image building process. The value can be a named volume or a bind mount location. | A named volume in the Docker daemon, with a name derived from the image name. | `launchCache` | | A cache containing layers created by buildpacks and used by the image launching process. The value can be a named volume or a bind mount location. | A named volume in the Docker daemon, with a name derived from the image name. | `createdDate` | `--createdDate` | A date that will be used to set the `Created` field in the generated image's metadata. The value must be a string in the ISO 8601 instant format, or `now` to use the current date and time. | A fixed date that enables {url-buildpacks-docs}/for-app-developers/concepts/reproducibility/[build reproducibility]. | `applicationDirectory` | `--applicationDirectory` | The path to a directory that application contents will be uploaded to in the builder image. Application contents will also be in this location in the generated image. | `/workspace` | `securityOptions` | `--securityOptions` | https://docs.docker.com/reference/cli/docker/container/run/#security-opt[Security options] that will be applied to the builder container, provided as an array of string values | `["label=disable"]` on Linux and macOS, `[]` on Windows |=== NOTE: The plugin detects the target Java compatibility of the project using the JavaPlugin's `targetCompatibility` property. When using the default Paketo builder and buildpacks, the plugin instructs the buildpacks to install the same Java version. You can override this behavior as shown in the xref:packaging-oci-image.adoc#build-image.examples.builder-configuration[builder configuration] examples. NOTE: The default builder `paketobuildpacks/builder-noble-java-tiny:latest` contains a reduced set of system libraries and does not include a shell. Applications that require a shell to run a start script, as might be the case when the {url-gradle-docs-application-plugin}[`application` plugin] has been applied to generate a distribution zip archive, or that depend upon a system library that is not present, should override the `runImage` configuration to use one that includes a shell and a broader set of system libraries, such as `paketobuildpacks/ubuntu-noble-run:latest`. [[build-image.customization.tags]] === Tags Format The values provided to the `tags` option should be *full* image references. The accepted format is `[domainHost:port/][path/]name[:tag][@digest]`. If the domain is missing, it defaults to `docker.io`. If the path is missing, it defaults to `library`. If the tag is missing, it defaults to `latest`. Some examples: * `my-image` leads to the image reference `docker.io/library/my-image:latest` * `my-repository/my-image` leads to `docker.io/my-repository/my-image:latest` * `example.com/my-repository/my-image:1.0.0` will be used as is [[build-image.examples]] == Examples [[build-image.examples.custom-image-builder]] === Custom Image Builder and Run Image If you need to customize the builder used to create the image or the run image used to launch the built image, configure the task as shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-builder.gradle[tags=builder] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-builder.gradle.kts[tags=builder] ---- ====== This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`, and the run image named `mine/java-cnb-run` and the tag `latest`. The builder and run image can be specified on the command line as well, as shown in this example: [source,shell] ---- $ gradle bootBuildImage --builder=mine/java-cnb-builder --runImage=mine/java-cnb-run ---- [[build-image.examples.builder-configuration]] === Builder Configuration If the builder exposes configuration options, those can be set using the `environment` property. The following is an example of {url-paketo-docs-java-buildpack}/#configuring-the-jvm-version[configuring the JVM version] used by the Paketo Java buildpacks at build time: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-env.gradle[tags=env] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-env.gradle.kts[tags=env] ---- ====== Environment variables can also be specified on the command line, as shown in the following example: [source,shell] ---- $ gradle bootBuildImage --environment BP_JVM_VERSION=17 ---- `--environment` can be used multiple times to specify multiple environment variables. If there is a network proxy between the Docker daemon the builder runs in and network locations that buildpacks download artifacts from, you will need to configure the builder to use the proxy. When using the Paketo builder, this can be accomplished by setting the `HTTPS_PROXY` and/or `HTTP_PROXY` environment variables as show in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-env-proxy.gradle[tags=env] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-env-proxy.gradle.kts[tags=env] ---- ====== [[build-image.examples.runtime-jvm-configuration]] === Runtime JVM Configuration Paketo Java buildpacks {url-paketo-docs-java-buildpack}/#runtime-jvm-configuration[configure the JVM runtime environment] by setting the `JAVA_TOOL_OPTIONS` environment variable. The buildpack-provided `JAVA_TOOL_OPTIONS` value can be modified to customize JVM runtime behavior when the application image is launched in a container. Environment variable modifications that should be stored in the image and applied to every deployment can be set as described in the {url-paketo-docs}/buildpacks/configuration/#environment-variables[Paketo documentation] and shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-env-runtime.gradle[tags=env-runtime] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-env-runtime.gradle.kts[tags=env-runtime] ---- ====== [[build-image.examples.custom-image-name]] === Custom Image Name By default, the image name is inferred from the `name` and the `version` of the project, something like `docker.io/library/${project.name}:${project.version}`. You can take control over the name by setting task properties, as shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-name.gradle[tags=image-name] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-name.gradle.kts[tags=image-name] ---- ====== Note that this configuration does not provide an explicit tag so `latest` is used. It is possible to specify a tag as well, either using `${project.version}`, any property available in the build or a hardcoded version. The image name can be specified on the command line as well, as shown in this example: [source,shell] ---- $ gradle bootBuildImage --imageName=example.com/library/my-app:v1 ---- [[build-image.examples.buildpacks]] === Buildpacks By default, the builder will use buildpacks included in the builder image and apply them in a pre-defined order. An alternative set of buildpacks can be provided to apply buildpacks that are not included in the builder, or to change the order of included buildpacks. When one or more buildpacks are provided, only the specified buildpacks will be applied. The following example instructs the builder to use a custom buildpack packaged in a `.tgz` file, followed by a buildpack included in the builder. [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-buildpacks.gradle[tags=buildpacks] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-buildpacks.gradle.kts[tags=buildpacks] ---- ====== Buildpacks can be specified in any of the forms shown below. A buildpack located in a CNB Builder (version may be omitted if there is only one buildpack in the builder matching the `buildpack-id`): * `urn:cnb:builder:buildpack-id` * `urn:cnb:builder:buildpack-id@0.0.1` * `buildpack-id` * `buildpack-id@0.0.1` A path to a directory containing buildpack content (not supported on Windows): * `\file:///path/to/buildpack/` * `/path/to/buildpack/` A path to a gzipped tar file containing buildpack content: * `\file:///path/to/buildpack.tgz` * `/path/to/buildpack.tgz` An OCI image containing a {url-buildpacks-docs}/for-buildpack-authors/how-to/distribute-buildpacks/package-buildpack/[packaged buildpack]: * `docker://example/buildpack` * `docker:///example/buildpack:latest` * `docker:///example/buildpack@sha256:45b23dee08...` * `example/buildpack` * `example/buildpack:latest` * `example/buildpack@sha256:45b23dee08...` [[build-image.examples.publish]] === Image Publishing The generated image can be published to a Docker registry by enabling a `publish` option. If the Docker registry requires authentication, the credentials can be configured using `docker.publishRegistry` properties. If the Docker registry does not require authentication, the `docker.publishRegistry` configuration can be omitted. NOTE: The registry that the image will be published to is determined by the registry part of the image name (`docker.example.com` in these examples). If `docker.publishRegistry` credentials are configured and include a `url` property, this value is passed to the registry but is not used to determine the publishing registry location. [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-publish.gradle[tags=publish] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-publish.gradle.kts[tags=publish] ---- ====== The publish option can be specified on the command line as well, as shown in this example: [source,shell] ---- $ gradle bootBuildImage --imageName=docker.example.com/library/my-app:v1 --publishImage ---- [[build-image.examples.caches]] === Builder Cache and Workspace Configuration The CNB builder caches layers that are used when building and launching an image. By default, these caches are stored as named volumes in the Docker daemon with names that are derived from the full name of the target image. If the image name changes frequently, for example when the project version is used as a tag in the image name, then the caches can be invalidated frequently. The cache volumes can be configured to use alternative names to give more control over cache lifecycle as shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-caches.gradle[tags=caches] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-caches.gradle.kts[tags=caches] ---- ====== Builders and buildpacks need a location to store temporary files during image building. By default, this temporary build workspace is stored in a named volume. The caches and the build workspace can be configured to use bind mounts instead of named volumes, as shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-bind-caches.gradle[tags=caches] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-bind-caches.gradle.kts[tags=caches] ---- ====== [[build-image.examples.docker]] === Docker Configuration [[build-image.examples.docker.minikube]] ==== Docker Configuration for minikube The plugin can communicate with the https://minikube.sigs.k8s.io/docs/tasks/docker_daemon/[Docker daemon provided by minikube] instead of the default local connection. On Linux and macOS, environment variables can be set using the command `eval $(minikube docker-env)` after minikube has been started. The plugin can also be configured to use the minikube daemon by providing connection details similar to those shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-docker-host.gradle[tags=docker-host] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-docker-host.gradle.kts[tags=docker-host] ---- ====== [[build-image.examples.docker.podman]] ==== Docker Configuration for podman The plugin can communicate with a https://podman.io/[podman container engine]. The plugin can be configured to use podman local connection by providing connection details similar to those shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-docker-host-podman.gradle[tags=docker-host] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-docker-host-podman.gradle.kts[tags=docker-host] ---- ====== TIP: With the `podman` CLI installed, the command `podman info --format='{{.Host.RemoteSocket.Path}}'` can be used to get the value for the `docker.host` configuration property shown in this example. [[build-image.examples.docker.colima]] ==== Docker Configuration for Colima The plugin can communicate with the Docker daemon provided by https://github.com/abiosoft/colima[Colima]. The `DOCKER_HOST` environment variable can be set by using the following command: [source,shell,subs="verbatim,attributes"] ---- $ export DOCKER_HOST=$(docker context inspect colima -f '{{.Endpoints.docker.Host}}') ---- The plugin can also be configured to use Colima daemon by providing connection details similar to those shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-docker-host-colima.gradle[tags=docker-host] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-docker-host-colima.gradle.kts[tags=docker-host] ---- ====== [[build-image.examples.docker.auth]] ==== Docker Configuration for Authentication If the builder or run image are stored in a private Docker registry that supports user authentication, authentication details can be provided using `docker.builderRegistry` properties as shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-docker-auth-user.gradle[tags=docker-auth-user] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-docker-auth-user.gradle.kts[tags=docker-auth-user] ---- ====== If the builder or run image is stored in a private Docker registry that supports token authentication, the token value can be provided using `docker.builderRegistry` as shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-docker-auth-token.gradle[tags=docker-auth-token] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-build-image-docker-auth-token.gradle.kts[tags=docker-auth-token] ---- ====== ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging.adoc ================================================ [[packaging-executable]] = Packaging Executable Archives The plugin can create executable archives (jar files and war files) that contain all of an application's dependencies and can then be run with `java -jar`. [[packaging-executable.jars]] == Packaging Executable Jars Executable jars can be built using the `bootJar` task. The task is automatically created when the `java` plugin is applied and is an instance of {apiref-gradle-plugin-boot-jar}[`BootJar`]. The `assemble` task is automatically configured to depend upon the `bootJar` task so running `assemble` (or `build`) will also run the `bootJar` task. [[packaging-executable.wars]] == Packaging Executable Wars Executable wars can be built using the `bootWar` task. The task is automatically created when the `war` plugin is applied and is an instance of {apiref-gradle-plugin-boot-war}[`BootWar`]. The `assemble` task is automatically configured to depend upon the `bootWar` task so running `assemble` (or `build`) will also run the `bootWar` task. [[packaging-executable.wars.deployable]] === Packaging Executable and Deployable Wars A war file can be packaged such that it can be executed using `java -jar` and deployed to an external container. To do so, the embedded servlet container runtime should be added to the `providedRuntime` configuration, for example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/war-container-dependency.gradle[tags=dependencies] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/war-container-dependency.gradle.kts[tags=dependencies] ---- ====== This ensures that the runtime jars are packaged in the war file's `WEB-INF/lib-provided` directory from where they will not conflict with the external container's own classes. NOTE: `providedRuntime` is preferred to Gradle's `compileOnly` configuration as, among other limitations, `compileOnly` dependencies are not on the test classpath so any web-based integration tests will fail. [[packaging-executable.and-plain-archives]] == Packaging Executable and Plain Archives By default, when the `bootJar` or `bootWar` tasks are configured, the `jar` or `war` tasks are configured to use `plain` as the convention for their archive classifier. This ensures that `bootJar` and `jar` or `bootWar` and `war` have different output locations, allowing both the executable archive and the plain archive to be built at the same time. If you prefer that the executable archive, rather than the plain archive, uses a classifier, configure the classifiers as shown in the following example for the `jar` and `bootJar` tasks: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-and-jar-classifiers.gradle[tags=classifiers] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-and-jar-classifiers.gradle.kts[tags=classifiers] ---- ====== Alternatively, if you prefer that the plain archive isn't built at all, disable its task as shown in the following example for the `jar` task: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/only-boot-jar.gradle[tags=disable-jar] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/only-boot-jar.gradle.kts[tags=disable-jar] ---- ====== WARNING: Do not disable the `jar` task when creating native images. See https://github.com/spring-projects/spring-boot/issues/33238[#33238] for details. [[packaging-executable.configuring]] == Configuring Executable Archive Packaging The {apiref-gradle-plugin-boot-jar}[`BootJar`] and {apiref-gradle-plugin-boot-war}[`BootWar`] tasks are subclasses of Gradle's `Jar` and `War` tasks respectively. As a result, all of the standard configuration options that are available when packaging a jar or war are also available when packaging an executable jar or war. A number of configuration options that are specific to executable jars and wars are also provided. [[packaging-executable.configuring.main-class]] === Configuring the Main Class By default, the executable archive's main class will be configured automatically by looking for a class with a `public static void main(String[])` method in the main source set's output. The main class can also be configured explicitly using the task's `mainClass` property: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-main-class.gradle[tags=main-class] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-main-class.gradle.kts[tags=main-class] ---- ====== Alternatively, the main class name can be configured project-wide using the `mainClass` property of the Spring Boot DSL: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/spring-boot-dsl-main-class.gradle[tags=main-class] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/spring-boot-dsl-main-class.gradle.kts[tags=main-class] ---- ====== If the {url-gradle-docs-application-plugin}[`application` plugin] has been applied its `mainClass` property must be configured and can be used for the same purpose: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/application-plugin-main-class.gradle[tags=main-class] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/application-plugin-main-class.gradle.kts[tags=main-class] ---- ====== Lastly, the `Start-Class` attribute can be configured on the task's manifest: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-manifest-main-class.gradle[tags=main-class] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-manifest-main-class.gradle.kts[tags=main-class] ---- ====== NOTE: If the main class is written in Kotlin, the name of the generated Java class should be used. By default, this is the name of the Kotlin class with the `Kt` suffix added. For example, `ExampleApplication` becomes `ExampleApplicationKt`. If another name is defined using `@JvmName` then that name should be used. [[packaging-executable.configuring.including-development-only-dependencies]] === Including Development-only Dependencies By default all dependencies declared in the `developmentOnly` configuration will be excluded from an executable jar or war. If you want to include dependencies declared in the `developmentOnly` configuration in your archive, configure the classpath of its task to include the configuration, as shown in the following example for the `bootWar` task: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-war-include-devtools.gradle[tags=include-devtools] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-war-include-devtools.gradle.kts[tags=include-devtools] ---- ====== [[packaging-executable.configuring.unpacking]] === Configuring Libraries that Require Unpacking Most libraries can be used directly when nested in an executable archive, however certain libraries can have problems. For example, JRuby includes its own nested jar support which assumes that `jruby-complete.jar` is always directly available on the file system. To deal with any problematic libraries, an executable archive can be configured to unpack specific nested jars to a temporary directory when the executable archive is run. Libraries can be identified as requiring unpacking using Ant-style patterns that match against the absolute path of the source jar file: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-requires-unpack.gradle[tags=requires-unpack] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-requires-unpack.gradle.kts[tags=requires-unpack] ---- ====== For more control a closure can also be used. The closure is passed a `FileTreeElement` and should return a `boolean` indicating whether or not unpacking is required. [[packaging-executable.configuring.properties-launcher]] === Using the PropertiesLauncher To use the `PropertiesLauncher` to launch an executable jar or war, configure the task's manifest to set the `Main-Class` attribute: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-war-properties-launcher.gradle[tags=properties-launcher] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-war-properties-launcher.gradle.kts[tags=properties-launcher] ---- ====== [[packaging-executable.configuring.layered-archives]] === Packaging Layered Jar or War By default, the `bootJar` task builds an archive that contains the application's classes and dependencies in `BOOT-INF/classes` and `BOOT-INF/lib` respectively. Similarly, `bootWar` builds an archive that contains the application's classes in `WEB-INF/classes` and dependencies in `WEB-INF/lib` and `WEB-INF/lib-provided`. For cases where a docker image needs to be built from the contents of the jar, it's useful to be able to separate these directories further so that they can be written into distinct layers. Layered jars use the same layout as regular boot packaged jars, but include an additional meta-data file that describes each layer. By default, the following layers are defined: * `dependencies` for any non-project dependency whose version does not contain `SNAPSHOT`. * `spring-boot-loader` for the jar loader classes. * `snapshot-dependencies` for any non-project dependency whose version contains `SNAPSHOT`. * `application` for project dependencies, application classes, and resources. The layers order is important as it determines how likely previous layers can be cached when part of the application changes. The default order is `dependencies`, `spring-boot-loader`, `snapshot-dependencies`, `application`. Content that is least likely to change should be added first, followed by layers that are more likely to change. To disable this feature, you can do so in the following manner: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-layered-disabled.gradle[tags=layered] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-layered-disabled.gradle.kts[tags=layered] ---- ====== When a layered jar or war is created, the `spring-boot-jarmode-tools` jar will be added as a dependency to your archive. With this jar on the classpath, you can launch your application in a special mode which allows the bootstrap code to run something entirely different from your application, for example, something that extracts the layers. If you wish to exclude this dependency, you can do so in the following manner: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-layered-exclude-tools.gradle[tags=layered] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-layered-exclude-tools.gradle.kts[tags=layered] ---- ====== [[packaging-executable.configuring.layered-archives.configuration]] ==== Custom Layers Configuration Depending on your application, you may want to tune how layers are created and add new ones. This can be done using configuration that describes how the jar or war can be separated into layers, and the order of those layers. The following example shows how the default ordering described above can be defined explicitly: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-layered-custom.gradle[tags=layered] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/boot-jar-layered-custom.gradle.kts[tags=layered] ---- ====== The `layered` DSL is defined using three parts: * The `application` closure defines how the application classes and resources should be layered. * The `dependencies` closure defines how dependencies should be layered. * The `layerOrder` method defines the order that the layers should be written. Nested `intoLayer` closures are used within `application` and `dependencies` sections to claim content for a layer. These closures are evaluated in the order that they are defined, from top to bottom. Any content not claimed by an earlier `intoLayer` closure remains available for subsequent ones to consider. The `intoLayer` closure claims content using nested `include` and `exclude` calls. The `application` closure uses Ant-style path matching for include/exclude parameters. The `dependencies` section uses `group:artifact[:version]` patterns. It also provides `includeProjectDependencies()` and `excludeProjectDependencies()` methods that can be used to include or exclude project dependencies. If no `include` call is made, then all content (not claimed by an earlier closure) is considered. If no `exclude` call is made, then no exclusions are applied. Looking at the `dependencies` closure in the example above, we can see that the first `intoLayer` will claim all project dependencies for the `application` layer. The next `intoLayer` will claim all SNAPSHOT dependencies for the `snapshot-dependencies` layer. The third and final `intoLayer` will claim anything left (in this case, any dependency that is not a project dependency or a SNAPSHOT) for the `dependencies` layer. The `application` closure has similar rules. First claiming `org/springframework/boot/loader/**` content for the `spring-boot-loader` layer. Then claiming any remaining classes and resources for the `application` layer. NOTE: The order that `intoLayer` closures are added is often different from the order that the layers are written. For this reason the `layerOrder` method must always be called and _must_ cover all layers referenced by the `intoLayer` calls. ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/publishing.adoc ================================================ [[publishing-your-application]] = Publishing your Application [[publishing-your-application.maven-publish]] == Publishing with the Maven-publish Plugin To publish your Spring Boot jar or war, add it to the publication using the `artifact` method on `MavenPublication`. Pass the task that produces that artifact that you wish to publish to the `artifact` method. For example, to publish the artifact produced by the default `bootJar` task: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$publishing/maven-publish.gradle[tags=publishing] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$publishing/maven-publish.gradle.kts[tags=publishing] ---- ====== [[publishing-your-application.distribution]] == Distributing with the Application Plugin When the {url-gradle-docs-application-plugin}[`application` plugin] is applied a distribution named `boot` is created. This distribution contains the archive produced by the `bootJar` or `bootWar` task and scripts to launch it on Unix-like platforms and Windows. Zip and tar distributions can be built by the `bootDistZip` and `bootDistTar` tasks respectively. To use the `application` plugin, its `mainClassName` property must be configured with the name of your application's main class. ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/reacting.adoc ================================================ [[reacting-to-other-plugins]] = Reacting to Other Plugins When another plugin is applied the Spring Boot plugin reacts by making various changes to the project's configuration. This section describes those changes. [[reacting-to-other-plugins.java]] == Reacting to the Java Plugin When Gradle's {url-gradle-docs-java-plugin}[`java` plugin] is applied to a project, the Spring Boot plugin: 1. Creates a {apiref-gradle-plugin-boot-jar}[`BootJar`] task named `bootJar` that will create an executable, uber jar for the project. The jar will contain everything on the runtime classpath of the main source set; classes are packaged in `BOOT-INF/classes` and jars are packaged in `BOOT-INF/lib` 2. Configures the `assemble` task to depend on the `bootJar` task. 3. Configures the `jar` task to use `plain` as the convention for its archive classifier. 4. Creates a {apiref-gradle-plugin-boot-build-image}[`BootBuildImage`] task named `bootBuildImage` that will create a OCI image using a https://buildpacks.io[buildpack]. 5. Creates a {apiref-gradle-plugin-boot-run}[`BootRun`] task named `bootRun` that can be used to run your application using the `main` source set to find its main method and provide its runtime classpath. 6. Creates a {apiref-gradle-plugin-boot-run}[`BootRun`] task named `bootTestRun` that can be used to run your application using the `test` source set to find its main method and provide its runtime classpath. 7. Creates a configuration named `bootArchives` that contains the artifact produced by the `bootJar` task. 8. Creates a configuration named `developmentOnly` for dependencies that are only required at development time, such as Spring Boot's Devtools, and should not be packaged in executable jars and wars. 9. Creates a configuration named `testAndDevelopmentOnly` for dependencies that are only required at development time and when writing and running tests and that should not be packaged in executable jars and wars. 10. Creates a configuration named `productionRuntimeClasspath`. It is equivalent to `runtimeClasspath` minus any dependencies that only appear in the `developmentOnly` or `testDevelopmentOnly` configurations. 11. Configures any `JavaCompile` tasks with no configured encoding to use `UTF-8`. 12. Configures any `JavaCompile` tasks to use the `-parameters` compiler argument. [[reacting-to-other-plugins.kotlin]] == Reacting to the Kotlin Plugin When {url-kotlin-docs-kotlin-plugin}[Kotlin's Gradle plugin] is applied to a project, the Spring Boot plugin: 1. Aligns the Kotlin version used in Spring Boot's dependency management with the version of the plugin. This is achieved by setting the `kotlin.version` property with a value that matches the version of the Kotlin plugin. 2. Configures any `KotlinCompile` tasks to use the `-java-parameters` compiler argument. [[reacting-to-other-plugins.war]] == Reacting to the War Plugin When Gradle's {url-gradle-docs-war-plugin}[`war` plugin] is applied to a project, the Spring Boot plugin: 1. Creates a {apiref-gradle-plugin-boot-war}[`BootWar`] task named `bootWar` that will create an executable, fat war for the project. In addition to the standard packaging, everything in the `providedRuntime` configuration will be packaged in `WEB-INF/lib-provided`. 2. Configures the `assemble` task to depend on the `bootWar` task. 3. Configures the `war` task to use `plain` as the convention for its archive classifier. 4. Configures the `bootArchives` configuration to contain the artifact produced by the `bootWar` task. [[reacting-to-other-plugins.dependency-management]] == Reacting to the Dependency Management Plugin When the {url-dependency-management-plugin-site}[`io.spring.dependency-management` plugin] is applied to a project, the Spring Boot plugin will automatically import the `spring-boot-dependencies` bom. [[reacting-to-other-plugins.application]] == Reacting to the Application Plugin When Gradle's {url-gradle-docs-application-plugin}[`application` plugin] is applied to a project, the Spring Boot plugin: 1. Creates a `CreateStartScripts` task named `bootStartScripts` that will create scripts that launch the artifact in the `bootArchives` configuration using `java -jar`. The task is configured to use the `applicationDefaultJvmArgs` property as a convention for its `defaultJvmOpts` property. 2. Creates a new distribution named `boot` and configures it to contain the artifact in the `bootArchives` configuration in its `lib` directory and the start scripts in its `bin` directory. 3. Configures the `bootRun` task to use the `mainClassName` property as a convention for its `main` property. 4. Configures the `bootRun` and `bootTestRun` tasks to use the `applicationDefaultJvmArgs` property as a convention for their `jvmArgs` property. 5. Configures the `bootJar` task to use the `mainClassName` property as a convention for the `Start-Class` entry in its manifest. 6. Configures the `bootWar` task to use the `mainClassName` property as a convention for the `Start-Class` entry in its manifest. [[reacting-to-other-plugins.nbt]] == Reacting to the GraalVM Native Image Plugin When the {url-native-build-tools-docs-gradle-plugin}[GraalVM Native Image plugin] is applied to a project, the Spring Boot plugin: . Applies the `org.springframework.boot.aot` plugin that: .. Registers `aot` and `aotTest` source sets. .. Registers a `ProcessAot` task named `processAot` that will generate AOT-optimized source for the application in the `aot` source set. .. Configures the Java compilation and process resources tasks for the `aot` source set to depend upon `processAot`. .. Registers a `ProcessTestAot` task named `processTestAot` that will generated AOT-optimized source for the application's tests in the `aotTest` source set. .. Configures the Java compilation and process resources tasks for the `aotTest` source set to depend upon `processTestAot`. . Adds the output of the `aot` source set to the classpath of the `main` GraalVM native binary. . Adds the output of the `aotTest` source set to the classpath of the `test` GraalVM native binary. . Configures the GraalVM extension to disable Toolchain detection. . Configures the `bootJar` task to include the reachability metadata produced by the `collectReachabilityMetadata` task in its jar. . Configures the `bootJar` task to add the `Spring-Boot-Native-Processed: true` manifest entry. [[reacting-to-other-plugins.cyclonedx]] == Reacting to the CycloneDX Plugin When the {url-cyclonedx-docs-gradle-plugin}[CycloneDX plugin] is applied to a project, the Spring Boot plugin: . Configures the `cyclonedxBom` task to: .. Use the `application` project type. .. Output a JSON-format SBOM to the `application.cdx.json` file. .. Disable the XML-format SBOM. .. Disable full license texts. . Adds the SBOM under `META-INF/sbom` in the generated jar or war file. . Adds the `Sbom-Format` and `Sbom-Location` to the manifest of the jar or war file. [[reacting-to-other-plugins.protobuf]] == Reacting to the Protobuf Plugin When the {url-protobuf-docs-gradle-plugin}[Protobuf plugin] is applied to a project, the Spring Boot plugin: . Configures `protoc` to use the artifact `com.google.protobuf:protoc`, aligning its version with that of Protobuf dependencies on the runtime classpath. . Configures the `grpc` plugin to use the artifact `io.grpc:protoc-gen-grpc-java`, aligning its version with that of gRPC dependencies on the runtime classpath. . Configures the `grpc` plugin of all generate proto tasks with the option `@generated=omit`. ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/running.adoc ================================================ [[running-your-application]] = Running your Application with Gradle To run your application without first building an archive use the `bootRun` task: [source,shell] ---- $ ./gradlew bootRun ---- The `bootRun` task is an instance of {apiref-gradle-plugin-boot-run}[`BootRun`] which is a `JavaExec` subclass. As such, all of the {url-gradle-dsl}/org.gradle.api.tasks.JavaExec.html[usual configuration options] for executing a Java process in Gradle are available to you. The task is automatically configured to use the runtime classpath of the main source set. By default, the main class will be configured automatically by looking for a class with a `public static void main(String[])` method in the main source set's output. The main class can also be configured explicitly using the task's `main` property: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$running/boot-run-main.gradle[tags=main] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$running/boot-run-main.gradle.kts[tags=main] ---- ====== Alternatively, the main class name can be configured project-wide using the `mainClass` property of the Spring Boot DSL: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$running/spring-boot-dsl-main-class-name.gradle[tags=main-class] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$running/spring-boot-dsl-main-class-name.gradle.kts[tags=main-class] ---- ====== By default, `bootRun` will configure the JVM to optimize its launch for faster startup during development. This behavior can be disabled by using the `optimizedLaunch` property, as shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$running/boot-run-disable-optimized-launch.gradle[tags=launch] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$running/boot-run-disable-optimized-launch.gradle.kts[tags=launch] ---- ====== If the {url-gradle-docs-application-plugin}[`application` plugin] has been applied, its `mainClass` property must be configured and can be used for the same purpose: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$running/application-plugin-main-class-name.gradle[tags=main-class] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$running/application-plugin-main-class-name.gradle.kts[tags=main-class] ---- ====== [[running-your-application.passing-arguments]] == Passing Arguments to Your Application Like all `JavaExec` tasks, arguments can be passed into `bootRun` from the command line using `--args=''` when using Gradle 4.9 or later. For example, to run your application with a profile named `dev` active the following command can be used: [source,shell] ---- $ ./gradlew bootRun --args='--spring.profiles.active=dev' ---- See {url-gradle-javadoc}/org/gradle/api/tasks/JavaExec.html#setArgsString(java.lang.String)[the javadoc for `JavaExec.setArgsString`] for further details. [[running-your-application.passing-system-properties]] == Passing System Properties to Your application Since `bootRun` is a standard `JavaExec` task, system properties can be passed to the application's JVM by specifying them in the build script. To make that value of a system property to be configurable set its value using a {url-gradle-dsl}/org.gradle.api.Project.html#N14FE1[project property]. To allow a project property to be optional, reference it using `findProperty`. Doing so also allows a default value to be provided using the `?:` Elvis operator, as shown in the following example: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$running/boot-run-system-property.gradle[tags=system-property] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$running/boot-run-system-property.gradle.kts[tags=system-property] ---- ====== The preceding example sets that `com.example.property` system property to the value of the `example` project property. If the `example` project property has not been set, the value of the system property will be `default`. Gradle allows project properties to be set in a variety of ways, including on the command line using the `-P` flag, as shown in the following example: [source,bash,indent=0,subs="verbatim,attributes"] ---- $ ./gradlew bootRun -Pexample=custom ---- The preceding example sets the value of the `example` project property to `custom`. `bootRun` will then use this as the value of the `com.example.property` system property. [[running-your-application.reloading-resources]] == Reloading Resources If devtools has been added to your project it will automatically monitor your application's classpath for changes. Note that modified files need to be recompiled for the classpath to update in order to trigger reloading with devtools. For more details on using devtools, refer to xref:reference:using/devtools.adoc#using.devtools.restart[this section of the reference documentation]. Alternatively, you can configure `bootRun` such that your application's static resources are loaded from their source location: [tabs] ====== Groovy:: + [source,groovy,indent=0,subs="verbatim,attributes"] ---- include::example$running/boot-run-source-resources.gradle[tags=source-resources] ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,attributes"] ---- include::example$running/boot-run-source-resources.gradle.kts[tags=source-resources] ---- ====== This makes them reloadable in the live application which can be helpful at development time. [[running-your-application.using-a-test-main-class]] == Using a Test Main Class In addition to `bootRun` a `bootTestRun` task is also registered. Like `bootRun`, `bootTestRun` is an instance of `BootRun` but it's configured to use a main class found in the output of the test source set rather than the main source set. It also uses the test source set's runtime classpath rather than the main source set's runtime classpath. As `bootTestRun` is an instance of `BootRun`, all of the configuration options described above for `bootRun` can also be used with `bootTestRun`. ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/partials/nav-gradle-plugin.adoc ================================================ * xref:gradle-plugin:index.adoc[] ** xref:gradle-plugin:getting-started.adoc[] ** xref:gradle-plugin:managing-dependencies.adoc[] ** xref:gradle-plugin:packaging.adoc[] ** xref:gradle-plugin:packaging-oci-image.adoc[] ** xref:gradle-plugin:publishing.adoc[] ** xref:gradle-plugin:running.adoc[] ** xref:gradle-plugin:aot.adoc[] ** xref:gradle-plugin:integrating-with-actuator.adoc[] ** xref:gradle-plugin:reacting.adoc[] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/dsl/SpringBootExtension.java ================================================ /* * 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.gradle.dsl; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.plugins.BasePlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Property; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; import org.jspecify.annotations.Nullable; import org.springframework.boot.gradle.tasks.buildinfo.BuildInfo; /** * Entry point to Spring Boot's Gradle DSL. * * @author Andy Wilkinson * @author Scott Frederick * @since 2.0.0 */ public class SpringBootExtension { private final Project project; private final Property mainClass; /** * Creates a new {@code SpringBootPluginExtension} that is associated with the given * {@code project}. * @param project the project */ public SpringBootExtension(Project project) { this.project = project; this.mainClass = this.project.getObjects().property(String.class); } /** * Returns the fully-qualified name of the application's main class. * @return the fully-qualified name of the application's main class * @since 2.4.0 */ public Property getMainClass() { return this.mainClass; } /** * Creates a new {@link BuildInfo} task named {@code bootBuildInfo} and adds its * output as a {@link SourceDirectorySet#srcDir source directory} of the main source * set's {@link SourceSet#getResources resources}. *

* By default, the task's project artifact will be the archive base name of the * {@code bootWar} or {@code bootJar} task. */ public void buildInfo() { buildInfo(null); } /** * Creates a new {@link BuildInfo} task named {@code bootBuildInfo} and adds its * output as a {@link SourceDirectorySet#srcDir source directory} of the main source * set's {@link SourceSet#getResources resources}. The task is passed to the given * {@code configurer} for further configuration. *

* By default, the task's project artifact will be the archive base name of the * {@code bootWar} or {@code bootJar} task. * @param configurer the task configurer */ public void buildInfo(@Nullable Action configurer) { TaskContainer tasks = this.project.getTasks(); TaskProvider bootBuildInfo = tasks.register("bootBuildInfo", BuildInfo.class, this::configureBuildInfoTask); this.project.getPlugins().withType(JavaPlugin.class, (plugin) -> { bootBuildInfo.configure((buildInfo) -> buildInfo.getProperties() .getArtifact() .convention(this.project.provider(this::determineArtifactBaseName))); this.project.getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets() .getByName(SourceSet.MAIN_SOURCE_SET_NAME) .getResources() .srcDir(bootBuildInfo); }); if (configurer != null) { bootBuildInfo.configure(configurer); } } private void configureBuildInfoTask(BuildInfo task) { task.setGroup(BasePlugin.BUILD_GROUP); task.setDescription("Generates a META-INF/build-info.properties file."); } private @Nullable String determineArtifactBaseName() { Jar artifactTask = findArtifactTask(); return (artifactTask != null) ? artifactTask.getArchiveBaseName().get() : null; } private @Nullable Jar findArtifactTask() { Jar artifactTask = (Jar) this.project.getTasks().findByName("bootWar"); if (artifactTask != null) { return artifactTask; } return (Jar) this.project.getTasks().findByName("bootJar"); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/dsl/package-info.java ================================================ /* * 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. */ /** * Spring Boot Gradle DSL. */ @NullMarked package org.springframework.boot.gradle.dsl; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java ================================================ /* * 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.gradle.plugin; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; import java.util.concurrent.Callable; import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.distribution.Distribution; import org.gradle.api.distribution.DistributionContainer; import org.gradle.api.file.CopySpec; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.JavaApplication; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.application.CreateStartScripts; import org.gradle.jvm.application.scripts.TemplateBasedScriptGenerator; import org.gradle.util.GradleVersion; import org.springframework.boot.gradle.tasks.run.BootRun; import org.springframework.util.Assert; /** * Action that is executed in response to the {@link ApplicationPlugin} being applied. * * @author Andy Wilkinson */ final class ApplicationPluginAction implements PluginApplicationAction { @Override public void execute(Project project) { JavaApplication javaApplication = project.getExtensions().getByType(JavaApplication.class); DistributionContainer distributions = project.getExtensions().getByType(DistributionContainer.class); Distribution distribution = distributions.create("boot"); distribution.getDistributionBaseName() .convention((project.provider(() -> javaApplication.getApplicationName() + "-boot"))); TaskProvider bootStartScripts = project.getTasks() .register("bootStartScripts", CreateStartScripts.class, (task) -> configureCreateStartScripts(project, javaApplication, distribution, task)); CopySpec binCopySpec = project.copySpec().into("bin").from(bootStartScripts); configureFilePermissions(binCopySpec, 0755); distribution.getContents().with(binCopySpec); applyApplicationDefaultJvmArgsToRunTasks(project.getTasks(), javaApplication); } private void applyApplicationDefaultJvmArgsToRunTasks(TaskContainer tasks, JavaApplication javaApplication) { applyApplicationDefaultJvmArgsToRunTask(tasks, javaApplication, SpringBootPlugin.BOOT_RUN_TASK_NAME); applyApplicationDefaultJvmArgsToRunTask(tasks, javaApplication, SpringBootPlugin.BOOT_TEST_RUN_TASK_NAME); } private void applyApplicationDefaultJvmArgsToRunTask(TaskContainer tasks, JavaApplication javaApplication, String taskName) { tasks.named(taskName, BootRun.class) .configure( (bootRun) -> bootRun.getJvmArguments().convention(javaApplication.getApplicationDefaultJvmArgs())); } private void configureCreateStartScripts(Project project, JavaApplication javaApplication, Distribution distribution, CreateStartScripts createStartScripts) { createStartScripts .setDescription("Generates OS-specific start scripts to run the project as a Spring Boot application."); ((TemplateBasedScriptGenerator) createStartScripts.getUnixStartScriptGenerator()) .setTemplate(project.getResources().getText().fromString(loadResource("/unixStartScript.txt"))); ((TemplateBasedScriptGenerator) createStartScripts.getWindowsStartScriptGenerator()) .setTemplate(project.getResources().getText().fromString(loadResource("/windowsStartScript.txt"))); project.getConfigurations().all((configuration) -> { if ("bootArchives".equals(configuration.getName())) { distribution.getContents().with(artifactFilesToLibCopySpec(project, configuration)); createStartScripts.setClasspath(configuration.getArtifacts().getFiles()); } }); createStartScripts.getConventionMapping() .map("outputDir", () -> project.getLayout().getBuildDirectory().dir("bootScripts").get().getAsFile()); createStartScripts.getConventionMapping().map("applicationName", javaApplication::getApplicationName); createStartScripts.getConventionMapping().map("defaultJvmOpts", javaApplication::getApplicationDefaultJvmArgs); } private CopySpec artifactFilesToLibCopySpec(Project project, Configuration configuration) { CopySpec copySpec = project.copySpec().into("lib").from(artifactFiles(configuration)); configureFilePermissions(copySpec, 0644); return copySpec; } private Callable artifactFiles(Configuration configuration) { return () -> configuration.getArtifacts().getFiles(); } @Override public Class> getPluginClass() { return ApplicationPlugin.class; } private String loadResource(String name) { try (InputStreamReader reader = new InputStreamReader(getResourceAsStream(name))) { char[] buffer = new char[4096]; int read; StringWriter writer = new StringWriter(); while ((read = reader.read(buffer)) > 0) { writer.write(buffer, 0, read); } return writer.toString(); } catch (IOException ex) { throw new GradleException("Failed to read '" + name + "'", ex); } } private InputStream getResourceAsStream(String name) { InputStream stream = getClass().getResourceAsStream(name); Assert.state(stream != null, "Resource '%s' not found'".formatted(name)); return stream; } private void configureFilePermissions(CopySpec copySpec, int mode) { if (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) { copySpec.filePermissions((filePermissions) -> filePermissions.unix(Integer.toString(mode, 8))); } else { configureFileMode(copySpec, mode); } } private void configureFileMode(CopySpec copySpec, int mode) { try { 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); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/CyclonedxPluginAction.java ================================================ /* * 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.gradle.plugin; import org.cyclonedx.gradle.CyclonedxAggregateTask; import org.cyclonedx.gradle.CyclonedxPlugin; import org.cyclonedx.model.Component; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Copy; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; import org.springframework.boot.gradle.tasks.bundling.BootJar; import org.springframework.boot.gradle.tasks.bundling.BootWar; /** * {@link Action} that is executed in response to the {@link CyclonedxPlugin} being * applied. * * @author Moritz Halbritter * @author Andy Wilkinson */ final class CyclonedxPluginAction implements PluginApplicationAction { @Override public Class> getPluginClass() { return CyclonedxPlugin.class; } @Override public void execute(Project project) { TaskProvider cycloneDxTaskProvider = project.getTasks() .named("cyclonedxBom", CyclonedxAggregateTask.class); configureCycloneDxTask(cycloneDxTaskProvider, project); configureJavaPlugin(project, cycloneDxTaskProvider); configureSpringBootPlugin(project, cycloneDxTaskProvider); } private void configureCycloneDxTask(TaskProvider taskProvider, Project project) { taskProvider.configure((task) -> { task.getProjectType().convention(Component.Type.APPLICATION); task.getXmlOutput().unsetConvention(); task.getJsonOutput() .convention(project.getLayout().getBuildDirectory().file("reports/cyclonedx/application.cdx.json")); task.getIncludeLicenseText().convention(false); }); } private void configureJavaPlugin(Project project, TaskProvider cycloneDxTaskProvider) { configurePlugin(project, JavaPlugin.class, (javaPlugin) -> { JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); SourceSet main = javaPluginExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); configureTask(project, main.getProcessResourcesTaskName(), Copy.class, (copy) -> { copy.dependsOn(cycloneDxTaskProvider); Provider sbomFileName = cycloneDxTaskProvider.flatMap( (cycloneDxTask) -> cycloneDxTask.getJsonOutput().map((file) -> file.getAsFile().getName())); copy.from(cycloneDxTaskProvider, (spec) -> spec.include((element) -> element.getName().equals(sbomFileName.get())) .into("META-INF/sbom")); }); }); } private void configureSpringBootPlugin(Project project, TaskProvider cycloneDxTaskProvider) { configurePlugin(project, SpringBootPlugin.class, (springBootPlugin) -> { configureBootJarTask(project, cycloneDxTaskProvider); configureBootWarTask(project, cycloneDxTaskProvider); }); } private void configureBootJarTask(Project project, TaskProvider cycloneDxTaskProvider) { configureTask(project, SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class, (bootJar) -> configureBootJarTask(bootJar, cycloneDxTaskProvider)); } private void configureBootWarTask(Project project, TaskProvider cycloneDxTaskProvider) { configureTask(project, SpringBootPlugin.BOOT_WAR_TASK_NAME, BootWar.class, (bootWar) -> configureBootWarTask(bootWar, cycloneDxTaskProvider)); } private void configureBootJarTask(BootJar task, TaskProvider cycloneDxTaskProvider) { configureJarTask(task, cycloneDxTaskProvider, ""); } private void configureBootWarTask(BootWar task, TaskProvider cycloneDxTaskProvider) { configureJarTask(task, cycloneDxTaskProvider, "WEB-INF/classes/"); } private void configureJarTask(Jar task, TaskProvider cycloneDxTaskProvider, String sbomLocationPrefix) { Provider sbomFileName = cycloneDxTaskProvider .map((cycloneDxTask) -> "META-INF/sbom/" + cycloneDxTask.getJsonOutput().get().getAsFile().getName()); task.manifest((manifest) -> { manifest.getAttributes().put("Sbom-Format", "CycloneDX"); manifest.getAttributes() .put("Sbom-Location", sbomFileName.map((fileName) -> sbomLocationPrefix + fileName)); }); } private void configureTask(Project project, String name, Class type, Action action) { project.getTasks().withType(type).configureEach((task) -> { if (task.getName().equals(name)) { action.execute(task); } }); } private > void configurePlugin(Project project, Class plugin, Action action) { project.getPlugins().withType(plugin, action); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/DependencyManagementPluginAction.java ================================================ /* * 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.gradle.plugin; import io.spring.gradle.dependencymanagement.DependencyManagementPlugin; import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.springframework.util.Assert; /** * {@link Action} that is performed in response to the {@link DependencyManagementPlugin} * being applied. * * @author Andy Wilkinson */ final class DependencyManagementPluginAction implements PluginApplicationAction { @Override public void execute(Project project) { DependencyManagementExtension extension = project.getExtensions() .findByType(DependencyManagementExtension.class); Assert.state(extension != null, "'extension' must not be null"); extension.imports((importsHandler) -> importsHandler.mavenBom(SpringBootPlugin.BOM_COORDINATES)); } @Override public Class> getPluginClass() { return DependencyManagementPlugin.class; } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JarTypeFileSpec.java ================================================ /* * 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.gradle.plugin; import java.io.File; import java.util.Set; import java.util.jar.JarFile; import org.gradle.api.file.FileCollection; import org.gradle.api.specs.Spec; /** * A {@link Spec} for {@link FileCollection#filter(Spec) filtering} {@code FileCollection} * to remove jar files based on their {@code Spring-Boot-Jar-Type} as defined in the * manifest. Jars of type {@code dependencies-starter} are excluded. * * @author Andy Wilkinson */ class JarTypeFileSpec implements Spec { private static final Set EXCLUDED_JAR_TYPES = Set.of("dependencies-starter", "development-tool"); @Override public boolean isSatisfiedBy(File file) { try (JarFile jar = new JarFile(file)) { String jarType = jar.getManifest().getMainAttributes().getValue("Spring-Boot-Jar-Type"); if (jarType != null && EXCLUDED_JAR_TYPES.contains(jarType)) { return false; } } catch (Exception ex) { // Continue } return true; } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java ================================================ /* * 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.gradle.plugin; import java.io.File; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.BasePlugin; import org.gradle.api.plugins.ExtensionContainer; import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.jvm.toolchain.JavaToolchainService; import org.gradle.jvm.toolchain.JavaToolchainSpec; import org.jspecify.annotations.Nullable; import org.springframework.boot.gradle.dsl.SpringBootExtension; import org.springframework.boot.gradle.tasks.bundling.BootBuildImage; import org.springframework.boot.gradle.tasks.bundling.BootJar; import org.springframework.boot.gradle.tasks.run.BootRun; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * {@link Action} that is executed in response to the {@link JavaPlugin} being applied. * * @author Andy Wilkinson * @author Scott Frederick */ final class JavaPluginAction implements PluginApplicationAction { private static final String PARAMETERS_COMPILER_ARG = "-parameters"; private final SinglePublishedArtifact singlePublishedArtifact; JavaPluginAction(SinglePublishedArtifact singlePublishedArtifact) { this.singlePublishedArtifact = singlePublishedArtifact; } @Override public Class> getPluginClass() { return JavaPlugin.class; } @Override public void execute(Project project) { classifyJarTask(project); configureBuildTask(project); configureProductionRuntimeClasspathConfiguration(project); configureDevelopmentOnlyConfiguration(project); configureTestAndDevelopmentOnlyConfiguration(project); TaskProvider resolveMainClassName = configureResolveMainClassNameTask(project); TaskProvider bootJar = configureBootJarTask(project, resolveMainClassName); configureBootBuildImageTask(project, bootJar); configureArtifactPublication(bootJar); configureBootRunTask(project, resolveMainClassName); TaskProvider resolveMainTestClassName = configureResolveMainTestClassNameTask(project); configureBootTestRunTask(project, resolveMainTestClassName); project.afterEvaluate(this::configureUtf8Encoding); configureParametersCompilerArg(project); configureAdditionalMetadataLocations(project); configureSpringBootStarterTestToDependOnJUnitPlatformLauncher(project); } private void classifyJarTask(Project project) { project.getTasks() .named(JavaPlugin.JAR_TASK_NAME, Jar.class) .configure((task) -> task.getArchiveClassifier().convention("plain")); } private void configureBuildTask(Project project) { project.getTasks() .named(BasePlugin.ASSEMBLE_TASK_NAME) .configure((task) -> task.dependsOn(this.singlePublishedArtifact)); } private TaskProvider configureResolveMainClassNameTask(Project project) { return project.getTasks() .register(SpringBootPlugin.RESOLVE_MAIN_CLASS_NAME_TASK_NAME, ResolveMainClassName.class, (resolveMainClassName) -> { ExtensionContainer extensions = project.getExtensions(); resolveMainClassName.setDescription("Resolves the name of the application's main class."); resolveMainClassName.setGroup(BasePlugin.BUILD_GROUP); Callable classpath = () -> project.getExtensions() .getByType(SourceSetContainer.class) .getByName(SourceSet.MAIN_SOURCE_SET_NAME) .getOutput(); resolveMainClassName.setClasspath(classpath); resolveMainClassName.getConfiguredMainClassName().convention(project.provider(() -> { String javaApplicationMainClass = getJavaApplicationMainClass(extensions); if (javaApplicationMainClass != null) { return javaApplicationMainClass; } SpringBootExtension springBootExtension = project.getExtensions() .findByType(SpringBootExtension.class); Assert.state(springBootExtension != null, "'springBootExtension' must not be null"); return springBootExtension.getMainClass().getOrNull(); })); resolveMainClassName.getOutputFile() .set(project.getLayout().getBuildDirectory().file("resolvedMainClassName")); }); } private TaskProvider configureResolveMainTestClassNameTask(Project project) { return project.getTasks() .register(SpringBootPlugin.RESOLVE_TEST_MAIN_CLASS_NAME_TASK_NAME, ResolveMainClassName.class, (resolveMainClassName) -> { resolveMainClassName.setDescription("Resolves the name of the application's test main class."); resolveMainClassName.setGroup(BasePlugin.BUILD_GROUP); Callable classpath = () -> { SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); return project.files(sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME).getOutput(), sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput()); }; resolveMainClassName.setClasspath(classpath); resolveMainClassName.getOutputFile() .set(project.getLayout().getBuildDirectory().file("resolvedMainTestClassName")); }); } private static @Nullable String getJavaApplicationMainClass(ExtensionContainer extensions) { JavaApplication javaApplication = extensions.findByType(JavaApplication.class); if (javaApplication == null) { return null; } return javaApplication.getMainClass().getOrNull(); } private TaskProvider configureBootJarTask(Project project, TaskProvider resolveMainClassName) { SourceSet mainSourceSet = javaPluginExtension(project).getSourceSets() .getByName(SourceSet.MAIN_SOURCE_SET_NAME); Configuration developmentOnly = project.getConfigurations() .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); Configuration testAndDevelopmentOnly = project.getConfigurations() .getByName(SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME); Configuration productionRuntimeClasspath = project.getConfigurations() .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME); Configuration runtimeClasspath = project.getConfigurations() .getByName(mainSourceSet.getRuntimeClasspathConfigurationName()); Callable classpath = () -> mainSourceSet.getRuntimeClasspath() .minus((developmentOnly.minus(productionRuntimeClasspath))) .minus((testAndDevelopmentOnly.minus(productionRuntimeClasspath))) .filter(new JarTypeFileSpec()); return project.getTasks().register(SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class, (bootJar) -> { bootJar.setDescription( "Assembles an executable jar archive containing the main classes and their dependencies."); bootJar.setGroup(BasePlugin.BUILD_GROUP); bootJar.classpath(classpath); Provider manifestStartClass = project .provider(() -> (String) bootJar.getManifest().getAttributes().get("Start-Class")); bootJar.getMainClass() .convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent() ? manifestStartClass : resolver.readMainClassName())); bootJar.getTargetJavaVersion() .set(project.provider(() -> javaPluginExtension(project).getTargetCompatibility())); bootJar.resolvedArtifacts(runtimeClasspath.getIncoming().getArtifacts().getResolvedArtifacts()); }); } private void configureBootBuildImageTask(Project project, TaskProvider bootJar) { project.getTasks().register(SpringBootPlugin.BOOT_BUILD_IMAGE_TASK_NAME, BootBuildImage.class, (buildImage) -> { buildImage.setDescription("Builds an OCI image of the application using the output of the bootJar task"); buildImage.setGroup(BasePlugin.BUILD_GROUP); buildImage.getArchiveFile().set(bootJar.get().getArchiveFile()); }); } private void configureArtifactPublication(TaskProvider bootJar) { this.singlePublishedArtifact.addJarCandidate(bootJar); } private void configureBootRunTask(Project project, TaskProvider resolveMainClassName) { Callable classpath = () -> { SourceSet mainSourceSet = javaPluginExtension(project).getSourceSets() .findByName(SourceSet.MAIN_SOURCE_SET_NAME); Assert.state(mainSourceSet != null, "'mainSourceSet' must not be null"); return mainSourceSet.getRuntimeClasspath().filter(new JarTypeFileSpec()); }; project.getTasks().register(SpringBootPlugin.BOOT_RUN_TASK_NAME, BootRun.class, (run) -> { run.setDescription("Runs this project as a Spring Boot application."); run.setGroup(ApplicationPlugin.APPLICATION_GROUP); run.classpath(classpath); run.getMainClass().convention(resolveMainClassName.flatMap(ResolveMainClassName::readMainClassName)); configureToolchainConvention(project, run); }); } private void configureBootTestRunTask(Project project, TaskProvider resolveMainClassName) { Callable classpath = () -> { SourceSet testSourceSet = javaPluginExtension(project).getSourceSets() .findByName(SourceSet.TEST_SOURCE_SET_NAME); Assert.state(testSourceSet != null, "'testSourceSet' must not be null"); return testSourceSet.getRuntimeClasspath().filter(new JarTypeFileSpec()); }; project.getTasks().register("bootTestRun", BootRun.class, (run) -> { run.setDescription("Runs this project as a Spring Boot application using the test runtime classpath."); run.setGroup(ApplicationPlugin.APPLICATION_GROUP); run.classpath(classpath); run.getMainClass().convention(resolveMainClassName.flatMap(ResolveMainClassName::readMainClassName)); configureToolchainConvention(project, run); }); } private void configureToolchainConvention(Project project, BootRun run) { JavaToolchainSpec toolchain = project.getExtensions().getByType(JavaPluginExtension.class).getToolchain(); JavaToolchainService toolchainService = project.getExtensions().getByType(JavaToolchainService.class); run.getJavaLauncher().convention(toolchainService.launcherFor(toolchain)); } private JavaPluginExtension javaPluginExtension(Project project) { return project.getExtensions().getByType(JavaPluginExtension.class); } private void configureUtf8Encoding(Project evaluatedProject) { evaluatedProject.getTasks().withType(JavaCompile.class).configureEach(this::configureUtf8Encoding); } private void configureUtf8Encoding(JavaCompile compile) { if (compile.getOptions().getEncoding() == null) { compile.getOptions().setEncoding("UTF-8"); } } private void configureParametersCompilerArg(Project project) { project.getTasks().withType(JavaCompile.class).configureEach((compile) -> { List compilerArgs = compile.getOptions().getCompilerArgs(); if (!compilerArgs.contains(PARAMETERS_COMPILER_ARG)) { compilerArgs.add(PARAMETERS_COMPILER_ARG); } }); } private void configureAdditionalMetadataLocations(Project project) { project.afterEvaluate((evaluated) -> evaluated.getTasks() .withType(JavaCompile.class) .configureEach(this::configureAdditionalMetadataLocations)); } private void configureAdditionalMetadataLocations(JavaCompile compile) { SourceSetContainer sourceSets = compile.getProject() .getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets(); sourceSets.stream() .filter((candidate) -> candidate.getCompileJavaTaskName().equals(compile.getName())) .map((match) -> match.getResources().getSrcDirs()) .findFirst() .ifPresent((locations) -> compile.doFirst(new AdditionalMetadataLocationsConfigurer(locations))); } @SuppressWarnings({ "rawtypes", "unchecked" }) private void configureProductionRuntimeClasspathConfiguration(Project project) { Configuration productionRuntimeClasspath = project.getConfigurations() .create(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME); Configuration runtimeClasspath = project.getConfigurations() .getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); productionRuntimeClasspath.attributes((attributes) -> { ProviderFactory providers = project.getProviders(); AttributeContainer sourceAttributes = runtimeClasspath.getAttributes(); for (Attribute attribute : sourceAttributes.keySet()) { attributes.attributeProvider(attribute, providers.provider(() -> sourceAttributes.getAttribute(attribute))); } }); productionRuntimeClasspath.setExtendsFrom(runtimeClasspath.getExtendsFrom()); productionRuntimeClasspath.setCanBeResolved(runtimeClasspath.isCanBeResolved()); productionRuntimeClasspath.setCanBeConsumed(runtimeClasspath.isCanBeConsumed()); productionRuntimeClasspath.shouldResolveConsistentlyWith(runtimeClasspath); } private void configureDevelopmentOnlyConfiguration(Project project) { Configuration developmentOnly = project.getConfigurations() .create(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); developmentOnly .setDescription("Configuration for development-only dependencies such as Spring Boot's DevTools."); Configuration runtimeClasspath = project.getConfigurations() .getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); runtimeClasspath.extendsFrom(developmentOnly); } private void configureTestAndDevelopmentOnlyConfiguration(Project project) { Configuration testAndDevelopmentOnly = project.getConfigurations() .create(SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME); testAndDevelopmentOnly .setDescription("Configuration for test and development-only dependencies such as Spring Boot's DevTools."); Configuration runtimeClasspath = project.getConfigurations() .getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); runtimeClasspath.extendsFrom(testAndDevelopmentOnly); Configuration testImplementation = project.getConfigurations() .getByName(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME); testImplementation.extendsFrom(testAndDevelopmentOnly); } private void configureSpringBootStarterTestToDependOnJUnitPlatformLauncher(Project project) { project.getDependencies() .components((components) -> components.withModule("org.springframework.boot:spring-boot-starter-test", (metadata) -> metadata.withVariant("runtimeElements", (variant) -> variant.withDependencies( (dependencies) -> dependencies.add("org.junit.platform:junit-platform-launcher") )))); } /** * Task {@link Action} to add additional meta-data locations. We need to use an * inner-class rather than a lambda due to * https://github.com/gradle/gradle/issues/5510. */ private static final class AdditionalMetadataLocationsConfigurer implements Action { private final Set locations; private AdditionalMetadataLocationsConfigurer(Set locations) { this.locations = locations; } @Override public void execute(Task task) { if (!(task instanceof JavaCompile compile)) { return; } if (hasConfigurationProcessorOnClasspath(compile)) { configureAdditionalMetadataLocations(compile); } } private boolean hasConfigurationProcessorOnClasspath(JavaCompile compile) { Set files = (compile.getOptions().getAnnotationProcessorPath() != null) ? compile.getOptions().getAnnotationProcessorPath().getFiles() : compile.getClasspath().getFiles(); return files.stream() .map(File::getName) .anyMatch((name) -> name.startsWith("spring-boot-configuration-processor")); } private void configureAdditionalMetadataLocations(JavaCompile compile) { compile.getOptions() .getCompilerArgs() .add("-Aorg.springframework.boot.configurationprocessor.additionalMetadataLocations=" + StringUtils.collectionToCommaDelimitedString(this.locations)); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/KotlinPluginAction.java ================================================ /* * 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.gradle.plugin; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.plugins.ExtraPropertiesExtension; import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper; import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapperKt; import org.jetbrains.kotlin.gradle.tasks.KotlinCompile; /** * {@link PluginApplicationAction} that reacts to Kotlin's Gradle plugin being applied by * configuring a {@code kotlin.version} property to align the version used for dependency * management for Kotlin with the version of its plugin. * * @author Andy Wilkinson */ class KotlinPluginAction implements PluginApplicationAction { @Override public void execute(Project project) { configureKotlinVersionProperty(project); enableJavaParametersOption(project); repairDamageToAotCompileConfigurations(project); } private void configureKotlinVersionProperty(Project project) { ExtraPropertiesExtension extraProperties = project.getExtensions().getExtraProperties(); if (!extraProperties.has("kotlin.version")) { String kotlinVersion = getKotlinVersion(project); extraProperties.set("kotlin.version", kotlinVersion); } } private String getKotlinVersion(Project project) { return KotlinPluginWrapperKt.getKotlinPluginVersion(project); } private void enableJavaParametersOption(Project project) { project.getTasks() .withType(KotlinCompile.class) .configureEach((compile) -> compile.getCompilerOptions().getJavaParameters().set(true)); } private void repairDamageToAotCompileConfigurations(Project project) { SpringBootAotPlugin aotPlugin = project.getPlugins().findPlugin(SpringBootAotPlugin.class); if (aotPlugin != null) { aotPlugin.repairKotlinPluginDamage(project); } } @Override public Class> getPluginClass() { return KotlinPluginWrapper.class; } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/NativeImagePluginAction.java ================================================ /* * 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.gradle.plugin; import java.util.LinkedHashSet; import java.util.Set; import java.util.stream.Collectors; import org.graalvm.buildtools.gradle.NativeImagePlugin; import org.graalvm.buildtools.gradle.dsl.GraalVMExtension; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.FileCollection; import org.gradle.api.java.archives.Manifest; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSetContainer; import org.springframework.boot.gradle.tasks.bundling.BootJar; /** * {@link Action} that is executed in response to the {@link NativeImagePlugin} being * applied. * * @author Andy Wilkinson * @author Scott Frederick */ class NativeImagePluginAction implements PluginApplicationAction { @Override public Class> getPluginClass() { return NativeImagePlugin.class; } @Override public void execute(Project project) { project.getPlugins().apply(SpringBootAotPlugin.class); project.getPlugins().withType(JavaPlugin.class).all((plugin) -> { JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); SourceSetContainer sourceSets = javaPluginExtension.getSourceSets(); GraalVMExtension graalVmExtension = configureGraalVmExtension(project); configureMainNativeBinaryClasspath(project, sourceSets, graalVmExtension); configureTestNativeBinaryClasspath(sourceSets, graalVmExtension); copyReachabilityMetadataToBootJar(project); configureJarManifestNativeAttribute(project); }); } private void configureMainNativeBinaryClasspath(Project project, SourceSetContainer sourceSets, GraalVMExtension graalVmExtension) { FileCollection runtimeClasspath = sourceSets.getByName(SpringBootAotPlugin.AOT_SOURCE_SET_NAME) .getRuntimeClasspath(); graalVmExtension.getBinaries().getByName(NativeImagePlugin.NATIVE_MAIN_EXTENSION).classpath(runtimeClasspath); Configuration nativeImageClasspath = project.getConfigurations().getByName("nativeImageClasspath"); nativeImageClasspath.setExtendsFrom(removeDevelopmentOnly(nativeImageClasspath.getExtendsFrom())); } private Iterable removeDevelopmentOnly(Set configurations) { return configurations.stream() .filter(this::isNotDevelopmentOnly) .collect(Collectors.toCollection(LinkedHashSet::new)); } private boolean isNotDevelopmentOnly(Configuration configuration) { return !SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME.equals(configuration.getName()) && !SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME.equals(configuration.getName()); } private void configureTestNativeBinaryClasspath(SourceSetContainer sourceSets, GraalVMExtension graalVmExtension) { FileCollection runtimeClasspath = sourceSets.getByName(SpringBootAotPlugin.AOT_TEST_SOURCE_SET_NAME) .getRuntimeClasspath(); graalVmExtension.getBinaries().getByName(NativeImagePlugin.NATIVE_TEST_EXTENSION).classpath(runtimeClasspath); } private GraalVMExtension configureGraalVmExtension(Project project) { GraalVMExtension extension = project.getExtensions().getByType(GraalVMExtension.class); extension.getToolchainDetection().set(false); return extension; } private void copyReachabilityMetadataToBootJar(Project project) { project.getTasks() .named(SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class) .configure((bootJar) -> bootJar.from(project.getTasks().named("collectReachabilityMetadata"))); } private void configureJarManifestNativeAttribute(Project project) { project.getTasks() .named(SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class) .configure(this::addNativeProcessedAttribute); } private void addNativeProcessedAttribute(BootJar bootJar) { bootJar.manifest(this::addNativeProcessedAttribute); } private void addNativeProcessedAttribute(Manifest manifest) { manifest.getAttributes().put("Spring-Boot-Native-Processed", true); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/PluginApplicationAction.java ================================================ /* * 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.gradle.plugin; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; /** * An {@link Action} to be executed on a {@link Project} in response to a particular type * of {@link Plugin} being applied. * * @author Andy Wilkinson */ interface PluginApplicationAction extends Action { /** * The class of the {@code Plugin} that, when applied, will trigger the execution of * this action. * @return the plugin class * @throws ClassNotFoundException if the plugin class cannot be found * @throws NoClassDefFoundError if an error occurs when defining the plugin class */ Class> getPluginClass() throws ClassNotFoundException, NoClassDefFoundError; } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ProtobufPluginAction.java ================================================ /* * 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.gradle.plugin; import java.util.List; import java.util.Optional; import com.google.protobuf.gradle.ExecutableLocator; import com.google.protobuf.gradle.GenerateProtoTask; import com.google.protobuf.gradle.GenerateProtoTask.PluginOptions; import com.google.protobuf.gradle.ProtobufExtension; import com.google.protobuf.gradle.ProtobufExtension.GenerateProtoTaskCollection; import com.google.protobuf.gradle.ProtobufPlugin; import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.DependencyResolveDetails; import org.gradle.api.artifacts.ModuleVersionSelector; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.artifacts.result.DependencyResult; import org.gradle.api.artifacts.result.ResolvedComponentResult; import org.jspecify.annotations.Nullable; /** * {@link Action} that is executed in response to the {@link ProtobufPlugin} being * applied. * * @author Andy Wilkinson */ final class ProtobufPluginAction implements PluginApplicationAction { private static final Dependency protocDependency = new Dependency("com.google.protobuf", "protoc"); private static final Dependency grpcDependency = new Dependency("io.grpc", "protoc-gen-grpc-java"); private static final List versionAlignment = List.of( protocDependency.alignVersionWith("com.google.protobuf", "protobuf-java-util"), grpcDependency.alignVersionWith("io.grpc", "grpc-util")); @Override public Class> getPluginClass() { return ProtobufPlugin.class; } @Override public void execute(Project project) { ProtobufExtension protobuf = project.getExtensions().getByType(ProtobufExtension.class); protobuf.protoc(this::configureProtoc); protobuf.plugins(this::configurePlugins); protobuf.generateProtoTasks(this::configureGenerateProtoTasks); project.getConfigurations() .named(this::isProtobufToolsLocator) .configureEach((configuration) -> configureProtobufToolsLocator(project, configuration)); } private void configureProtoc(ExecutableLocator protoc) { protoc.setArtifact(protocDependency.asDependencySpec()); } private ExecutableLocator configurePlugins(NamedDomainObjectContainer plugins) { return plugins.create("grpc", (grpc) -> grpc.setArtifact(grpcDependency.asDependencySpec())); } private void configureGenerateProtoTasks(GenerateProtoTaskCollection tasks) { tasks.all().configureEach(this::configureGenerateProtoTask); } private void configureGenerateProtoTask(GenerateProtoTask task) { task.plugins((plugins) -> plugins.create("grpc", this::configureGrpcOptions)); } private void configureGrpcOptions(PluginOptions grpc) { grpc.option("@generated=omit"); } private boolean isProtobufToolsLocator(String name) { return name.startsWith("protobufToolsLocator_"); } private void configureProtobufToolsLocator(Project project, Configuration configuration) { configuration.getResolutionStrategy().eachDependency((details) -> { VersionAlignment versionAlignment = versionAlignmentFor(details); if (versionAlignment != null) { versionAlignment.applyIfPossible(project, details); } }); } private @Nullable VersionAlignment versionAlignmentFor(DependencyResolveDetails details) { if ("null".equals(details.getRequested().getVersion())) { for (VersionAlignment alignment : versionAlignment) { if (alignment.accepts(details)) { return alignment; } } } return null; } private record Dependency(String group, String module) { private VersionAlignment alignVersionWith(String group, String module) { return new VersionAlignment(this, new Dependency(group, module)); } private String asDependencySpec() { return this.group + ":" + this.module; } } private record VersionAlignment(Dependency target, Dependency source) { void applyIfPossible(Project project, DependencyResolveDetails details) { versionFromRuntimeClasspath(project, source()).ifPresent(details::useVersion); } boolean accepts(DependencyResolveDetails details) { ModuleVersionSelector requested = details.getRequested(); return target().group().equals(requested.getGroup()) && target().module().equals(requested.getName()); } private Optional versionFromRuntimeClasspath(Project project, Dependency source) { return project.getConfigurations() .getByName("runtimeClasspath") .getIncoming() .getResolutionResult() .getAllDependencies() .stream() .map(DependencyResult::getFrom) .map(ResolvedComponentResult::getId) .filter(ModuleComponentIdentifier.class::isInstance) .map(ModuleComponentIdentifier.class::cast) .filter((id) -> id.getGroup().equals(source.group()) && id.getModule().equals(source.module())) .map(ModuleComponentIdentifier::getVersion) .findFirst(); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java ================================================ /* * 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.gradle.plugin; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Objects; import java.util.stream.Collectors; import org.gradle.api.DefaultTask; import org.gradle.api.InvalidUserDataException; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.Transformer; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFile; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.gradle.work.DisableCachingByDefault; import org.jspecify.annotations.Nullable; import org.springframework.boot.loader.tools.MainClassFinder; import org.springframework.util.Assert; /** * {@link Task} for resolving the name of the application's main class. * * @author Andy Wilkinson * @since 2.4 */ @DisableCachingByDefault(because = "Not worth caching") public class ResolveMainClassName extends DefaultTask { private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication"; private final RegularFileProperty outputFile; private final Property configuredMainClass; private @Nullable FileCollection classpath; /** * Creates a new instance of the {@code ResolveMainClassName} task. */ public ResolveMainClassName() { this.outputFile = getProject().getObjects().fileProperty(); this.configuredMainClass = getProject().getObjects().property(String.class); } /** * Returns the classpath that the task will examine when resolving the main class * name. * @return the classpath */ @Classpath public FileCollection getClasspath() { Assert.state(this.classpath != null, "'classpath' must not be null"); return this.classpath; } /** * Sets the classpath that the task will examine when resolving the main class name. * @param classpath the classpath */ public void setClasspath(FileCollection classpath) { setClasspath((Object) classpath); } /** * Sets the classpath that the task will examine when resolving the main class name. * The given {@code classpath} is evaluated as per {@link Project#files(Object...)}. * @param classpath the classpath * @since 2.5.10 */ public void setClasspath(Object classpath) { this.classpath = getProject().files(classpath); } /** * Returns the property for the task's output file that will contain the name of the * main class. * @return the output file */ @OutputFile public RegularFileProperty getOutputFile() { return this.outputFile; } /** * Returns the property for the explicitly configured main class name that should be * used in favor of resolving the main class name from the classpath. * @return the configured main class name property */ @Input @Optional public Property getConfiguredMainClassName() { return this.configuredMainClass; } @TaskAction void resolveAndStoreMainClassName() throws IOException { File outputFile = this.outputFile.getAsFile().get(); outputFile.getParentFile().mkdirs(); String mainClassName = resolveMainClassName(); Files.writeString(outputFile.toPath(), mainClassName, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } private String resolveMainClassName() { String configuredMainClass = this.configuredMainClass.getOrNull(); if (configuredMainClass != null) { return configuredMainClass; } return getClasspath().filter(File::isDirectory) .getFiles() .stream() .map(this::findMainClass) .filter(Objects::nonNull) .findFirst() .orElse(""); } private @Nullable String findMainClass(File file) { try { return MainClassFinder.findSingleMainClass(file, SPRING_BOOT_APPLICATION_CLASS_NAME); } catch (IOException ex) { return null; } } Provider readMainClassName() { String classpath = getClasspath().filter(File::isDirectory) .getFiles() .stream() .map(File::getAbsolutePath) .collect(Collectors.joining(File.pathSeparator)); return this.outputFile.map(new ClassNameReader(classpath)); } private static final class ClassNameReader implements Transformer { private final String classpath; private ClassNameReader(String classpath) { this.classpath = classpath; } @Override public String transform(RegularFile file) { if (file.getAsFile().length() == 0) { throw new InvalidUserDataException( "Main class name has not been configured and it could not be resolved from classpath " + this.classpath); } Path output = file.getAsFile().toPath(); try { return Files.readString(output); } catch (IOException ex) { throw new RuntimeException("Failed to read main class name from '" + output + "'"); } } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SinglePublishedArtifact.java ================================================ /* * 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.gradle.plugin; import org.gradle.api.Buildable; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.PublishArtifact; import org.gradle.api.artifacts.PublishArtifactSet; import org.gradle.api.artifacts.dsl.ArtifactHandler; import org.gradle.api.tasks.TaskDependency; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; import org.jspecify.annotations.Nullable; import org.springframework.boot.gradle.tasks.bundling.BootJar; import org.springframework.boot.gradle.tasks.bundling.BootWar; /** * A wrapper for a {@link PublishArtifactSet} that ensures that only a single artifact is * published, with a war file taking precedence over a jar file. * * @author Andy Wilkinson * @author Scott Frederick */ final class SinglePublishedArtifact implements Buildable { private final Configuration configuration; private final ArtifactHandler handler; private @Nullable PublishArtifact currentArtifact; SinglePublishedArtifact(Configuration configuration, ArtifactHandler handler) { this.configuration = configuration; this.handler = handler; } void addWarCandidate(TaskProvider candidate) { add(candidate); } void addJarCandidate(TaskProvider candidate) { if (this.currentArtifact == null) { add(candidate); } } private void add(TaskProvider artifact) { this.configuration.getArtifacts().remove(this.currentArtifact); this.currentArtifact = this.handler.add(this.configuration.getName(), artifact); } @Override public TaskDependency getBuildDependencies() { return this.configuration.getArtifacts().getBuildDependencies(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootAotPlugin.java ================================================ /* * 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.gradle.plugin; import java.util.Set; import java.util.stream.Stream; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.DependencySet; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.attributes.LibraryElements; import org.gradle.api.attributes.Usage; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.Directory; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.PluginContainer; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.jvm.toolchain.JavaToolchainService; import org.gradle.jvm.toolchain.JavaToolchainSpec; import org.springframework.boot.gradle.tasks.aot.AbstractAot; import org.springframework.boot.gradle.tasks.aot.ProcessAot; import org.springframework.boot.gradle.tasks.aot.ProcessTestAot; /** * Gradle plugin for Spring Boot AOT. * * @author Andy Wilkinson * @since 3.0.0 */ public class SpringBootAotPlugin implements Plugin { /** * Name of the main {@code aot} {@link SourceSet source set}. */ public static final String AOT_SOURCE_SET_NAME = "aot"; /** * Name of the {@code aotTest} {@link SourceSet source set}. */ public static final String AOT_TEST_SOURCE_SET_NAME = "aotTest"; /** * Name of the default {@link ProcessAot} task. */ public static final String PROCESS_AOT_TASK_NAME = "processAot"; /** * Name of the default {@link ProcessAot} task. */ public static final String PROCESS_TEST_AOT_TASK_NAME = "processTestAot"; @Override public void apply(Project project) { PluginContainer plugins = project.getPlugins(); plugins.withType(JavaPlugin.class).all((javaPlugin) -> { JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); SourceSetContainer sourceSets = javaPluginExtension.getSourceSets(); SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); SourceSet aotSourceSet = configureSourceSet(project, AOT_SOURCE_SET_NAME, mainSourceSet); SourceSet testSourceSet = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME); SourceSet aotTestSourceSet = configureSourceSet(project, AOT_TEST_SOURCE_SET_NAME, testSourceSet); plugins.withType(SpringBootPlugin.class).all((bootPlugin) -> { registerProcessAotTask(project, aotSourceSet, mainSourceSet); registerProcessTestAotTask(project, mainSourceSet, aotTestSourceSet, testSourceSet); }); }); } private SourceSet configureSourceSet(Project project, String newSourceSetName, SourceSet existingSourceSet) { JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); SourceSetContainer sourceSets = javaPluginExtension.getSourceSets(); return sourceSets.create(newSourceSetName, (sourceSet) -> { existingSourceSet.setRuntimeClasspath(existingSourceSet.getRuntimeClasspath().plus(sourceSet.getOutput())); project.getConfigurations() .getByName(sourceSet.getCompileClasspathConfigurationName()) .attributes((attributes) -> { configureClassesAndResourcesLibraryElementsAttribute(project, attributes); configureJavaRuntimeUsageAttribute(project, attributes); }); }); } private void configureClassesAndResourcesLibraryElementsAttribute(Project project, AttributeContainer attributes) { LibraryElements classesAndResources = project.getObjects() .named(LibraryElements.class, LibraryElements.CLASSES_AND_RESOURCES); attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, classesAndResources); } private void configureJavaRuntimeUsageAttribute(Project project, AttributeContainer attributes) { Usage javaRuntime = project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME); attributes.attribute(Usage.USAGE_ATTRIBUTE, javaRuntime); } private void registerProcessAotTask(Project project, SourceSet aotSourceSet, SourceSet mainSourceSet) { TaskProvider resolveMainClassName = project.getTasks() .named(SpringBootPlugin.RESOLVE_MAIN_CLASS_NAME_TASK_NAME, ResolveMainClassName.class); Configuration aotClasspath = createAotProcessingClasspath(project, PROCESS_AOT_TASK_NAME, mainSourceSet, Set.of(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME, SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME)); project.getDependencies().add(aotClasspath.getName(), project.files(mainSourceSet.getOutput())); Configuration compileClasspath = project.getConfigurations() .getByName(aotSourceSet.getCompileClasspathConfigurationName()); compileClasspath.extendsFrom(aotClasspath); Provider resourcesOutput = project.getLayout() .getBuildDirectory() .dir("generated/" + aotSourceSet.getName() + "Resources"); TaskProvider processAot = project.getTasks() .register(PROCESS_AOT_TASK_NAME, ProcessAot.class, (task) -> { configureAotTask(project, aotSourceSet, task, resourcesOutput); task.getApplicationMainClass() .set(resolveMainClassName.flatMap(ResolveMainClassName::readMainClassName)); task.setClasspath(aotClasspath); }); aotSourceSet.getJava().srcDir(processAot.map(ProcessAot::getSourcesOutput)); aotSourceSet.getResources().srcDir(resourcesOutput); ConfigurableFileCollection classesOutputFiles = project.files(processAot.map(ProcessAot::getClassesOutput)); mainSourceSet.setRuntimeClasspath(mainSourceSet.getRuntimeClasspath().plus(classesOutputFiles)); project.getDependencies().add(aotSourceSet.getImplementationConfigurationName(), classesOutputFiles); configureDependsOn(project, aotSourceSet, processAot); } private void configureAotTask(Project project, SourceSet sourceSet, AbstractAot task, Provider resourcesOutput) { task.getSourcesOutput() .set(project.getLayout().getBuildDirectory().dir("generated/" + sourceSet.getName() + "Sources")); task.getResourcesOutput().set(resourcesOutput); task.getClassesOutput() .set(project.getLayout().getBuildDirectory().dir("generated/" + sourceSet.getName() + "Classes")); task.getGroupId().set(project.provider(() -> String.valueOf(project.getGroup()))); task.getArtifactId().set(project.provider(project::getName)); configureToolchainConvention(project, task); } private void configureToolchainConvention(Project project, AbstractAot aotTask) { JavaToolchainSpec toolchain = project.getExtensions().getByType(JavaPluginExtension.class).getToolchain(); JavaToolchainService toolchainService = project.getExtensions().getByType(JavaToolchainService.class); aotTask.getJavaLauncher().convention(toolchainService.launcherFor(toolchain)); } @SuppressWarnings({ "unchecked", "rawtypes" }) private Configuration createAotProcessingClasspath(Project project, String taskName, SourceSet inputSourceSet, Set developmentOnlyConfigurationNames) { Configuration base = project.getConfigurations() .getByName(inputSourceSet.getRuntimeClasspathConfigurationName()); return project.getConfigurations().create(taskName + "Classpath", (classpath) -> { classpath.setCanBeConsumed(false); if (!classpath.isCanBeResolved()) { throw new IllegalStateException("Unexpected"); } classpath.setCanBeResolved(true); classpath.setDescription("Classpath of the " + taskName + " task."); removeDevelopmentOnly(base.getExtendsFrom(), developmentOnlyConfigurationNames) .forEach(classpath::extendsFrom); classpath.attributes((attributes) -> { ProviderFactory providers = project.getProviders(); AttributeContainer baseAttributes = base.getAttributes(); for (Attribute attribute : baseAttributes.keySet()) { attributes.attributeProvider(attribute, providers.provider(() -> baseAttributes.getAttribute(attribute))); } }); }); } private Stream removeDevelopmentOnly(Set configurations, Set developmentOnlyConfigurationNames) { return configurations.stream() .filter((configuration) -> !developmentOnlyConfigurationNames.contains(configuration.getName())); } private void configureDependsOn(Project project, SourceSet aotSourceSet, TaskProvider processAot) { project.getTasks() .named(aotSourceSet.getProcessResourcesTaskName()) .configure((processResources) -> processResources.dependsOn(processAot)); } private void registerProcessTestAotTask(Project project, SourceSet mainSourceSet, SourceSet aotTestSourceSet, SourceSet testSourceSet) { Configuration aotClasspath = createAotProcessingClasspath(project, PROCESS_TEST_AOT_TASK_NAME, testSourceSet, Set.of(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME)); addJUnitPlatformLauncherDependency(project, aotClasspath); Configuration compileClasspath = project.getConfigurations() .getByName(aotTestSourceSet.getCompileClasspathConfigurationName()); compileClasspath.extendsFrom(aotClasspath); Provider resourcesOutput = project.getLayout() .getBuildDirectory() .dir("generated/" + aotTestSourceSet.getName() + "Resources"); TaskProvider processTestAot = project.getTasks() .register(PROCESS_TEST_AOT_TASK_NAME, ProcessTestAot.class, (task) -> { configureAotTask(project, aotTestSourceSet, task, resourcesOutput); task.setClasspath(aotClasspath); task.setClasspathRoots(testSourceSet.getOutput()); }); aotTestSourceSet.getJava().srcDir(processTestAot.map(ProcessTestAot::getSourcesOutput)); aotTestSourceSet.getResources().srcDir(resourcesOutput); project.getDependencies().add(aotClasspath.getName(), project.files(mainSourceSet.getOutput())); project.getDependencies().add(aotClasspath.getName(), project.files(testSourceSet.getOutput())); ConfigurableFileCollection classesOutputFiles = project .files(processTestAot.map(ProcessTestAot::getClassesOutput)); testSourceSet.setRuntimeClasspath(testSourceSet.getRuntimeClasspath().plus(classesOutputFiles)); project.getDependencies().add(aotTestSourceSet.getImplementationConfigurationName(), classesOutputFiles); configureDependsOn(project, aotTestSourceSet, processTestAot); } private void addJUnitPlatformLauncherDependency(Project project, Configuration configuration) { DependencyHandler dependencyHandler = project.getDependencies(); Dependency springBootDependencies = dependencyHandler .create(dependencyHandler.platform(SpringBootPlugin.BOM_COORDINATES)); DependencySet dependencies = configuration.getDependencies(); dependencies.add(springBootDependencies); dependencies.add(dependencyHandler.create("org.junit.platform:junit-platform-launcher")); } void repairKotlinPluginDamage(Project project) { project.getPlugins().withType(JavaPlugin.class).configureEach((javaPlugin) -> { repairKotlinPluginDamage(project, SpringBootAotPlugin.AOT_SOURCE_SET_NAME); repairKotlinPluginDamage(project, SpringBootAotPlugin.AOT_TEST_SOURCE_SET_NAME); }); } private void repairKotlinPluginDamage(Project project, String sourceSetName) { JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); SourceSetContainer sourceSets = javaPluginExtension.getSourceSets(); Configuration compileClasspath = project.getConfigurations() .getByName(sourceSets.getByName(sourceSetName).getCompileClasspathConfigurationName()); configureJavaRuntimeUsageAttribute(project, compileClasspath.getAttributes()); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java ================================================ /* * 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.gradle.plugin; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.util.GradleVersion; import org.jspecify.annotations.Nullable; import org.springframework.boot.gradle.dsl.SpringBootExtension; import org.springframework.boot.gradle.tasks.bundling.BootBuildImage; import org.springframework.boot.gradle.tasks.bundling.BootJar; import org.springframework.boot.gradle.tasks.bundling.BootWar; import org.springframework.boot.gradle.util.VersionExtractor; /** * Gradle plugin for Spring Boot. * * @author Phillip Webb * @author Dave Syer * @author Andy Wilkinson * @author Danny Hyun * @author Scott Frederick * @since 1.2.7 */ public class SpringBootPlugin implements Plugin { private static final @Nullable String SPRING_BOOT_VERSION = VersionExtractor .forClass(DependencyManagementPluginAction.class); /** * The name of the {@link Configuration} that contains Spring Boot archives. * @since 2.0.0 */ public static final String BOOT_ARCHIVES_CONFIGURATION_NAME = "bootArchives"; /** * The name of the default {@link BootJar} task. * @since 2.0.0 */ public static final String BOOT_JAR_TASK_NAME = "bootJar"; /** * The name of the default {@link BootWar} task. * @since 2.0.0 */ public static final String BOOT_WAR_TASK_NAME = "bootWar"; /** * The name of the default {@link BootBuildImage} task. * @since 2.3.0 */ public static final String BOOT_BUILD_IMAGE_TASK_NAME = "bootBuildImage"; static final String BOOT_RUN_TASK_NAME = "bootRun"; static final String BOOT_TEST_RUN_TASK_NAME = "bootTestRun"; /** * The name of the {@code developmentOnly} configuration. * @since 2.3.0 */ public static final String DEVELOPMENT_ONLY_CONFIGURATION_NAME = "developmentOnly"; /** * The name of the {@code testAndDevelopmentOnly} configuration. * @since 3.2.0 */ public static final String TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME = "testAndDevelopmentOnly"; /** * The name of the {@code productionRuntimeClasspath} configuration. */ public static final String PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME = "productionRuntimeClasspath"; /** * The name of the {@link ResolveMainClassName} task used to resolve a main class from * the output of the {@code main} source set. * @since 3.0.0 */ public static final String RESOLVE_MAIN_CLASS_NAME_TASK_NAME = "resolveMainClassName"; /** * The name of the {@link ResolveMainClassName} task used to resolve a main class from * the output of the {@code test} source set then, if needed, the output of the * {@code main} source set. * @since 3.1.0 */ public static final String RESOLVE_TEST_MAIN_CLASS_NAME_TASK_NAME = "resolveTestMainClassName"; /** * The coordinates {@code (group:name:version)} of the * {@code spring-boot-dependencies} bom. */ public static final String BOM_COORDINATES = "org.springframework.boot:spring-boot-dependencies:" + SPRING_BOOT_VERSION; @Override public void apply(Project project) { verifyGradleVersion(); createExtension(project); Configuration bootArchives = createBootArchivesConfiguration(project); registerPluginActions(project, bootArchives); } private void verifyGradleVersion() { GradleVersion currentVersion = GradleVersion.current(); if (currentVersion.compareTo(GradleVersion.version("8.14")) < 0) { throw new GradleException("Spring Boot plugin requires Gradle 8.x (8.14 or later) or 9.x. " + "The current version is " + currentVersion); } } private void createExtension(Project project) { project.getExtensions().create("springBoot", SpringBootExtension.class, project); } private Configuration createBootArchivesConfiguration(Project project) { Configuration bootArchives = project.getConfigurations().create(BOOT_ARCHIVES_CONFIGURATION_NAME); bootArchives.setDescription("Configuration for Spring Boot archive artifacts."); bootArchives.setCanBeResolved(false); return bootArchives; } private void registerPluginActions(Project project, Configuration bootArchives) { SinglePublishedArtifact singlePublishedArtifact = new SinglePublishedArtifact(bootArchives, project.getArtifacts()); List actions = Arrays.asList(new JavaPluginAction(singlePublishedArtifact), new WarPluginAction(singlePublishedArtifact), new DependencyManagementPluginAction(), new ApplicationPluginAction(), new KotlinPluginAction(), new NativeImagePluginAction(), new CyclonedxPluginAction(), new ProtobufPluginAction()); for (PluginApplicationAction action : actions) { withPluginClassOfAction(action, (pluginClass) -> project.getPlugins().withType(pluginClass, (plugin) -> action.execute(project))); } } private void withPluginClassOfAction(PluginApplicationAction action, Consumer>> consumer) { Class> pluginClass; try { pluginClass = action.getPluginClass(); } catch (Throwable ex) { // Plugin class unavailable. return; } consumer.accept(pluginClass); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java ================================================ /* * 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.gradle.plugin; import java.util.concurrent.Callable; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.BasePlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.WarPlugin; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.War; import org.springframework.boot.gradle.tasks.bundling.BootBuildImage; import org.springframework.boot.gradle.tasks.bundling.BootWar; /** * {@link Action} that is executed in response to the {@link WarPlugin} being applied. * * @author Andy Wilkinson * @author Scott Frederick */ class WarPluginAction implements PluginApplicationAction { private final SinglePublishedArtifact singlePublishedArtifact; WarPluginAction(SinglePublishedArtifact singlePublishedArtifact) { this.singlePublishedArtifact = singlePublishedArtifact; } @Override public Class> getPluginClass() { return WarPlugin.class; } @Override public void execute(Project project) { classifyWarTask(project); TaskProvider bootWar = configureBootWarTask(project); configureBootBuildImageTask(project, bootWar); configureArtifactPublication(bootWar); } private void classifyWarTask(Project project) { project.getTasks() .named(WarPlugin.WAR_TASK_NAME, War.class) .configure((war) -> war.getArchiveClassifier().convention("plain")); } private TaskProvider configureBootWarTask(Project project) { Configuration developmentOnly = project.getConfigurations() .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); Configuration testAndDevelopmentOnly = project.getConfigurations() .getByName(SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME); Configuration productionRuntimeClasspath = project.getConfigurations() .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME); SourceSet mainSourceSet = project.getExtensions() .getByType(SourceSetContainer.class) .getByName(SourceSet.MAIN_SOURCE_SET_NAME); Configuration runtimeClasspath = project.getConfigurations() .getByName(mainSourceSet.getRuntimeClasspathConfigurationName()); Callable classpath = () -> mainSourceSet.getRuntimeClasspath() .minus(providedRuntimeConfiguration(project)) .minus((developmentOnly.minus(productionRuntimeClasspath))) .minus((testAndDevelopmentOnly.minus(productionRuntimeClasspath))) .filter(new JarTypeFileSpec()); Callable providedClasspath = () -> providedRuntimeConfiguration(project) .filter(new JarTypeFileSpec()); TaskProvider resolveMainClassName = project.getTasks() .named(SpringBootPlugin.RESOLVE_MAIN_CLASS_NAME_TASK_NAME, ResolveMainClassName.class); TaskProvider bootWarProvider = project.getTasks() .register(SpringBootPlugin.BOOT_WAR_TASK_NAME, BootWar.class, (bootWar) -> { bootWar.setGroup(BasePlugin.BUILD_GROUP); bootWar.setDescription("Assembles an executable war archive containing webapp" + " content, and the main classes and their dependencies."); bootWar.providedClasspath(providedClasspath); bootWar.setClasspath(classpath); Provider manifestStartClass = project .provider(() -> (String) bootWar.getManifest().getAttributes().get("Start-Class")); bootWar.getMainClass() .convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent() ? manifestStartClass : resolver.readMainClassName())); bootWar.getTargetJavaVersion() .set(project.provider(() -> javaPluginExtension(project).getTargetCompatibility())); bootWar.resolvedArtifacts(runtimeClasspath.getIncoming().getArtifacts().getResolvedArtifacts()); }); return bootWarProvider; } private FileCollection providedRuntimeConfiguration(Project project) { ConfigurationContainer configurations = project.getConfigurations(); return configurations.getByName(WarPlugin.PROVIDED_RUNTIME_CONFIGURATION_NAME); } private void configureBootBuildImageTask(Project project, TaskProvider bootWar) { project.getTasks() .named(SpringBootPlugin.BOOT_BUILD_IMAGE_TASK_NAME, BootBuildImage.class) .configure((buildImage) -> buildImage.getArchiveFile().set(bootWar.get().getArchiveFile())); } private void configureArtifactPublication(TaskProvider bootWar) { this.singlePublishedArtifact.addWarCandidate(bootWar); } private JavaPluginExtension javaPluginExtension(Project project) { return project.getExtensions().getByType(JavaPluginExtension.class); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/package-info.java ================================================ /* * 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. */ /** * Central classes for the Spring Boot Gradle plugin. */ @NullMarked package org.springframework.boot.gradle.plugin; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/AbstractAot.java ================================================ /* * 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.gradle.tasks.aot; import java.util.ArrayList; import java.util.List; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.OutputDirectory; import org.gradle.work.DisableCachingByDefault; /** * Specialization of {@link JavaExec} to be used as a base class for tasks that perform * ahead-of-time processing. * * @author Andy Wilkinson * @since 3.0.0 */ @DisableCachingByDefault(because = "Cacheability can only be determined by a concrete implementation") public abstract class AbstractAot extends JavaExec { private final DirectoryProperty sourcesDir; private final DirectoryProperty resourcesDir; private final DirectoryProperty classesDir; private final Property groupId; private final Property artifactId; protected AbstractAot() { this.sourcesDir = getProject().getObjects().directoryProperty(); this.resourcesDir = getProject().getObjects().directoryProperty(); this.classesDir = getProject().getObjects().directoryProperty(); this.groupId = getProject().getObjects().property(String.class); this.artifactId = getProject().getObjects().property(String.class); } /** * The group ID of the application that is to be processed ahead-of-time. * @return the group ID property */ @Input public final Property getGroupId() { return this.groupId; } /** * The artifact ID of the application that is to be processed ahead-of-time. * @return the artifact ID property */ @Input public final Property getArtifactId() { return this.artifactId; } /** * The directory to which AOT-generated sources should be written. * @return the sources directory property */ @OutputDirectory public final DirectoryProperty getSourcesOutput() { return this.sourcesDir; } /** * The directory to which AOT-generated resources should be written. * @return the resources directory property */ @OutputDirectory public final DirectoryProperty getResourcesOutput() { return this.resourcesDir; } /** * The directory to which AOT-generated classes should be written. * @return the classes directory property */ @OutputDirectory public final DirectoryProperty getClassesOutput() { return this.classesDir; } List processorArgs() { List args = new ArrayList<>(); args.add(getSourcesOutput().getAsFile().get().getAbsolutePath()); args.add(getResourcesOutput().getAsFile().get().getAbsolutePath()); args.add(getClassesOutput().getAsFile().get().getAbsolutePath()); args.add(getGroupId().get()); args.add(getArtifactId().get()); args.addAll(super.getArgs()); return args; } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/ProcessAot.java ================================================ /* * 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.gradle.tasks.aot; import java.util.ArrayList; import java.util.List; import org.gradle.api.provider.Property; import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.TaskAction; /** * Custom {@link JavaExec} task for ahead-of-time processing of a Spring Boot application. * * @author Andy Wilkinson * @since 3.0.0 */ @CacheableTask public abstract class ProcessAot extends AbstractAot { public ProcessAot() { getMainClass().set("org.springframework.boot.SpringApplicationAotProcessor"); } /** * Returns the main class of the application that is to be processed ahead-of-time. * @return the application main class property */ @Input public abstract Property getApplicationMainClass(); @Override @TaskAction public void exec() { List args = new ArrayList<>(); args.add(getApplicationMainClass().get()); args.addAll(processorArgs()); setArgs(args); super.exec(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/ProcessTestAot.java ================================================ /* * 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.gradle.tasks.aot; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTree; import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.IgnoreEmptyDirectories; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.JavaExec; 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.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** * Custom {@link JavaExec} task for ahead-of-time processing of a Spring Boot * application's tests. * * @author Andy Wilkinson * @since 3.0.0 */ @CacheableTask public abstract class ProcessTestAot extends AbstractAot { private @Nullable FileCollection classpathRoots; public ProcessTestAot() { getMainClass().set("org.springframework.boot.test.context.SpringBootTestAotProcessor"); } /** * Returns the classpath roots that should be scanned for test classes to process. * @return the classpath roots */ @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public final @Nullable FileCollection getClasspathRoots() { return this.classpathRoots; } /** * Sets the classpath roots that should be scanned for test classes to process. * @param classpathRoots the classpath roots */ public void setClasspathRoots(FileCollection classpathRoots) { this.classpathRoots = classpathRoots; } @InputFiles @SkipWhenEmpty @IgnoreEmptyDirectories @PathSensitive(PathSensitivity.RELATIVE) final FileTree getInputClasses() { Assert.state(this.classpathRoots != null, "'classpathRoots' must not be null"); return this.classpathRoots.getAsFileTree(); } @Override @TaskAction public void exec() { List args = new ArrayList<>(); FileCollection classpathRoots = getClasspathRoots(); Assert.state(classpathRoots != null, "'classpathRoots' must not be null"); args.add(classpathRoots.getFiles() .stream() .filter(File::exists) .map(File::getAbsolutePath) .collect(Collectors.joining(File.pathSeparator))); args.addAll(processorArgs()); setArgs(args); super.exec(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/package-info.java ================================================ /* * 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. */ /** * Support for ahead-of-time processing of an application built with Gradle. */ @NullMarked package org.springframework.boot.gradle.tasks.aot; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfo.java ================================================ /* * 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.gradle.tasks.buildinfo; import java.io.File; import java.io.IOException; import org.gradle.api.Action; import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskExecutionException; import org.gradle.work.DisableCachingByDefault; import org.springframework.boot.loader.tools.BuildPropertiesWriter; import org.springframework.boot.loader.tools.BuildPropertiesWriter.ProjectDetails; /** * {@link Task} for generating a build info properties file from * {@link BuildInfoProperties}. * * @author Andy Wilkinson * @since 2.0.0 */ @DisableCachingByDefault(because = "Not worth caching") public abstract class BuildInfo extends DefaultTask { private final BuildInfoProperties properties; public BuildInfo() { this.properties = getProject().getObjects().newInstance(BuildInfoProperties.class, getExcludes()); getDestinationDir().convention(getProject().getLayout().getBuildDirectory().dir(getName())); getFilename().convention("META-INF/build-info.properties"); } /** * Returns the names of the properties to exclude from the output. * @return names of the properties to exclude * @since 3.0.0 */ @Internal public abstract SetProperty getExcludes(); /** * Returns the name of the file that is written to the {@link #getDestinationDir * destination dir}. Convention is {@code META-INF/build-info.properties}. * @return the name of the written file * @since 4.1.0 */ @Input public abstract Property getFilename(); /** * Generates the build info properties file in the configured * {@link #getDestinationDir destination dir}. */ @TaskAction public void generateBuildProperties() { try { ProjectDetails details = new ProjectDetails(this.properties.getGroupIfNotExcluded(), this.properties.getArtifactIfNotExcluded(), this.properties.getVersionIfNotExcluded(), this.properties.getNameIfNotExcluded(), this.properties.getTimeIfNotExcluded(), this.properties.getAdditionalIfNotExcluded()); File outputFile = new File(getDestinationDir().getAsFile().get(), getFilename().get()); new BuildPropertiesWriter(outputFile).writeBuildProperties(details); } catch (IOException ex) { throw new TaskExecutionException(this, ex); } } /** * Returns the directory to which the build info file will be written. Convention is * build/${taskName}. * @return the destination directory */ @OutputDirectory public abstract DirectoryProperty getDestinationDir(); /** * Returns the {@link BuildInfoProperties properties} that will be included in the * written file. * @return the properties */ @Nested public BuildInfoProperties getProperties() { return this.properties; } /** * Executes the given {@code action} on the {@link #getProperties()} properties. * @param action the action */ public void properties(Action action) { action.execute(this.properties); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoProperties.java ================================================ /* * 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.gradle.tasks.buildinfo; import java.io.Serializable; import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.function.Supplier; import javax.inject.Inject; import org.gradle.api.Project; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Optional; import org.jspecify.annotations.Nullable; /** * The properties that are written into the {@code META-INF/build-info.properties} file. * * @author Andy Wilkinson * @since 2.0.0 */ @SuppressWarnings("serial") public abstract class BuildInfoProperties implements Serializable { private final SetProperty excludes; private final Supplier<@Nullable String> creationTime = () -> DateTimeFormatter.ISO_INSTANT.format(Instant.now()); @Inject public BuildInfoProperties(Project project, SetProperty excludes) { this.excludes = excludes; getGroup().convention(project.provider(() -> project.getGroup().toString())); getVersion().convention(project.provider(() -> project.getVersion().toString())); getArtifact() .convention(project.provider(() -> project.findProperty("archivesBaseName")).map(Object::toString)); getName().convention(project.provider(project::getName)); } /** * Returns the {@code build.group} property. Defaults to the {@link Project#getGroup() * Project's group}. * @return the group property */ @Internal public abstract Property getGroup(); /** * Returns the {@code build.artifact} property. * @return the artifact property */ @Internal public abstract Property getArtifact(); /** * Returns the {@code build.version} property. Defaults to the * {@link Project#getVersion() Project's version}. * @return the version */ @Internal public abstract Property getVersion(); /** * Returns the {@code build.name} property. Defaults to the {@link Project#getName() * Project's name}. * @return the name */ @Internal public abstract Property getName(); /** * Returns the {@code build.time} property. * @return the time */ @Internal public abstract Property getTime(); /** * Returns the additional properties that will be included. When written, the name of * each additional property is prefixed with {@code build.}. * @return the additional properties */ @Internal public abstract MapProperty getAdditional(); @Input @Optional @Nullable String getArtifactIfNotExcluded() { return getIfNotExcluded(getArtifact(), "artifact"); } @Input @Optional @Nullable String getGroupIfNotExcluded() { return getIfNotExcluded(getGroup(), "group"); } @Input @Optional @Nullable String getNameIfNotExcluded() { return getIfNotExcluded(getName(), "name"); } @Input @Optional @Nullable Instant getTimeIfNotExcluded() { String time = getIfNotExcluded(getTime(), "time", this.creationTime); return (time != null) ? Instant.parse(time) : null; } @Input @Optional @Nullable String getVersionIfNotExcluded() { return getIfNotExcluded(getVersion(), "version"); } @Input Map getAdditionalIfNotExcluded() { return coerceToStringValues(applyExclusions(getAdditional().getOrElse(Collections.emptyMap()))); } private @Nullable T getIfNotExcluded(Property property, String name) { Supplier<@Nullable T> supplier = () -> null; return getIfNotExcluded(property, name, supplier); } private @Nullable T getIfNotExcluded(Property property, String name, Supplier<@Nullable T> defaultValue) { if (this.excludes.getOrElse(Collections.emptySet()).contains(name)) { return null; } if (property.isPresent()) { return property.get(); } return defaultValue.get(); } private Map coerceToStringValues(Map input) { Map output = new HashMap<>(); input.forEach((key, value) -> { if (value instanceof Provider provider) { value = provider.getOrNull(); } if (value != null) { output.put(key, value.toString()); } }); return output; } private Map applyExclusions(Map input) { Map output = new HashMap<>(); Set exclusions = this.excludes.getOrElse(Collections.emptySet()); input.forEach((key, value) -> { boolean isExcluded = exclusions.contains(key); if (!isExcluded) { output.put(key, value); } }); return output; } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/package-info.java ================================================ /* * 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. */ /** * Support for producing build info for consumption by Spring Boot's actuator. */ @NullMarked package org.springframework.boot.gradle.tasks.buildinfo; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java ================================================ /* * 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.gradle.tasks.bundling; import java.util.Set; import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.result.ResolvedArtifactResult; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTreeElement; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.jspecify.annotations.Nullable; /** * A Spring Boot "fat" archive task. * * @author Andy Wilkinson * @author Moritz Halbritter * @since 2.0.0 */ public interface BootArchive extends Task { /** * Returns the fully-qualified name of the application's main class. * @return the fully-qualified name of the application's main class * @since 2.4.0 */ @Input Property getMainClass(); /** * Adds Ant-style patterns that identify files that must be unpacked from the archive * when it is launched. * @param patterns the patterns */ void requiresUnpack(String... patterns); /** * Adds a spec that identifies files that must be unpacked from the archive when it is * launched. * @param spec the spec */ void requiresUnpack(Spec spec); /** * Returns the classpath that will be included in the archive. * @return the classpath */ @Optional @Classpath @Nullable FileCollection getClasspath(); /** * Adds files to the classpath to include in the archive. The given {@code classpath} * is evaluated as per {@link Project#files(Object...)}. * @param classpath the additions to the classpath */ void classpath(Object... classpath); /** * Sets the classpath to include in the archive. The given {@code classpath} is * evaluated as per {@link Project#files(Object...)}. * @param classpath the classpath * @since 2.0.7 */ void setClasspath(Object classpath); /** * Sets the classpath to include in the archive. * @param classpath the classpath * @since 2.0.7 */ void setClasspath(FileCollection classpath); /** * Returns the target Java version of the project (e.g. as provided by the * {@code targetCompatibility} build property). * @return the target Java version */ @Input @Optional Property getTargetJavaVersion(); /** * Registers the given lazily provided {@code resolvedArtifacts}. They are used to map * from the files in the {@link #getClasspath classpath} to their dependency * coordinates. * @param resolvedArtifacts the lazily provided resolved artifacts * @since 3.0.7 */ void resolvedArtifacts(Provider> resolvedArtifacts); /** * Returns whether the JAR tools should be included as a dependency in the layered * archive. * @return whether the JAR tools should be included * @since 3.3.0 */ @Input Property getIncludeTools(); } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.function.Function; import org.gradle.api.file.ConfigurableFilePermissions; import org.gradle.api.file.CopySpec; import org.gradle.api.file.FileCopyDetails; import org.gradle.api.file.FileTreeElement; import org.gradle.api.internal.file.copy.CopyAction; import org.gradle.api.java.archives.Attributes; import org.gradle.api.java.archives.Manifest; import org.gradle.api.provider.Property; import org.gradle.api.specs.Spec; import org.gradle.api.specs.Specs; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.util.PatternSet; import org.gradle.util.GradleVersion; import org.jspecify.annotations.Nullable; /** * Support class for implementations of {@link BootArchive}. * * @author Andy Wilkinson * @author Phillip Webb * @author Scott Frederick * @see BootJar * @see BootWar */ class BootArchiveSupport { private static final byte[] ZIP_FILE_HEADER = new byte[] { 'P', 'K', 3, 4 }; private static final String UNSPECIFIED_VERSION = "unspecified"; private static final Set DEFAULT_LAUNCHER_CLASSES; static { Set defaultLauncherClasses = new HashSet<>(); defaultLauncherClasses.add("org.springframework.boot.loader.launch.JarLauncher"); defaultLauncherClasses.add("org.springframework.boot.loader.launch.PropertiesLauncher"); defaultLauncherClasses.add("org.springframework.boot.loader.launch.WarLauncher"); DEFAULT_LAUNCHER_CLASSES = Collections.unmodifiableSet(defaultLauncherClasses); } private final PatternSet requiresUnpack = new PatternSet(); private final PatternSet exclusions = new PatternSet(); private final String loaderMainClass; private final Spec librarySpec; private final Function compressionResolver; BootArchiveSupport(String loaderMainClass, Spec librarySpec, Function compressionResolver) { this.loaderMainClass = loaderMainClass; this.librarySpec = librarySpec; this.compressionResolver = compressionResolver; this.requiresUnpack.include(Specs.satisfyNone()); } void configureManifest(Manifest manifest, String mainClass, String classes, String lib, @Nullable String classPathIndex, @Nullable String layersIndex, String jdkVersion, String implementationTitle, @Nullable Object implementationVersion) { Attributes attributes = manifest.getAttributes(); attributes.putIfAbsent("Main-Class", this.loaderMainClass); attributes.putIfAbsent("Start-Class", mainClass); attributes.computeIfAbsent("Spring-Boot-Version", (name) -> determineSpringBootVersion()); attributes.putIfAbsent("Spring-Boot-Classes", classes); attributes.putIfAbsent("Spring-Boot-Lib", lib); if (classPathIndex != null) { attributes.putIfAbsent("Spring-Boot-Classpath-Index", classPathIndex); } if (layersIndex != null) { attributes.putIfAbsent("Spring-Boot-Layers-Index", layersIndex); } attributes.putIfAbsent("Build-Jdk-Spec", jdkVersion); attributes.putIfAbsent("Implementation-Title", implementationTitle); if (implementationVersion != null) { String versionString = implementationVersion.toString(); if (!UNSPECIFIED_VERSION.equals(versionString)) { attributes.putIfAbsent("Implementation-Version", versionString); } } } private String determineSpringBootVersion() { String version = getClass().getPackage().getImplementationVersion(); return (version != null) ? version : "unknown"; } CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies) { return createCopyAction(jar, resolvedDependencies, null, null); } CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, @Nullable LayerResolver layerResolver, @Nullable String jarmodeToolsLocation) { File output = jar.getArchiveFile().get().getAsFile(); Manifest manifest = jar.getManifest(); boolean preserveFileTimestamps = jar.isPreserveFileTimestamps(); Integer dirPermissions = getUnixNumericDirPermissions(jar); Integer filePermissions = getUnixNumericFilePermissions(jar); boolean includeDefaultLoader = isUsingDefaultLoader(jar); Spec requiresUnpack = this.requiresUnpack.getAsSpec(); Spec exclusions = this.exclusions.getAsExcludeSpec(); Spec librarySpec = this.librarySpec; Function compressionResolver = this.compressionResolver; String encoding = jar.getMetadataCharset(); CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirPermissions, filePermissions, includeDefaultLoader, jarmodeToolsLocation, requiresUnpack, exclusions, librarySpec, compressionResolver, encoding, resolvedDependencies, layerResolver); return action; } private @Nullable Integer getUnixNumericDirPermissions(CopySpec copySpec) { return (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) ? asUnixNumeric(copySpec.getDirPermissions()) : getDirMode(copySpec); } private @Nullable Integer getUnixNumericFilePermissions(CopySpec copySpec) { return (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) ? asUnixNumeric(copySpec.getFilePermissions()) : getFileMode(copySpec); } private @Nullable Integer asUnixNumeric(Property permissions) { return permissions.isPresent() ? permissions.get().toUnixNumeric() : null; } private @Nullable Integer getDirMode(CopySpec copySpec) { try { return (Integer) copySpec.getClass().getMethod("getDirMode").invoke(copySpec); } catch (Exception ex) { throw new RuntimeException("Failed to get dir mode from CopySpec", ex); } } private @Nullable Integer getFileMode(CopySpec copySpec) { try { 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) { return DEFAULT_LAUNCHER_CLASSES.contains(jar.getManifest().getAttributes().get("Main-Class")); } void requiresUnpack(String... patterns) { this.requiresUnpack.include(patterns); } void requiresUnpack(Spec spec) { this.requiresUnpack.include(spec); } void excludeNonZipLibraryFiles(FileCopyDetails details) { if (this.librarySpec.isSatisfiedBy(details)) { excludeNonZipFiles(details); } } void excludeNonZipFiles(FileCopyDetails details) { if (!isZip(details.getFile())) { details.exclude(); } } private boolean isZip(File file) { try { try (FileInputStream fileInputStream = new FileInputStream(file)) { return isZip(fileInputStream); } } catch (IOException ex) { return false; } } private boolean isZip(InputStream inputStream) throws IOException { for (byte headerByte : ZIP_FILE_HEADER) { if (inputStream.read() != headerByte) { return false; } } return true; } void moveModuleInfoToRoot(CopySpec spec) { spec.filesMatching("module-info.class", this::moveToRoot); } void moveToRoot(FileCopyDetails details) { details.setRelativePath(details.getRelativeSourcePath()); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.IOException; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.gradle.api.Action; import org.gradle.api.DefaultTask; import org.gradle.api.InvalidUserDataException; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; import org.gradle.work.DisableCachingByDefault; import org.springframework.boot.buildpack.platform.build.BuildRequest; import org.springframework.boot.buildpack.platform.build.Builder; import org.springframework.boot.buildpack.platform.build.BuildpackReference; import org.springframework.boot.buildpack.platform.build.Cache; import org.springframework.boot.buildpack.platform.build.Creator; import org.springframework.boot.buildpack.platform.build.PullPolicy; import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException; 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.ImageReference; import org.springframework.boot.buildpack.platform.io.ZipFileTarArchive; import org.springframework.boot.gradle.util.VersionExtractor; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** * A {@link Task} for bundling an application into an OCI image using a * buildpack. * * @author Andy Wilkinson * @author Scott Frederick * @author Rafael Ceccone * @author Jeroen Meijer * @author Julian Liebig * @since 2.3.0 */ @DisableCachingByDefault public abstract class BootBuildImage extends DefaultTask { private final Property pullPolicy; private final String projectName; private final CacheSpec buildWorkspace; private final CacheSpec buildCache; private final CacheSpec launchCache; private final DockerSpec docker; public BootBuildImage() { this.projectName = getProject().getName(); Project project = getProject(); Property projectVersion = project.getObjects() .property(String.class) .convention(project.provider(() -> project.getVersion().toString())); getImageName().convention(project.provider(() -> { ImageName imageName = ImageName.of(this.projectName); if ("unspecified".equals(projectVersion.get())) { return ImageReference.of(imageName).toString(); } return ImageReference.of(imageName, projectVersion.get()).toString(); })); getTrustBuilder().convention((Boolean) null); getCleanCache().convention(false); getVerboseLogging().convention(false); getPublish().convention(false); this.buildWorkspace = getProject().getObjects().newInstance(CacheSpec.class); this.buildCache = getProject().getObjects().newInstance(CacheSpec.class); this.launchCache = getProject().getObjects().newInstance(CacheSpec.class); this.docker = getProject().getObjects().newInstance(DockerSpec.class); this.pullPolicy = getProject().getObjects().property(PullPolicy.class); getSecurityOptions().convention((Iterable) null); getEffectiveEnvironment().putAll(getEnvironment()); getEffectiveEnvironment().putAll(getEnvironmentFromCommandLine().map(BootBuildImage::asMap)); } private static Map asMap(List variables) { Map environment = new LinkedHashMap<>(); for (String variable : variables) { int index = variable.indexOf('='); if (index <= 0) { throw new InvalidUserDataException( "Invalid value for option '--environment'. Expected 'NAME=VALUE' but got '" + variable + "'."); } String name = variable.substring(0, index); String value = variable.substring(index + 1); environment.put(name, value); } return environment; } /** * Returns the property for the archive file from which the image will be built. * @return the archive file property */ @InputFile @PathSensitive(PathSensitivity.RELATIVE) public abstract RegularFileProperty getArchiveFile(); /** * Returns the name of the image that will be built. When {@code null}, the name will * be derived from the {@link Project Project's} {@link Project#getName() name} and * {@link Project#getVersion version}. * @return name of the image */ @Input @Optional @Option(option = "imageName", description = "The name of the image to generate") public abstract Property getImageName(); /** * Returns the builder that will be used to build the image. When {@code null}, the * default builder will be used. * @return the builder */ @Input @Optional @Option(option = "builder", description = "The name of the builder image to use") public abstract Property getBuilder(); /** * Whether to treat the builder as trusted. * @return whether to trust the builder * @since 3.4.0 */ @Input @Optional @Option(option = "trustBuilder", description = "Consider the builder trusted") public abstract Property getTrustBuilder(); /** * Returns the run image that will be included in the built image. When {@code null}, * the run image bundled with the builder will be used. * @return the run image */ @Input @Optional @Option(option = "runImage", description = "The name of the run image to use") public abstract Property getRunImage(); /** * Returns the environment that will be used when building the image. * @return the environment */ @Internal public abstract MapProperty getEnvironment(); /** * Returns environment variables contributed from the command line. Each entry must be * in the form NAME=VALUE. * @return the environment variables from the command line */ @Internal @Option(option = "environment", description = "Environment variable that will be used when building the image " + "(NAME=VALUE). Can be specified multiple times.") abstract ListProperty getEnvironmentFromCommandLine(); @Input abstract MapProperty getEffectiveEnvironment(); /** * Returns whether caches should be cleaned before packaging. * @return whether caches should be cleaned * @since 3.0.0 */ @Input @Option(option = "cleanCache", description = "Clean caches before packaging") public abstract Property getCleanCache(); /** * Whether verbose logging should be enabled while building the image. * @return whether verbose logging should be enabled * @since 3.0.0 */ @Input public abstract Property getVerboseLogging(); /** * Returns image pull policy that will be used when building the image. * @return whether images should be pulled */ @Input @Optional @Option(option = "pullPolicy", description = "The image pull policy") public Property getPullPolicy() { return this.pullPolicy; } /** * Sets image pull policy that will be used when building the image. * @param pullPolicy the pull policy to use */ public void setPullPolicy(String pullPolicy) { getPullPolicy().set(PullPolicy.valueOf(pullPolicy)); } /** * Whether the built image should be pushed to a registry. * @return whether the built image should be pushed * @since 3.0.0 */ @Input @Option(option = "publishImage", description = "Publish the built image to a registry") public abstract Property getPublish(); /** * Returns the buildpacks that will be used when building the image. * @return the buildpack references */ @Input @Optional public abstract ListProperty getBuildpacks(); /** * Returns the volume bindings that will be mounted to the container when building the * image. * @return the bindings */ @Input @Optional public abstract ListProperty getBindings(); /** * Returns the tags that will be created for the built image. * @return the tags */ @Input @Optional public abstract ListProperty getTags(); /** * Returns the network the build container will connect to. * @return the network */ @Input @Optional @Option(option = "network", description = "Connect detect and build containers to network") public abstract Property getNetwork(); /** * Returns the build temporary workspace that will be used when building the image. * @return the cache * @since 3.2.0 */ @Nested @Optional public CacheSpec getBuildWorkspace() { return this.buildWorkspace; } /** * Customizes the {@link CacheSpec} for the build temporary workspace using the given * {@code action}. * @param action the action * @since 3.2.0 */ public void buildWorkspace(Action action) { action.execute(this.buildWorkspace); } /** * Returns the build cache that will be used when building the image. * @return the cache */ @Nested @Optional public CacheSpec getBuildCache() { return this.buildCache; } /** * Customizes the {@link CacheSpec} for the build cache using the given * {@code action}. * @param action the action */ public void buildCache(Action action) { action.execute(this.buildCache); } /** * Returns the launch cache that will be used when building the image. * @return the cache */ @Nested @Optional public CacheSpec getLaunchCache() { return this.launchCache; } /** * Customizes the {@link CacheSpec} for the launch cache using the given * {@code action}. * @param action the action */ public void launchCache(Action action) { action.execute(this.launchCache); } /** * Returns the date that will be used as the {@code Created} date of the image. When * {@code null}, a fixed date that enables build reproducibility will be used. * @return the created date */ @Input @Optional @Option(option = "createdDate", description = "The date to use as the created date of the image") public abstract Property getCreatedDate(); /** * Returns the directory that contains application content in the image. When * {@code null}, a default location will be used. * @return the application directory */ @Input @Optional @Option(option = "applicationDirectory", description = "The directory containing application content in the image") public abstract Property getApplicationDirectory(); /** * Returns the security options that will be applied to the builder container. * @return the security options */ @Input @Optional @Option(option = "securityOptions", description = "Security options that will be applied to the builder container") public abstract ListProperty getSecurityOptions(); /** * Returns the platform (os/architecture/variant) that will be used for all pulled * images. When {@code null}, the system will choose a platform based on the host * operating system and architecture. * @return the image platform */ @Input @Optional @Option(option = "imagePlatform", description = "The platform (os/architecture/variant) that will be used for all pulled images") public abstract Property getImagePlatform(); /** * Returns the Docker configuration the builder will use. * @return docker configuration. * @since 2.4.0 */ @Nested public DockerSpec getDocker() { return this.docker; } /** * Configures the Docker connection using the given {@code action}. * @param action the action to apply * @since 2.4.0 */ public void docker(Action action) { action.execute(this.docker); } @TaskAction void buildImage() throws DockerEngineException, IOException { Builder builder = new Builder(this.docker.asDockerConfiguration()); BuildRequest request = createRequest(); builder.build(request); } BuildRequest createRequest() { return customize(BuildRequest.of(getImageName().map(ImageReference::of).get(), (owner) -> new ZipFileTarArchive(getArchiveFile().get().getAsFile(), owner))); } private BuildRequest customize(BuildRequest request) { request = customizeBuilder(request); if (getTrustBuilder().isPresent()) { request = request.withTrustBuilder(getTrustBuilder().get()); } request = customizeRunImage(request); request = customizeEnvironment(request); request = customizeCreator(request); request = request.withCleanCache(getCleanCache().get()); request = request.withVerboseLogging(getVerboseLogging().get()); request = customizePullPolicy(request); request = request.withPublish(getPublish().get()); request = customizeBuildpacks(request); request = customizeBindings(request); request = customizeTags(request); request = customizeCaches(request); request = request.withNetwork(getNetwork().getOrNull()); request = customizeCreatedDate(request); request = customizeApplicationDirectory(request); request = customizeSecurityOptions(request); if (getImagePlatform().isPresent()) { request = request.withImagePlatform(getImagePlatform().get()); } return request; } private BuildRequest customizeBuilder(BuildRequest request) { String builder = getBuilder().getOrNull(); if (StringUtils.hasText(builder)) { return request.withBuilder(ImageReference.of(builder)); } return request; } private BuildRequest customizeRunImage(BuildRequest request) { String runImage = getRunImage().getOrNull(); if (StringUtils.hasText(runImage)) { return request.withRunImage(ImageReference.of(runImage)); } return request; } private BuildRequest customizeEnvironment(BuildRequest request) { Map environment = getEffectiveEnvironment().getOrElse(Collections.emptyMap()); if (!environment.isEmpty()) { request = request.withEnv(environment); } return request; } private BuildRequest customizeCreator(BuildRequest request) { String springBootVersion = VersionExtractor.forClass(BootBuildImage.class); if (StringUtils.hasText(springBootVersion)) { return request.withCreator(Creator.withVersion(springBootVersion)); } return request; } private BuildRequest customizePullPolicy(BuildRequest request) { PullPolicy pullPolicy = getPullPolicy().getOrNull(); if (pullPolicy != null) { request = request.withPullPolicy(pullPolicy); } return request; } private BuildRequest customizeBuildpacks(BuildRequest request) { List buildpacks = getBuildpacks().getOrNull(); if (!CollectionUtils.isEmpty(buildpacks)) { return request.withBuildpacks(buildpacks.stream().map(BuildpackReference::of).toList()); } return request; } private BuildRequest customizeBindings(BuildRequest request) { List bindings = getBindings().getOrNull(); if (!CollectionUtils.isEmpty(bindings)) { return request.withBindings(bindings.stream().map(Binding::of).toList()); } return request; } private BuildRequest customizeTags(BuildRequest request) { List tags = getTags().getOrNull(); if (!CollectionUtils.isEmpty(tags)) { return request.withTags(tags.stream().map(ImageReference::of).toList()); } return request; } private BuildRequest customizeCaches(BuildRequest request) { Cache buildWorkspaceCache = this.buildWorkspace.asCache(); if (buildWorkspaceCache != null) { request = request.withBuildWorkspace(buildWorkspaceCache); } Cache buildCache = this.buildCache.asCache(); if (buildCache != null) { request = request.withBuildCache(buildCache); } Cache launchCache = this.launchCache.asCache(); if (launchCache != null) { request = request.withLaunchCache(launchCache); } return request; } private BuildRequest customizeCreatedDate(BuildRequest request) { String createdDate = getCreatedDate().getOrNull(); if (createdDate != null) { return request.withCreatedDate(createdDate); } return request; } private BuildRequest customizeApplicationDirectory(BuildRequest request) { String applicationDirectory = getApplicationDirectory().getOrNull(); if (applicationDirectory != null) { return request.withApplicationDirectory(applicationDirectory); } return request; } private BuildRequest customizeSecurityOptions(BuildRequest request) { if (getSecurityOptions().isPresent()) { List securityOptions = getSecurityOptions().getOrNull(); if (securityOptions != null) { return request.withSecurityOptions(securityOptions); } } return request; } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import java.util.Collections; import java.util.Set; import java.util.concurrent.Callable; import java.util.function.Function; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.artifacts.result.ResolvedArtifactResult; import org.gradle.api.file.CopySpec; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileCopyDetails; import org.gradle.api.file.FileTreeElement; import org.gradle.api.internal.file.copy.CopyAction; import org.gradle.api.provider.Provider; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.bundling.Jar; import org.gradle.work.DisableCachingByDefault; import org.jspecify.annotations.Nullable; /** * A custom {@link Jar} task that produces a Spring Boot executable jar. * * @author Andy Wilkinson * @author Madhura Bhave * @author Scott Frederick * @author Phillip Webb * @since 2.0.0 */ @DisableCachingByDefault(because = "Not worth caching") public abstract class BootJar extends Jar implements BootArchive { private static final String LAUNCHER = "org.springframework.boot.loader.launch.JarLauncher"; private static final String CLASSES_DIRECTORY = "BOOT-INF/classes/"; private static final String LIB_DIRECTORY = "BOOT-INF/lib/"; private static final String LAYERS_INDEX = "BOOT-INF/layers.idx"; private static final String CLASSPATH_INDEX = "BOOT-INF/classpath.idx"; private final BootArchiveSupport support; private final CopySpec bootInfSpec; private final LayeredSpec layered; private final Provider projectName; private final Provider projectVersion; private final ResolvedDependencies resolvedDependencies; private @Nullable FileCollection classpath; /** * Creates a new {@code BootJar} task. */ public BootJar() { this.support = new BootArchiveSupport(LAUNCHER, new LibrarySpec(), new ZipCompressionResolver()); Project project = getProject(); this.bootInfSpec = project.copySpec().into("BOOT-INF"); this.layered = project.getObjects().newInstance(LayeredSpec.class); configureBootInfSpec(this.bootInfSpec); getMainSpec().with(this.bootInfSpec); this.projectName = project.provider(project::getName); this.projectVersion = project.provider(project::getVersion); this.resolvedDependencies = new ResolvedDependencies(project); getIncludeTools().convention(true); } private void configureBootInfSpec(CopySpec bootInfSpec) { bootInfSpec.into("classes", fromCallTo(this::classpathDirectories)); bootInfSpec.into("lib", fromCallTo(this::classpathFiles)).eachFile(this.support::excludeNonZipFiles); this.support.moveModuleInfoToRoot(bootInfSpec); moveMetaInfToRoot(bootInfSpec); } private Iterable classpathDirectories() { return classpathEntries(File::isDirectory); } private Iterable classpathFiles() { return classpathEntries(File::isFile); } private Iterable classpathEntries(Spec filter) { return (this.classpath != null) ? this.classpath.filter(filter) : Collections.emptyList(); } private void moveMetaInfToRoot(CopySpec spec) { spec.eachFile((file) -> { String path = file.getRelativeSourcePath().getPathString(); if (path.startsWith("META-INF/") && !path.equals("META-INF/aop.xml") && !path.endsWith(".kotlin_module") && !path.startsWith("META-INF/services/")) { this.support.moveToRoot(file); } }); } @Override public void resolvedArtifacts(Provider> resolvedArtifacts) { this.resolvedDependencies.resolvedArtifacts(resolvedArtifacts); } @Nested ResolvedDependencies getResolvedDependencies() { return this.resolvedDependencies; } @Override public void copy() { this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX, this.getTargetJavaVersion().get().getMajorVersion(), this.projectName.get(), this.projectVersion.get()); super.copy(); } private boolean isLayeredDisabled() { return !getLayered().getEnabled().get(); } @Override protected CopyAction createCopyAction() { LayerResolver layerResolver = null; if (!isLayeredDisabled()) { layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); } String jarmodeToolsLocation = isIncludeJarmodeTools() ? LIB_DIRECTORY : null; return this.support.createCopyAction(this, this.resolvedDependencies, layerResolver, jarmodeToolsLocation); } private boolean isIncludeJarmodeTools() { return Boolean.TRUE.equals(this.getIncludeTools().get()); } @Override public void requiresUnpack(String... patterns) { this.support.requiresUnpack(patterns); } @Override public void requiresUnpack(Spec spec) { this.support.requiresUnpack(spec); } /** * Returns the spec that describes the layers in a layered jar. * @return the spec for the layers * @since 2.3.0 */ @Nested public LayeredSpec getLayered() { return this.layered; } /** * Configures the jar's layering using the given {@code action}. * @param action the action to apply * @since 2.3.0 */ public void layered(Action action) { action.execute(this.layered); } @Override public @Nullable FileCollection getClasspath() { return this.classpath; } @Override public void classpath(Object... classpath) { FileCollection existingClasspath = this.classpath; this.classpath = getProject().files((existingClasspath != null) ? existingClasspath : Collections.emptyList(), classpath); } @Override public void setClasspath(Object classpath) { this.classpath = getProject().files(classpath); } @Override public void setClasspath(FileCollection classpath) { this.classpath = getProject().files(classpath); } /** * Returns a {@code CopySpec} that can be used to add content to the {@code BOOT-INF} * directory of the jar. * @return a {@code CopySpec} for {@code BOOT-INF} * @since 2.0.3 */ @Internal public CopySpec getBootInf() { CopySpec child = getProject().copySpec(); this.bootInfSpec.with(child); return child; } /** * Calls the given {@code action} to add content to the {@code BOOT-INF} directory of * the jar. * @param action the {@code Action} to call * @return the {@code CopySpec} for {@code BOOT-INF} that was passed to the * {@code Action} * @since 2.0.3 */ public CopySpec bootInf(Action action) { CopySpec bootInf = getBootInf(); action.execute(bootInf); return bootInf; } /** * Return the {@link ZipCompression} that should be used when adding the file * represented by the given {@code details} to the jar. By default, any * {@link #isLibrary(FileCopyDetails) library} is {@link ZipCompression#STORED stored} * and all other files are {@link ZipCompression#DEFLATED deflated}. * @param details the file copy details * @return the compression to use */ protected ZipCompression resolveZipCompression(FileCopyDetails details) { return isLibrary(details) ? ZipCompression.STORED : ZipCompression.DEFLATED; } /** * Return if the {@link FileCopyDetails} are for a library. By default any file in * {@code BOOT-INF/lib} is considered to be a library. * @param details the file copy details * @return {@code true} if the details are for a library * @since 2.3.0 */ protected boolean isLibrary(FileCopyDetails details) { String path = details.getRelativePath().getPathString(); return path.startsWith(LIB_DIRECTORY); } /** * Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read. * @param the result type * @param callable the callable * @return an action to add the callable to the spec */ private static Action fromCallTo(Callable callable) { return (spec) -> spec.from(callTo(callable)); } /** * Syntactic sugar that makes {@link CopySpec#from} calls a little easier to read. * @param the result type * @param callable the callable * @return the callable */ private static Callable callTo(Callable callable) { return callable; } private final class LibrarySpec implements Spec { @Override public boolean isSatisfiedBy(FileCopyDetails details) { return isLibrary(details); } } private final class ZipCompressionResolver implements Function { @Override public ZipCompression apply(FileCopyDetails details) { return resolveZipCompression(details); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java ================================================ /* * 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.gradle.tasks.bundling; import java.util.Collections; import java.util.Set; import java.util.concurrent.Callable; import java.util.function.Function; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.artifacts.result.ResolvedArtifactResult; import org.gradle.api.file.CopySpec; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileCopyDetails; import org.gradle.api.file.FileTreeElement; import org.gradle.api.internal.file.copy.CopyAction; import org.gradle.api.provider.Provider; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.bundling.War; import org.gradle.work.DisableCachingByDefault; import org.jspecify.annotations.Nullable; /** * A custom {@link War} task that produces a Spring Boot executable war. * * @author Andy Wilkinson * @author Phillip Webb * @author Scott Frederick * @since 2.0.0 */ @DisableCachingByDefault(because = "Not worth caching") public abstract class BootWar extends War implements BootArchive { private static final String LAUNCHER = "org.springframework.boot.loader.launch.WarLauncher"; private static final String CLASSES_DIRECTORY = "WEB-INF/classes/"; private static final String LIB_PROVIDED_DIRECTORY = "WEB-INF/lib-provided/"; private static final String LIB_DIRECTORY = "WEB-INF/lib/"; private static final String LAYERS_INDEX = "WEB-INF/layers.idx"; private static final String CLASSPATH_INDEX = "WEB-INF/classpath.idx"; private final BootArchiveSupport support; private final LayeredSpec layered; private final Provider projectName; private final Provider projectVersion; private final ResolvedDependencies resolvedDependencies; private @Nullable FileCollection providedClasspath; /** * Creates a new {@code BootWar} task. */ public BootWar() { this.support = new BootArchiveSupport(LAUNCHER, new LibrarySpec(), new ZipCompressionResolver()); Project project = getProject(); this.layered = project.getObjects().newInstance(LayeredSpec.class); getWebInf().into("lib-provided", fromCallTo(this::getProvidedLibFiles)); this.support.moveModuleInfoToRoot(getRootSpec()); getRootSpec().eachFile(this.support::excludeNonZipLibraryFiles); this.projectName = project.provider(project::getName); this.projectVersion = project.provider(project::getVersion); this.resolvedDependencies = new ResolvedDependencies(project); getIncludeTools().convention(true); } private Object getProvidedLibFiles() { return (this.providedClasspath != null) ? this.providedClasspath : Collections.emptyList(); } @Override public void resolvedArtifacts(Provider> resolvedArtifacts) { this.resolvedDependencies.resolvedArtifacts(resolvedArtifacts); } @Nested ResolvedDependencies getResolvedDependencies() { return this.resolvedDependencies; } @Override public void copy() { this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX, this.getTargetJavaVersion().get().getMajorVersion(), this.projectName.get(), this.projectVersion.get()); super.copy(); } private boolean isLayeredDisabled() { return !this.layered.getEnabled().get(); } @Override protected CopyAction createCopyAction() { LayerResolver layerResolver = null; if (!isLayeredDisabled()) { layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); } String jarmodeToolsLocation = isIncludeJarmodeTools() ? LIB_DIRECTORY : null; return this.support.createCopyAction(this, this.resolvedDependencies, layerResolver, jarmodeToolsLocation); } private boolean isIncludeJarmodeTools() { return Boolean.TRUE.equals(this.getIncludeTools().get()); } @Override public void requiresUnpack(String... patterns) { this.support.requiresUnpack(patterns); } @Override public void requiresUnpack(Spec spec) { this.support.requiresUnpack(spec); } /** * Returns the provided classpath, the contents of which will be included in the * {@code WEB-INF/lib-provided} directory of the war. * @return the provided classpath */ @Optional @Classpath public @Nullable FileCollection getProvidedClasspath() { return this.providedClasspath; } /** * Adds files to the provided classpath to include in the {@code WEB-INF/lib-provided} * directory of the war. The given {@code classpath} is evaluated as per * {@link Project#files(Object...)}. * @param classpath the additions to the classpath */ public void providedClasspath(Object... classpath) { FileCollection existingClasspath = this.providedClasspath; this.providedClasspath = getProject() .files((existingClasspath != null) ? existingClasspath : Collections.emptyList(), classpath); } /** * Sets the provided classpath to include in the {@code WEB-INF/lib-provided} * directory of the war. * @param classpath the classpath * @since 2.0.7 */ public void setProvidedClasspath(FileCollection classpath) { this.providedClasspath = getProject().files(classpath); } /** * Sets the provided classpath to include in the {@code WEB-INF/lib-provided} * directory of the war. The given {@code classpath} is evaluated as per * {@link Project#files(Object...)}. * @param classpath the classpath * @since 2.0.7 */ public void setProvidedClasspath(Object classpath) { this.providedClasspath = getProject().files(classpath); } /** * Return the {@link ZipCompression} that should be used when adding the file * represented by the given {@code details} to the jar. By default, any * {@link #isLibrary(FileCopyDetails) library} is {@link ZipCompression#STORED stored} * and all other files are {@link ZipCompression#DEFLATED deflated}. * @param details the file copy details * @return the compression to use */ protected ZipCompression resolveZipCompression(FileCopyDetails details) { return isLibrary(details) ? ZipCompression.STORED : ZipCompression.DEFLATED; } /** * Returns the spec that describes the layers in a layered jar. * @return the spec for the layers * @since 2.5.0 */ @Nested public LayeredSpec getLayered() { return this.layered; } /** * Configures the war's layering using the given {@code action}. * @param action the action to apply * @since 2.5.0 */ public void layered(Action action) { action.execute(this.layered); } /** * Return if the {@link FileCopyDetails} are for a library. By default any file in * {@code WEB-INF/lib} or {@code WEB-INF/lib-provided} is considered to be a library. * @param details the file copy details * @return {@code true} if the details are for a library */ protected boolean isLibrary(FileCopyDetails details) { String path = details.getRelativePath().getPathString(); return path.startsWith(LIB_DIRECTORY) || path.startsWith(LIB_PROVIDED_DIRECTORY); } /** * Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read. * @param the result type * @param callable the callable * @return an action to add the callable to the spec */ private static Action fromCallTo(Callable callable) { return (spec) -> spec.from(callTo(callable)); } /** * Syntactic sugar that makes {@link CopySpec#from} calls a little easier to read. * @param the result type * @param callable the callable * @return the callable */ private static Callable callTo(Callable callable) { return callable; } private final class LibrarySpec implements Spec { @Override public boolean isSatisfiedBy(FileCopyDetails details) { return isLibrary(details); } } private final class ZipCompressionResolver implements Function { @Override public ZipCompression apply(FileCopyDetails details) { return resolveZipCompression(details); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.regex.Pattern; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import org.apache.commons.compress.archivers.zip.UnixStat; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.gradle.api.GradleException; import org.gradle.api.file.FileCopyDetails; import org.gradle.api.file.FileTreeElement; import org.gradle.api.internal.file.copy.CopyAction; import org.gradle.api.internal.file.copy.CopyActionProcessingStream; import org.gradle.api.java.archives.Attributes; import org.gradle.api.java.archives.Manifest; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.WorkResult; import org.gradle.api.tasks.WorkResults; import org.gradle.util.GradleVersion; import org.jspecify.annotations.Nullable; import org.springframework.boot.gradle.tasks.bundling.ResolvedDependencies.DependencyDescriptor; import org.springframework.boot.loader.tools.FileUtils; import org.springframework.boot.loader.tools.JarModeLibrary; import org.springframework.boot.loader.tools.Layer; import org.springframework.boot.loader.tools.LayersIndex; import org.springframework.boot.loader.tools.LibraryCoordinates; import org.springframework.boot.loader.tools.NativeImageArgFile; import org.springframework.boot.loader.tools.ReachabilityMetadataProperties; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; import org.springframework.util.function.ThrowingSupplier; /** * A {@link CopyAction} for creating a Spring Boot zip archive (typically a jar or war). * Stores jar files without compression as required by Spring Boot's loader. * * @author Andy Wilkinson * @author Phillip Webb * @author Scott Frederick */ class BootZipCopyAction implements CopyAction { static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = OffsetDateTime.of(1980, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC) .toInstant() .toEpochMilli(); private static final Pattern REACHABILITY_METADATA_PROPERTIES_LOCATION_PATTERN = Pattern .compile(ReachabilityMetadataProperties.REACHABILITY_METADATA_PROPERTIES_LOCATION_TEMPLATE.formatted(".*", ".*", ".*")); private final File output; private final Manifest manifest; private final boolean preserveFileTimestamps; private final @Nullable Integer dirMode; private final @Nullable Integer fileMode; private final boolean includeDefaultLoader; private final @Nullable String jarmodeToolsLocation; private final Spec requiresUnpack; private final Spec exclusions; private final Spec librarySpec; private final Function compressionResolver; private final @Nullable String encoding; private final ResolvedDependencies resolvedDependencies; private final @Nullable LayerResolver layerResolver; BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, @Nullable Integer dirMode, @Nullable Integer fileMode, boolean includeDefaultLoader, @Nullable String jarmodeToolsLocation, Spec requiresUnpack, Spec exclusions, Spec librarySpec, Function compressionResolver, @Nullable String encoding, ResolvedDependencies resolvedDependencies, @Nullable LayerResolver layerResolver) { this.output = output; this.manifest = manifest; this.preserveFileTimestamps = preserveFileTimestamps; this.dirMode = dirMode; this.fileMode = fileMode; this.includeDefaultLoader = includeDefaultLoader; this.jarmodeToolsLocation = jarmodeToolsLocation; this.requiresUnpack = requiresUnpack; this.exclusions = exclusions; this.librarySpec = librarySpec; this.compressionResolver = compressionResolver; this.encoding = encoding; this.resolvedDependencies = resolvedDependencies; this.layerResolver = layerResolver; } @Override public WorkResult execute(CopyActionProcessingStream copyActions) { try { writeArchive(copyActions); return WorkResults.didWork(true); } catch (IOException ex) { throw new GradleException("Failed to create " + this.output, ex); } } private void writeArchive(CopyActionProcessingStream copyActions) throws IOException { OutputStream output = new FileOutputStream(this.output); try { writeArchive(copyActions, output); } finally { closeQuietly(output); } } private void writeArchive(CopyActionProcessingStream copyActions, OutputStream output) throws IOException { ZipArchiveOutputStream zipOutput = new ZipArchiveOutputStream(output); try { setEncodingIfNecessary(zipOutput); Processor processor = new Processor(zipOutput); copyActions.process(processor::process); processor.finish(); } finally { closeQuietly(zipOutput); } } private void setEncodingIfNecessary(ZipArchiveOutputStream zipOutputStream) { if (this.encoding != null) { zipOutputStream.setEncoding(this.encoding); } } private void closeQuietly(OutputStream outputStream) { try { outputStream.close(); } catch (IOException ex) { // Ignore } } /** * Internal process used to copy {@link FileCopyDetails file details} to the zip file. */ private class Processor { private final ZipArchiveOutputStream out; private final @Nullable LayersIndex layerIndex; private LoaderZipEntries.@Nullable WrittenEntries writtenLoaderEntries; private final Set writtenDirectories = new LinkedHashSet<>(); private final Map writtenLibraries = new LinkedHashMap<>(); private final Map reachabilityMetadataProperties = new HashMap<>(); Processor(ZipArchiveOutputStream out) { this.out = out; this.layerIndex = (BootZipCopyAction.this.layerResolver != null) ? new LayersIndex(BootZipCopyAction.this.layerResolver.getLayers()) : null; } void process(FileCopyDetails details) { if (skipProcessing(details)) { return; } try { writeLoaderEntriesIfNecessary(details); if (details.isDirectory()) { processDirectory(details); } else { processFile(details); } } catch (IOException ex) { throw new GradleException("Failed to add " + details + " to " + BootZipCopyAction.this.output, ex); } } private boolean skipProcessing(FileCopyDetails details) { return BootZipCopyAction.this.exclusions.isSatisfiedBy(details) || (this.writtenLoaderEntries != null && this.writtenLoaderEntries.isWrittenDirectory(details)); } private void processDirectory(FileCopyDetails details) throws IOException { String name = details.getRelativePath().getPathString(); ZipArchiveEntry entry = new ZipArchiveEntry(name + '/'); prepareEntry(entry, name, getTime(details), getDirMode(details)); this.out.putArchiveEntry(entry); this.out.closeArchiveEntry(); this.writtenDirectories.add(name); } private void processFile(FileCopyDetails details) throws IOException { String name = details.getRelativePath().getPathString(); ZipArchiveEntry entry = new ZipArchiveEntry(name); prepareEntry(entry, name, getTime(details), getFileMode(details)); ZipCompression compression = BootZipCopyAction.this.compressionResolver.apply(details); if (compression == ZipCompression.STORED) { prepareStoredEntry(details, entry); } this.out.putArchiveEntry(entry); details.copyTo(this.out); this.out.closeArchiveEntry(); if (BootZipCopyAction.this.librarySpec.isSatisfiedBy(details)) { this.writtenLibraries.put(name, details); } if (REACHABILITY_METADATA_PROPERTIES_LOCATION_PATTERN.matcher(name).matches()) { this.reachabilityMetadataProperties.put(name, details); } if (BootZipCopyAction.this.layerResolver != null) { Layer layer = BootZipCopyAction.this.layerResolver.getLayer(details); Assert.state(this.layerIndex != null, "'layerIndex' must not be null"); Assert.state(layer != null, "'layer' must not be null"); this.layerIndex.add(layer, name); } } private void writeParentDirectoriesIfNecessary(String name, @Nullable Long time) throws IOException { String parentDirectory = getParentDirectory(name); if (parentDirectory != null && this.writtenDirectories.add(parentDirectory)) { ZipArchiveEntry entry = new ZipArchiveEntry(parentDirectory + '/'); prepareEntry(entry, parentDirectory, time, getDirMode()); this.out.putArchiveEntry(entry); this.out.closeArchiveEntry(); } } private @Nullable String getParentDirectory(String name) { int lastSlash = name.lastIndexOf('/'); if (lastSlash == -1) { return null; } return name.substring(0, lastSlash); } void finish() throws IOException { writeLoaderEntriesIfNecessary(null); writeJarToolsIfNecessary(); writeSignatureFileIfNecessary(); writeClassPathIndexIfNecessary(); writeNativeImageArgFileIfNecessary(); // We must write the layer index last writeLayersIndexIfNecessary(); } private void writeLoaderEntriesIfNecessary(@Nullable FileCopyDetails details) throws IOException { if (!BootZipCopyAction.this.includeDefaultLoader || this.writtenLoaderEntries != null) { return; } if (isInMetaInf(details)) { // Always write loader entries after META-INF directory (see gh-16698) return; } LoaderZipEntries loaderEntries = new LoaderZipEntries(getTime(), getDirMode(), getFileMode()); this.writtenLoaderEntries = loaderEntries.writeTo(this.out); if (BootZipCopyAction.this.layerResolver != null) { for (String name : this.writtenLoaderEntries.getFiles()) { Layer layer = BootZipCopyAction.this.layerResolver.getLayer(name); Assert.state(this.layerIndex != null, "'layerIndex' must not be null"); this.layerIndex.add(layer, name); } } } private boolean isInMetaInf(@Nullable FileCopyDetails details) { if (details == null) { return false; } String[] segments = details.getRelativePath().getSegments(); return segments.length > 0 && "META-INF".equals(segments[0]); } private void writeJarToolsIfNecessary() throws IOException { if (BootZipCopyAction.this.jarmodeToolsLocation != null) { writeJarModeLibrary(BootZipCopyAction.this.jarmodeToolsLocation, JarModeLibrary.TOOLS); } } private void writeJarModeLibrary(String location, JarModeLibrary library) throws IOException { String name = location + library.getName(); writeEntry(name, ZipEntryContentWriter.fromInputStream(library.openStream()), false, (entry) -> prepareStoredEntry(library::openStream, false, entry)); if (BootZipCopyAction.this.layerResolver != null) { Layer layer = BootZipCopyAction.this.layerResolver.getLayer(library); Assert.state(this.layerIndex != null, "'layerIndex' must not be null"); this.layerIndex.add(layer, name); } } private void writeSignatureFileIfNecessary() throws IOException { if (hasSignedLibrary()) { writeEntry("META-INF/BOOT.SF", (out) -> { }, false); } } private boolean hasSignedLibrary() throws IOException { for (FileCopyDetails writtenLibrary : this.writtenLibraries.values()) { if (FileUtils.isSignedJarFile(writtenLibrary.getFile())) { return true; } } return false; } private void writeClassPathIndexIfNecessary() throws IOException { Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes(); String classPathIndex = (String) manifestAttributes.get("Spring-Boot-Classpath-Index"); if (classPathIndex != null) { Set libraryNames = this.writtenLibraries.keySet(); List lines = libraryNames.stream().map((line) -> "- \"" + line + "\"").toList(); ZipEntryContentWriter writer = ZipEntryContentWriter.fromLines((BootZipCopyAction.this.encoding != null) ? BootZipCopyAction.this.encoding : StandardCharsets.UTF_8.name(), lines); writeEntry(classPathIndex, writer, true); } } private void writeNativeImageArgFileIfNecessary() throws IOException { Set excludes = new LinkedHashSet<>(); for (Map.Entry entry : this.writtenLibraries.entrySet()) { DependencyDescriptor descriptor = BootZipCopyAction.this.resolvedDependencies .find(entry.getValue().getFile()); LibraryCoordinates coordinates = (descriptor != null) ? descriptor.getCoordinates() : null; FileCopyDetails propertiesFile = (coordinates != null) ? this.reachabilityMetadataProperties .get(ReachabilityMetadataProperties.getLocation(coordinates)) : null; if (propertiesFile != null) { try (InputStream inputStream = propertiesFile.open()) { ReachabilityMetadataProperties properties = ReachabilityMetadataProperties .fromInputStream(inputStream); if (properties.isOverridden()) { excludes.add(entry.getKey()); } } } } NativeImageArgFile argFile = new NativeImageArgFile(excludes); argFile.writeIfNecessary((lines) -> { ZipEntryContentWriter writer = ZipEntryContentWriter.fromLines((BootZipCopyAction.this.encoding != null) ? BootZipCopyAction.this.encoding : StandardCharsets.UTF_8.name(), lines); writeEntry(NativeImageArgFile.LOCATION, writer, true); }); } private void writeLayersIndexIfNecessary() throws IOException { if (BootZipCopyAction.this.layerResolver != null) { Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes(); String name = (String) manifestAttributes.get("Spring-Boot-Layers-Index"); Assert.state(StringUtils.hasText(name), "Missing layer index manifest attribute"); Layer layer = BootZipCopyAction.this.layerResolver.getLayer(name); Assert.state(this.layerIndex != null, "'layerIndex' must not be null"); this.layerIndex.add(layer, name); writeEntry(name, this.layerIndex::writeTo, false); } } private void writeEntry(String name, ZipEntryContentWriter entryWriter, boolean addToLayerIndex) throws IOException { writeEntry(name, entryWriter, addToLayerIndex, ZipEntryCustomizer.NONE); } private void writeEntry(String name, ZipEntryContentWriter entryWriter, boolean addToLayerIndex, ZipEntryCustomizer entryCustomizer) throws IOException { ZipArchiveEntry entry = new ZipArchiveEntry(name); prepareEntry(entry, name, getTime(), getFileMode()); entryCustomizer.customize(entry); this.out.putArchiveEntry(entry); entryWriter.writeTo(this.out); this.out.closeArchiveEntry(); if (addToLayerIndex && BootZipCopyAction.this.layerResolver != null) { Layer layer = BootZipCopyAction.this.layerResolver.getLayer(name); Assert.state(this.layerIndex != null, "'layerIndex' must not be null"); this.layerIndex.add(layer, name); } } private void prepareEntry(ZipArchiveEntry entry, String name, @Nullable Long time, int mode) throws IOException { writeParentDirectoriesIfNecessary(name, time); entry.setUnixMode(mode); if (time != null) { entry.setTime(DefaultTimeZoneOffset.INSTANCE.removeFrom(time)); } } private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException { prepareStoredEntry(details::open, BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details), archiveEntry); } private void prepareStoredEntry(ThrowingSupplier input, boolean unpack, ZipArchiveEntry archiveEntry) throws IOException { new StoredEntryPreparator(input, unpack).prepareStoredEntry(archiveEntry); } private @Nullable Long getTime() { return getTime(null); } private @Nullable Long getTime(@Nullable FileCopyDetails details) { if (!BootZipCopyAction.this.preserveFileTimestamps) { return CONSTANT_TIME_FOR_ZIP_ENTRIES; } if (details != null) { return details.getLastModified(); } return null; } private int getDirMode() { return (BootZipCopyAction.this.dirMode != null) ? BootZipCopyAction.this.dirMode : UnixStat.DEFAULT_DIR_PERM; } private int getFileMode() { return (BootZipCopyAction.this.fileMode != null) ? BootZipCopyAction.this.fileMode : UnixStat.DEFAULT_FILE_PERM; } private int getDirMode(FileCopyDetails details) { return (BootZipCopyAction.this.dirMode != null) ? BootZipCopyAction.this.dirMode : getPermissions(details); } private int getFileMode(FileCopyDetails details) { return (BootZipCopyAction.this.fileMode != null) ? BootZipCopyAction.this.fileMode : getPermissions(details); } private int getPermissions(FileCopyDetails details) { return (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) ? details.getPermissions().toUnixNumeric() : getMode(details); } private int getMode(FileCopyDetails details) { try { return (int) details.getClass().getMethod("getMode").invoke(details); } catch (Exception ex) { throw new RuntimeException("Failed to get mode from FileCopyDetails", ex); } } } /** * Callback interface used to customize a {@link ZipArchiveEntry}. */ @FunctionalInterface private interface ZipEntryCustomizer { ZipEntryCustomizer NONE = (entry) -> { }; /** * Customize the entry. * @param entry the entry to customize * @throws IOException on IO error */ void customize(ZipArchiveEntry entry) throws IOException; } /** * Callback used to write a zip entry data. */ @FunctionalInterface private interface ZipEntryContentWriter { /** * Write the entry data. * @param out the output stream used to write the data * @throws IOException on IO error */ void writeTo(ZipArchiveOutputStream out) throws IOException; /** * Create a new {@link ZipEntryContentWriter} that will copy content from the * given {@link InputStream}. * @param in the source input stream * @return a new {@link ZipEntryContentWriter} instance */ static ZipEntryContentWriter fromInputStream(InputStream in) { return (out) -> { StreamUtils.copy(in, out); in.close(); }; } /** * Create a new {@link ZipEntryContentWriter} that will copy content from the * given lines. * @param encoding the required character encoding * @param lines the lines to write * @return a new {@link ZipEntryContentWriter} instance */ static ZipEntryContentWriter fromLines(String encoding, Collection lines) { return (out) -> { OutputStreamWriter writer = new OutputStreamWriter(out, encoding); for (String line : lines) { writer.append(line).append("\n"); } writer.flush(); }; } } /** * Prepares a {@link ZipEntry#STORED stored} {@link ZipArchiveEntry entry} with CRC * and size information. Also adds an {@code UNPACK} comment, if needed. */ private static class StoredEntryPreparator { private static final int BUFFER_SIZE = 32 * 1024; private final boolean unpack; private final CRC32 crc = new CRC32(); private long size; StoredEntryPreparator(ThrowingSupplier input, boolean unpack) throws IOException { this.unpack = unpack; try (InputStream stream = input.get()) { load(stream); } } private void load(InputStream inputStream) throws IOException { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { this.crc.update(buffer, 0, bytesRead); this.size += bytesRead; } } void prepareStoredEntry(ZipArchiveEntry entry) { entry.setSize(this.size); entry.setCompressedSize(this.size); entry.setCrc(this.crc.getValue()); entry.setMethod(ZipEntry.STORED); if (this.unpack) { entry.setComment("UNPACK"); } } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/CacheSpec.java ================================================ /* * 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.gradle.tasks.bundling; import javax.inject.Inject; import org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.build.Cache; /** * Configuration for an image building cache. * * @author Scott Frederick * @since 2.6.0 */ public class CacheSpec { private final ObjectFactory objectFactory; private @Nullable Cache cache; @Inject public CacheSpec(ObjectFactory objectFactory) { this.objectFactory = objectFactory; } public @Nullable Cache asCache() { return this.cache; } /** * Configures a volume cache using the given {@code action}. * @param action the action */ public void volume(Action action) { if (this.cache != null) { throw new GradleException("Each image building cache can be configured only once"); } VolumeCacheSpec spec = this.objectFactory.newInstance(VolumeCacheSpec.class); action.execute(spec); this.cache = Cache.volume(spec.getName().get()); } /** * Configures a bind cache using the given {@code action}. * @param action the action */ public void bind(Action action) { if (this.cache != null) { throw new GradleException("Each image building cache can be configured only once"); } BindCacheSpec spec = this.objectFactory.newInstance(BindCacheSpec.class); action.execute(spec); this.cache = Cache.bind(spec.getSource().get()); } /** * Configuration for an image building cache stored in a Docker volume. */ public abstract static class VolumeCacheSpec { /** * Returns the name of the cache. * @return the cache name */ @Input public abstract Property getName(); } /** * Configuration for an image building cache stored in a bind mount. */ public abstract static class BindCacheSpec { /** * Returns the source of the cache. * @return the cache source */ @Input public abstract Property getSource(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DefaultTimeZoneOffset.java ================================================ /* * 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.gradle.tasks.bundling; import java.nio.file.attribute.FileTime; import java.util.TimeZone; import java.util.zip.ZipEntry; /** * Utility class that can be used to change a UTC time based on the * {@link java.util.TimeZone#getDefault() default TimeZone}. This is required because * {@link ZipEntry#setTime(long)} expects times in the default timezone and not UTC. * * @author Phillip Webb */ class DefaultTimeZoneOffset { static final DefaultTimeZoneOffset INSTANCE = new DefaultTimeZoneOffset(TimeZone.getDefault()); private final TimeZone defaultTimeZone; DefaultTimeZoneOffset(TimeZone defaultTimeZone) { this.defaultTimeZone = defaultTimeZone; } /** * Remove the default offset from the given time. * @param time the time to remove the default offset from * @return the time with the default offset removed */ FileTime removeFrom(FileTime time) { return FileTime.fromMillis(removeFrom(time.toMillis())); } /** * Remove the default offset from the given time. * @param time the time to remove the default offset from * @return the time with the default offset removed */ long removeFrom(long time) { return time - this.defaultTimeZone.getOffset(time); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java ================================================ /* * 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.gradle.tasks.bundling; import javax.inject.Inject; import org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication; /** * Encapsulates Docker configuration options. * * @author Wei Jiang * @author Scott Frederick * @since 2.4.0 */ public abstract class DockerSpec { private final DockerRegistrySpec builderRegistry; private final DockerRegistrySpec publishRegistry; @Inject public DockerSpec(ObjectFactory objects) { this.builderRegistry = objects.newInstance(DockerRegistrySpec.class); this.publishRegistry = objects.newInstance(DockerRegistrySpec.class); getBindHostToBuilder().convention(false); getTlsVerify().convention(false); } DockerSpec(DockerRegistrySpec builderRegistry, DockerRegistrySpec publishRegistry) { this.builderRegistry = builderRegistry; this.publishRegistry = publishRegistry; } @Input @Optional public abstract Property getContext(); @Input @Optional public abstract Property getHost(); @Input @Optional public abstract Property getTlsVerify(); @Input @Optional public abstract Property getCertPath(); @Input @Optional public abstract Property getBindHostToBuilder(); /** * Returns the {@link DockerRegistrySpec} that configures authentication to the * builder registry. * @return the registry spec */ @Nested public DockerRegistrySpec getBuilderRegistry() { return this.builderRegistry; } /** * Customizes the {@link DockerRegistrySpec} that configures authentication to the * builder registry. * @param action the action to apply */ public void builderRegistry(Action action) { action.execute(this.builderRegistry); } /** * Returns the {@link DockerRegistrySpec} that configures authentication to the * publishing registry. * @return the registry spec */ @Nested public DockerRegistrySpec getPublishRegistry() { return this.publishRegistry; } /** * Customizes the {@link DockerRegistrySpec} that configures authentication to the * publishing registry. * @param action the action to apply */ public void publishRegistry(Action action) { action.execute(this.publishRegistry); } /** * Returns this configuration as a {@link BuilderDockerConfiguration} instance. This * method should only be called when the configuration is complete and will no longer * be changed. * @return the Docker configuration */ BuilderDockerConfiguration asDockerConfiguration() { BuilderDockerConfiguration dockerConfiguration = new BuilderDockerConfiguration(); dockerConfiguration = customizeHost(dockerConfiguration); dockerConfiguration = dockerConfiguration.withBindHostToBuilder(getBindHostToBuilder().get()); dockerConfiguration = customizeBuilderAuthentication(dockerConfiguration); dockerConfiguration = customizePublishAuthentication(dockerConfiguration); return dockerConfiguration; } private BuilderDockerConfiguration customizeHost(BuilderDockerConfiguration dockerConfiguration) { String context = getContext().getOrNull(); String host = getHost().getOrNull(); if (context != null && host != null) { throw new GradleException( "Invalid Docker configuration, either context or host can be provided but not both"); } if (context != null) { return dockerConfiguration.withContext(context); } if (host != null) { return dockerConfiguration.withHost(host, getTlsVerify().get(), getCertPath().getOrNull()); } return dockerConfiguration; } private BuilderDockerConfiguration customizeBuilderAuthentication(BuilderDockerConfiguration dockerConfiguration) { return dockerConfiguration.withBuilderRegistryAuthentication(getRegistryAuthentication("builder", this.builderRegistry, DockerRegistryAuthentication.configuration(null))); } private BuilderDockerConfiguration customizePublishAuthentication(BuilderDockerConfiguration dockerConfiguration) { return dockerConfiguration .withPublishRegistryAuthentication(getRegistryAuthentication("publish", this.publishRegistry, DockerRegistryAuthentication.configuration(DockerRegistryAuthentication.EMPTY_USER))); } private DockerRegistryAuthentication getRegistryAuthentication(String type, @Nullable DockerRegistrySpec registry, DockerRegistryAuthentication fallback) { if (registry == null || registry.hasEmptyAuth()) { return fallback; } if (registry.hasTokenAuth() && !registry.hasUserAuth()) { return DockerRegistryAuthentication.token(registry.getToken().get()); } if (registry.hasUserAuth() && !registry.hasTokenAuth()) { return DockerRegistryAuthentication.user(registry.getUsername().get(), registry.getPassword().get(), registry.getUrl().getOrNull(), registry.getEmail().getOrNull()); } throw new GradleException("Invalid Docker " + type + " registry configuration, either token or username/password must be provided"); } /** * Encapsulates Docker registry authentication configuration options. */ public abstract static class DockerRegistrySpec { /** * Returns the username to use when authenticating to the Docker registry. * @return the registry username */ @Input @Optional public abstract Property getUsername(); /** * Returns the password to use when authenticating to the Docker registry. * @return the registry password */ @Input @Optional public abstract Property getPassword(); /** * Returns the Docker registry URL. * @return the registry URL */ @Input @Optional public abstract Property getUrl(); /** * Returns the email address associated with the Docker registry username. * @return the registry email address */ @Input @Optional public abstract Property getEmail(); /** * Returns the identity token to use when authenticating to the Docker registry. * @return the registry identity token */ @Input @Optional public abstract Property getToken(); boolean hasEmptyAuth() { return nonePresent(getUsername(), getPassword(), getUrl(), getEmail(), getToken()); } private boolean nonePresent(Property... properties) { for (Property property : properties) { if (property.isPresent()) { return false; } } return true; } boolean hasUserAuth() { return allPresent(getUsername(), getPassword()); } private boolean allPresent(Property... properties) { for (Property property : properties) { if (!property.isPresent()) { return false; } } return true; } boolean hasTokenAuth() { return getToken().isPresent(); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import org.gradle.api.file.FileCopyDetails; import org.gradle.api.specs.Spec; import org.jspecify.annotations.Nullable; import org.springframework.boot.gradle.tasks.bundling.ResolvedDependencies.DependencyDescriptor; import org.springframework.boot.loader.tools.Layer; import org.springframework.boot.loader.tools.Library; import org.springframework.boot.loader.tools.LibraryCoordinates; /** * Resolver backed by a {@link LayeredSpec} that provides the destination {@link Layer} * for each copied {@link FileCopyDetails}. * * @author Madhura Bhave * @author Scott Frederick * @author Phillip Webb * @author Paddy Drury * @see BootZipCopyAction */ class LayerResolver { private final ResolvedDependencies resolvedDependencies; private final LayeredSpec layeredConfiguration; private final Spec librarySpec; LayerResolver(ResolvedDependencies resolvedDependencies, LayeredSpec layeredConfiguration, Spec librarySpec) { this.resolvedDependencies = resolvedDependencies; this.layeredConfiguration = layeredConfiguration; this.librarySpec = librarySpec; } @Nullable Layer getLayer(FileCopyDetails details) { try { if (this.librarySpec.isSatisfiedBy(details)) { return getLayer(asLibrary(details)); } return getLayer(details.getSourcePath()); } catch (UnsupportedOperationException ex) { return null; } } Layer getLayer(Library library) { return this.layeredConfiguration.asLayers().getLayer(library); } Layer getLayer(String applicationResource) { return this.layeredConfiguration.asLayers().getLayer(applicationResource); } Iterable getLayers() { return this.layeredConfiguration.asLayers(); } private Library asLibrary(FileCopyDetails details) { File file = details.getFile(); DependencyDescriptor dependency = this.resolvedDependencies.find(file); if (dependency == null) { return new Library(null, file, null, null, false, false, true); } LibraryCoordinates coordinates = dependency.getCoordinates(); boolean projectDependency = dependency.isProjectDependency(); return new Library(null, file, null, coordinates, false, projectDependency, true); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayeredSpec.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; import org.gradle.api.Action; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.jspecify.annotations.Nullable; import org.springframework.boot.loader.tools.Layer; import org.springframework.boot.loader.tools.Layers; import org.springframework.boot.loader.tools.Library; import org.springframework.boot.loader.tools.layer.ApplicationContentFilter; import org.springframework.boot.loader.tools.layer.ContentFilter; import org.springframework.boot.loader.tools.layer.ContentSelector; import org.springframework.boot.loader.tools.layer.CustomLayers; import org.springframework.boot.loader.tools.layer.IncludeExcludeContentSelector; import org.springframework.boot.loader.tools.layer.LibraryContentFilter; import org.springframework.util.Assert; /** * Encapsulates the configuration for a layered archive. * * @author Madhura Bhave * @author Scott Frederick * @author Phillip Webb * @since 2.3.0 */ public abstract class LayeredSpec { private ApplicationSpec application; private DependenciesSpec dependencies; private @Nullable Layers layers; @Inject public LayeredSpec(ObjectFactory objects) { this.application = objects.newInstance(ApplicationSpec.class); this.dependencies = objects.newInstance(DependenciesSpec.class); getEnabled().convention(true); } /** * Returns whether the layers.idx should be included in the archive. * @return whether the layers.idx should be included * @since 3.0.0 */ @Input public abstract Property getEnabled(); /** * Returns the {@link ApplicationSpec} that controls the layers to which application * classes and resources belong. * @return the application spec */ @Input public ApplicationSpec getApplication() { return this.application; } /** * Sets the {@link ApplicationSpec} that controls the layers to which application * classes are resources belong. * @param spec the application spec */ public void setApplication(ApplicationSpec spec) { this.application = spec; } /** * Customizes the {@link ApplicationSpec} using the given {@code action}. * @param action the action */ public void application(Action action) { action.execute(this.application); } /** * Returns the {@link DependenciesSpec} that controls the layers to which dependencies * belong. * @return the dependencies spec */ @Input public DependenciesSpec getDependencies() { return this.dependencies; } /** * Sets the {@link DependenciesSpec} that controls the layers to which dependencies * belong. * @param spec the dependencies spec */ public void setDependencies(DependenciesSpec spec) { this.dependencies = spec; } /** * Customizes the {@link DependenciesSpec} using the given {@code action}. * @param action the action */ public void dependencies(Action action) { action.execute(this.dependencies); } /** * Returns the order of the layers in the archive from least to most frequently * changing. * @return the layer order */ @Input @Optional public abstract ListProperty getLayerOrder(); /** * Return this configuration as a {@link Layers} instance. This method should only be * called when the configuration is complete and will no longer be changed. * @return the layers */ Layers asLayers() { if (this.layers == null) { this.layers = createLayers(); } return this.layers; } private Layers createLayers() { List layerOrder = getLayerOrder().getOrNull(); if (layerOrder == null || layerOrder.isEmpty()) { Assert.state(this.application.isEmpty() && this.dependencies.isEmpty(), "The 'layerOrder' must be defined when using custom layering"); return Layers.IMPLICIT; } List layers = layerOrder.stream().map(Layer::new).toList(); return new CustomLayers(layers, this.application.asSelectors(), this.dependencies.asSelectors()); } /** * Base class for specs that control the layers to which a category of content should * belong. * * @param the type of {@link IntoLayerSpec} used by this spec */ public abstract static class IntoLayersSpec implements Serializable { private final List intoLayers; private final Function specFactory; boolean isEmpty() { return this.intoLayers.isEmpty(); } IntoLayersSpec(Function specFactory, IntoLayerSpec... spec) { this.intoLayers = new ArrayList<>(Arrays.asList(spec)); this.specFactory = specFactory; } public void intoLayer(String layer) { this.intoLayers.add(this.specFactory.apply(layer)); } public void intoLayer(String layer, Action action) { S spec = this.specFactory.apply(layer); action.execute(spec); this.intoLayers.add(spec); } List> asSelectors(Function> selectorFactory) { return this.intoLayers.stream().map(selectorFactory).toList(); } } /** * Spec that controls the content that should be part of a particular layer. */ public static class IntoLayerSpec implements Serializable { private final String intoLayer; private final List includes = new ArrayList<>(); private final List excludes = new ArrayList<>(); /** * Creates a new {@code IntoLayerSpec} that will control the content of the given * layer. * @param intoLayer the layer */ public IntoLayerSpec(String intoLayer) { this.intoLayer = intoLayer; } /** * Adds patterns that control the content that is included in the layer. If no * includes are specified then all content is included. If includes are specified * then content must match an inclusion and not match any exclusions to be * included. * @param patterns the patterns to be included */ public void include(String... patterns) { this.includes.addAll(Arrays.asList(patterns)); } /** * Adds patterns that control the content that is excluded from the layer. If no * excludes a specified no content is excluded. If exclusions are specified then * any content that matches an exclusion will be excluded irrespective of whether * it matches an include. * @param patterns the patterns to be excluded */ public void exclude(String... patterns) { this.includes.addAll(Arrays.asList(patterns)); } ContentSelector asSelector(Function> filterFactory) { Layer layer = new Layer(this.intoLayer); return new IncludeExcludeContentSelector<>(layer, this.includes, this.excludes, filterFactory); } String getIntoLayer() { return this.intoLayer; } List getIncludes() { return this.includes; } List getExcludes() { return this.excludes; } } /** * Spec that controls the dependencies that should be part of a particular layer. * * @since 2.4.0 */ public static class DependenciesIntoLayerSpec extends IntoLayerSpec { private boolean includeProjectDependencies; private boolean excludeProjectDependencies; /** * Creates a new {@code IntoLayerSpec} that will control the content of the given * layer. * @param intoLayer the layer */ public DependenciesIntoLayerSpec(String intoLayer) { super(intoLayer); } /** * Configures the layer to include project dependencies. If no includes are * specified then all content is included. If includes are specified then content * must match an inclusion and not match any exclusions to be included. */ public void includeProjectDependencies() { this.includeProjectDependencies = true; } /** * Configures the layer to exclude project dependencies. If no excludes a * specified no content is excluded. If exclusions are specified then any content * that matches an exclusion will be excluded irrespective of whether it matches * an include. */ public void excludeProjectDependencies() { this.excludeProjectDependencies = true; } ContentSelector asLibrarySelector(Function> filterFactory) { Layer layer = new Layer(getIntoLayer()); List> includeFilters = getIncludes().stream() .map(filterFactory) .collect(Collectors.toCollection(ArrayList::new)); if (this.includeProjectDependencies) { includeFilters.add(Library::isLocal); } List> excludeFilters = getExcludes().stream() .map(filterFactory) .collect(Collectors.toCollection(ArrayList::new)); if (this.excludeProjectDependencies) { excludeFilters.add(Library::isLocal); } return new IncludeExcludeContentSelector<>(layer, includeFilters, excludeFilters); } } /** * An {@link IntoLayersSpec} that controls the layers to which application classes and * resources belong. */ public static class ApplicationSpec extends IntoLayersSpec { @Inject public ApplicationSpec() { super(new IntoLayerSpecFactory()); } /** * Creates a new {@code ApplicationSpec} with the given {@code contents}. * @param contents specs for the layers in which application content should be * included */ public ApplicationSpec(IntoLayerSpec... contents) { super(new IntoLayerSpecFactory(), contents); } List> asSelectors() { return asSelectors((spec) -> spec.asSelector(ApplicationContentFilter::new)); } private static final class IntoLayerSpecFactory implements Function, Serializable { @Override public IntoLayerSpec apply(String layer) { return new IntoLayerSpec(layer); } } } /** * An {@link IntoLayersSpec} that controls the layers to which dependencies belong. */ public static class DependenciesSpec extends IntoLayersSpec implements Serializable { @Inject public DependenciesSpec() { super(new IntoLayerSpecFactory()); } /** * Creates a new {@code DependenciesSpec} with the given {@code contents}. * @param contents specs for the layers in which dependencies should be included */ public DependenciesSpec(DependenciesIntoLayerSpec... contents) { super(new IntoLayerSpecFactory(), contents); } List> asSelectors() { return asSelectors( (spec) -> ((DependenciesIntoLayerSpec) spec).asLibrarySelector(LibraryContentFilter::new)); } private static final class IntoLayerSpecFactory implements Function, Serializable { @Override public DependenciesIntoLayerSpec apply(String layer) { return new DependenciesIntoLayerSpec(layer); } } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.LinkedHashSet; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.gradle.api.file.FileTreeElement; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; /** * Internal utility used to copy entries from the {@code spring-boot-loader.jar}. * * @author Andy Wilkinson * @author Phillip Webb * @author Scott Frederick */ class LoaderZipEntries { private final @Nullable Long entryTime; private final int dirMode; private final int fileMode; LoaderZipEntries(@Nullable Long entryTime, int dirMode, int fileMode) { this.entryTime = entryTime; this.dirMode = dirMode; this.fileMode = fileMode; } WrittenEntries writeTo(ZipArchiveOutputStream out) throws IOException { WrittenEntries written = new WrittenEntries(); try (ZipInputStream loaderJar = new ZipInputStream( getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) { java.util.zip.ZipEntry entry = loaderJar.getNextEntry(); while (entry != null) { if (entry.isDirectory() && !entry.getName().equals("META-INF/")) { writeDirectory(new ZipArchiveEntry(entry), out); written.addDirectory(entry); } else if (entry.getName().endsWith(".class") || entry.getName().startsWith("META-INF/services/")) { writeFile(new ZipArchiveEntry(entry), loaderJar, out); written.addFile(entry); } entry = loaderJar.getNextEntry(); } } return written; } private InputStream getResourceAsStream(String name) { InputStream stream = getClass().getResourceAsStream(name); Assert.state(stream != null, "Resource '%s not found'".formatted(name)); return stream; } private void writeDirectory(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException { prepareEntry(entry, this.dirMode); out.putArchiveEntry(entry); out.closeArchiveEntry(); } private void writeFile(ZipArchiveEntry entry, ZipInputStream in, ZipArchiveOutputStream out) throws IOException { prepareEntry(entry, this.fileMode); out.putArchiveEntry(entry); copy(in, out); out.closeArchiveEntry(); } private void prepareEntry(ZipArchiveEntry entry, int unixMode) { if (this.entryTime != null) { entry.setTime(DefaultTimeZoneOffset.INSTANCE.removeFrom(this.entryTime)); } entry.setUnixMode(unixMode); } private void copy(InputStream in, OutputStream out) throws IOException { StreamUtils.copy(in, out); } /** * Tracks entries that have been written. */ static class WrittenEntries { private final Set directories = new LinkedHashSet<>(); private final Set files = new LinkedHashSet<>(); private void addDirectory(ZipEntry entry) { this.directories.add(entry.getName()); } private void addFile(ZipEntry entry) { this.files.add(entry.getName()); } boolean isWrittenDirectory(FileTreeElement element) { String path = element.getRelativePath().getPathString(); if (element.isDirectory() && !path.endsWith(("/"))) { path += "/"; } return this.directories.contains(path); } Set getFiles() { return this.files; } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/ResolvedDependencies.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import org.gradle.api.Project; import org.gradle.api.artifacts.ResolvedConfiguration; import org.gradle.api.artifacts.component.ComponentArtifactIdentifier; import org.gradle.api.artifacts.component.ComponentIdentifier; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.artifacts.component.ProjectComponentIdentifier; import org.gradle.api.artifacts.result.ResolvedArtifactResult; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.internal.component.external.model.ModuleComponentArtifactIdentifier; import org.jspecify.annotations.Nullable; import org.springframework.boot.loader.tools.LibraryCoordinates; /** * Maps from {@link File} to {@link ComponentArtifactIdentifier}. * * @author Madhura Bhave * @author Scott Frederick * @author Phillip Webb * @author Paddy Drury * @author Andy Wilkinson */ class ResolvedDependencies { private final Map projectCoordinatesByPath; private final ListProperty artifactIds; private final ListProperty artifactFiles; ResolvedDependencies(Project project) { this.artifactIds = project.getObjects().listProperty(ComponentArtifactIdentifier.class); this.artifactFiles = project.getObjects().listProperty(File.class); this.projectCoordinatesByPath = projectCoordinatesByPath(project); } private static Map projectCoordinatesByPath(Project project) { return project.getRootProject() .getAllprojects() .stream() .collect(Collectors.toMap(Project::getPath, ResolvedDependencies::libraryCoordinates)); } private static LibraryCoordinates libraryCoordinates(Project project) { return LibraryCoordinates.of(Objects.toString(project.getGroup()), project.getName(), Objects.toString(project.getVersion())); } @Input ListProperty getArtifactIds() { return this.artifactIds; } @Classpath ListProperty getArtifactFiles() { return this.artifactFiles; } void resolvedArtifacts(Provider> resolvedArtifacts) { this.artifactFiles.addAll( resolvedArtifacts.map((artifacts) -> artifacts.stream().map(ResolvedArtifactResult::getFile).toList())); this.artifactIds.addAll( resolvedArtifacts.map((artifacts) -> artifacts.stream().map(ResolvedArtifactResult::getId).toList())); } @Nullable DependencyDescriptor find(File file) { ComponentArtifactIdentifier id = findArtifactIdentifier(file); if (id == null) { return null; } if (id instanceof ModuleComponentArtifactIdentifier moduleComponentId) { ModuleComponentIdentifier moduleId = moduleComponentId.getComponentIdentifier(); return new DependencyDescriptor( LibraryCoordinates.of(moduleId.getGroup(), moduleId.getModule(), moduleId.getVersion()), false); } ComponentIdentifier componentIdentifier = id.getComponentIdentifier(); if (componentIdentifier instanceof ProjectComponentIdentifier projectComponentId) { String projectPath = projectComponentId.getProjectPath(); LibraryCoordinates projectCoordinates = this.projectCoordinatesByPath.get(projectPath); if (projectCoordinates != null) { return new DependencyDescriptor(projectCoordinates, true); } } return null; } private @Nullable ComponentArtifactIdentifier findArtifactIdentifier(File file) { List files = this.artifactFiles.get(); for (int i = 0; i < files.size(); i++) { if (file.equals(files.get(i))) { return this.artifactIds.get().get(i); } } return null; } /** * Describes a dependency in a {@link ResolvedConfiguration}. */ static final class DependencyDescriptor { private final LibraryCoordinates coordinates; private final boolean projectDependency; private DependencyDescriptor(LibraryCoordinates coordinates, boolean projectDependency) { this.coordinates = coordinates; this.projectDependency = projectDependency; } LibraryCoordinates getCoordinates() { return this.coordinates; } boolean isProjectDependency() { return this.projectDependency; } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/ZipCompression.java ================================================ /* * 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.gradle.tasks.bundling; import java.util.zip.ZipEntry; /** * An enumeration of supported compression options for an entry in a ZIP archive. * * @author Andy Wilkinson * @since 2.0.0 */ public enum ZipCompression { /** * The entry should be {@link ZipEntry#STORED} in the archive. */ STORED, /** * The entry should be {@link ZipEntry#DEFLATED} in the archive. */ DEFLATED } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/package-info.java ================================================ /* * 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. */ /** * Support for creating executable jars and wars. */ @NullMarked package org.springframework.boot.gradle.tasks.bundling; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/run/BootRun.java ================================================ /* * 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.gradle.tasks.run; import java.io.File; import java.util.Set; import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetOutput; import org.gradle.work.DisableCachingByDefault; /** * Custom {@link JavaExec} task for running a Spring Boot application. * * @author Andy Wilkinson * @since 2.0.0 */ @DisableCachingByDefault(because = "Application should always run") public abstract class BootRun extends JavaExec { public BootRun() { getOptimizedLaunch().convention(true); } /** * Returns the property for whether the JVM's launch should be optimized. The property * defaults to {@code true}. * @return whether the JVM's launch should be optimized * @since 3.0.0 */ @Input public abstract Property getOptimizedLaunch(); /** * Adds the {@link SourceDirectorySet#getSrcDirs() source directories} of the given * {@code sourceSet's} {@link SourceSet#getResources() resources} to the start of the * classpath in place of the {@link SourceSet#getOutput output's} * {@link SourceSetOutput#getResourcesDir() resources directory}. * @param sourceSet the source set */ public void sourceResources(SourceSet sourceSet) { File resourcesDir = sourceSet.getOutput().getResourcesDir(); Set srcDirs = sourceSet.getResources().getSrcDirs(); setClasspath(getProject().files(srcDirs, getClasspath()).filter((file) -> !file.equals(resourcesDir))); } @Override public void exec() { if (getOptimizedLaunch().get()) { setJvmArgs(getJvmArgs()); jvmArgs("-XX:TieredStopAtLevel=1"); } if (System.console() != null) { // Record that the console is available here for AnsiOutput to detect later getEnvironment().put("spring.output.ansi.console-available", true); } super.exec(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/run/package-info.java ================================================ /* * 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. */ /** * Support for running Spring Boot applications. */ @NullMarked package org.springframework.boot.gradle.tasks.run; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/util/VersionExtractor.java ================================================ /* * 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.gradle.util; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.jar.Attributes; import java.util.jar.JarFile; import org.jspecify.annotations.Nullable; /** * Extracts version information for a Class. * * @author Andy Wilkinson * @author Scott Frederick * @since 2.3.0 */ public final class VersionExtractor { private VersionExtractor() { } /** * Return the version information for the provided {@link Class}. * @param cls the Class to retrieve the version for * @return the version, or {@code null} if a version can not be extracted */ public static @Nullable String forClass(Class cls) { String implementationVersion = cls.getPackage().getImplementationVersion(); if (implementationVersion != null) { return implementationVersion; } URL codeSourceLocation = cls.getProtectionDomain().getCodeSource().getLocation(); try { URLConnection connection = codeSourceLocation.openConnection(); if (connection instanceof JarURLConnection jarURLConnection) { return getImplementationVersion(jarURLConnection.getJarFile()); } try (JarFile jarFile = new JarFile(new File(codeSourceLocation.toURI()))) { return getImplementationVersion(jarFile); } } catch (Exception ex) { return null; } } private static String getImplementationVersion(JarFile jarFile) throws IOException { return jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/util/package-info.java ================================================ /* * 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. */ /** * Shared utility classes. */ @NullMarked package org.springframework.boot.gradle.util; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/resources/unixStartScript.txt ================================================ #!/usr/bin/env sh ############################################################################## ## ## ${applicationName} start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: \$0 may be a link PRG="\$0" # Need this for relative symlinks. while [ -h "\$PRG" ] ; do ls=`ls -ld "\$PRG"` link=`expr "\$ls" : '.*-> \\(.*\\)\$'` if expr "\$link" : '/.*' > /dev/null; then PRG="\$link" else PRG=`dirname "\$PRG"`"/\$link" fi done SAVED="`pwd`" cd "`dirname \"\$PRG\"`/${appHomeRelativePath}" >/dev/null APP_HOME="`pwd -P`" cd "\$SAVED" >/dev/null APP_NAME="${applicationName}" APP_BASE_NAME=`basename "\$0"` # Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script. DEFAULT_JVM_OPTS=${defaultJvmOpts} # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "\$*" } die ( ) { echo echo "\$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac JARPATH=$classpath # Determine the Java command to use to start the JVM. if [ -n "\$JAVA_HOME" ] ; then if [ -x "\$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="\$JAVA_HOME/jre/sh/java" else JAVACMD="\$JAVA_HOME/bin/java" fi if [ ! -x "\$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "\$cygwin" = "false" -a "\$darwin" = "false" -a "\$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ \$? -eq 0 ] ; then if [ "\$MAX_FD" = "maximum" -o "\$MAX_FD" = "max" ] ; then MAX_FD="\$MAX_FD_LIMIT" fi ulimit -n \$MAX_FD if [ \$? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: \$MAX_FD" fi else warn "Could not query maximum file descriptor limit: \$MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if \$darwin; then GRADLE_OPTS="\$GRADLE_OPTS \\"-Xdock:name=\$APP_NAME\\" \\"-Xdock:icon=\$APP_HOME/media/gradle.icns\\"" fi # For Cygwin, switch paths to Windows format before running java if \$cygwin ; then APP_HOME=`cygpath --path --mixed "\$APP_HOME"` JARPATH=`cygpath --path --mixed "\$JARPATH"` JAVACMD=`cygpath --unix "\$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in \$ROOTDIRSRAW ; do ROOTDIRS="\$ROOTDIRS\$SEP\$dir" SEP="|" done OURCYGPATTERN="(^(\$ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "\$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="\$OURCYGPATTERN|(\$GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "\$@" ; do CHECK=`echo "\$arg"|egrep -c "\$OURCYGPATTERN" -` CHECK2=`echo "\$arg"|egrep -c "^-"` ### Determine if an option if [ \$CHECK -ne 0 ] && [ \$CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args\$i`=`cygpath --path --ignore --mixed "\$arg"` else eval `echo args\$i`="\"\$arg\"" fi i=\$((i+1)) done case \$i in (0) set -- ;; (1) set -- "\$args0" ;; (2) set -- "\$args0" "\$args1" ;; (3) set -- "\$args0" "\$args1" "\$args2" ;; (4) set -- "\$args0" "\$args1" "\$args2" "\$args3" ;; (5) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" ;; (6) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" ;; (7) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" ;; (8) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" ;; (9) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" "\$args8" ;; esac fi # Escape application args save ( ) { for i do printf %s\\\\n "\$i" | sed "s/'/'\\\\\\\\''/g;1s/^/'/;\\\$s/\\\$/' \\\\\\\\/" ; done echo " " } APP_ARGS=\$(save "\$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- \$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar} <% if ( appNameSystemProperty ) { %>"\"-D${appNameSystemProperty}=\$APP_BASE_NAME\"" <% } %>-jar "\"\$JARPATH\"" "\$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "\$(uname)" = "Darwin" ] && [ "\$HOME" = "\$PWD" ]; then cd "\$(dirname "\$0")" fi exec "\$JAVACMD" "\$@" ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/main/resources/windowsStartScript.txt ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem ${applicationName} startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=.\ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME%${appHomeRelativePath} @rem Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script. set DEFAULT_JVM_OPTS=${defaultJvmOpts} @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set JARPATH=$classpath @rem Execute ${applicationName} "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %${optsEnvironmentVar}% <% if ( appNameSystemProperty ) { %>"-D${appNameSystemProperty}=%APP_BASE_NAME%"<% } %> -jar "%JARPATH%" %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable ${exitEnvironmentVar} if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%${exitEnvironmentVar}%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/TaskConfigurationAvoidanceTests.java ================================================ /* * 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.gradle; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; import java.util.regex.Pattern; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.core.importer.Location; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; import org.gradle.api.Action; import org.gradle.api.tasks.TaskCollection; import org.gradle.api.tasks.TaskContainer; /** * Tests that verify the plugin's compliance with task configuration avoidance. * * @author Andy Wilkinson */ @AnalyzeClasses(packages = "org.springframework.boot.gradle", importOptions = TaskConfigurationAvoidanceTests.DoNotIncludeTests.class) class TaskConfigurationAvoidanceTests { @ArchTest void noApisThatCauseEagerTaskConfigurationShouldBeCalled(JavaClasses classes) { ProhibitedMethods prohibited = new ProhibitedMethods(); prohibited.on(TaskContainer.class) .methodsNamed("create", "findByPath, getByPath") .method("withType", Class.class, Action.class); prohibited.on(TaskCollection.class).methodsNamed("findByName", "getByName"); ArchRuleDefinition.noClasses() .should() .callMethodWhere(DescribedPredicate.describe("it would cause eager task configuration", prohibited)) .check(classes); } static class DoNotIncludeTests implements ImportOption { @Override public boolean includes(Location location) { return !location.matches(Pattern.compile(".*Tests\\.class")); } } private static final class ProhibitedMethods implements Predicate { private final List> prohibited = new ArrayList<>(); private ProhibitedConfigurer on(Class type) { return new ProhibitedConfigurer(type); } @Override public boolean test(JavaMethodCall methodCall) { for (Predicate spec : this.prohibited) { if (spec.test(methodCall)) { return true; } } return false; } private final class ProhibitedConfigurer { private final Class type; private ProhibitedConfigurer(Class type) { this.type = type; } private ProhibitedConfigurer methodsNamed(String... names) { for (String name : names) { ProhibitedMethods.this.prohibited.add(new ProhibitMethodsNamed(this.type, name)); } return this; } private ProhibitedConfigurer method(String name, Class... parameterTypes) { ProhibitedMethods.this.prohibited .add(new ProhibitMethod(this.type, name, Arrays.asList(parameterTypes))); return this; } } static class ProhibitMethodsNamed implements Predicate { private final Class owner; private final String name; ProhibitMethodsNamed(Class owner, String name) { this.owner = owner; this.name = name; } @Override public boolean test(JavaMethodCall methodCall) { return methodCall.getTargetOwner().isEquivalentTo(this.owner) && methodCall.getName().equals(this.name); } } private static final class ProhibitMethod extends ProhibitMethodsNamed { private final List> parameterTypes; private ProhibitMethod(Class owner, String name, List> parameterTypes) { super(owner, name); this.parameterTypes = parameterTypes; } @Override public boolean test(JavaMethodCall methodCall) { return super.test(methodCall) && match(methodCall.getTarget().getParameterTypes()); } private boolean match(List callParameterTypes) { if (this.parameterTypes.size() != callParameterTypes.size()) { return false; } for (int i = 0; i < this.parameterTypes.size(); i++) { if (!callParameterTypes.get(i).toErasure().isEquivalentTo(this.parameterTypes.get(i))) { return false; } } return true; } } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/AotDocumentationTests.java ================================================ /* * 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.gradle.docs; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.gradle.junit.GradleMultiDslExtension; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for AOT documentation. * * @author Andy Wilkinson */ @ExtendWith(GradleMultiDslExtension.class) class AotDocumentationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void applyNativeImagePlugin() { assertThat(this.gradleBuild.script(Examples.DIR + "aot/apply-native-image-plugin").build("tasks").getOutput()) .contains("nativeCompile") .contains("aotClasses"); } @TestTemplate void applyAotPlugin() { assertThat(this.gradleBuild.script(Examples.DIR + "aot/apply-aot-plugin").build("tasks").getOutput()) .contains("aotClasses") .doesNotContain("nativeCompile"); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/Examples.java ================================================ /* * 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.gradle.docs; /** * @author Phillip Webb */ final class Examples { static final String DIR = "src/docs/antora/modules/gradle-plugin/examples/"; private Examples() { } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/GettingStartedDocumentationTests.java ================================================ /* * 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.gradle.docs; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.gradle.junit.GradleMultiDslExtension; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; /** * Tests for the getting started documentation. * * @author Andy Wilkinson * @author Jean-Baptiste Nizet */ @ExtendWith(GradleMultiDslExtension.class) class GettingStartedDocumentationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; // NOTE: We can't run any 'apply-plugin' tests because during a release the // jar won't be there @TestTemplate void typicalPluginsAppliesExceptedPlugins() { this.gradleBuild.script(Examples.DIR + "getting-started/typical-plugins").build("verify"); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/IntegratingWithActuatorDocumentationTests.java ================================================ /* * 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.gradle.docs; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.Properties; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.gradle.junit.GradleMultiDslExtension; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for the generating build info documentation. * * @author Andy Wilkinson * @author Jean-Baptiste Nizet */ @ExtendWith(GradleMultiDslExtension.class) class IntegratingWithActuatorDocumentationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void basicBuildInfo() { this.gradleBuild.script(Examples.DIR + "integrating-with-actuator/build-info-basic").build("bootBuildInfo"); assertThat(new File(this.gradleBuild.getProjectDir(), "build/bootBuildInfo/META-INF/build-info.properties")) .isFile(); } @TestTemplate void buildInfoCustomValues() { this.gradleBuild.script(Examples.DIR + "integrating-with-actuator/build-info-custom-values") .build("bootBuildInfo"); File file = new File(this.gradleBuild.getProjectDir(), "build/bootBuildInfo/META-INF/build-info.properties"); assertThat(file).isFile(); Properties properties = buildInfoProperties(file); assertThat(properties).containsEntry("build.artifact", "example-app"); assertThat(properties).containsEntry("build.version", "1.2.3"); assertThat(properties).containsEntry("build.group", "com.example"); assertThat(properties).containsEntry("build.name", "Example application"); assertThat(properties).containsKey("build.time"); } @TestTemplate void buildInfoAdditional() { this.gradleBuild.script(Examples.DIR + "integrating-with-actuator/build-info-additional") .build("bootBuildInfo"); File file = new File(this.gradleBuild.getProjectDir(), "build/bootBuildInfo/META-INF/build-info.properties"); assertThat(file).isFile(); Properties properties = buildInfoProperties(file); assertThat(properties).containsEntry("build.a", "alpha"); assertThat(properties).containsEntry("build.b", "bravo"); } @TestTemplate void buildInfoExcludeTime() { this.gradleBuild.script(Examples.DIR + "integrating-with-actuator/build-info-exclude-time") .build("bootBuildInfo"); File file = new File(this.gradleBuild.getProjectDir(), "build/bootBuildInfo/META-INF/build-info.properties"); assertThat(file).isFile(); Properties properties = buildInfoProperties(file); assertThat(properties).doesNotContainKey("build.time"); } private Properties buildInfoProperties(File file) { assertThat(file).isFile(); Properties properties = new Properties(); try (FileReader reader = new FileReader(file)) { properties.load(reader); return properties; } catch (IOException ex) { throw new RuntimeException(ex); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/ManagingDependenciesDocumentationTests.java ================================================ /* * 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.gradle.docs; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.gradle.junit.GradleMultiDslExtension; import org.springframework.boot.testsupport.gradle.testkit.Dsl; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumingThat; /** * Tests for the managing dependencies documentation. * * @author Andy Wilkinson * @author Jean-Baptiste Nizet */ @ExtendWith(GradleMultiDslExtension.class) class ManagingDependenciesDocumentationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void dependenciesExampleEvaluatesSuccessfully() { this.gradleBuild.script(Examples.DIR + "managing-dependencies/dependencies").build(); } @TestTemplate void customManagedVersions() { assertThat(this.gradleBuild.script(Examples.DIR + "managing-dependencies/custom-version") .build("slf4jVersion") .getOutput()).contains("1.7.20"); } @TestTemplate void dependencyManagementInIsolation() { assertThat(this.gradleBuild.script(Examples.DIR + "managing-dependencies/configure-bom") .build("dependencyManagement") .getOutput()).contains("org.springframework.boot:spring-boot-starter "); } @TestTemplate void dependencyManagementInIsolationWithPluginsBlock() { assumingThat(this.gradleBuild.getDsl() == Dsl.KOTLIN, () -> assertThat( this.gradleBuild.script(Examples.DIR + "managing-dependencies/configure-bom-with-plugins") .build("dependencyManagement") .getOutput()) .contains("org.springframework.boot:spring-boot-starter TEST-SNAPSHOT")); } @TestTemplate void configurePlatform() { assertThat(this.gradleBuild.script(Examples.DIR + "managing-dependencies/configure-platform") .build("dependencies", "--configuration", "compileClasspath") .getOutput()).contains("org.springframework.boot:spring-boot-starter "); } @TestTemplate void customManagedVersionsWithPlatform() { assertThat(this.gradleBuild.script(Examples.DIR + "managing-dependencies/custom-version-with-platform") .build("dependencies", "--configuration", "compileClasspath") .getOutput()).contains("1.7.20"); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java ================================================ /* * 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.gradle.docs; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Collections; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import org.gradle.testkit.runner.BuildResult; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.gradle.junit.GradleMultiDslExtension; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for the packaging documentation. * * @author Andy Wilkinson * @author Jean-Baptiste Nizet * @author Scott Frederick */ @ExtendWith(GradleMultiDslExtension.class) class PackagingDocumentationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void warContainerDependencyEvaluatesSuccessfully() { this.gradleBuild.script(Examples.DIR + "packaging/war-container-dependency").build(); } @TestTemplate void bootJarMainClass() throws IOException { this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-main-class").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); try (JarFile jar = new JarFile(file)) { assertThat(jar.getManifest().getMainAttributes().getValue("Start-Class")) .isEqualTo("com.example.ExampleApplication"); } } @TestTemplate void bootJarManifestMainClass() throws IOException { this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-manifest-main-class").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); try (JarFile jar = new JarFile(file)) { assertThat(jar.getManifest().getMainAttributes().getValue("Start-Class")) .isEqualTo("com.example.ExampleApplication"); } } @TestTemplate void applicationPluginMainClass() throws IOException { this.gradleBuild.script(Examples.DIR + "packaging/application-plugin-main-class").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); try (JarFile jar = new JarFile(file)) { assertThat(jar.getManifest().getMainAttributes().getValue("Start-Class")) .isEqualTo("com.example.ExampleApplication"); } } @TestTemplate void springBootDslMainClass() throws IOException { this.gradleBuild.script(Examples.DIR + "packaging/spring-boot-dsl-main-class").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); try (JarFile jar = new JarFile(file)) { assertThat(jar.getManifest().getMainAttributes().getValue("Start-Class")) .isEqualTo("com.example.ExampleApplication"); } } @TestTemplate void bootWarIncludeDevtools() throws IOException { jarFile(new File(this.gradleBuild.getProjectDir(), "spring-boot-devtools-1.2.3.RELEASE.jar")); this.gradleBuild.script(Examples.DIR + "packaging/boot-war-include-devtools").build("bootWar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".war"); assertThat(file).isFile(); try (JarFile jar = new JarFile(file)) { assertThat(jar.getEntry("WEB-INF/lib/spring-boot-devtools-1.2.3.RELEASE.jar")).isNotNull(); } } @TestTemplate void bootJarRequiresUnpack() throws IOException { this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-requires-unpack").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); try (JarFile jar = new JarFile(file)) { JarEntry entry = jar.getJarEntry("BOOT-INF/lib/jruby-complete-1.7.25.jar"); assertThat(entry).isNotNull(); assertThat(entry.getComment()).isEqualTo("UNPACK"); } } @TestTemplate void bootWarPropertiesLauncher() throws IOException { this.gradleBuild.script(Examples.DIR + "packaging/boot-war-properties-launcher").build("bootWar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".war"); assertThat(file).isFile(); try (JarFile jar = new JarFile(file)) { assertThat(jar.getManifest().getMainAttributes().getValue("Main-Class")) .isEqualTo("org.springframework.boot.loader.launch.PropertiesLauncher"); } } @TestTemplate void onlyBootJar() throws IOException { this.gradleBuild.script(Examples.DIR + "packaging/only-boot-jar").build("assemble"); File plainJar = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + "-plain.jar"); assertThat(plainJar).doesNotExist(); File bootJar = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(bootJar).isFile(); try (JarFile jar = new JarFile(bootJar)) { assertThat(jar.getEntry("BOOT-INF/")).isNotNull(); } } @TestTemplate void classifiedBootJar() throws IOException { this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-and-jar-classifiers").build("assemble"); File plainJar = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(plainJar).isFile(); try (JarFile jar = new JarFile(plainJar)) { assertThat(jar.getEntry("BOOT-INF/")).isNull(); } File bootJar = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + "-boot.jar"); assertThat(bootJar).isFile(); try (JarFile jar = new JarFile(bootJar)) { assertThat(jar.getEntry("BOOT-INF/")).isNotNull(); } } @TestTemplate void bootJarLayeredDisabled() throws IOException { this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-layered-disabled").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); try (JarFile jar = new JarFile(file)) { JarEntry entry = jar.getJarEntry("BOOT-INF/layers.idx"); assertThat(entry).isNull(); } } @TestTemplate void bootJarLayeredCustom() throws IOException { this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-layered-custom").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); try (JarFile jar = new JarFile(file)) { JarEntry entry = jar.getJarEntry("BOOT-INF/layers.idx"); assertThat(entry).isNotNull(); assertThat(Collections.list(jar.entries()) .stream() .map(JarEntry::getName) .filter((name) -> name.startsWith("BOOT-INF/lib/spring-boot"))).isNotEmpty(); } } @TestTemplate void bootJarLayeredExcludeTools() throws IOException { this.gradleBuild.script(Examples.DIR + "packaging/boot-jar-layered-exclude-tools").build("bootJar"); File file = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(file).isFile(); try (JarFile jar = new JarFile(file)) { JarEntry entry = jar.getJarEntry("BOOT-INF/layers.idx"); assertThat(entry).isNotNull(); assertThat(Collections.list(jar.entries()) .stream() .map(JarEntry::getName) .filter((name) -> name.startsWith("BOOT-INF/lib/spring-boot"))).isEmpty(); } } @TestTemplate void bootBuildImageWithBuilder() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-builder") .build("bootBuildImageBuilder"); assertThat(result.getOutput()).contains("builder=mine/java-cnb-builder").contains("runImage=mine/java-cnb-run"); } @TestTemplate void bootBuildImageWithCustomBuildpackJvmVersion() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-env") .build("bootBuildImageEnvironment"); assertThat(result.getOutput()).contains("BP_JVM_VERSION=17"); } @TestTemplate void bootBuildImageWithCustomProxySettings() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-env-proxy") .build("bootBuildImageEnvironment"); assertThat(result.getOutput()).contains("HTTP_PROXY=http://proxy.example.com") .contains("HTTPS_PROXY=https://proxy.example.com"); } @TestTemplate void bootBuildImageWithCustomRuntimeConfiguration() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-env-runtime") .build("bootBuildImageEnvironment"); assertThat(result.getOutput()).contains("BPE_DELIM_JAVA_TOOL_OPTIONS= ") .contains("BPE_APPEND_JAVA_TOOL_OPTIONS=-XX:+HeapDumpOnOutOfMemoryError"); } @TestTemplate void bootBuildImageWithCustomImageName() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-name") .build("bootBuildImageName"); assertThat(result.getOutput()).contains("example.com/library/" + this.gradleBuild.getProjectDir().getName()); } @TestTemplate void bootBuildImageWithDockerHostMinikube() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-docker-host") .build("bootBuildImageDocker"); assertThat(result.getOutput()).contains("host=tcp://192.168.99.100:2376") .contains("tlsVerify=true") .contains("certPath=/home/user/.minikube/certs"); } @TestTemplate void bootBuildImageWithDockerHostPodman() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-docker-host-podman") .build("bootBuildImageDocker"); assertThat(result.getOutput()).contains("host=unix:///run/user/1000/podman/podman.sock") .contains("bindHostToBuilder=true"); } @TestTemplate void bootBuildImageWithDockerHostColima() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-docker-host-colima") .build("bootBuildImageDocker"); assertThat(result.getOutput()) .contains("host=unix://" + System.getProperty("user.home") + "/.colima/docker.sock"); } @TestTemplate void bootBuildImageWithDockerUserAuth() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-docker-auth-user") .build("bootBuildImageDocker"); assertThat(result.getOutput()).contains("username=user") .contains("password=secret") .contains("url=https://docker.example.com/v1/") .contains("email=user@example.com"); } @TestTemplate void bootBuildImageWithDockerTokenAuth() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-docker-auth-token") .build("bootBuildImageDocker"); assertThat(result.getOutput()).contains("token=9cbaf023786cd7..."); } @TestTemplate void bootBuildImagePublish() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-publish") .build("bootBuildImagePublish"); assertThat(result.getOutput()).contains("true"); } @TestTemplate void bootBuildImageWithBuildpacks() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-buildpacks") .build("bootBuildImageBuildpacks"); assertThat(result.getOutput()).contains("file:///path/to/example-buildpack.tgz") .contains("urn:cnb:builder:paketo-buildpacks/java"); } @TestTemplate void bootBuildImageWithCaches() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-caches") .build("bootBuildImageCaches"); assertThat(result.getOutput()).containsPattern("buildCache=cache-gradle-[\\d]+.build") .containsPattern("launchCache=cache-gradle-[\\d]+.launch"); } @TestTemplate void bootBuildImageWithBindCaches() { BuildResult result = this.gradleBuild.script(Examples.DIR + "packaging/boot-build-image-bind-caches") .build("bootBuildImageCaches"); assertThat(result.getOutput()).containsPattern("buildWorkspace=/tmp/cache-gradle-[\\d]+.work") .containsPattern("buildCache=/tmp/cache-gradle-[\\d]+.build") .containsPattern("launchCache=/tmp/cache-gradle-[\\d]+.launch"); } protected void jarFile(File file) throws IOException { try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(file))) { jar.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); new Manifest().write(jar); jar.closeEntry(); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PublishingDocumentationTests.java ================================================ /* * 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.gradle.docs; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.gradle.junit.GradleMultiDslExtension; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for the publishing documentation. * * @author Andy Wilkinson * @author Jean-Baptiste Nizet */ @ExtendWith(GradleMultiDslExtension.class) class PublishingDocumentationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void mavenPublish() { assertThat(this.gradleBuild.script(Examples.DIR + "publishing/maven-publish") .build("publishingConfiguration") .getOutput()).contains("MavenPublication").contains("https://repo.example.com"); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java ================================================ /* * 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.gradle.docs; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.gradle.junit.GradleMultiDslExtension; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for the documentation about running a Spring Boot application. * * @author Andy Wilkinson * @author Jean-Baptiste Nizet */ @ExtendWith(GradleMultiDslExtension.class) class RunningDocumentationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void bootRunMain() throws IOException { writeMainClass(); assertThat(this.gradleBuild.script(Examples.DIR + "running/boot-run-main").build("bootRun").getOutput()) .contains("com.example.ExampleApplication"); } @TestTemplate void applicationPluginMainClassName() throws IOException { writeMainClass(); assertThat(this.gradleBuild.script(Examples.DIR + "running/application-plugin-main-class-name") .build("bootRun") .getOutput()).contains("com.example.ExampleApplication"); } @TestTemplate void springBootDslMainClassName() throws IOException { writeMainClass(); assertThat(this.gradleBuild.script(Examples.DIR + "running/spring-boot-dsl-main-class-name") .build("bootRun") .getOutput()).contains("com.example.ExampleApplication"); } @TestTemplate void bootRunSourceResources() { assertThat(this.gradleBuild.script(Examples.DIR + "running/boot-run-source-resources") .build("configuredClasspath") .getOutput()).contains(new File("src/main/resources").getPath()); } @TestTemplate void bootRunDisableOptimizedLaunch() { assertThat(this.gradleBuild.script(Examples.DIR + "running/boot-run-disable-optimized-launch") .build("optimizedLaunch") .getOutput()).contains("false"); } @TestTemplate void bootRunSystemPropertyDefaultValue() { assertThat(this.gradleBuild.script(Examples.DIR + "running/boot-run-system-property") .build("configuredSystemProperties") .getOutput()).contains("com.example.property = default"); } @TestTemplate void bootRunSystemProperty() { assertThat(this.gradleBuild.script(Examples.DIR + "running/boot-run-system-property") .build("-Pexample=custom", "configuredSystemProperties") .getOutput()).contains("com.example.property = custom"); } private void writeMainClass() throws IOException { File exampleApplication = new File(this.gradleBuild.getProjectDir(), "src/main/java/com/example/ExampleApplication.java"); exampleApplication.getParentFile().mkdirs(); try (PrintWriter writer = new PrintWriter(new FileWriter(exampleApplication))) { writer.println("package com.example;"); writer.println("public class ExampleApplication {"); writer.println(" public static void main(String[] args) {"); writer.println(" System.out.println(ExampleApplication.class.getName());"); writer.println(" }"); writer.println("}"); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/package-info.java ================================================ /* * 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. */ @NullMarked package org.springframework.boot.gradle.docs; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/dsl/BuildInfoDslIntegrationTests.java ================================================ /* * 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.gradle.dsl; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.Properties; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.gradle.tasks.buildinfo.BuildInfo; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link BuildInfo} created using the * {@link org.springframework.boot.gradle.dsl.SpringBootExtension DSL}. * * @author Andy Wilkinson */ @GradleCompatibility class BuildInfoDslIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void basicJar() throws IOException { BuildTask task = this.gradleBuild.build("bootJar").task(":bootBuildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Properties properties = buildInfoProperties(); assertThat(properties).containsEntry("build.name", this.gradleBuild.getProjectDir().getName()); assertThat(properties).containsEntry("build.artifact", this.gradleBuild.getProjectDir().getName()); assertThat(properties).containsEntry("build.group", "com.example"); assertThat(properties).containsEntry("build.version", "1.0"); File jar = new File(this.gradleBuild.getProjectDir(), "build/libs/").listFiles()[0]; try (JarFile jarFile = new JarFile(jar)) { JarEntry entry = jarFile.getJarEntry("META-INF/build-info.properties"); assertThat(entry).isNotNull(); Properties jarProperties = new Properties(); jarProperties.load(jarFile.getInputStream(entry)); assertThat(jarProperties).isEqualTo(properties); } } @TestTemplate void jarWithCustomName() { BuildTask task = this.gradleBuild.build("bootBuildInfo", "--stacktrace").task(":bootBuildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Properties properties = buildInfoProperties(); assertThat(properties).containsEntry("build.name", this.gradleBuild.getProjectDir().getName()); assertThat(properties).containsEntry("build.artifact", "foo"); assertThat(properties).containsEntry("build.group", "com.example"); assertThat(properties).containsEntry("build.version", "1.0"); } @TestTemplate void basicWar() throws IOException { BuildTask task = this.gradleBuild.build("bootWar").task(":bootBuildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Properties properties = buildInfoProperties(); assertThat(properties).containsEntry("build.name", this.gradleBuild.getProjectDir().getName()); assertThat(properties).containsEntry("build.artifact", this.gradleBuild.getProjectDir().getName()); assertThat(properties).containsEntry("build.group", "com.example"); assertThat(properties).containsEntry("build.version", "1.0"); File war = new File(this.gradleBuild.getProjectDir(), "build/libs/").listFiles()[0]; try (JarFile warFile = new JarFile(war)) { JarEntry entry = warFile.getJarEntry("WEB-INF/classes/META-INF/build-info.properties"); assertThat(entry).isNotNull(); Properties jarProperties = new Properties(); jarProperties.load(warFile.getInputStream(entry)); assertThat(jarProperties).isEqualTo(properties); } } @TestTemplate void warWithCustomName() { BuildTask task = this.gradleBuild.build("bootBuildInfo", "--stacktrace").task(":bootBuildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Properties properties = buildInfoProperties(); assertThat(properties).containsEntry("build.name", this.gradleBuild.getProjectDir().getName()); assertThat(properties).containsEntry("build.artifact", "foo"); assertThat(properties).containsEntry("build.group", "com.example"); assertThat(properties).containsEntry("build.version", "1.0"); } @TestTemplate void additionalProperties() { BuildTask task = this.gradleBuild.build("bootBuildInfo", "--stacktrace").task(":bootBuildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Properties properties = buildInfoProperties(); assertThat(properties).containsEntry("build.name", this.gradleBuild.getProjectDir().getName()); assertThat(properties).containsEntry("build.artifact", this.gradleBuild.getProjectDir().getName()); assertThat(properties).containsEntry("build.group", "com.example"); assertThat(properties).containsEntry("build.version", "1.0"); assertThat(properties).containsEntry("build.a", "alpha"); assertThat(properties).containsEntry("build.b", "bravo"); } private Properties buildInfoProperties() { File file = new File(this.gradleBuild.getProjectDir(), "build/bootBuildInfo/META-INF/build-info.properties"); assertThat(file).isFile(); Properties properties = new Properties(); try (FileReader reader = new FileReader(file)) { properties.load(reader); return properties; } catch (IOException ex) { throw new RuntimeException(ex); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleBuildFieldSetter.java ================================================ /* * 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.gradle.junit; import java.lang.reflect.Field; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; /** * {@link BeforeEachCallback} to set a test class's {@code gradleBuild} field prior to * test execution. * * @author Andy Wilkinson */ final class GradleBuildFieldSetter implements BeforeEachCallback { private final GradleBuild gradleBuild; GradleBuildFieldSetter(GradleBuild gradleBuild) { this.gradleBuild = gradleBuild; } @Override public void beforeEach(ExtensionContext context) throws Exception { Field field = ReflectionUtils.findField(context.getRequiredTestClass(), "gradleBuild"); Assert.notNull(field, "Field named gradleBuild not found in " + context.getRequiredTestClass().getName()); field.setAccessible(true); field.set(context.getRequiredTestInstance(), this.gradleBuild); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibility.java ================================================ /* * 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.gradle.junit; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; /** * {@link Extension} that runs {@link TestTemplate templated tests} against multiple * versions of Gradle. Test classes using the extension must have a non-private and * non-final {@link GradleBuild} field named {@code gradleBuild}. * * @author Andy Wilkinson */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Inherited @ExtendWith(GradleCompatibilityExtension.class) public @interface GradleCompatibility { /** * Whether to include running Gradle with {@code --cache-configuration} cache in the * compatibility matrix. * @return {@code true} to enable the configuration cache, {@code false} otherwise */ boolean configurationCache() default false; /** * Minimum version of Gradle against which compatibility should be tested. * @return minimum version */ String minimumVersion() default ""; } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java ================================================ /* * 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.gradle.junit; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.TreeSet; import java.util.stream.Collectors; import java.util.stream.Stream; import org.gradle.util.GradleVersion; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.platform.commons.util.AnnotationUtils; import org.springframework.boot.gradle.testkit.PluginClasspathGradleBuild; import org.springframework.boot.testsupport.BuildOutput; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import org.springframework.boot.testsupport.gradle.testkit.GradleBuildExtension; import org.springframework.boot.testsupport.gradle.testkit.GradleVersions; import org.springframework.util.StringUtils; /** * {@link Extension} that runs {@link TestTemplate templated tests} against multiple * versions of Gradle. Test classes using the extension must have a non-private and * non-final {@link GradleBuild} field named {@code gradleBuild}. * * @author Andy Wilkinson */ final class GradleCompatibilityExtension implements TestTemplateInvocationContextProvider { private static final List GRADLE_VERSIONS = GradleVersions.allCompatible(); @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { GradleVersion highestVersion = GRADLE_VERSIONS.stream() .map(GradleVersion::version) .collect(Collectors.toCollection(TreeSet::new)) .last(); Class testClass = context.getRequiredTestClass(); GradleCompatibility gradleCompatibility = AnnotationUtils.findAnnotation(testClass, GradleCompatibility.class) .get(); Stream gradleVersions = GRADLE_VERSIONS.stream(); if (StringUtils.hasText(gradleCompatibility.minimumVersion())) { GradleVersion lowerInclusive = GradleVersion.version(gradleCompatibility.minimumVersion()); gradleVersions = gradleVersions .filter((version) -> GradleVersion.version(version).compareTo(lowerInclusive) >= 0); } return gradleVersions.flatMap((version) -> { List invocationContexts = new ArrayList<>(); BuildOutput buildOutput = new BuildOutput(testClass); invocationContexts.add(new GradleVersionTestTemplateInvocationContext(version, false, buildOutput)); boolean configurationCache = gradleCompatibility.configurationCache(); if (configurationCache && GradleVersion.version(version).equals(highestVersion)) { invocationContexts.add(new GradleVersionTestTemplateInvocationContext(version, true, buildOutput)); } return invocationContexts.stream(); }); } @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } private static final class GradleVersionTestTemplateInvocationContext implements TestTemplateInvocationContext { private final BuildOutput buildOutput; private final String gradleVersion; private final boolean configurationCache; GradleVersionTestTemplateInvocationContext(String gradleVersion, boolean configurationCache, BuildOutput buildOutput) { this.buildOutput = buildOutput; this.gradleVersion = gradleVersion; this.configurationCache = configurationCache; } @Override public String getDisplayName(int invocationIndex) { return "Gradle " + this.gradleVersion + ((this.configurationCache) ? " --configuration-cache" : ""); } @Override public List getAdditionalExtensions() { GradleBuild gradleBuild = new PluginClasspathGradleBuild(this.buildOutput) .gradleVersion(this.gradleVersion); if (this.configurationCache) { gradleBuild.configurationCache(); } return Arrays.asList(new GradleBuildFieldSetter(gradleBuild), new GradleBuildExtension()); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleMultiDslExtension.java ================================================ /* * 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.gradle.junit; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.springframework.boot.gradle.testkit.PluginClasspathGradleBuild; import org.springframework.boot.testsupport.BuildOutput; import org.springframework.boot.testsupport.gradle.testkit.Dsl; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import org.springframework.boot.testsupport.gradle.testkit.GradleBuildExtension; import org.springframework.boot.testsupport.gradle.testkit.GradleVersions; /** * {@link Extension} that runs {@link TestTemplate templated tests} against the Groovy and * Kotlin DSLs. Test classes using the extension most have a non-private non-final * {@link GradleBuild} field named {@code gradleBuild}. * * @author Andy Wilkinson */ public class GradleMultiDslExtension implements TestTemplateInvocationContextProvider { @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { BuildOutput buildOutput = new BuildOutput(context.getRequiredTestClass()); return Stream.of(Dsl.values()).map((dsl) -> new DslTestTemplateInvocationContext(buildOutput, dsl)); } @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } private static final class DslTestTemplateInvocationContext implements TestTemplateInvocationContext { private final BuildOutput buildOutput; private final Dsl dsl; DslTestTemplateInvocationContext(BuildOutput buildOutput, Dsl dsl) { this.buildOutput = buildOutput; this.dsl = dsl; } @Override public List getAdditionalExtensions() { PluginClasspathGradleBuild gradleBuild = new PluginClasspathGradleBuild(this.buildOutput, this.dsl); if (this.dsl == Dsl.KOTLIN) { gradleBuild.kotlin(); } gradleBuild.gradleVersion(GradleVersions.minimumCompatible()); return Arrays.asList(new GradleBuildFieldSetter(gradleBuild), new GradleBuildExtension()); } @Override public String getDisplayName(int invocationIndex) { return this.dsl.getName(); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleProjectBuilder.java ================================================ /* * 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.gradle.junit; import java.io.File; import org.gradle.api.Project; import org.gradle.testfixtures.ProjectBuilder; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Helper class to build Gradle {@link Project Projects} for test fixtures. Wraps * functionality of Gradle's own {@link ProjectBuilder}. * * @author Christoph Dreis */ public final class GradleProjectBuilder { private @Nullable File projectDir; private @Nullable String name; private GradleProjectBuilder() { } public static GradleProjectBuilder builder() { return new GradleProjectBuilder(); } public GradleProjectBuilder withProjectDir(File dir) { this.projectDir = dir; return this; } public GradleProjectBuilder withName(String name) { this.name = name; return this; } public Project build() { Assert.notNull(this.projectDir, "ProjectDir must not be null"); ProjectBuilder builder = ProjectBuilder.builder(); builder.withProjectDir(this.projectDir); File userHome = new File(this.projectDir, "userHome"); builder.withGradleUserHomeDir(userHome); if (StringUtils.hasText(this.name)) { builder.withName(this.name); } return builder.build(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/package-info.java ================================================ /* * 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. */ @NullMarked package org.springframework.boot.gradle.junit; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/package-info.java ================================================ /* * 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. */ @NullMarked package org.springframework.boot.gradle; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests.java ================================================ /* * 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.gradle.plugin; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.gradle.util.GradleVersion; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link ApplicationPluginAction}. * * @author Andy Wilkinson */ @GradleCompatibility class ApplicationPluginActionIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void noBootDistributionWithoutApplicationPluginApplied() { assertThat(this.gradleBuild.build("distributionExists", "-PdistributionName=boot").getOutput()) .contains("boot exists = false"); } @TestTemplate void applyingApplicationPluginCreatesBootDistribution() { assertThat(this.gradleBuild.build("distributionExists", "-PdistributionName=boot", "-PapplyApplicationPlugin") .getOutput()).contains("boot exists = true"); } @TestTemplate void noBootStartScriptsTaskWithoutApplicationPluginApplied() { assertThat(this.gradleBuild.build("taskExists", "-PtaskName=bootStartScripts").getOutput()) .contains("bootStartScripts exists = false"); } @TestTemplate void applyingApplicationPluginCreatesBootStartScriptsTask() { assertThat(this.gradleBuild.build("taskExists", "-PtaskName=bootStartScripts", "-PapplyApplicationPlugin") .getOutput()).contains("bootStartScripts exists = true"); } @TestTemplate void createsBootStartScriptsTaskUsesApplicationPluginsDefaultJvmOpts() { assertThat(this.gradleBuild.build("startScriptsDefaultJvmOpts", "-PapplyApplicationPlugin").getOutput()) .contains("bootStartScripts defaultJvmOpts = [-Dcom.example.a=alpha, -Dcom.example.b=bravo]"); } @TestTemplate void zipDistributionForJarCanBeBuilt() throws IOException { BuildTask task = this.gradleBuild.build("bootDistZip").task(":bootDistZip"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); String name = this.gradleBuild.getProjectDir().getName(); File distribution = new File(this.gradleBuild.getProjectDir(), "build/distributions/" + name + "-boot.zip"); assertThat(distribution).isFile(); assertThat(zipEntryNames(distribution)).containsExactlyInAnyOrder(name + "-boot/", name + "-boot/lib/", name + "-boot/lib/" + name + ".jar", name + "-boot/bin/", name + "-boot/bin/" + name, name + "-boot/bin/" + name + ".bat"); } @TestTemplate void tarDistributionForJarCanBeBuilt() throws IOException { BuildTask task = this.gradleBuild.build("bootDistTar").task(":bootDistTar"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); String name = this.gradleBuild.getProjectDir().getName(); File distribution = new File(this.gradleBuild.getProjectDir(), "build/distributions/" + name + "-boot.tar"); assertThat(distribution).isFile(); assertThat(tarEntryNames(distribution)).containsExactlyInAnyOrder(name + "-boot/", name + "-boot/lib/", name + "-boot/lib/" + name + ".jar", name + "-boot/bin/", name + "-boot/bin/" + name, name + "-boot/bin/" + name + ".bat"); } @TestTemplate void zipDistributionForWarCanBeBuilt() throws IOException { BuildTask task = this.gradleBuild.build("bootDistZip").task(":bootDistZip"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); String name = this.gradleBuild.getProjectDir().getName(); File distribution = new File(this.gradleBuild.getProjectDir(), "build/distributions/" + name + "-boot.zip"); assertThat(distribution).isFile(); assertThat(zipEntryNames(distribution)).containsExactlyInAnyOrder(name + "-boot/", name + "-boot/lib/", name + "-boot/lib/" + name + ".war", name + "-boot/bin/", name + "-boot/bin/" + name, name + "-boot/bin/" + name + ".bat"); } @TestTemplate void tarDistributionForWarCanBeBuilt() throws IOException { BuildTask task = this.gradleBuild.build("bootDistTar").task(":bootDistTar"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); String name = this.gradleBuild.getProjectDir().getName(); File distribution = new File(this.gradleBuild.getProjectDir(), "build/distributions/" + name + "-boot.tar"); assertThat(distribution).isFile(); assertThat(tarEntryNames(distribution)).containsExactlyInAnyOrder(name + "-boot/", name + "-boot/lib/", name + "-boot/lib/" + name + ".war", name + "-boot/bin/", name + "-boot/bin/" + name, name + "-boot/bin/" + name + ".bat"); } @TestTemplate void applicationNameCanBeUsedToCustomizeDistributionName() throws IOException { BuildTask task = this.gradleBuild.build("bootDistTar").task(":bootDistTar"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); File distribution = new File(this.gradleBuild.getProjectDir(), "build/distributions/custom-boot.tar"); assertThat(distribution).isFile(); String name = this.gradleBuild.getProjectDir().getName(); assertThat(tarEntryNames(distribution)).containsExactlyInAnyOrder("custom-boot/", "custom-boot/lib/", "custom-boot/lib/" + name + ".jar", "custom-boot/bin/", "custom-boot/bin/custom", "custom-boot/bin/custom.bat"); } @TestTemplate void scriptsHaveCorrectPermissions() throws IOException { BuildTask task = this.gradleBuild.build("bootDistTar").task(":bootDistTar"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); String name = this.gradleBuild.getProjectDir().getName(); File distribution = new File(this.gradleBuild.getProjectDir(), "build/distributions/" + name + "-boot.tar"); assertThat(distribution).isFile(); tarEntries(distribution, (entry) -> { int filePermissions = entry.getMode() & 0777; if (entry.isFile() && !entry.getName().startsWith(name + "-boot/bin/")) { assertThat(filePermissions).isEqualTo(0644); } else { assertThat(filePermissions).isEqualTo(0755); } }); } @TestTemplate void taskConfigurationIsAvoided() throws IOException { BuildResult result = this.gradleBuild.build("help"); String output = result.getOutput(); BufferedReader reader = new BufferedReader(new StringReader(output)); String line; Set configured = new HashSet<>(); while ((line = reader.readLine()) != null) { if (line.startsWith("Configuring :")) { configured.add(line.substring("Configuring :".length())); } } if (GradleVersion.version(this.gradleBuild.getGradleVersion()).compareTo(GradleVersion.version("7.3.3")) < 0) { assertThat(configured).containsExactly("help"); } else { assertThat(configured).containsExactlyInAnyOrder("help", "clean"); } } private List zipEntryNames(File distribution) throws IOException { List entryNames = new ArrayList<>(); try (ZipFile zipFile = new ZipFile(distribution)) { Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { entryNames.add(entries.nextElement().getName()); } } return entryNames; } private List tarEntryNames(File distribution) throws IOException { List entryNames = new ArrayList<>(); try (TarArchiveInputStream input = new TarArchiveInputStream(new FileInputStream(distribution))) { TarArchiveEntry entry; while ((entry = input.getNextEntry()) != null) { entryNames.add(entry.getName()); } } return entryNames; } private void tarEntries(File distribution, Consumer consumer) throws IOException { try (TarArchiveInputStream input = new TarArchiveInputStream(new FileInputStream(distribution))) { TarArchiveEntry entry; while ((entry = input.getNextEntry()) != null) { consumer.accept(entry); } } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/CyclonedxPluginActionIntegrationTests.java ================================================ /* * 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.gradle.plugin; import java.io.File; import java.io.IOException; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link CyclonedxPluginAction}. * * @author Andy Wilkinson */ @GradleCompatibility class CyclonedxPluginActionIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void sbomIsIncludedInUberJar() throws IOException { sbomIsIncludedInUberArchive("bootJar", ""); } @TestTemplate void sbomIsIncludedInUberWar() throws IOException { sbomIsIncludedInUberArchive("bootWar", "WEB-INF/classes/"); } private void sbomIsIncludedInUberArchive(String taskName, String sbomLocationPrefix) throws IOException { BuildResult result = this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("7.6.6").build(taskName); BuildTask task = result.task(":cyclonedxBom"); assertThat(task).isNotNull().extracting(BuildTask::getOutcome).isEqualTo(TaskOutcome.SUCCESS); File[] libs = new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles(); assertThat(libs).hasSize(1); try (JarFile jar = new JarFile(libs[0])) { assertThat(jar.getManifest().getMainAttributes().getValue("Sbom-Format")).isEqualTo("CycloneDX"); String sbomLocation = jar.getManifest().getMainAttributes().getValue("Sbom-Location"); assertThat(sbomLocation).isEqualTo(sbomLocationPrefix + "META-INF/sbom/application.cdx.json"); List entryNames = jar.stream().map(JarEntry::getName).toList(); assertThat(entryNames).contains(sbomLocation); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/DependencyManagementPluginActionIntegrationTests.java ================================================ /* * 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.gradle.plugin; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for the configuration applied by * {@link DependencyManagementPluginAction}. * * @author Andy Wilkinson */ @GradleCompatibility class DependencyManagementPluginActionIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void noDependencyManagementIsAppliedByDefault() { BuildTask task = this.gradleBuild.build("doesNotHaveDependencyManagement") .task(":doesNotHaveDependencyManagement"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate void bomIsImportedWhenDependencyManagementPluginIsApplied() { BuildTask task = this.gradleBuild.build("hasDependencyManagement", "-PapplyDependencyManagementPlugin") .task(":hasDependencyManagement"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java ================================================ /* * 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.gradle.plugin; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.StringReader; import java.util.HashSet; import java.util.Set; import java.util.jar.JarOutputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.gradle.util.GradleVersion; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link JavaPluginAction}. * * @author Andy Wilkinson */ @GradleCompatibility(configurationCache = true) class JavaPluginActionIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void noBootJarTaskWithoutJavaPluginApplied() { assertThat(this.gradleBuild.build("tasks").getOutput()).doesNotContain("bootJar"); } @TestTemplate void applyingJavaPluginCreatesBootJarTask() { assertThat(this.gradleBuild.build("tasks").getOutput()).contains("bootJar"); } @TestTemplate void noBootRunTaskWithoutJavaPluginApplied() { assertThat(this.gradleBuild.build("tasks").getOutput()).doesNotContain("bootRun"); } @TestTemplate void noBootTestRunTaskWithoutJavaPluginApplied() { assertThat(this.gradleBuild.build("tasks").getOutput()).doesNotContain("bootTestRun"); } @TestTemplate void applyingJavaPluginCreatesBootRunTask() { assertThat(this.gradleBuild.build("tasks").getOutput()).contains("bootRun"); } @TestTemplate void applyingJavaPluginCreatesBootTestRunTask() { assertThat(this.gradleBuild.build("tasks").getOutput()).contains("bootTestRun"); } @TestTemplate void javaCompileTasksUseUtf8Encoding() { assertThat(this.gradleBuild.build("build").getOutput()).contains("compileJava = UTF-8") .contains("compileTestJava = UTF-8"); } @TestTemplate void javaCompileTasksUseParametersCompilerFlagByDefault() { assertThat(this.gradleBuild.build("build").getOutput()).contains("compileJava compiler args: [-parameters]") .contains("compileTestJava compiler args: [-parameters]"); } @TestTemplate void javaCompileTasksUseParametersAndAdditionalCompilerFlags() { assertThat(this.gradleBuild.build("build").getOutput()) .contains("compileJava compiler args: [-parameters, -Xlint:all]") .contains("compileTestJava compiler args: [-parameters, -Xlint:all]"); } @TestTemplate void javaCompileTasksCanOverrideDefaultParametersCompilerFlag() { assertThat(this.gradleBuild.build("build").getOutput()).contains("compileJava compiler args: [-Xlint:all]") .contains("compileTestJava compiler args: [-Xlint:all]"); } @TestTemplate void assembleRunsBootJarAndJar() { BuildResult result = this.gradleBuild.build("assemble"); BuildTask bootJar = result.task(":bootJar"); assertThat(bootJar).isNotNull(); assertThat(bootJar.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); BuildTask jar = result.task(":jar"); assertThat(jar).isNotNull(); assertThat(jar.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs"); assertThat(buildLibs.listFiles()).containsExactlyInAnyOrder( new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".jar"), new File(buildLibs, this.gradleBuild.getProjectDir().getName() + "-plain.jar")); } @TestTemplate void errorMessageIsHelpfulWhenMainClassCannotBeResolved() { BuildResult result = this.gradleBuild.buildAndFail("build", "-PapplyJavaPlugin"); BuildTask task = result.task(":bootJar"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.FAILED); assertThat(result.getOutput()).contains("Main class name has not been configured and it could not be resolved"); } @TestTemplate void additionalMetadataLocationsConfiguredWhenProcessorIsPresent() throws IOException { createMinimalMainSource(); File libs = new File(this.gradleBuild.getProjectDir(), "libs"); libs.mkdirs(); new JarOutputStream(new FileOutputStream(new File(libs, "spring-boot-configuration-processor-1.2.3.jar"))) .close(); BuildResult result = this.gradleBuild.build("compileJava"); BuildTask task = result.task(":compileJava"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("compileJava compiler args: [-parameters, -Aorg.springframework.boot." + "configurationprocessor.additionalMetadataLocations=" + new File(this.gradleBuild.getProjectDir(), "src/main/resources").getCanonicalPath()); } @TestTemplate void additionalMetadataLocationsNotConfiguredWhenProcessorIsAbsent() throws IOException { createMinimalMainSource(); BuildResult result = this.gradleBuild.build("compileJava"); BuildTask task = result.task(":compileJava"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("compileJava compiler args: [-parameters]"); } @TestTemplate void applyingJavaPluginCreatesDevelopmentOnlyConfiguration() { assertThat(this.gradleBuild.build("help").getOutput()).contains("developmentOnly exists = true"); } @TestTemplate void applyingJavaPluginCreatesTestAndDevelopmentOnlyConfiguration() { assertThat(this.gradleBuild.build("help").getOutput()).contains("testAndDevelopmentOnly exists = true"); } @TestTemplate void testCompileClasspathIncludesTestAndDevelopmentOnlyDependencies() { assertThat(this.gradleBuild.build("help").getOutput()).contains("commons-lang3-3.12.0.jar"); } @TestTemplate void testRuntimeClasspathIncludesTestAndDevelopmentOnlyDependencies() { assertThat(this.gradleBuild.build("help").getOutput()).contains("commons-lang3-3.12.0.jar"); } @TestTemplate void testCompileClasspathDoesNotIncludeDevelopmentOnlyDependencies() { assertThat(this.gradleBuild.build("help").getOutput()).doesNotContain("commons-lang3-3.12.0.jar"); } @TestTemplate void testRuntimeClasspathDoesNotIncludeDevelopmentOnlyDependencies() { assertThat(this.gradleBuild.build("help").getOutput()).doesNotContain("commons-lang3-3.12.0.jar"); } @TestTemplate void compileClasspathDoesNotIncludeTestAndDevelopmentOnlyDependencies() { assertThat(this.gradleBuild.build("help").getOutput()).doesNotContain("commons-lang3-3.12.0.jar"); } @TestTemplate void runtimeClasspathIncludesTestAndDevelopmentOnlyDependencies() { assertThat(this.gradleBuild.build("help").getOutput()).contains("commons-lang3-3.12.0.jar"); } @TestTemplate void compileClasspathDoesNotIncludeDevelopmentOnlyDependencies() { assertThat(this.gradleBuild.build("help").getOutput()).doesNotContain("commons-lang3-3.12.0.jar"); } @TestTemplate void runtimeClasspathIncludesDevelopmentOnlyDependencies() { assertThat(this.gradleBuild.build("help").getOutput()).contains("commons-lang3-3.12.0.jar"); } @TestTemplate void productionRuntimeClasspathIsConfiguredWithAttributesThatMatchRuntimeClasspath() { String output = this.gradleBuild.build("build").getOutput(); Matcher matcher = Pattern.compile("runtimeClasspath: (\\[.*])").matcher(output); assertThat(matcher.find()).as("%s found in %s", matcher, output).isTrue(); String attributes = matcher.group(1); assertThat(output).contains("productionRuntimeClasspath: " + attributes); } @TestTemplate void productionRuntimeClasspathIsConfiguredWithResolvabilityAndConsumabilityThatMatchesRuntimeClasspath() { String output = this.gradleBuild.build("build").getOutput(); assertThat(output).contains("runtimeClasspath canBeResolved: true"); assertThat(output).contains("runtimeClasspath canBeConsumed: false"); assertThat(output).contains("productionRuntimeClasspath canBeResolved: true"); assertThat(output).contains("productionRuntimeClasspath canBeConsumed: false"); } @TestTemplate void taskConfigurationIsAvoided() throws IOException { BuildResult result = this.gradleBuild.build("help"); String output = result.getOutput(); BufferedReader reader = new BufferedReader(new StringReader(output)); String line; Set configured = new HashSet<>(); while ((line = reader.readLine()) != null) { if (line.startsWith("Configuring :")) { configured.add(line.substring("Configuring :".length())); } } if (!this.gradleBuild.isConfigurationCache() && GradleVersion.version(this.gradleBuild.getGradleVersion()) .compareTo(GradleVersion.version("7.3.3")) < 0) { assertThat(configured).containsExactly("help"); } else { assertThat(configured).containsExactlyInAnyOrder("help", "clean"); } } private void createMinimalMainSource() throws IOException { File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/com/example"); examplePackage.mkdirs(); new File(examplePackage, "Application.java").createNewFile(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java ================================================ /* * 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.gradle.plugin; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.util.HashSet; import java.util.Set; import org.gradle.testkit.runner.BuildResult; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledForJreRange; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.gradle.testkit.PluginClasspathGradleBuild; import org.springframework.boot.testsupport.BuildOutput; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import org.springframework.boot.testsupport.gradle.testkit.GradleBuildExtension; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link KotlinPluginAction}. * * @author Andy Wilkinson */ @DisabledForJreRange(min = JRE.JAVA_20) @ExtendWith(GradleBuildExtension.class) class KotlinPluginActionIntegrationTests { GradleBuild gradleBuild = new PluginClasspathGradleBuild(new BuildOutput(getClass())).kotlin(); @Test void noKotlinVersionPropertyWithoutKotlinPlugin() { assertThat(this.gradleBuild.build("kotlinVersion").getOutput()).contains("Kotlin version: none"); } @Test void kotlinVersionPropertyIsSet() { expectConfigurationCacheRequestedDeprecationWarning(); String output = this.gradleBuild.build("kotlinVersion", "dependencies", "--configuration", "compileClasspath") .getOutput(); assertThat(output).containsPattern("Kotlin version: [0-9]\\.[0-9]\\.[0-9]+"); } @Test void kotlinCompileTasksUseJavaParametersFlagByDefault() { expectConfigurationCacheRequestedDeprecationWarning(); assertThat(this.gradleBuild.build("kotlinCompileTasksJavaParameters").getOutput()) .contains("compileKotlin java parameters: true") .contains("compileTestKotlin java parameters: true"); } @Test void kotlinCompileTasksCanOverrideDefaultJavaParametersFlag() { expectConfigurationCacheRequestedDeprecationWarning(); assertThat(this.gradleBuild.build("kotlinCompileTasksJavaParameters").getOutput()) .contains("compileKotlin java parameters: false") .contains("compileTestKotlin java parameters: false"); } @Test void taskConfigurationIsAvoided() throws IOException { expectConfigurationCacheRequestedDeprecationWarning(); BuildResult result = this.gradleBuild.build("help"); String output = result.getOutput(); BufferedReader reader = new BufferedReader(new StringReader(output)); String line; Set configured = new HashSet<>(); while ((line = reader.readLine()) != null) { if (line.startsWith("Configuring :")) { configured.add(line.substring("Configuring :".length())); } } assertThat(configured).containsExactlyInAnyOrder("help", "clean"); } @Test void compileAotJavaHasTransitiveRuntimeDependenciesOnItsClasspathWhenUsingKotlin() { expectConfigurationCacheRequestedDeprecationWarning(); expectResolvableUsageIsAlreadyAllowedWarning(); String output = this.gradleBuild.build("compileAotJavaClasspath").getOutput(); assertThat(output).contains("org.jboss.logging" + File.separatorChar + "jboss-logging"); } @Test void compileAotTestJavaHasTransitiveRuntimeDependenciesOnItsClasspathWhenUsingKotlin() { expectConfigurationCacheRequestedDeprecationWarning(); expectResolvableUsageIsAlreadyAllowedWarning(); String output = this.gradleBuild.build("compileAotTestJavaClasspath").getOutput(); assertThat(output).contains("org.jboss.logging" + File.separatorChar + "jboss-logging"); } private void expectConfigurationCacheRequestedDeprecationWarning() { this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.14") .expectDeprecationMessages("The StartParameter.isConfigurationCacheRequested property has been deprecated"); } private void expectResolvableUsageIsAlreadyAllowedWarning() { this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.4") .expectDeprecationMessages("The resolvable usage is already allowed on configuration " + "':aotRuntimeClasspath'. This behavior has been deprecated."); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests.java ================================================ /* * 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.gradle.plugin; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link NativeImagePluginAction}. * * @author Andy Wilkinson * @author Scott Frederick */ @GradleCompatibility(minimumVersion = "8.3") class NativeImagePluginActionIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void applyingNativeImagePluginAppliesAotPlugin() { assertThat(this.gradleBuild.build("aotPluginApplied").getOutput()) .contains("org.springframework.boot.aot applied = true"); } @TestTemplate void reachabilityMetadataConfigurationFilesAreCopiedToJar() throws IOException { writeDummySpringApplicationAotProcessorMainClass(); BuildResult result = this.gradleBuild.build("bootJar"); BuildTask task = result.task(":bootJar"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs"); File jarFile = new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(buildLibs.listFiles()).contains(jarFile); assertThat(getEntryNames(jarFile)).contains( "META-INF/native-image/ch.qos.logback/logback-classic/1.2.11/reflect-config.json", "META-INF/native-image/org.jline/jline/3.21.0/jni-config.json", "META-INF/native-image/org.jline/jline/3.21.0/proxy-config.json", "META-INF/native-image/org.jline/jline/3.21.0/reflect-config.json", "META-INF/native-image/org.jline/jline/3.21.0/resource-config.json"); } @TestTemplate void reachabilityMetadataConfigurationFilesFromFileRepositoryAreCopiedToJar() throws IOException { writeDummySpringApplicationAotProcessorMainClass(); FileSystemUtils.copyRecursively(new File("src/test/resources/reachability-metadata-repository"), new File(this.gradleBuild.getProjectDir(), "reachability-metadata-repository")); BuildResult result = this.gradleBuild.build("bootJar"); BuildTask task = result.task(":bootJar"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs"); File jarFile = new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".jar"); assertThat(buildLibs.listFiles()).contains(jarFile); assertThat(getEntryNames(jarFile)).contains( "META-INF/native-image/ch.qos.logback/logback-classic/1.2.11/reflect-config.json", "META-INF/native-image/org.jline/jline/3.21.0/jni-config.json", "META-INF/native-image/org.jline/jline/3.21.0/proxy-config.json", "META-INF/native-image/org.jline/jline/3.21.0/reflect-config.json", "META-INF/native-image/org.jline/jline/3.21.0/resource-config.json"); } @TestTemplate void developmentOnlyDependenciesDoNotAppearInNativeImageClasspath() { writeDummySpringApplicationAotProcessorMainClass(); BuildResult result = this.gradleBuild.build("checkNativeImageClasspath"); assertThat(result.getOutput()).doesNotContain("commons-lang"); } @TestTemplate void testAndDevelopmentOnlyDependenciesDoNotAppearInNativeImageClasspath() { writeDummySpringApplicationAotProcessorMainClass(); BuildResult result = this.gradleBuild.build("checkNativeImageClasspath"); assertThat(result.getOutput()).doesNotContain("commons-lang"); } @TestTemplate void classesGeneratedDuringAotProcessingAreOnTheNativeImageClasspath() { BuildResult result = this.gradleBuild.build("checkNativeImageClasspath"); assertThat(result.getOutput()).contains(projectPath("build/classes/java/aot"), projectPath("build/resources/aot"), projectPath("build/generated/aotClasses")); } @TestTemplate void classesGeneratedDuringAotTestProcessingAreOnTheTestNativeImageClasspath() { BuildResult result = this.gradleBuild .scriptProperty("junitVersion", TestTemplate.class.getPackage().getImplementationVersion()) .build("checkTestNativeImageClasspath"); assertThat(result.getOutput()).contains(projectPath("build/classes/java/aotTest"), projectPath("build/resources/aotTest"), projectPath("build/generated/aotTestClasses")); } @TestTemplate void nativeEntryIsAddedToManifest() throws IOException { writeDummySpringApplicationAotProcessorMainClass(); BuildResult result = this.gradleBuild.build("bootJar"); BuildTask task = result.task(":bootJar"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs"); try (JarFile jarFile = new JarFile(new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".jar"))) { Manifest manifest = jarFile.getManifest(); assertThat(manifest.getMainAttributes().getValue("Spring-Boot-Native-Processed")).isEqualTo("true"); } } private String projectPath(String path) { try { return new File(this.gradleBuild.getProjectDir(), path).getCanonicalPath(); } catch (IOException ex) { throw new RuntimeException(ex); } } private void writeDummySpringApplicationAotProcessorMainClass() { File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/org/springframework/boot"); examplePackage.mkdirs(); File main = new File(examplePackage, "SpringApplicationAotProcessor.java"); try (PrintWriter writer = new PrintWriter(new FileWriter(main))) { writer.println("package org.springframework.boot;"); writer.println(); writer.println("import java.io.IOException;"); writer.println(); writer.println("public class SpringApplicationAotProcessor {"); writer.println(); writer.println(" public static void main(String[] args) {"); writer.println(" }"); writer.println(); writer.println("}"); } catch (IOException ex) { throw new RuntimeException(ex); } } protected List getEntryNames(File file) throws IOException { List entryNames = new ArrayList<>(); try (JarFile jarFile = new JarFile(file)) { Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { entryNames.add(entries.nextElement().getName()); } } return entryNames; } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/OnlyDependencyManagementIntegrationTests.java ================================================ /* * 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.gradle.plugin; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for configuring a project to only use Spring Boot's dependency * management. * * @author Andy Wilkinson */ @GradleCompatibility class OnlyDependencyManagementIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void dependencyManagementCanBeConfiguredUsingCoordinatesConstant() { assertThat(this.gradleBuild.build("dependencyManagement").getOutput()) .contains("org.springframework.boot:spring-boot-starter "); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/ProtobufPluginActionIntegrationTests.java ================================================ /* * 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.gradle.plugin; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link ProtobufPluginAction}. * * @author Andy Wilkinson */ @GradleCompatibility class ProtobufPluginActionIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void configuresProtocArtifact() { assertThat(this.gradleBuild.build("protocArtifact").getOutput()) .contains("protoc artifact: 'com.google.protobuf:protoc'"); } @TestTemplate void configuresGrpcPlugin() { assertThat(this.gradleBuild.build("grpcPlugin").getOutput()) .contains("grpc plugin artifact: 'io.grpc:protoc-gen-grpc-java'"); } @TestTemplate void configuresGenerateProtoTasksToOmitGenerated() { assertThat(this.gradleBuild.build("generateProtoTasksGrpcPluginOptions").getOutput()) .contains("generateProto: [[@generated=omit]]") .contains("generateTestProto: [[@generated=omit]]"); } @TestTemplate void alignsVersionOfProtocDependency() { assertThat(this.gradleBuild.build("dependencies", "--configuration", "protobufToolsLocator_protoc").getOutput()) .contains("com.google.protobuf:protoc:null -> 4.34.0"); } @TestTemplate void alignsVersionOfGrpcDependency() { assertThat(this.gradleBuild.build("dependencies", "--configuration", "protobufToolsLocator_grpc").getOutput()) .contains("io.grpc:protoc-gen-grpc-java:null -> 1.79.0"); } @TestTemplate void usesVersionOfProtocDependencyWhenSpecified() { assertThat(this.gradleBuild.build("dependencies", "--configuration", "protobufToolsLocator_protoc").getOutput()) .contains("com.google.protobuf:protoc:4.33.5"); } @TestTemplate void usesVersionOfGrpcPluginDependencyWhenSpecified() { assertThat(this.gradleBuild.build("dependencies", "--configuration", "protobufToolsLocator_grpc").getOutput()) .contains("io.grpc:protoc-gen-grpc-java:1.78.0"); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests.java ================================================ /* * 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.gradle.plugin; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.List; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.condition.EnabledOnJre; import org.junit.jupiter.api.condition.JRE; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; /** * Integration tests for {@link SpringBootAotPlugin}. * * @author Andy Wilkinson */ @GradleCompatibility class SpringBootAotPluginIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void noProcessAotTaskWithoutAotPluginApplied() { assertThat(this.gradleBuild.build("taskExists", "-PtaskName=processAot").getOutput()) .contains("processAot exists = false"); } @TestTemplate void noProcessTestAotTaskWithoutAotPluginApplied() { assertThat(this.gradleBuild.build("taskExists", "-PtaskName=processTestAot").getOutput()) .contains("processTestAot exists = false"); } @TestTemplate void applyingAotPluginCreatesProcessAotTask() { assertThat(this.gradleBuild.build("taskExists", "-PtaskName=processAot").getOutput()) .contains("processAot exists = true"); } @TestTemplate void applyingAotPluginCreatesProcessTestAotTask() { assertThat(this.gradleBuild.build("taskExists", "-PtaskName=processTestAot").getOutput()) .contains("processTestAot exists = true"); } @TestTemplate void processAotHasLibraryResourcesOnItsClasspath() throws IOException { File settings = new File(this.gradleBuild.getProjectDir(), "settings.gradle"); Files.write(settings.toPath(), List.of("include 'library'")); File library = new File(this.gradleBuild.getProjectDir(), "library"); library.mkdirs(); Files.write(library.toPath().resolve("build.gradle"), List.of("plugins {", " id 'java-library'", "}")); assertThat(this.gradleBuild.build("processAotClasspath").getOutput()).contains("library.jar"); } @TestTemplate void processTestAotHasLibraryResourcesOnItsClasspath() throws IOException { File settings = new File(this.gradleBuild.getProjectDir(), "settings.gradle"); Files.write(settings.toPath(), List.of("include 'library'")); File library = new File(this.gradleBuild.getProjectDir(), "library"); library.mkdirs(); Files.write(library.toPath().resolve("build.gradle"), List.of("plugins {", " id 'java-library'", "}")); assertThat(this.gradleBuild.build("processTestAotClasspath").getOutput()).contains("library.jar"); } @TestTemplate void processAotHasTransitiveRuntimeDependenciesOnItsClasspath() { String output = this.gradleBuild.build("processAotClasspath").getOutput(); assertThat(output).contains("org.jboss.logging" + File.separatorChar + "jboss-logging"); } @TestTemplate void processTestAotHasTransitiveRuntimeDependenciesOnItsClasspath() { String output = this.gradleBuild.build("processTestAotClasspath").getOutput(); assertThat(output).contains("org.jboss.logging" + File.separatorChar + "jboss-logging"); } @TestTemplate void processAotDoesNotHaveDevelopmentOnlyDependenciesOnItsClasspath() { String output = this.gradleBuild.build("processAotClasspath").getOutput(); assertThat(output).doesNotContain("commons-lang"); } @TestTemplate void processTestAotDoesNotHaveDevelopmentOnlyDependenciesOnItsClasspath() { String output = this.gradleBuild.build("processTestAotClasspath").getOutput(); assertThat(output).doesNotContain("commons-lang"); } @TestTemplate void processAotDoesNotHaveTestAndDevelopmentOnlyDependenciesOnItsClasspath() { String output = this.gradleBuild.build("processAotClasspath").getOutput(); assertThat(output).doesNotContain("commons-lang"); } @TestTemplate void processTestAotHasTestAndDevelopmentOnlyDependenciesOnItsClasspath() { String output = this.gradleBuild.build("processTestAotClasspath").getOutput(); assertThat(output).contains("commons-lang"); } @TestTemplate void processAotRunsWhenProjectHasMainSource() throws IOException { writeMainClass("org.springframework.boot", "SpringApplicationAotProcessor"); writeMainClass("com.example", "Main"); BuildTask task = this.gradleBuild.build("processAot").task(":processAot"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate void processTestAotIsSkippedWhenProjectHasNoTestSource() { BuildTask task = this.gradleBuild.build("processTestAot").task(":processTestAot"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.NO_SOURCE); } // gh-37343 @TestTemplate @EnabledOnJre(JRE.JAVA_17) void applyingAotPluginDoesNotPreventConfigurationOfJavaToolchainLanguageVersion() { assertThatNoException().isThrownBy(() -> this.gradleBuild.build("help").getOutput()); } private void writeMainClass(String packageName, String className) throws IOException { File java = new File(this.gradleBuild.getProjectDir(), "src/main/java/" + packageName.replace(".", "/") + "/" + className + ".java"); java.getParentFile().mkdirs(); Files.writeString(java.toPath(), """ package %s; public class %s { public static void main(String[] args) { } } """.formatted(packageName, className)); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootPluginIntegrationTests.java ================================================ /* * 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.gradle.plugin; import org.gradle.testkit.runner.BuildResult; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledForJreRange; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.gradle.testkit.PluginClasspathGradleBuild; import org.springframework.boot.testsupport.BuildOutput; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import org.springframework.boot.testsupport.gradle.testkit.GradleBuildExtension; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link SpringBootPlugin}. * * @author Andy Wilkinson */ @ExtendWith(GradleBuildExtension.class) class SpringBootPluginIntegrationTests { final GradleBuild gradleBuild = new PluginClasspathGradleBuild(new BuildOutput(getClass())); @Test @DisabledForJreRange(min = JRE.JAVA_24) void failFastWithVersionOfGradle8LowerThanRequired() { BuildResult result = this.gradleBuild.gradleVersion("8.13").buildAndFail(); assertThat(result.getOutput()).contains( "Spring Boot plugin requires Gradle 8.x (8.14 or later) or 9.x. The current version is Gradle 8.13"); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootPluginTests.java ================================================ /* * 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.gradle.plugin; import java.io.File; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.gradle.junit.GradleProjectBuilder; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link SpringBootPlugin}. * * @author Martin Chalupa * @author Andy Wilkinson */ @ClassPathExclusions("kotlin-daemon-client-*.jar") class SpringBootPluginTests { @TempDir @SuppressWarnings("NullAway.Init") File temp; @Test void bootArchivesConfigurationsCannotBeResolved() { Project project = GradleProjectBuilder.builder().withProjectDir(this.temp).build(); project.getPlugins().apply(SpringBootPlugin.class); Configuration bootArchives = project.getConfigurations() .getByName(SpringBootPlugin.BOOT_ARCHIVES_CONFIGURATION_NAME); assertThat(bootArchives.isCanBeResolved()).isFalse(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/WarPluginActionIntegrationTests.java ================================================ /* * 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.gradle.plugin; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.gradle.util.GradleVersion; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link JavaPluginAction}. * * @author Andy Wilkinson */ @GradleCompatibility class WarPluginActionIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void noBootWarTaskWithoutWarPluginApplied() { assertThat(this.gradleBuild.build("taskExists", "-PtaskName=bootWar").getOutput()) .contains("bootWar exists = false"); } @TestTemplate void applyingWarPluginCreatesBootWarTask() { assertThat(this.gradleBuild.build("taskExists", "-PtaskName=bootWar", "-PapplyWarPlugin").getOutput()) .contains("bootWar exists = true"); } @TestTemplate void assembleRunsBootWarAndWar() { BuildResult result = this.gradleBuild.build("assemble"); BuildTask bootWar = result.task(":bootWar"); assertThat(bootWar).isNotNull(); assertThat(bootWar.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); BuildTask war = result.task(":war"); assertThat(war).isNotNull(); assertThat(war.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs"); List expected = new ArrayList<>(); expected.add(new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".war")); expected.add(new File(buildLibs, this.gradleBuild.getProjectDir().getName() + "-plain.war")); if (this.gradleBuild.gradleVersionIsAtLeast("9.0-milestone-2")) { expected.add(new File(buildLibs, this.gradleBuild.getProjectDir().getName() + "-plain.jar")); } assertThat(buildLibs.listFiles()).containsExactlyInAnyOrderElementsOf(expected); } @TestTemplate void errorMessageIsHelpfulWhenMainClassCannotBeResolved() { BuildResult result = this.gradleBuild.buildAndFail("build", "-PapplyWarPlugin"); BuildTask task = result.task(":bootWar"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.FAILED); assertThat(result.getOutput()).contains("Main class name has not been configured and it could not be resolved"); } @TestTemplate void taskConfigurationIsAvoided() throws IOException { BuildResult result = this.gradleBuild.build("help"); String output = result.getOutput(); BufferedReader reader = new BufferedReader(new StringReader(output)); String line; Set configured = new HashSet<>(); while ((line = reader.readLine()) != null) { if (line.startsWith("Configuring :")) { configured.add(line.substring("Configuring :".length())); } } if (GradleVersion.version(this.gradleBuild.getGradleVersion()).compareTo(GradleVersion.version("7.3.3")) < 0) { assertThat(configured).containsExactly("help"); } else { assertThat(configured).containsExactlyInAnyOrder("help", "clean"); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests.java ================================================ /* * 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.gradle.tasks.buildinfo; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.time.Instant; import java.util.Properties; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.FileUtils; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for the {@link BuildInfo} task. * * @author Andy Wilkinson * @author Vedran Pavic */ @GradleCompatibility(configurationCache = true) class BuildInfoIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void defaultValues() { BuildTask task = this.gradleBuild.build("buildInfo").task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Properties buildInfoProperties = buildInfoProperties(); assertThat(buildInfoProperties).containsKey("build.time"); assertThat(buildInfoProperties).doesNotContainKey("build.artifact"); assertThat(buildInfoProperties).doesNotContainKey("build.group"); assertThat(buildInfoProperties).containsEntry("build.name", this.gradleBuild.getProjectDir().getName()); assertThat(buildInfoProperties).containsEntry("build.version", "unspecified"); } @TestTemplate void basicExecution() { BuildTask task = this.gradleBuild.build("buildInfo").task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Properties buildInfoProperties = buildInfoProperties(); assertThat(buildInfoProperties).containsKey("build.time"); assertThat(buildInfoProperties).containsEntry("build.artifact", "foo"); assertThat(buildInfoProperties).containsEntry("build.group", "foo"); assertThat(buildInfoProperties).containsEntry("build.additional", "foo"); assertThat(buildInfoProperties).containsEntry("build.name", "foo"); assertThat(buildInfoProperties).containsEntry("build.version", "0.1.0"); } @TestTemplate void notUpToDateWhenExecutedTwiceAsTimeChanges() { BuildTask task = this.gradleBuild.build("buildInfo").task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Properties first = buildInfoProperties(); String firstBuildTime = first.getProperty("build.time"); assertThat(firstBuildTime).isNotNull(); task = this.gradleBuild.build("buildInfo").task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Properties second = buildInfoProperties(); String secondBuildTime = second.getProperty("build.time"); assertThat(secondBuildTime).isNotNull(); assertThat(Instant.parse(firstBuildTime)).isBefore(Instant.parse(secondBuildTime)); } @TestTemplate void upToDateWhenExecutedTwiceWithFixedTime() { BuildTask task = this.gradleBuild.build("buildInfo", "-PnullTime").task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); task = this.gradleBuild.build("buildInfo", "-PnullTime").task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE); } @TestTemplate void notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion() { BuildTask task = this.gradleBuild.scriptProperty("projectVersion", "0.1.0") .build("buildInfo") .task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); task = this.gradleBuild.scriptProperty("projectVersion", "0.2.0").build("buildInfo").task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate void notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedGradlePropertiesProjectVersion() throws IOException { Path gradleProperties = new File(this.gradleBuild.getProjectDir(), "gradle.properties").toPath(); Files.writeString(gradleProperties, "version=0.1.0", StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); BuildTask task = this.gradleBuild.build("buildInfo").task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Files.writeString(gradleProperties, "version=0.2.0", StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); task = this.gradleBuild.build("buildInfo").task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate void reproducibleOutputWithFixedTime() throws IOException, InterruptedException { BuildTask task = this.gradleBuild.build("buildInfo", "-PnullTime").task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); File buildInfoProperties = new File(this.gradleBuild.getProjectDir(), "build/buildInfo/META-INF/build-info.properties"); String firstHash = FileUtils.sha1Hash(buildInfoProperties); assertThat(buildInfoProperties.delete()).isTrue(); Thread.sleep(1500); task = this.gradleBuild.build("buildInfo", "-PnullTime").task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); String secondHash = FileUtils.sha1Hash(buildInfoProperties); assertThat(firstHash).isEqualTo(secondHash); } @TestTemplate void excludeProperties() { BuildTask task = this.gradleBuild.build("buildInfo").task(":buildInfo"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Properties buildInfoProperties = buildInfoProperties(); assertThat(buildInfoProperties).doesNotContainKey("build.group"); assertThat(buildInfoProperties).doesNotContainKey("build.artifact"); assertThat(buildInfoProperties).doesNotContainKey("build.version"); assertThat(buildInfoProperties).doesNotContainKey("build.name"); } private Properties buildInfoProperties() { File file = new File(this.gradleBuild.getProjectDir(), "build/buildInfo/META-INF/build-info.properties"); assertThat(file).isFile(); Properties properties = new Properties(); try (FileReader reader = new FileReader(file)) { properties.load(reader); return properties; } catch (IOException ex) { throw new RuntimeException(ex); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoTests.java ================================================ /* * 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.gradle.tasks.buildinfo; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Properties; import org.gradle.api.Project; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.gradle.junit.GradleProjectBuilder; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatException; /** * Tests for {@link BuildInfo}. * * @author Andy Wilkinson * @author Vedran Pavic */ @ClassPathExclusions("kotlin-daemon-client-*") class BuildInfoTests { @TempDir @SuppressWarnings("NullAway.Init") File temp; @Test void basicExecution() { Properties properties = buildInfoProperties(createTask(createProject("test"))); assertThat(properties).containsKey("build.time"); assertThat(properties).doesNotContainKey("build.artifact"); assertThat(properties).doesNotContainKey("build.group"); assertThat(properties).containsEntry("build.name", "test"); assertThat(properties).containsEntry("build.version", "unspecified"); } @Test void customArtifactIsReflectedInProperties() { BuildInfo task = createTask(createProject("test")); task.getProperties().getArtifact().set("custom"); assertThat(buildInfoProperties(task)).containsEntry("build.artifact", "custom"); } @Test void artifactCanBeExcludedFromProperties() { BuildInfo task = createTask(createProject("test")); task.getExcludes().addAll("artifact"); assertThat(buildInfoProperties(task)).doesNotContainKey("build.artifact"); } @Test void projectGroupIsReflectedInProperties() { BuildInfo task = createTask(createProject("test")); task.getProject().setGroup("com.example"); assertThat(buildInfoProperties(task)).containsEntry("build.group", "com.example"); } @Test void customGroupIsReflectedInProperties() { BuildInfo task = createTask(createProject("test")); task.getProperties().getGroup().set("com.example"); assertThat(buildInfoProperties(task)).containsEntry("build.group", "com.example"); } @Test void groupCanBeExcludedFromProperties() { BuildInfo task = createTask(createProject("test")); task.getExcludes().add("group"); assertThat(buildInfoProperties(task)).doesNotContainKey("build.group"); } @Test void customNameIsReflectedInProperties() { BuildInfo task = createTask(createProject("test")); task.getProperties().getName().set("Example"); assertThat(buildInfoProperties(task)).containsEntry("build.name", "Example"); } @Test void nameCanBeExcludedFromProperties() { BuildInfo task = createTask(createProject("test")); task.getExcludes().add("name"); assertThat(buildInfoProperties(task)).doesNotContainKey("build.name"); } @Test void projectVersionIsReflectedInProperties() { BuildInfo task = createTask(createProject("test")); task.getProject().setVersion("1.2.3"); assertThat(buildInfoProperties(task)).containsEntry("build.version", "1.2.3"); } @Test void customVersionIsReflectedInProperties() { BuildInfo task = createTask(createProject("test")); task.getProperties().getVersion().set("2.3.4"); assertThat(buildInfoProperties(task)).containsEntry("build.version", "2.3.4"); } @Test void versionCanBeExcludedFromProperties() { BuildInfo task = createTask(createProject("test")); task.getExcludes().add("version"); assertThat(buildInfoProperties(task)).doesNotContainKey("build.version"); } @Test void timeIsSetInProperties() { BuildInfo task = createTask(createProject("test")); assertThat(buildInfoProperties(task)).containsKey("build.time"); } @Test void timeCanBeExcludedFromProperties() { BuildInfo task = createTask(createProject("test")); task.getExcludes().add("time"); assertThat(buildInfoProperties(task)).doesNotContainKey("build.time"); } @Test void timeCanBeCustomizedInProperties() { BuildInfo task = createTask(createProject("test")); String isoTime = DateTimeFormatter.ISO_INSTANT.format(Instant.now()); task.getProperties().getTime().set(isoTime); assertThat(buildInfoProperties(task)).containsEntry("build.time", isoTime); } @Test void additionalPropertiesAreReflectedInProperties() { BuildInfo task = createTask(createProject("test")); task.getProperties().getAdditional().put("a", "alpha"); task.getProperties().getAdditional().put("b", "bravo"); assertThat(buildInfoProperties(task)).containsEntry("build.a", "alpha").containsEntry("build.b", "bravo"); } @Test void additionalPropertiesCanBeExcluded() { BuildInfo task = createTask(createProject("test")); task.getProperties().getAdditional().put("a", "alpha"); task.getExcludes().add("b"); assertThat(buildInfoProperties(task)).containsEntry("build.a", "alpha").doesNotContainKey("b"); } @Test @SuppressWarnings("NullAway") // Test null check void nullAdditionalPropertyProducesInformativeFailure() { BuildInfo task = createTask(createProject("test")); assertThatException().isThrownBy(() -> task.getProperties().getAdditional().put("a", null)) .withMessage("Cannot add an entry with a null value to a property of type Map."); } @Test void filenameCanBeCustomized() { Project project = createProject("test"); BuildInfo task = createTask(project); task.getFilename().set("custom-location-build-info.properties"); task.generateBuildProperties(); assertThat(new File(project.getLayout().getBuildDirectory().dir("testBuildInfo").get().getAsFile(), "custom-location-build-info.properties")) .exists(); } private Project createProject(String projectName) { File projectDir = new File(this.temp, projectName); return GradleProjectBuilder.builder().withProjectDir(projectDir).withName(projectName).build(); } private BuildInfo createTask(Project project) { return project.getTasks().register("testBuildInfo", BuildInfo.class).get(); } private Properties buildInfoProperties(BuildInfo task) { task.generateBuildProperties(); return buildInfoProperties( new File(task.getDestinationDir().get().getAsFile(), "META-INF/build-info.properties")); } private Properties buildInfoProperties(File file) { assertThat(file).isFile(); Properties properties = new Properties(); try (FileReader reader = new FileReader(file)) { properties.load(reader); return properties; } catch (IOException ex) { throw new RuntimeException(ex); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringReader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.function.Consumer; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.stream.Stream; import java.util.zip.ZipEntry; import org.apache.commons.compress.archivers.zip.UnixStat; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.loader.tools.JarModeLibrary; import org.springframework.boot.testsupport.FileUtils; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import org.springframework.util.FileSystemUtils; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link BootJar} and {@link BootWar}. * * @author Andy Wilkinson * @author Madhura Bhave * @author Scott Frederick * @author Moritz Halbritter */ abstract class AbstractBootArchiveIntegrationTests { private final String taskName; private final String libPath; private final String classesPath; private final String indexPath; @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; protected AbstractBootArchiveIntegrationTests(String taskName, String libPath, String classesPath, String indexPath) { this.taskName = taskName; this.libPath = libPath; this.classesPath = classesPath; this.indexPath = indexPath; } @TestTemplate void basicBuild() { BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate void reproducibleArchive() throws IOException, InterruptedException { BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); File jar = new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0]; String firstHash = FileUtils.sha1Hash(jar); Thread.sleep(1500); task = this.gradleBuild.build("clean", this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); String secondHash = FileUtils.sha1Hash(jar); assertThat(firstHash).isEqualTo(secondHash); } @TestTemplate void upToDateWhenBuiltTwice() { BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE); } @TestTemplate void applicationPluginMainClassNameIsUsed() throws IOException { BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Start-Class")) .isEqualTo("com.example.CustomMain"); } } @TestTemplate void springBootExtensionMainClassNameIsUsed() throws IOException { BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Start-Class")) .isEqualTo("com.example.CustomMain"); } } @TestTemplate void duplicatesAreHandledGracefully() { BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate void developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault() throws IOException { File srcMainResources = new File(this.gradleBuild.getProjectDir(), "src/main/resources"); srcMainResources.mkdirs(); new File(srcMainResources, "resource").createNewFile(); BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { Stream libEntryNames = jarFile.stream() .filter((entry) -> !entry.isDirectory()) .map(JarEntry::getName) .filter((name) -> name.startsWith(this.libPath)); assertThat(libEntryNames).containsExactly(this.libPath + "commons-io-2.6.jar"); Stream classesEntryNames = jarFile.stream() .filter((entry) -> !entry.isDirectory()) .map(JarEntry::getName) .filter((name) -> name.startsWith(this.classesPath)); assertThat(classesEntryNames).containsExactly(this.classesPath + "resource"); } } @TestTemplate void developmentOnlyDependenciesCanBeIncludedInTheArchive() throws IOException { BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { Stream libEntryNames = jarFile.stream() .filter((entry) -> !entry.isDirectory()) .map(JarEntry::getName) .filter((name) -> name.startsWith(this.libPath)); assertThat(libEntryNames).containsExactly(this.libPath + "commons-io-2.6.jar", this.libPath + "commons-lang3-3.9.jar"); } } @TestTemplate void versionMismatchBetweenTransitiveDevelopmentOnlyImplementationDependenciesDoesNotRemoveDependencyFromTheArchive() throws IOException { BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { Stream libEntryNames = jarFile.stream() .filter((entry) -> !entry.isDirectory()) .map(JarEntry::getName) .filter((name) -> name.startsWith(this.libPath)); assertThat(libEntryNames).containsExactly(this.libPath + "two-1.0.jar", this.libPath + "commons-io-2.19.0.jar"); } } @TestTemplate void testAndDevelopmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault() throws IOException { File srcMainResources = new File(this.gradleBuild.getProjectDir(), "src/main/resources"); srcMainResources.mkdirs(); new File(srcMainResources, "resource").createNewFile(); BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { Stream libEntryNames = jarFile.stream() .filter((entry) -> !entry.isDirectory()) .map(JarEntry::getName) .filter((name) -> name.startsWith(this.libPath)); assertThat(libEntryNames).containsExactly(this.libPath + "commons-io-2.6.jar"); Stream classesEntryNames = jarFile.stream() .filter((entry) -> !entry.isDirectory()) .map(JarEntry::getName) .filter((name) -> name.startsWith(this.classesPath)); assertThat(classesEntryNames).containsExactly(this.classesPath + "resource"); } } @TestTemplate void testAndDevelopmentOnlyDependenciesCanBeIncludedInTheArchive() throws IOException { BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { Stream libEntryNames = jarFile.stream() .filter((entry) -> !entry.isDirectory()) .map(JarEntry::getName) .filter((name) -> name.startsWith(this.libPath)); assertThat(libEntryNames).containsExactly(this.libPath + "commons-io-2.6.jar", this.libPath + "commons-lang3-3.9.jar"); } } @TestTemplate void jarTypeFilteringIsApplied() throws IOException { File flatDirRepository = new File(this.gradleBuild.getProjectDir(), "repository"); createDependenciesStarterJar(new File(flatDirRepository, "starter.jar")); createDependenciesDeveloperToolsJar(new File(flatDirRepository, "devonly.jar")); createStandardJar(new File(flatDirRepository, "standard.jar")); BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { Stream libEntryNames = jarFile.stream() .filter((entry) -> !entry.isDirectory()) .map(JarEntry::getName) .filter((name) -> name.startsWith(this.libPath)); assertThat(libEntryNames).containsExactly(this.libPath + "standard.jar"); } } @TestTemplate void startClassIsSetByResolvingTheMainClass() throws IOException { copyMainClassApplication(); BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { Attributes mainAttributes = jarFile.getManifest().getMainAttributes(); assertThat(mainAttributes.getValue("Start-Class")) .isEqualTo("com.example." + this.taskName.toLowerCase(Locale.ENGLISH) + ".main.CustomMainClass"); } task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE); } @TestTemplate void upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered() { BuildTask task = this.gradleBuild.scriptProperty("layered", "") .build("" + this.taskName) .task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); task = this.gradleBuild.scriptProperty("layered", "layered {}") .build("" + this.taskName) .task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE); } @TestTemplate void notUpToDateWhenBuiltWithoutLayersAndThenWithLayers() { BuildTask task = this.gradleBuild.scriptProperty("layerEnablement", "enabled = false") .build(this.taskName) .task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); task = this.gradleBuild.scriptProperty("layerEnablement", "enabled = true") .build(this.taskName) .task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate void notUpToDateWhenBuiltWithToolsAndThenWithoutTools() { BuildTask task = this.gradleBuild.scriptProperty("includeTools", "") .build(this.taskName) .task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); task = this.gradleBuild.scriptProperty("includeTools", "includeTools = false") .build(this.taskName) .task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate void layersWithCustomSourceSet() { BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate void implicitLayers() throws IOException { writeMainClass(); writeResource(); BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Map> indexedLayers; String toolsJar = this.libPath + JarModeLibrary.TOOLS.getName(); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { assertThat(jarFile.getEntry(toolsJar)).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "commons-lang3-3.9.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "spring-core-5.2.5.RELEASE.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "spring-jcl-5.2.5.RELEASE.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "library-1.0-SNAPSHOT.jar")).isNotNull(); assertThat(jarFile.getEntry(this.classesPath + "example/Main.class")).isNotNull(); assertThat(jarFile.getEntry(this.classesPath + "static/file.txt")).isNotNull(); indexedLayers = readLayerIndex(jarFile); } List layerNames = Arrays.asList("dependencies", "spring-boot-loader", "snapshot-dependencies", "application"); assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames); Set expectedDependencies = new TreeSet<>(); expectedDependencies.add(this.libPath + "commons-lang3-3.9.jar"); expectedDependencies.add(this.libPath + "spring-core-5.2.5.RELEASE.jar"); expectedDependencies.add(this.libPath + "spring-jcl-5.2.5.RELEASE.jar"); expectedDependencies.add(this.libPath + "jul-to-slf4j-1.7.28.jar"); expectedDependencies.add(this.libPath + "log4j-api-2.12.1.jar"); expectedDependencies.add(this.libPath + "log4j-to-slf4j-2.12.1.jar"); expectedDependencies.add(this.libPath + "logback-classic-1.2.3.jar"); expectedDependencies.add(this.libPath + "logback-core-1.2.3.jar"); expectedDependencies.add(this.libPath + "slf4j-api-1.7.28.jar"); expectedDependencies.add(this.libPath + "spring-boot-starter-logging-2.2.0.RELEASE.jar"); Set expectedSnapshotDependencies = new TreeSet<>(); expectedSnapshotDependencies.add(this.libPath + "library-1.0-SNAPSHOT.jar"); (toolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(toolsJar); assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies); assertThat(indexedLayers.get("spring-boot-loader")).containsExactly("org/"); assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies); assertThat(indexedLayers.get("application")) .containsExactly(getExpectedApplicationLayerContents(this.classesPath)); BuildResult listLayers = this.gradleBuild.build("listLayers"); task = listLayers.task(":listLayers"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); String listLayersOutput = listLayers.getOutput(); assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames); BuildResult extractLayers = this.gradleBuild.build("extractLayers"); task = extractLayers.task(":extractLayers"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertExtractedLayers(layerNames, indexedLayers); } abstract String[] getExpectedApplicationLayerContents(String... additionalFiles); @TestTemplate void multiModuleImplicitLayers() throws IOException { writeSettingsGradle(); writeMainClass(); writeResource(); BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Map> indexedLayers; String toolsJar = this.libPath + JarModeLibrary.TOOLS.getName(); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { assertThat(jarFile.getEntry(toolsJar)).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "alpha-1.2.3.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "bravo-1.2.3.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "charlie-1.2.3.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "commons-lang3-3.9.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "spring-core-5.2.5.RELEASE.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "spring-jcl-5.2.5.RELEASE.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "library-1.0-SNAPSHOT.jar")).isNotNull(); assertThat(jarFile.getEntry(this.classesPath + "example/Main.class")).isNotNull(); assertThat(jarFile.getEntry(this.classesPath + "static/file.txt")).isNotNull(); indexedLayers = readLayerIndex(jarFile); } List layerNames = Arrays.asList("dependencies", "spring-boot-loader", "snapshot-dependencies", "application"); assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames); Set expectedDependencies = new TreeSet<>(); expectedDependencies.add(this.libPath + "commons-lang3-3.9.jar"); expectedDependencies.add(this.libPath + "spring-core-5.2.5.RELEASE.jar"); expectedDependencies.add(this.libPath + "spring-jcl-5.2.5.RELEASE.jar"); Set expectedSnapshotDependencies = new TreeSet<>(); expectedSnapshotDependencies.add(this.libPath + "library-1.0-SNAPSHOT.jar"); (toolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(toolsJar); assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies); assertThat(indexedLayers.get("spring-boot-loader")).containsExactly("org/"); assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies); assertThat(indexedLayers.get("application")) .containsExactly(getExpectedApplicationLayerContents(this.classesPath, this.libPath + "alpha-1.2.3.jar", this.libPath + "bravo-1.2.3.jar", this.libPath + "charlie-1.2.3.jar")); BuildResult listLayers = this.gradleBuild.build("listLayers"); task = listLayers.task(":listLayers"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); String listLayersOutput = listLayers.getOutput(); assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames); BuildResult extractLayers = this.gradleBuild.build("extractLayers"); task = extractLayers.task(":extractLayers"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertExtractedLayers(layerNames, indexedLayers); } @TestTemplate void customLayers() throws IOException { writeMainClass(); writeResource(); BuildResult build = this.gradleBuild.build(this.taskName); BuildTask task = build.task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Map> indexedLayers; String toolsJar = this.libPath + JarModeLibrary.TOOLS.getName(); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { assertThat(jarFile.getEntry(toolsJar)).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "commons-lang3-3.9.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "spring-core-5.2.5.RELEASE.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "spring-jcl-5.2.5.RELEASE.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "library-1.0-SNAPSHOT.jar")).isNotNull(); assertThat(jarFile.getEntry(this.classesPath + "example/Main.class")).isNotNull(); assertThat(jarFile.getEntry(this.classesPath + "static/file.txt")).isNotNull(); assertThat(jarFile.getEntry(this.indexPath + "layers.idx")).isNotNull(); indexedLayers = readLayerIndex(jarFile); } List layerNames = Arrays.asList("dependencies", "commons-dependencies", "snapshot-dependencies", "static", "app"); assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames); Set expectedDependencies = new TreeSet<>(); expectedDependencies.add(this.libPath + "spring-core-5.2.5.RELEASE.jar"); expectedDependencies.add(this.libPath + "spring-jcl-5.2.5.RELEASE.jar"); List expectedSnapshotDependencies = new ArrayList<>(); expectedSnapshotDependencies.add(this.libPath + "library-1.0-SNAPSHOT.jar"); (toolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(toolsJar); assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies); assertThat(indexedLayers.get("commons-dependencies")).containsExactly(this.libPath + "commons-lang3-3.9.jar"); assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies); assertThat(indexedLayers.get("static")).containsExactly(this.classesPath + "static/"); List appLayer = new ArrayList<>(indexedLayers.get("app")); String[] appLayerContents = getExpectedApplicationLayerContents(this.classesPath + "example/"); assertThat(appLayer).containsSubsequence(appLayerContents); appLayer.removeAll(Arrays.asList(appLayerContents)); assertThat(appLayer).containsExactly("org/"); BuildResult listLayers = this.gradleBuild.build("listLayers"); task = listLayers.task(":listLayers"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); String listLayersOutput = listLayers.getOutput(); assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames); BuildResult extractLayers = this.gradleBuild.build("extractLayers"); task = extractLayers.task(":extractLayers"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertExtractedLayers(layerNames, indexedLayers); } @TestTemplate void multiModuleCustomLayers() throws IOException { writeSettingsGradle(); writeMainClass(); writeResource(); BuildResult build = this.gradleBuild.build(this.taskName); BuildTask task = build.task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); Map> indexedLayers; String toolsJar = this.libPath + JarModeLibrary.TOOLS.getName(); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { assertThat(jarFile.getEntry(toolsJar)).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "alpha-1.2.3.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "bravo-1.2.3.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "charlie-1.2.3.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "commons-lang3-3.9.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "spring-core-5.2.5.RELEASE.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "spring-jcl-5.2.5.RELEASE.jar")).isNotNull(); assertThat(jarFile.getEntry(this.libPath + "library-1.0-SNAPSHOT.jar")).isNotNull(); assertThat(jarFile.getEntry(this.classesPath + "example/Main.class")).isNotNull(); assertThat(jarFile.getEntry(this.classesPath + "static/file.txt")).isNotNull(); assertThat(jarFile.getEntry(this.indexPath + "layers.idx")).isNotNull(); indexedLayers = readLayerIndex(jarFile); } List layerNames = Arrays.asList("dependencies", "commons-dependencies", "snapshot-dependencies", "subproject-dependencies", "static", "app"); assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames); Set expectedSubprojectDependencies = new TreeSet<>(); expectedSubprojectDependencies.add(this.libPath + "alpha-1.2.3.jar"); expectedSubprojectDependencies.add(this.libPath + "bravo-1.2.3.jar"); expectedSubprojectDependencies.add(this.libPath + "charlie-1.2.3.jar"); Set expectedDependencies = new TreeSet<>(); expectedDependencies.add(this.libPath + "spring-core-5.2.5.RELEASE.jar"); expectedDependencies.add(this.libPath + "spring-jcl-5.2.5.RELEASE.jar"); List expectedSnapshotDependencies = new ArrayList<>(); expectedSnapshotDependencies.add(this.libPath + "library-1.0-SNAPSHOT.jar"); (toolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(toolsJar); assertThat(indexedLayers.get("subproject-dependencies")) .containsExactlyElementsOf(expectedSubprojectDependencies); assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies); assertThat(indexedLayers.get("commons-dependencies")).containsExactly(this.libPath + "commons-lang3-3.9.jar"); assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies); assertThat(indexedLayers.get("static")).containsExactly(this.classesPath + "static/"); List appLayer = new ArrayList<>(indexedLayers.get("app")); String[] appLayerContents = getExpectedApplicationLayerContents(this.classesPath + "example/"); assertThat(appLayer).containsSubsequence(appLayerContents); appLayer.removeAll(Arrays.asList(appLayerContents)); assertThat(appLayer).containsExactly("org/"); BuildResult listLayers = this.gradleBuild.build("listLayers"); task = listLayers.task(":listLayers"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); String listLayersOutput = listLayers.getOutput(); assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames); BuildResult extractLayers = this.gradleBuild.build("extractLayers"); task = extractLayers.task(":extractLayers"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertExtractedLayers(layerNames, indexedLayers); } @TestTemplate void classesFromASecondarySourceSetCanBeIncludedInTheArchive() throws IOException { writeMainClass(); File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/secondary/java/example"); examplePackage.mkdirs(); File main = new File(examplePackage, "Secondary.java"); try (PrintWriter writer = new PrintWriter(new FileWriter(main))) { writer.println("package example;"); writer.println(); writer.println("public class Secondary {}"); } catch (IOException ex) { throw new RuntimeException(ex); } BuildResult build = this.gradleBuild.build(this.taskName); BuildTask task = build.task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { Stream classesEntryNames = jarFile.stream() .filter((entry) -> !entry.isDirectory()) .map(JarEntry::getName) .filter((name) -> name.startsWith(this.classesPath)); assertThat(classesEntryNames).containsExactly(this.classesPath + "example/Main.class", this.classesPath + "example/Secondary.class"); } } @TestTemplate void javaVersionIsSetInManifest() throws IOException { BuildResult result = this.gradleBuild.build(this.taskName); BuildTask task = result.task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Build-Jdk-Spec")).isNotEmpty(); } } @TestTemplate void defaultDirAndFileModesAreUsed() throws IOException { BuildResult result = this.gradleBuild.build(this.taskName); BuildTask task = result.task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (ZipFile jarFile = ZipFile.builder() .setFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0]) .get()) { Enumeration entries = jarFile.getEntries(); while (entries.hasMoreElements()) { ZipArchiveEntry entry = entries.nextElement(); if (entry.getName().startsWith("META-INF/")) { continue; } if (entry.isDirectory()) { assertEntryMode(entry, UnixStat.DEFAULT_DIR_PERM); } else { assertEntryMode(entry, UnixStat.DEFAULT_FILE_PERM); } } } } @TestTemplate void dirModeAndFileModeAreApplied() throws IOException { Assumptions.assumeTrue(this.gradleBuild.gradleVersionIsLessThan("9.0-milestone-1")); BuildResult result = this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.8-rc-1") .expectDeprecationMessages("The CopyProcessingSpec.setDirMode(Integer) method has been deprecated", "The CopyProcessingSpec.setFileMode(Integer) method has been deprecated", "upgrading_version_8.html#unix_file_permissions_deprecated") .build(this.taskName); BuildTask task = result.task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); try (ZipFile jarFile = ZipFile.builder() .setFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0]) .get()) { Enumeration entries = jarFile.getEntries(); while (entries.hasMoreElements()) { ZipArchiveEntry entry = entries.nextElement(); if (entry.getName().startsWith("META-INF/")) { continue; } if (entry.isDirectory()) { assertEntryMode(entry, 0500); } else { assertEntryMode(entry, 0400); } } } } @TestTemplate void signed() throws IOException { BuildTask task = this.gradleBuild.build(this.taskName).task(":" + this.taskName); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); File jar = new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0]; try (JarFile jarFile = new JarFile(jar)) { assertThat(jarFile.getEntry("META-INF/BOOT.SF")).isNotNull(); } } private void copyMainClassApplication() throws IOException { copyApplication("main"); } protected void copyApplication(String name) throws IOException { File output = new File(this.gradleBuild.getProjectDir(), "src/main/java/com/example/" + this.taskName.toLowerCase(Locale.ROOT) + "/" + name); output.mkdirs(); FileSystemUtils.copyRecursively( new File("src/test/resources/com/example/" + this.taskName.toLowerCase(Locale.ENGLISH) + "/" + name), output); } private void createStandardJar(File location) throws IOException { createJar(location, (attributes) -> { }); } private void createDependenciesStarterJar(File location) throws IOException { 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 attributesConfigurer) throws IOException { location.getParentFile().mkdirs(); Manifest manifest = new Manifest(); Attributes attributes = manifest.getMainAttributes(); attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); attributesConfigurer.accept(attributes); new JarOutputStream(new FileOutputStream(location), manifest).close(); } private void writeSettingsGradle() { try (PrintWriter writer = new PrintWriter( new FileWriter(new File(this.gradleBuild.getProjectDir(), "settings.gradle")))) { writer.println("include 'alpha', 'bravo', 'charlie'"); new File(this.gradleBuild.getProjectDir(), "alpha").mkdirs(); new File(this.gradleBuild.getProjectDir(), "bravo").mkdirs(); new File(this.gradleBuild.getProjectDir(), "charlie").mkdirs(); } catch (IOException ex) { throw new RuntimeException(ex); } } private void writeMainClass() { File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example"); examplePackage.mkdirs(); File main = new File(examplePackage, "Main.java"); try (PrintWriter writer = new PrintWriter(new FileWriter(main))) { writer.println("package example;"); writer.println(); writer.println("import java.io.IOException;"); writer.println(); writer.println("public class Main {"); writer.println(); writer.println(" public static void main(String[] args) {"); writer.println(" }"); writer.println(); writer.println("}"); } catch (IOException ex) { throw new RuntimeException(ex); } } private void writeResource() { try { Path path = this.gradleBuild.getProjectDir() .toPath() .resolve(Paths.get("src", "main", "resources", "static", "file.txt")); Files.createDirectories(path.getParent()); Files.createFile(path); } catch (IOException ex) { throw new RuntimeException(ex); } } private Map> readLayerIndex(JarFile jarFile) throws IOException { Map> index = new LinkedHashMap<>(); ZipEntry indexEntry = jarFile.getEntry(this.indexPath + "layers.idx"); try (BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(indexEntry)))) { String line = reader.readLine(); String layer = null; while (line != null) { if (line.startsWith("- ")) { layer = line.substring(3, line.length() - 2); } else if (line.startsWith(" - ")) { index.computeIfAbsent(layer, (key) -> new ArrayList<>()).add(line.substring(5, line.length() - 1)); } line = reader.readLine(); } return index; } } private Map> readExtractedLayers(File root, List layerNames) throws IOException { Map> extractedLayers = new LinkedHashMap<>(); for (String layerName : layerNames) { File layer = new File(root, layerName); assertThat(layer).isDirectory(); List files; try (Stream pathStream = Files.walk(layer.toPath())) { files = pathStream.filter((path) -> path.toFile().isFile()) .map(layer.toPath()::relativize) .map(Path::toString) .map(StringUtils::cleanPath) .toList(); } extractedLayers.put(layerName, files); } return extractedLayers; } private void assertExtractedLayers(List layerNames, Map> indexedLayers) throws IOException { Map> extractedLayers = readExtractedLayers(this.gradleBuild.getProjectDir(), layerNames); assertThat(extractedLayers.keySet()).isEqualTo(indexedLayers.keySet()); extractedLayers.forEach((name, contents) -> { List index = indexedLayers.get(name); assertThat(index).isNotNull(); List unexpected = new ArrayList<>(); for (String file : contents) { if (!isInIndex(index, file)) { unexpected.add(name); } } assertThat(unexpected).isEmpty(); }); } private boolean isInIndex(List index, String file) { for (String candidate : index) { if (file.equals(candidate) || candidate.endsWith("/") && file.startsWith(candidate)) { return true; } } return false; } private static void assertEntryMode(ZipArchiveEntry entry, int expectedMode) { assertThat(entry.getUnixMode()) .withFailMessage(() -> "Expected mode " + Integer.toOctalString(expectedMode) + " for entry " + entry.getName() + " but actual is " + Integer.toOctalString(entry.getUnixMode())) .isEqualTo(expectedMode); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.UUID; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; import org.gradle.api.Action; import org.gradle.api.DomainObjectSet; import org.gradle.api.Project; import org.gradle.api.artifacts.ArtifactCollection; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.DependencySet; import org.gradle.api.artifacts.ProjectDependency; import org.gradle.api.artifacts.ResolvableDependencies; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.artifacts.result.ResolvedArtifactResult; import org.gradle.api.internal.file.archive.ZipEntryConstants; import org.gradle.api.tasks.bundling.AbstractArchiveTask; import org.gradle.api.tasks.bundling.Jar; import org.gradle.internal.component.external.model.ModuleComponentArtifactIdentifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.gradle.junit.GradleProjectBuilder; import org.springframework.boot.loader.tools.JarModeLibrary; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; /** * Abstract base class for testing {@link BootArchive} implementations. * * @param the type of the concrete BootArchive implementation * @author Andy Wilkinson * @author Scott Frederick * @author Moritz Halbritter */ abstract class AbstractBootArchiveTests { @TempDir @SuppressWarnings("NullAway.Init") File temp; private final Class taskClass; private final String launcherClass; private final String libPath; private final String classesPath; private final String indexPath; private Project project; private T task; protected AbstractBootArchiveTests(Class taskClass, String launcherClass, String libPath, String classesPath, String indexPath) { this.taskClass = taskClass; this.launcherClass = launcherClass; this.libPath = libPath; this.classesPath = classesPath; this.indexPath = indexPath; } @BeforeEach void createTask() { File projectDir = new File(this.temp, "project"); projectDir.mkdirs(); this.project = GradleProjectBuilder.builder().withProjectDir(projectDir).build(); this.project.setDescription("Test project for " + this.taskClass.getSimpleName()); this.task = this.project.getTasks().register("testArchive", this.taskClass, this::configure).get(); } @Test void basicArchiveCreation() throws IOException { this.task.getMainClass().set("com.example.Main"); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Main-Class")).isEqualTo(this.launcherClass); assertThat(jarFile.getManifest().getMainAttributes().getValue("Start-Class")).isEqualTo("com.example.Main"); assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classes")) .isEqualTo(this.classesPath); assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isEqualTo(this.libPath); assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Version")).isNotNull(); assertThat(jarFile.getManifest().getMainAttributes().getValue("Implementation-Title")) .isEqualTo(this.project.getName()); assertThat(jarFile.getManifest().getMainAttributes().getValue("Implementation-Version")).isNull(); } } @Test void whenImplementationNameIsCustomizedItShouldAppearInArchiveManifest() throws IOException { this.task.getMainClass().set("com.example.Main"); this.task.getManifest().getAttributes().put("Implementation-Title", "Customized"); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Implementation-Title")) .isEqualTo("Customized"); } } @Test void whenProjectVersionIsSetThenImplementationVersionShouldAppearInArchiveManifest() throws IOException { this.project.setVersion("1.0.0"); this.task.getMainClass().set("com.example.Main"); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Implementation-Version")).isEqualTo("1.0.0"); } } @Test void whenImplementationVersionIsCustomizedItShouldAppearInArchiveManifest() throws IOException { this.project.setVersion("1.0.0"); this.task.getMainClass().set("com.example.Main"); this.task.getManifest().getAttributes().put("Implementation-Version", "Customized"); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Implementation-Version")) .isEqualTo("Customized"); } } @Test void classpathJarsArePackagedBeneathLibPathAndAreStored() throws IOException { this.task.getMainClass().set("com.example.Main"); this.task.classpath(jarFile("one.jar"), jarFile("two.jar")); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry(this.libPath + "one.jar")).isNotNull() .extracting(ZipEntry::getMethod) .isEqualTo(ZipEntry.STORED); assertThat(jarFile.getEntry(this.libPath + "two.jar")).isNotNull() .extracting(ZipEntry::getMethod) .isEqualTo(ZipEntry.STORED); } } @Test void classpathDirectoriesArePackagedBeneathClassesPath() throws IOException { this.task.getMainClass().set("com.example.Main"); File classpathDirectory = new File(this.temp, "classes"); File applicationClass = new File(classpathDirectory, "com/example/Application.class"); applicationClass.getParentFile().mkdirs(); applicationClass.createNewFile(); this.task.classpath(classpathDirectory); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry(this.classesPath + "com/example/Application.class")).isNotNull(); } } @Test void moduleInfoClassIsPackagedInTheRootOfTheArchive() throws IOException { this.task.getMainClass().set("com.example.Main"); File classpathDirectory = new File(this.temp, "classes"); File moduleInfoClass = new File(classpathDirectory, "module-info.class"); moduleInfoClass.getParentFile().mkdirs(); moduleInfoClass.createNewFile(); File applicationClass = new File(classpathDirectory, "com/example/Application.class"); applicationClass.getParentFile().mkdirs(); applicationClass.createNewFile(); this.task.classpath(classpathDirectory); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry(this.classesPath + "com/example/Application.class")).isNotNull(); assertThat(jarFile.getEntry("com/example/Application.class")).isNull(); assertThat(jarFile.getEntry("module-info.class")).isNotNull(); assertThat(jarFile.getEntry(this.classesPath + "/module-info.class")).isNull(); } } @Test void classpathCanBeSetUsingAFileCollection() throws IOException { this.task.getMainClass().set("com.example.Main"); this.task.classpath(jarFile("one.jar")); this.task.setClasspath(this.task.getProject().files(jarFile("two.jar"))); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry(this.libPath + "one.jar")).isNull(); assertThat(jarFile.getEntry(this.libPath + "two.jar")).isNotNull(); } } @Test void classpathCanBeSetUsingAnObject() throws IOException { this.task.getMainClass().set("com.example.Main"); this.task.classpath(jarFile("one.jar")); this.task.setClasspath(jarFile("two.jar")); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry(this.libPath + "one.jar")).isNull(); assertThat(jarFile.getEntry(this.libPath + "two.jar")).isNotNull(); } } @Test void filesOnTheClasspathThatAreNotZipFilesAreSkipped() throws IOException { this.task.getMainClass().set("com.example.Main"); this.task.classpath(new File("test.pom")); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry(this.libPath + "/test.pom")).isNull(); } } @Test void loaderIsWrittenToTheRootOfTheJarAfterManifest() throws IOException { this.task.getMainClass().set("com.example.Main"); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry("org/springframework/boot/loader/launch/LaunchedClassLoader.class")) .isNotNull(); assertThat(jarFile.getEntry("org/springframework/boot/loader/")).isNotNull(); } // gh-16698 try (ZipInputStream zipInputStream = new ZipInputStream( new FileInputStream(this.task.getArchiveFile().get().getAsFile()))) { assertThat(zipInputStream.getNextEntry().getName()).isEqualTo("META-INF/"); assertThat(zipInputStream.getNextEntry().getName()).isEqualTo("META-INF/MANIFEST.MF"); } } @Test void loaderIsWrittenToTheRootOfTheJarWhenUsingThePropertiesLauncher() throws IOException { this.task.getMainClass().set("com.example.Main"); executeTask(); this.task.getManifest() .getAttributes() .put("Main-Class", "org.springframework.boot.loader.launch.PropertiesLauncher"); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry("org/springframework/boot/loader/launch/LaunchedClassLoader.class")) .isNotNull(); assertThat(jarFile.getEntry("org/springframework/boot/loader/")).isNotNull(); } } @Test void unpackCommentIsAddedToEntryIdentifiedByAPattern() throws IOException { this.task.getMainClass().set("com.example.Main"); this.task.classpath(jarFile("one.jar"), jarFile("two.jar")); this.task.requiresUnpack("**/one.jar"); executeTask(); 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 + "two.jar").getComment()).isNull(); } } @Test void unpackCommentIsAddedToEntryIdentifiedByASpec() throws IOException { this.task.getMainClass().set("com.example.Main"); this.task.classpath(jarFile("one.jar"), jarFile("two.jar")); this.task.requiresUnpack((element) -> element.getName().endsWith("two.jar")); executeTask(); 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 + "one.jar").getComment()).isNull(); } } @Test void customMainClassInTheManifestIsHonored() throws IOException { this.task.getMainClass().set("com.example.Main"); this.task.getManifest().getAttributes().put("Main-Class", "com.example.CustomLauncher"); executeTask(); assertThat(this.task.getArchiveFile().get().getAsFile()).exists(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Main-Class")) .isEqualTo("com.example.CustomLauncher"); assertThat(jarFile.getManifest().getMainAttributes().getValue("Start-Class")).isEqualTo("com.example.Main"); assertThat(jarFile.getEntry("org/springframework/boot/loader/launch/LaunchedClassLoader.class")).isNull(); } } @Test void customStartClassInTheManifestIsHonored() throws IOException { this.task.getMainClass().set("com.example.Main"); this.task.getManifest().getAttributes().put("Start-Class", "com.example.CustomMain"); executeTask(); assertThat(this.task.getArchiveFile().get().getAsFile()).exists(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Main-Class")).isEqualTo(this.launcherClass); assertThat(jarFile.getManifest().getMainAttributes().getValue("Start-Class")) .isEqualTo("com.example.CustomMain"); } } @Test void fileTimestampPreservationCanBeDisabled() throws IOException { this.task.getMainClass().set("com.example.Main"); this.task.setPreserveFileTimestamps(false); executeTask(); assertThat(this.task.getArchiveFile().get().getAsFile()).exists(); long expectedTime = DefaultTimeZoneOffset.INSTANCE.removeFrom(BootZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); assertThat(entry.getTime()).isEqualTo(expectedTime); } } } @Test void constantTimestampMatchesGradleInternalTimestamp() { assertThat(DefaultTimeZoneOffset.INSTANCE.removeFrom(BootZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES)) .isEqualTo(ZipEntryConstants.CONSTANT_TIME_FOR_ZIP_ENTRIES); } @Test void archiveIsReproducibleByDefault() 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")); executeTask(); assertThat(this.task.getArchiveFile().get().getAsFile()).exists(); List files = new ArrayList<>(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); assertThat(entry.getLastModifiedTime().toMillis()) .isEqualTo(ZipEntryConstants.CONSTANT_TIME_FOR_ZIP_ENTRIES); 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 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)); } } } } @Test void devtoolsJarIsExcludedByDefault() throws IOException { this.task.getMainClass().set("com.example.Main"); this.task.classpath(newFile("spring-boot-devtools-0.1.2.jar")); executeTask(); assertThat(this.task.getArchiveFile().get().getAsFile()).exists(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry(this.libPath + "spring-boot-devtools-0.1.2.jar")).isNull(); } } @Test void allEntriesUseUnixPlatformAndUtf8NameEncoding() throws IOException { this.task.getMainClass().set("com.example.Main"); this.task.setMetadataCharset("UTF-8"); File classpathDirectory = new File(this.temp, "classes"); File resource = new File(classpathDirectory, "some-resource.xml"); resource.getParentFile().mkdirs(); resource.createNewFile(); this.task.classpath(classpathDirectory); executeTask(); File archivePath = this.task.getArchiveFile().get().getAsFile(); try (ZipFile zip = ZipFile.builder().setFile(archivePath).get()) { Enumeration entries = zip.getEntries(); while (entries.hasMoreElements()) { ZipArchiveEntry entry = entries.nextElement(); assertThat(entry.getPlatform()).isEqualTo(ZipArchiveEntry.PLATFORM_UNIX); assertThat(entry.getGeneralPurposeBit().usesUTF8ForNames()).isTrue(); } } } @Test void loaderIsWrittenFirstThenApplicationClassesThenLibraries() throws IOException { this.task.getMainClass().set("com.example.Main"); File classpathDirectory = new File(this.temp, "classes"); File applicationClass = new File(classpathDirectory, "com/example/Application.class"); applicationClass.getParentFile().mkdirs(); applicationClass.createNewFile(); this.task.classpath(classpathDirectory, jarFile("first-library.jar"), jarFile("second-library.jar"), jarFile("third-library.jar")); this.task.requiresUnpack("second-library.jar"); executeTask(); assertThat(getEntryNames(this.task.getArchiveFile().get().getAsFile())).containsSubsequence( "org/springframework/boot/loader/", this.classesPath + "com/example/Application.class", this.libPath + "first-library.jar", this.libPath + "second-library.jar", this.libPath + "third-library.jar"); } @Test void archiveShouldBeLayeredByDefault() throws IOException { addContent(); executeTask(); try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classes")) .isEqualTo(this.classesPath); assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isEqualTo(this.libPath); assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Layers-Index")) .isEqualTo(this.indexPath + "layers.idx"); assertThat(getEntryNames(jarFile)).contains(this.libPath + JarModeLibrary.TOOLS.getName()); } } @Test void jarWhenLayersDisabledShouldNotContainLayersIndex() throws IOException { List entryNames = getEntryNames( createLayeredJar((configuration) -> configuration.getEnabled().set(false))); assertThat(entryNames).isNotEmpty().doesNotContain(this.indexPath + "layers.idx"); } @Test void whenJarIsLayeredThenManifestContainsEntryForLayersIndexInPlaceOfClassesAndLib() throws IOException { try (JarFile jarFile = new JarFile(createLayeredJar())) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classes")) .isEqualTo(this.classesPath); assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isEqualTo(this.libPath); assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Layers-Index")) .isEqualTo(this.indexPath + "layers.idx"); } } @Test void whenJarIsLayeredThenLayersIndexIsPresentAndCorrect() throws IOException { try (JarFile jarFile = new JarFile(createLayeredJar())) { List entryNames = getEntryNames(jarFile); assertThat(entryNames).contains(this.libPath + "first-library.jar", this.libPath + "second-library.jar", this.libPath + "third-library-SNAPSHOT.jar", this.libPath + "first-project-library.jar", this.libPath + "second-project-library-SNAPSHOT.jar", this.classesPath + "com/example/Application.class", this.classesPath + "application.properties", this.classesPath + "static/test.css"); List index = entryLines(jarFile, this.indexPath + "layers.idx"); assertThat(getLayerNames(index)).containsExactly("dependencies", "spring-boot-loader", "snapshot-dependencies", "application"); String toolsJar = this.libPath + JarModeLibrary.TOOLS.getName(); List expected = new ArrayList<>(); expected.add("- \"dependencies\":"); expected.add(" - \"" + this.libPath + "first-library.jar\""); expected.add(" - \"" + this.libPath + "first-project-library.jar\""); expected.add(" - \"" + this.libPath + "fourth-library.jar\""); expected.add(" - \"" + this.libPath + "second-library.jar\""); if (!toolsJar.contains("SNAPSHOT")) { expected.add(" - \"" + toolsJar + "\""); } expected.add("- \"spring-boot-loader\":"); expected.add(" - \"org/\""); expected.add("- \"snapshot-dependencies\":"); expected.add(" - \"" + this.libPath + "second-project-library-SNAPSHOT.jar\""); if (toolsJar.contains("SNAPSHOT")) { expected.add(" - \"" + toolsJar + "\""); } expected.add(" - \"" + this.libPath + "third-library-SNAPSHOT.jar\""); expected.add("- \"application\":"); Set applicationContents = new TreeSet<>(); applicationContents.add(" - \"" + this.classesPath + "\""); applicationContents.add(" - \"" + this.indexPath + "classpath.idx\""); applicationContents.add(" - \"" + this.indexPath + "layers.idx\""); applicationContents.add(" - \"META-INF/\""); expected.addAll(applicationContents); assertThat(index).containsExactlyElementsOf(expected); } } @Test void whenJarIsLayeredWithCustomStrategiesThenLayersIndexIsPresentAndCorrect() throws IOException { File jar = createLayeredJar((layered) -> { layered.application((application) -> { application.intoLayer("resources", (spec) -> spec.include("static/**")); application.intoLayer("application"); }); layered.dependencies((dependencies) -> { dependencies.intoLayer("my-snapshot-deps", (spec) -> spec.include("com.example:*:*.SNAPSHOT")); dependencies.intoLayer("my-internal-deps", (spec) -> spec.include("com.example:*:*")); dependencies.intoLayer("my-deps"); }); layered.getLayerOrder() .set(List.of("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application")); }); try (JarFile jarFile = new JarFile(jar)) { List entryNames = getEntryNames(jar); assertThat(entryNames).contains(this.libPath + "first-library.jar", this.libPath + "second-library.jar", this.libPath + "third-library-SNAPSHOT.jar", this.libPath + "first-project-library.jar", this.libPath + "second-project-library-SNAPSHOT.jar", this.classesPath + "com/example/Application.class", this.classesPath + "application.properties", this.classesPath + "static/test.css"); List index = entryLines(jarFile, this.indexPath + "layers.idx"); assertThat(getLayerNames(index)).containsExactly("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application"); String toolsJar = this.libPath + JarModeLibrary.TOOLS.getName(); List expected = new ArrayList<>(); expected.add("- \"my-deps\":"); expected.add(" - \"" + toolsJar + "\""); expected.add("- \"my-internal-deps\":"); expected.add(" - \"" + this.libPath + "first-library.jar\""); expected.add(" - \"" + this.libPath + "first-project-library.jar\""); expected.add(" - \"" + this.libPath + "fourth-library.jar\""); expected.add(" - \"" + this.libPath + "second-library.jar\""); expected.add("- \"my-snapshot-deps\":"); expected.add(" - \"" + this.libPath + "second-project-library-SNAPSHOT.jar\""); expected.add(" - \"" + this.libPath + "third-library-SNAPSHOT.jar\""); expected.add("- \"resources\":"); expected.add(" - \"" + this.classesPath + "static/\""); expected.add("- \"application\":"); Set applicationContents = new TreeSet<>(); applicationContents.add(" - \"" + this.classesPath + "application.properties\""); applicationContents.add(" - \"" + this.classesPath + "com/\""); applicationContents.add(" - \"" + this.indexPath + "classpath.idx\""); applicationContents.add(" - \"" + this.indexPath + "layers.idx\""); applicationContents.add(" - \"META-INF/\""); applicationContents.add(" - \"org/\""); expected.addAll(applicationContents); assertThat(index).containsExactlyElementsOf(expected); } } @Test void whenArchiveIsLayeredThenToolsJarIsIncluded() throws IOException { List entryNames = getEntryNames(createLayeredJar()); assertThat(entryNames).contains(this.libPath + JarModeLibrary.TOOLS.getName()); } @Test void shouldAddToolsToTheJar() throws IOException { this.task.getMainClass().set("com.example.Main"); executeTask(); List entryNames = getEntryNames(this.task.getArchiveFile().get().getAsFile()); assertThat(entryNames).isNotEmpty().contains(this.libPath + JarModeLibrary.TOOLS.getName()); } @Test void whenIncludeToolsIsFalseThenToolsAreNotAddedToTheJar() throws IOException { this.task.getIncludeTools().set(false); this.task.getMainClass().set("com.example.Main"); executeTask(); List entryNames = getEntryNames(this.task.getArchiveFile().get().getAsFile()); assertThat(entryNames).isNotEmpty().doesNotContain(this.libPath + JarModeLibrary.TOOLS.getName()); } protected File jarFile(String name) throws IOException { File file = newFile(name); try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(file))) { jar.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); new Manifest().write(jar); jar.closeEntry(); } return file; } private T configure(T task) { AbstractArchiveTask archiveTask = task; archiveTask.getArchiveBaseName().set("test"); File destination = new File(this.temp, "destination"); destination.mkdirs(); archiveTask.getDestinationDirectory().set(destination); return task; } protected abstract void executeTask(); protected T getTask() { return this.task; } protected List getEntryNames(File file) throws IOException { try (JarFile jarFile = new JarFile(file)) { return getEntryNames(jarFile); } } protected List getEntryNames(JarFile jarFile) { List entryNames = new ArrayList<>(); Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { entryNames.add(entries.nextElement().getName()); } return entryNames; } protected File newFiles(String... names) throws IOException { File dir = new File(this.temp, UUID.randomUUID().toString()); dir.mkdir(); List 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 { File file = new File(this.temp, name); file.createNewFile(); return file; } File createLayeredJar() throws IOException { return createLayeredJar(false); } File createLayeredJar(boolean addReachabilityProperties) throws IOException { return createLayeredJar(addReachabilityProperties, (spec) -> { }); } File createLayeredJar(Action action) throws IOException { return createLayeredJar(false, action); } File createLayeredJar(boolean addReachabilityProperties, Action action) throws IOException { applyLayered(action); addContent(addReachabilityProperties); executeTask(); return getTask().getArchiveFile().get().getAsFile(); } File createPopulatedJar() throws IOException { return createPopulatedJar(false); } File createPopulatedJar(boolean addReachabilityProperties) throws IOException { addContent(addReachabilityProperties); executeTask(); return getTask().getArchiveFile().get().getAsFile(); } abstract void applyLayered(Action action); void addContent() throws IOException { addContent(false); } @SuppressWarnings("unchecked") void addContent(boolean addReachabilityProperties) throws IOException { this.task.getMainClass().set("com.example.Main"); File classesJavaMain = new File(this.temp, "classes/java/main"); File applicationClass = new File(classesJavaMain, "com/example/Application.class"); applicationClass.getParentFile().mkdirs(); applicationClass.createNewFile(); File resourcesMain = new File(this.temp, "resources/main"); File applicationProperties = new File(resourcesMain, "application.properties"); applicationProperties.getParentFile().mkdirs(); applicationProperties.createNewFile(); File staticResources = new File(resourcesMain, "static"); staticResources.mkdir(); File css = new File(staticResources, "test.css"); css.createNewFile(); if (addReachabilityProperties) { createReachabilityProperties(resourcesMain, "com.example", "first-library", "1.0.0", "true"); createReachabilityProperties(resourcesMain, "com.example", "second-library", "1.0.0", "true"); createReachabilityProperties(resourcesMain, "com.example", "fourth-library", "1.0.0", "false"); } this.task.classpath(classesJavaMain, resourcesMain, jarFile("first-library.jar"), jarFile("second-library.jar"), jarFile("third-library-SNAPSHOT.jar"), jarFile("fourth-library.jar"), jarFile("first-project-library.jar"), jarFile("second-project-library-SNAPSHOT.jar")); Set resolvedArtifacts = new LinkedHashSet<>(); resolvedArtifacts.add(mockArtifact("first-library.jar", "com.example", "first-library", "1.0.0")); resolvedArtifacts.add(mockArtifact("second-library.jar", "com.example", "second-library", "1.0.0")); resolvedArtifacts .add(mockArtifact("third-library-SNAPSHOT.jar", "com.example", "third-library", "1.0.0.SNAPSHOT")); resolvedArtifacts.add(mockArtifact("fourth-library.jar", "com.example", "fourth-library", "1.0.0")); resolvedArtifacts .add(mockArtifact("first-project-library.jar", "com.example", "first-project-library", "1.0.0")); resolvedArtifacts.add(mockArtifact("second-project-library-SNAPSHOT.jar", "com.example", "second-project-library", "1.0.0.SNAPSHOT")); ArtifactCollection artifacts = mock(ArtifactCollection.class); given(artifacts.getResolvedArtifacts()).willReturn(this.project.provider(() -> resolvedArtifacts)); ResolvableDependencies resolvableDependencies = mock(ResolvableDependencies.class); given(resolvableDependencies.getArtifacts()).willReturn(artifacts); Configuration configuration = mock(Configuration.class); given(configuration.getIncoming()).willReturn(resolvableDependencies); DependencySet dependencies = mock(DependencySet.class); DomainObjectSet projectDependencies = mock(DomainObjectSet.class); given(dependencies.withType(ProjectDependency.class)).willReturn(projectDependencies); given(configuration.getAllDependencies()).willReturn(dependencies); willAnswer((invocation) -> { invocation.getArgument(0, Action.class).execute(resolvableDependencies); return null; }).given(resolvableDependencies).afterResolve(any(Action.class)); given(configuration.getIncoming()).willReturn(resolvableDependencies); populateResolvedDependencies(configuration); } protected void createReachabilityProperties(File directory, String groupId, String artifactId, String version, String override) throws IOException { File targetDirectory = new File(directory, "META-INF/native-image/%s/%s/%s".formatted(groupId, artifactId, version)); File target = new File(targetDirectory, "reachability-metadata.properties"); targetDirectory.mkdirs(); FileCopyUtils.copy("override=%s\n".formatted(override).getBytes(StandardCharsets.ISO_8859_1), target); } private void populateResolvedDependencies(Configuration configuration) { getTask().resolvedArtifacts(configuration.getIncoming().getArtifacts().getResolvedArtifacts()); } private ResolvedArtifactResult mockArtifact(String fileName, String group, String module, String version) { ModuleComponentArtifactIdentifier moduleId = mock(ModuleComponentArtifactIdentifier.class); ModuleComponentIdentifier componentId = mock(ModuleComponentIdentifier.class); given(moduleId.getComponentIdentifier()).willReturn(componentId); given(componentId.getGroup()).willReturn(group); given(componentId.getModule()).willReturn(module); given(componentId.getVersion()).willReturn(version); ResolvedArtifactResult libraryArtifact = mock(ResolvedArtifactResult.class); File file = new File(this.temp, fileName).getAbsoluteFile(); given(libraryArtifact.getFile()).willReturn(file); given(libraryArtifact.getId()).willReturn(moduleId); return libraryArtifact; } List entryLines(JarFile jarFile, String entryName) throws IOException { try (BufferedReader reader = new BufferedReader( new InputStreamReader(jarFile.getInputStream(jarFile.getEntry(entryName))))) { return reader.lines().toList(); } } private Set getLayerNames(List index) { Set layerNames = new LinkedHashSet<>(); for (String line : index) { if (line.startsWith("- ")) { layerNames.add(line.substring(3, line.length() - 2)); } } return layerNames; } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.gradle.api.Project; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.buildpack.platform.build.BuildRequest; import org.springframework.boot.buildpack.platform.build.BuildpackReference; 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.ImageReference; import org.springframework.boot.gradle.junit.GradleProjectBuilder; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link BootBuildImage}. * * @author Andy Wilkinson * @author Scott Frederick * @author Andrey Shlykov * @author Jeroen Meijer * @author Rafael Ceccone */ class BootBuildImageTests { Project project; private BootBuildImage buildImage; @BeforeEach void setUp(@TempDir File temp) { File projectDir = new File(temp, "project"); projectDir.mkdirs(); this.project = GradleProjectBuilder.builder().withProjectDir(projectDir).withName("build-image-test").build(); this.project.setDescription("Test project for BootBuildImage"); this.buildImage = this.project.getTasks().register("buildImage", BootBuildImage.class).get(); } @Test void whenProjectVersionIsUnspecifiedThenItIsIgnoredWhenDerivingImageName() { assertThat(this.buildImage.getImageName().get()).isEqualTo("docker.io/library/build-image-test"); BuildRequest request = this.buildImage.createRequest(); assertThat(request.getName().getDomain()).isEqualTo("docker.io"); assertThat(request.getName().getName()).isEqualTo("library/build-image-test"); assertThat(request.getName().getTag()).isEqualTo("latest"); assertThat(request.getName().getDigest()).isNull(); } @Test void whenProjectVersionIsSpecifiedThenItIsUsedInTagOfImageName() { this.project.setVersion("1.2.3"); assertThat(this.buildImage.getImageName().get()).isEqualTo("docker.io/library/build-image-test:1.2.3"); BuildRequest request = this.buildImage.createRequest(); assertThat(request.getName().getDomain()).isEqualTo("docker.io"); assertThat(request.getName().getName()).isEqualTo("library/build-image-test"); assertThat(request.getName().getTag()).isEqualTo("1.2.3"); assertThat(request.getName().getDigest()).isNull(); } @Test void whenImageNameIsSpecifiedThenItIsUsedInRequest() { this.project.setVersion("1.2.3"); this.buildImage.getImageName().set("example.com/test/build-image:1.0"); assertThat(this.buildImage.getImageName().get()).isEqualTo("example.com/test/build-image:1.0"); BuildRequest request = this.buildImage.createRequest(); assertThat(request.getName().getDomain()).isEqualTo("example.com"); assertThat(request.getName().getName()).isEqualTo("test/build-image"); assertThat(request.getName().getTag()).isEqualTo("1.0"); assertThat(request.getName().getDigest()).isNull(); } @Test void springBootVersionDefaultValueIsUsed() { BuildRequest request = this.buildImage.createRequest(); assertThat(request.getCreator().getName()).isEqualTo("Spring Boot"); assertThat(request.getCreator().getVersion()).isEmpty(); } @Test void whenIndividualEntriesAreAddedToTheEnvironmentThenTheyAreIncludedInTheRequest() { this.buildImage.getEnvironment().put("ALPHA", "a"); this.buildImage.getEnvironment().put("BRAVO", "b"); assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "a") .containsEntry("BRAVO", "b") .hasSize(2); } @Test void whenEntriesAreAddedToTheEnvironmentThenTheyAreIncludedInTheRequest() { Map environment = new HashMap<>(); environment.put("ALPHA", "a"); environment.put("BRAVO", "b"); this.buildImage.getEnvironment().putAll(environment); assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "a") .containsEntry("BRAVO", "b") .hasSize(2); } @Test void whenTheEnvironmentIsSetItIsIncludedInTheRequest() { Map environment = new HashMap<>(); environment.put("ALPHA", "a"); environment.put("BRAVO", "b"); this.buildImage.getEnvironment().set(environment); assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "a") .containsEntry("BRAVO", "b") .hasSize(2); } @Test void whenTheEnvironmentIsSetItReplacesAnyExistingEntriesAndIsIncludedInTheRequest() { Map environment = new HashMap<>(); environment.put("ALPHA", "a"); environment.put("BRAVO", "b"); this.buildImage.getEnvironment().put("C", "Charlie"); this.buildImage.getEnvironment().set(environment); assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "a") .containsEntry("BRAVO", "b") .hasSize(2); } @Test void whenEnvironmentVariablesAreSetOnTheCommandLineTheyAreIncludedInTheRequest() { this.buildImage.getEnvironmentFromCommandLine().add("ALPHA=a"); this.buildImage.getEnvironmentFromCommandLine().add("BRAVO=b"); assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "a") .containsEntry("BRAVO", "b") .hasSize(2); } @Test void environmentVariablesFromTheCommandLineOverrideThoseInTheBuildScript() { this.buildImage.getEnvironment().put("ALPHA", "a"); this.buildImage.getEnvironmentFromCommandLine().add("ALPHA=apple"); this.buildImage.getEnvironmentFromCommandLine().add("BRAVO=banana"); assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "apple") .containsEntry("BRAVO", "banana") .hasSize(2); } @Test void whenUsingDefaultConfigurationThenRequestHasVerboseLoggingDisabled() { assertThat(this.buildImage.createRequest().isVerboseLogging()).isFalse(); } @Test void whenVerboseLoggingIsEnabledThenRequestHasVerboseLoggingEnabled() { this.buildImage.getVerboseLogging().set(true); assertThat(this.buildImage.createRequest().isVerboseLogging()).isTrue(); } @Test void whenUsingDefaultConfigurationThenRequestHasCleanCacheDisabled() { assertThat(this.buildImage.createRequest().isCleanCache()).isFalse(); } @Test void whenCleanCacheIsEnabledThenRequestHasCleanCacheEnabled() { this.buildImage.getCleanCache().set(true); assertThat(this.buildImage.createRequest().isCleanCache()).isTrue(); } @Test void whenUsingDefaultConfigurationThenRequestHasPublishDisabled() { assertThat(this.buildImage.createRequest().isPublish()).isFalse(); } @Test void whenNoBuilderIsConfiguredThenRequestHasDefaultBuilder() { BuildRequest request = this.buildImage.createRequest(); assertThat(request.getBuilder().getName()).isEqualTo("paketobuildpacks/builder-noble-java-tiny"); assertThat(request.isTrustBuilder()).isTrue(); } @Test void whenBuilderIsConfiguredThenRequestUsesSpecifiedBuilder() { this.buildImage.getBuilder().set("example.com/test/builder:1.2"); BuildRequest request = this.buildImage.createRequest(); assertThat(request.getBuilder().getName()).isEqualTo("test/builder"); assertThat(request.isTrustBuilder()).isFalse(); } @Test void whenTrustBuilderIsEnabledThenRequestHasTrustBuilderEnabled() { this.buildImage.getBuilder().set("example.com/test/builder:1.2"); this.buildImage.getTrustBuilder().set(true); assertThat(this.buildImage.createRequest().isTrustBuilder()).isTrue(); } @Test void whenNoRunImageIsConfiguredThenRequestUsesDefaultRunImage() { assertThat(this.buildImage.createRequest().getRunImage()).isNull(); } @Test void whenRunImageIsConfiguredThenRequestUsesSpecifiedRunImage() { this.buildImage.getRunImage().set("example.com/test/run:1.0"); ImageReference runImage = this.buildImage.createRequest().getRunImage(); assertThat(runImage).isNotNull(); assertThat(runImage.getName()).isEqualTo("test/run"); } @Test void whenUsingDefaultConfigurationThenRequestHasAlwaysPullPolicy() { assertThat(this.buildImage.createRequest().getPullPolicy()).isEqualTo(PullPolicy.ALWAYS); } @Test void whenPullPolicyIsConfiguredThenRequestHasPullPolicy() { this.buildImage.getPullPolicy().set(PullPolicy.NEVER); assertThat(this.buildImage.createRequest().getPullPolicy()).isEqualTo(PullPolicy.NEVER); } @Test void whenNoBuildpacksAreConfiguredThenRequestUsesDefaultBuildpacks() { assertThat(this.buildImage.createRequest().getBuildpacks()).isEmpty(); } @Test void whenBuildpacksAreConfiguredThenRequestHasBuildpacks() { this.buildImage.getBuildpacks().set(Arrays.asList("example/buildpack1", "example/buildpack2")); assertThat(this.buildImage.createRequest().getBuildpacks()) .containsExactly(BuildpackReference.of("example/buildpack1"), BuildpackReference.of("example/buildpack2")); } @Test void whenEntriesAreAddedToBuildpacksThenRequestHasBuildpacks() { this.buildImage.getBuildpacks().addAll(Arrays.asList("example/buildpack1", "example/buildpack2")); assertThat(this.buildImage.createRequest().getBuildpacks()) .containsExactly(BuildpackReference.of("example/buildpack1"), BuildpackReference.of("example/buildpack2")); } @Test void whenIndividualEntriesAreAddedToBuildpacksThenRequestHasBuildpacks() { this.buildImage.getBuildpacks().add("example/buildpack1"); this.buildImage.getBuildpacks().add("example/buildpack2"); assertThat(this.buildImage.createRequest().getBuildpacks()) .containsExactly(BuildpackReference.of("example/buildpack1"), BuildpackReference.of("example/buildpack2")); } @Test void whenNoBindingsAreConfiguredThenRequestHasNoBindings() { assertThat(this.buildImage.createRequest().getBindings()).isEmpty(); } @Test void whenBindingsAreConfiguredThenRequestHasBindings() { this.buildImage.getBindings().set(Arrays.asList("host-src:container-dest:ro", "volume-name:container-dest:rw")); assertThat(this.buildImage.createRequest().getBindings()) .containsExactly(Binding.of("host-src:container-dest:ro"), Binding.of("volume-name:container-dest:rw")); } @Test void whenEntriesAreAddedToBindingsThenRequestHasBindings() { this.buildImage.getBindings() .addAll(Arrays.asList("host-src:container-dest:ro", "volume-name:container-dest:rw")); assertThat(this.buildImage.createRequest().getBindings()) .containsExactly(Binding.of("host-src:container-dest:ro"), Binding.of("volume-name:container-dest:rw")); } @Test void whenIndividualEntriesAreAddedToBindingsThenRequestHasBindings() { this.buildImage.getBindings().add("host-src:container-dest:ro"); this.buildImage.getBindings().add("volume-name:container-dest:rw"); assertThat(this.buildImage.createRequest().getBindings()) .containsExactly(Binding.of("host-src:container-dest:ro"), Binding.of("volume-name:container-dest:rw")); } @Test void whenNetworkIsConfiguredThenRequestHasNetwork() { this.buildImage.getNetwork().set("test"); assertThat(this.buildImage.createRequest().getNetwork()).isEqualTo("test"); } @Test void whenNoTagsAreConfiguredThenRequestHasNoTags() { assertThat(this.buildImage.createRequest().getTags()).isEmpty(); } @Test void whenTagsAreConfiguredThenRequestHasTags() { this.buildImage.getTags() .set(Arrays.asList("my-app:latest", "example.com/my-app:0.0.1-SNAPSHOT", "example.com/my-app:latest")); assertThat(this.buildImage.createRequest().getTags()).containsExactly(ImageReference.of("my-app:latest"), ImageReference.of("example.com/my-app:0.0.1-SNAPSHOT"), ImageReference.of("example.com/my-app:latest")); } @Test void whenEntriesAreAddedToTagsThenRequestHasTags() { this.buildImage.getTags() .addAll(Arrays.asList("my-app:latest", "example.com/my-app:0.0.1-SNAPSHOT", "example.com/my-app:latest")); assertThat(this.buildImage.createRequest().getTags()).containsExactly(ImageReference.of("my-app:latest"), ImageReference.of("example.com/my-app:0.0.1-SNAPSHOT"), ImageReference.of("example.com/my-app:latest")); } @Test void whenIndividualEntriesAreAddedToTagsThenRequestHasTags() { this.buildImage.getTags().add("my-app:latest"); this.buildImage.getTags().add("example.com/my-app:0.0.1-SNAPSHOT"); this.buildImage.getTags().add("example.com/my-app:latest"); assertThat(this.buildImage.createRequest().getTags()).containsExactly(ImageReference.of("my-app:latest"), ImageReference.of("example.com/my-app:0.0.1-SNAPSHOT"), ImageReference.of("example.com/my-app:latest")); } @Test void whenSecurityOptionsAreNotConfiguredThenRequestHasNoSecurityOptions() { assertThat(this.buildImage.createRequest().getSecurityOptions()).isNull(); } @Test void whenSecurityOptionsAreEmptyThenRequestHasEmptySecurityOptions() { this.buildImage.getSecurityOptions().set(Collections.emptyList()); assertThat(this.buildImage.createRequest().getSecurityOptions()).isEmpty(); } @Test void whenSecurityOptionsAreConfiguredThenRequestHasSecurityOptions() { this.buildImage.getSecurityOptions().add("label=user:USER"); this.buildImage.getSecurityOptions().add("label=role:ROLE"); assertThat(this.buildImage.createRequest().getSecurityOptions()).containsExactly("label=user:USER", "label=role:ROLE"); } @Test void whenImagePlatformIsNotConfiguredThenRequestHasNoImagePlatform() { assertThat(this.buildImage.createRequest().getImagePlatform()).isNull(); } @Test void whenImagePlatformIsConfiguredThenRequestHasImagePlatform() { this.buildImage.getImagePlatform().set("linux/arm64/v1"); assertThat(this.buildImage.createRequest().getImagePlatform()).isEqualTo(ImagePlatform.of("linux/arm64/v1")); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.IOException; import java.util.Arrays; import java.util.Set; import java.util.TreeSet; import org.gradle.testkit.runner.BuildResult; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link BootJar}. * * @author Andy Wilkinson * @author Madhura Bhave * @author Paddy Drury */ @GradleCompatibility(configurationCache = true) class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests { BootJarIntegrationTests() { super("bootJar", "BOOT-INF/lib/", "BOOT-INF/classes/", "BOOT-INF/"); } @TestTemplate void whenAResolvableCopyOfAnUnresolvableConfigurationIsResolvedThenResolutionSucceeds() { Assumptions.assumeTrue(this.gradleBuild.gradleVersionIsLessThan("9.0-milestone-1")); this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.0").build("build"); } @TestTemplate void packagedApplicationClasspath() throws IOException { copyClasspathApplication(); BuildResult result = this.gradleBuild.build("launch"); String output = result.getOutput(); assertThat(output).containsPattern("1\\. .*classes"); assertThat(output).containsPattern("2\\. .*library-1.0-SNAPSHOT.jar"); assertThat(output).containsPattern("3\\. .*commons-lang3-3.9.jar"); assertThat(output).containsPattern("4\\. .*spring-boot-jarmode-tools.*.jar"); assertThat(output).doesNotContain("5. "); } @TestTemplate void explodedApplicationClasspath() throws IOException { copyClasspathApplication(); BuildResult result = this.gradleBuild.build("launch"); String output = result.getOutput(); assertThat(output).containsPattern("1\\. .*classes"); assertThat(output).containsPattern("2\\. .*spring-boot-jarmode-tools.*.jar"); assertThat(output).containsPattern("3\\. .*library-1.0-SNAPSHOT.jar"); assertThat(output).containsPattern("4\\. .*commons-lang3-3.9.jar"); assertThat(output).doesNotContain("5. "); } private void copyClasspathApplication() throws IOException { copyApplication("classpath"); } @Override String[] getExpectedApplicationLayerContents(String... additionalFiles) { Set contents = new TreeSet<>(Arrays.asList(additionalFiles)); contents.addAll(Arrays.asList("BOOT-INF/classpath.idx", "BOOT-INF/layers.idx", "META-INF/")); return contents.toArray(new String[0]); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import java.io.IOException; import java.util.jar.JarFile; import org.gradle.api.Action; import org.gradle.api.JavaVersion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link BootJar}. * * @author Andy Wilkinson * @author Madhura Bhave * @author Scott Frederick * @author Paddy Drury */ @ClassPathExclusions("kotlin-daemon-client-*") class BootJarTests extends AbstractBootArchiveTests { BootJarTests() { super(BootJar.class, "org.springframework.boot.loader.launch.JarLauncher", "BOOT-INF/lib/", "BOOT-INF/classes/", "BOOT-INF/"); } @BeforeEach void setUp() { this.getTask().getTargetJavaVersion().set(JavaVersion.VERSION_17); } @Test void contentCanBeAddedToBootInfUsingCopySpecFromGetter() throws IOException { BootJar bootJar = getTask(); bootJar.getMainClass().set("com.example.Application"); bootJar.getBootInf().into("test").from(new File("build.gradle").getAbsolutePath()); bootJar.copy(); try (JarFile jarFile = new JarFile(bootJar.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getJarEntry("BOOT-INF/test/build.gradle")).isNotNull(); } } @Test void contentCanBeAddedToBootInfUsingCopySpecAction() throws IOException { BootJar bootJar = getTask(); bootJar.getMainClass().set("com.example.Application"); bootJar.bootInf((copySpec) -> copySpec.into("test").from(new File("build.gradle").getAbsolutePath())); bootJar.copy(); try (JarFile jarFile = new JarFile(bootJar.getArchiveFile().get().getAsFile())) { assertThat(jarFile.getJarEntry("BOOT-INF/test/build.gradle")).isNotNull(); } } @Test void jarsInLibAreStored() throws IOException { try (JarFile jarFile = new JarFile(createLayeredJar())) { assertThat(jarFile.getEntry("BOOT-INF/lib/first-library.jar").getMethod()).isZero(); assertThat(jarFile.getEntry("BOOT-INF/lib/second-library.jar").getMethod()).isZero(); assertThat(jarFile.getEntry("BOOT-INF/lib/third-library-SNAPSHOT.jar").getMethod()).isZero(); assertThat(jarFile.getEntry("BOOT-INF/lib/first-project-library.jar").getMethod()).isZero(); assertThat(jarFile.getEntry("BOOT-INF/lib/second-project-library-SNAPSHOT.jar").getMethod()).isZero(); } } @Test void whenJarIsLayeredClasspathIndexPointsToLayeredLibs() throws IOException { try (JarFile jarFile = new JarFile(createLayeredJar())) { assertThat(entryLines(jarFile, "BOOT-INF/classpath.idx")).containsExactly( "- \"BOOT-INF/lib/first-library.jar\"", "- \"BOOT-INF/lib/second-library.jar\"", "- \"BOOT-INF/lib/third-library-SNAPSHOT.jar\"", "- \"BOOT-INF/lib/fourth-library.jar\"", "- \"BOOT-INF/lib/first-project-library.jar\"", "- \"BOOT-INF/lib/second-project-library-SNAPSHOT.jar\""); } } @Test void classpathIndexPointsToBootInfLibs() throws IOException { try (JarFile jarFile = new JarFile(createPopulatedJar())) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classpath-Index")) .isEqualTo("BOOT-INF/classpath.idx"); assertThat(entryLines(jarFile, "BOOT-INF/classpath.idx")).containsExactly( "- \"BOOT-INF/lib/first-library.jar\"", "- \"BOOT-INF/lib/second-library.jar\"", "- \"BOOT-INF/lib/third-library-SNAPSHOT.jar\"", "- \"BOOT-INF/lib/fourth-library.jar\"", "- \"BOOT-INF/lib/first-project-library.jar\"", "- \"BOOT-INF/lib/second-project-library-SNAPSHOT.jar\""); } } @Test void metaInfEntryIsPackagedInTheRootOfTheArchive() throws IOException { getTask().getMainClass().set("com.example.Main"); File classpathDirectory = new File(this.temp, "classes"); File metaInfEntry = new File(classpathDirectory, "META-INF/test"); metaInfEntry.getParentFile().mkdirs(); metaInfEntry.createNewFile(); File applicationClass = new File(classpathDirectory, "com/example/Application.class"); applicationClass.getParentFile().mkdirs(); applicationClass.createNewFile(); getTask().classpath(classpathDirectory); executeTask(); try (JarFile jarFile = new JarFile(getTask().getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry("BOOT-INF/classes/com/example/Application.class")).isNotNull(); assertThat(jarFile.getEntry("com/example/Application.class")).isNull(); assertThat(jarFile.getEntry("BOOT-INF/classes/META-INF/test")).isNull(); assertThat(jarFile.getEntry("META-INF/test")).isNotNull(); } } @Test void aopXmlIsPackagedBeneathClassesDirectory() throws IOException { getTask().getMainClass().set("com.example.Main"); File classpathDirectory = new File(this.temp, "classes"); File aopXml = new File(classpathDirectory, "META-INF/aop.xml"); aopXml.getParentFile().mkdirs(); aopXml.createNewFile(); File applicationClass = new File(classpathDirectory, "com/example/Application.class"); applicationClass.getParentFile().mkdirs(); applicationClass.createNewFile(); getTask().classpath(classpathDirectory); executeTask(); try (JarFile jarFile = new JarFile(getTask().getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry("BOOT-INF/classes/com/example/Application.class")).isNotNull(); assertThat(jarFile.getEntry("com/example/Application.class")).isNull(); assertThat(jarFile.getEntry("BOOT-INF/classes/META-INF/aop.xml")).isNotNull(); assertThat(jarFile.getEntry("META-INF/aop.xml")).isNull(); } } @Test void kotlinModuleIsPackagedBeneathClassesDirectory() throws IOException { getTask().getMainClass().set("com.example.Main"); File classpathDirectory = new File(this.temp, "classes"); File kotlinModule = new File(classpathDirectory, "META-INF/example.kotlin_module"); kotlinModule.getParentFile().mkdirs(); kotlinModule.createNewFile(); File applicationClass = new File(classpathDirectory, "com/example/Application.class"); applicationClass.getParentFile().mkdirs(); applicationClass.createNewFile(); getTask().classpath(classpathDirectory); executeTask(); try (JarFile jarFile = new JarFile(getTask().getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry("BOOT-INF/classes/com/example/Application.class")).isNotNull(); assertThat(jarFile.getEntry("com/example/Application.class")).isNull(); assertThat(jarFile.getEntry("BOOT-INF/classes/META-INF/example.kotlin_module")).isNotNull(); assertThat(jarFile.getEntry("META-INF/example.kotlin_module")).isNull(); } } @Test void metaInfServicesEntryIsPackagedBeneathClassesDirectory() throws IOException { getTask().getMainClass().set("com.example.Main"); File classpathDirectory = new File(this.temp, "classes"); File service = new File(classpathDirectory, "META-INF/services/com.example.Service"); service.getParentFile().mkdirs(); service.createNewFile(); File applicationClass = new File(classpathDirectory, "com/example/Application.class"); applicationClass.getParentFile().mkdirs(); applicationClass.createNewFile(); getTask().classpath(classpathDirectory); executeTask(); try (JarFile jarFile = new JarFile(getTask().getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry("BOOT-INF/classes/com/example/Application.class")).isNotNull(); assertThat(jarFile.getEntry("com/example/Application.class")).isNull(); assertThat(jarFile.getEntry("BOOT-INF/classes/META-INF/services/com.example.Service")).isNotNull(); assertThat(jarFile.getEntry("META-INF/services/com.example.Service")).isNull(); } } @Test void nativeImageArgFileWithExcludesIsWritten() throws IOException { try (JarFile jarFile = new JarFile(createLayeredJar(true))) { assertThat(entryLines(jarFile, "META-INF/native-image/argfile")).containsExactly("--exclude-config", "\\Qfirst-library.jar\\E", "^/META-INF/native-image/.*", "--exclude-config", "\\Qsecond-library.jar\\E", "^/META-INF/native-image/.*"); } } @Test void nativeImageArgFileIsNotWrittenWhenExcludesAreEmpty() throws IOException { try (JarFile jarFile = new JarFile(createLayeredJar(false))) { assertThat(jarFile.getEntry("META-INF/native-image/argfile")).isNull(); } } @Test void javaVersionIsWrittenToManifest() throws IOException { try (JarFile jarFile = new JarFile(createPopulatedJar())) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Build-Jdk-Spec")) .isEqualTo(JavaVersion.VERSION_17.getMajorVersion()); } } @Override void applyLayered(Action action) { getTask().layered(action); } @Override protected void executeTask() { getTask().copy(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.IOException; import java.util.Arrays; import java.util.Set; import java.util.TreeSet; import org.assertj.core.api.Assumptions; import org.gradle.util.GradleVersion; import org.springframework.boot.gradle.junit.GradleCompatibility; /** * Integration tests for {@link BootWar}. * * @author Andy Wilkinson * @author Scott Frederick */ @GradleCompatibility(configurationCache = true) class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests { BootWarIntegrationTests() { super("bootWar", "WEB-INF/lib/", "WEB-INF/classes/", "WEB-INF/"); } @Override String[] getExpectedApplicationLayerContents(String... additionalFiles) { Set contents = new TreeSet<>(Arrays.asList(additionalFiles)); contents.addAll(Arrays.asList("WEB-INF/classpath.idx", "WEB-INF/layers.idx", "META-INF/")); return contents.toArray(new String[0]); } @Override void multiModuleImplicitLayers() throws IOException { whenTestingWithTheConfigurationCacheAssumeThatTheGradleVersionIsLessThan8(); super.multiModuleImplicitLayers(); } @Override void multiModuleCustomLayers() throws IOException { whenTestingWithTheConfigurationCacheAssumeThatTheGradleVersionIsLessThan8(); super.multiModuleCustomLayers(); } private void whenTestingWithTheConfigurationCacheAssumeThatTheGradleVersionIsLessThan8() { if (this.gradleBuild.isConfigurationCache()) { // With Gradle 8.0, a configuration cache bug prevents ResolvedDependencies // from processing dependencies on the runtime classpath Assumptions.assumeThat(GradleVersion.version(this.gradleBuild.getGradleVersion())) .isLessThan(GradleVersion.version("8.0")); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import java.io.IOException; import java.util.jar.JarFile; import org.gradle.api.Action; import org.gradle.api.JavaVersion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link BootWar}. * * @author Andy Wilkinson * @author Scott Frederick */ @ClassPathExclusions("kotlin-daemon-client-*") class BootWarTests extends AbstractBootArchiveTests { BootWarTests() { super(BootWar.class, "org.springframework.boot.loader.launch.WarLauncher", "WEB-INF/lib/", "WEB-INF/classes/", "WEB-INF/"); } @BeforeEach void setUp() { this.getTask().getTargetJavaVersion().set(JavaVersion.VERSION_17); } @Test void providedClasspathJarsArePackagedInWebInfLibProvided() throws IOException { getTask().getMainClass().set("com.example.Main"); getTask().providedClasspath(jarFile("one.jar"), jarFile("two.jar")); executeTask(); try (JarFile jarFile = new JarFile(getTask().getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry("WEB-INF/lib-provided/one.jar")).isNotNull(); assertThat(jarFile.getEntry("WEB-INF/lib-provided/two.jar")).isNotNull(); } } @Test void providedClasspathCanBeSetUsingAFileCollection() throws IOException { getTask().getMainClass().set("com.example.Main"); getTask().providedClasspath(jarFile("one.jar")); getTask().setProvidedClasspath(getTask().getProject().files(jarFile("two.jar"))); executeTask(); try (JarFile jarFile = new JarFile(getTask().getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry("WEB-INF/lib-provided/one.jar")).isNull(); assertThat(jarFile.getEntry("WEB-INF/lib-provided/two.jar")).isNotNull(); } } @Test void providedClasspathCanBeSetUsingAnObject() throws IOException { getTask().getMainClass().set("com.example.Main"); getTask().providedClasspath(jarFile("one.jar")); getTask().setProvidedClasspath(jarFile("two.jar")); executeTask(); try (JarFile jarFile = new JarFile(getTask().getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry("WEB-INF/lib-provided/one.jar")).isNull(); assertThat(jarFile.getEntry("WEB-INF/lib-provided/two.jar")).isNotNull(); } } @Test void devtoolsJarIsExcludedByDefaultWhenItsOnTheProvidedClasspath() throws IOException { getTask().getMainClass().set("com.example.Main"); getTask().providedClasspath(newFile("spring-boot-devtools-0.1.2.jar")); executeTask(); try (JarFile jarFile = new JarFile(getTask().getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry("WEB-INF/lib-provided/spring-boot-devtools-0.1.2.jar")).isNull(); } } @Test void webappResourcesInDirectoriesThatOverlapWithLoaderCanBePackaged() throws IOException { File webappDirectory = new File(this.temp, "src/main/webapp"); webappDirectory.mkdirs(); File orgDirectory = new File(webappDirectory, "org"); orgDirectory.mkdir(); new File(orgDirectory, "foo.txt").createNewFile(); getTask().from(webappDirectory); getTask().getMainClass().set("com.example.Main"); executeTask(); try (JarFile jarFile = new JarFile(getTask().getArchiveFile().get().getAsFile())) { assertThat(jarFile.getEntry("org/")).isNotNull(); assertThat(jarFile.getEntry("org/foo.txt")).isNotNull(); } } @Test void libProvidedEntriesAreWrittenAfterLibEntries() throws IOException { getTask().getMainClass().set("com.example.Main"); getTask().classpath(jarFile("library.jar")); getTask().providedClasspath(jarFile("provided-library.jar")); executeTask(); assertThat(getEntryNames(getTask().getArchiveFile().get().getAsFile())) .containsSubsequence("WEB-INF/lib/library.jar", "WEB-INF/lib-provided/provided-library.jar"); } @Test void whenWarIsLayeredClasspathIndexPointsToLayeredLibs() throws IOException { try (JarFile jarFile = new JarFile(createLayeredJar())) { assertThat(entryLines(jarFile, "WEB-INF/classpath.idx")).containsExactly( "- \"WEB-INF/lib/first-library.jar\"", "- \"WEB-INF/lib/second-library.jar\"", "- \"WEB-INF/lib/third-library-SNAPSHOT.jar\"", "- \"WEB-INF/lib/fourth-library.jar\"", "- \"WEB-INF/lib/first-project-library.jar\"", "- \"WEB-INF/lib/second-project-library-SNAPSHOT.jar\""); } } @Test void classpathIndexPointsToWebInfLibs() throws IOException { try (JarFile jarFile = new JarFile(createPopulatedJar())) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classpath-Index")) .isEqualTo("WEB-INF/classpath.idx"); assertThat(entryLines(jarFile, "WEB-INF/classpath.idx")).containsExactly( "- \"WEB-INF/lib/first-library.jar\"", "- \"WEB-INF/lib/second-library.jar\"", "- \"WEB-INF/lib/third-library-SNAPSHOT.jar\"", "- \"WEB-INF/lib/fourth-library.jar\"", "- \"WEB-INF/lib/first-project-library.jar\"", "- \"WEB-INF/lib/second-project-library-SNAPSHOT.jar\""); } } @Test void javaVersionIsWrittenToManifest() throws IOException { try (JarFile jarFile = new JarFile(createPopulatedJar())) { assertThat(jarFile.getManifest().getMainAttributes().getValue("Build-Jdk-Spec")) .isEqualTo(JavaVersion.VERSION_17.getMajorVersion()); } } @Override protected void executeTask() { getTask().copy(); } @Override void applyLayered(Action action) { getTask().layered(action); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DefaultTimeZoneOffsetTests.java ================================================ /* * 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.gradle.tasks.bundling; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.Calendar; import java.util.TimeZone; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link DefaultTimeZoneOffset} * * @author Phillip Webb */ class DefaultTimeZoneOffsetTests { // gh-21005 @Test void removeFromWithLongInDifferentTimeZonesReturnsSameValue() { long time = OffsetDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli(); TimeZone timeZone1 = TimeZone.getTimeZone("GMT"); TimeZone timeZone2 = TimeZone.getTimeZone("GMT+8"); TimeZone timeZone3 = TimeZone.getTimeZone("GMT-8"); long result1 = new DefaultTimeZoneOffset(timeZone1).removeFrom(time); long result2 = new DefaultTimeZoneOffset(timeZone2).removeFrom(time); long result3 = new DefaultTimeZoneOffset(timeZone3).removeFrom(time); long dosTime1 = toDosTime(Calendar.getInstance(timeZone1), result1); long dosTime2 = toDosTime(Calendar.getInstance(timeZone2), result2); long dosTime3 = toDosTime(Calendar.getInstance(timeZone3), result3); assertThat(dosTime1).isEqualTo(dosTime2).isEqualTo(dosTime3); } @Test void removeFromWithFileTimeReturnsFileTime() { long time = OffsetDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli(); long result = new DefaultTimeZoneOffset(TimeZone.getTimeZone("GMT+8")).removeFrom(time); assertThat(result).isNotEqualTo(time).isEqualTo(946656000000L); } /** * Identical functionality to package-private * org.apache.commons.compress.archivers.zip.ZipUtil.toDosTime(Calendar, long, byte[], * int) method used by {@link ZipArchiveOutputStream} to convert times. * @param calendar the source calendar * @param time the time to convert * @return the DOS time */ private long toDosTime(Calendar calendar, long time) { calendar.setTimeInMillis(time); final int year = calendar.get(Calendar.YEAR); final int month = calendar.get(Calendar.MONTH) + 1; return ((year - 1980) << 25) | (month << 21) | (calendar.get(Calendar.DAY_OF_MONTH) << 16) | (calendar.get(Calendar.HOUR_OF_DAY) << 11) | (calendar.get(Calendar.MINUTE) << 5) | (calendar.get(Calendar.SECOND) >> 1); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import java.util.Base64; import org.gradle.api.GradleException; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication; import org.springframework.boot.gradle.junit.GradleProjectBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link DockerSpec}. * * @author Wei Jiang * @author Scott Frederick */ class DockerSpecTests { private DockerSpec dockerSpec; @BeforeEach void prepareDockerSpec(@TempDir File temp) { this.dockerSpec = GradleProjectBuilder.builder() .withProjectDir(temp) .build() .getObjects() .newInstance(DockerSpec.class); } @Test void asDockerConfigurationWithDefaults() { BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); assertThat(dockerConfiguration.connection()).isNull(); DockerRegistryAuthentication builderRegistryAuthentication = dockerConfiguration .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); assertThat(builderRegistryAuthentication.getAuthHeader()).isNull(); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); assertThat(decoded(publishRegistryAuthentication.getAuthHeader())).contains("\"username\" : \"\"") .contains("\"password\" : \"\"") .contains("\"email\" : \"\"") .contains("\"serveraddress\" : \"\""); } @Test void asDockerConfigurationWithHostConfiguration() { this.dockerSpec.getHost().set("docker.example.com"); this.dockerSpec.getTlsVerify().set(true); this.dockerSpec.getCertPath().set("/tmp/ca-cert"); BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); DockerConnectionConfiguration.Host host = (DockerConnectionConfiguration.Host) dockerConfiguration.connection(); assertThat(host).isNotNull(); assertThat(host.address()).isEqualTo("docker.example.com"); assertThat(host.secure()).isTrue(); assertThat(host.certificatePath()).isEqualTo("/tmp/ca-cert"); assertThat(dockerConfiguration.bindHostToBuilder()).isFalse(); DockerRegistryAuthentication builderRegistryAuthentication = dockerConfiguration .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); assertThat(builderRegistryAuthentication.getAuthHeader()).isNull(); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); assertThat(decoded(publishRegistryAuthentication.getAuthHeader())).contains("\"username\" : \"\"") .contains("\"password\" : \"\"") .contains("\"email\" : \"\"") .contains("\"serveraddress\" : \"\""); } @Test void asDockerConfigurationWithHostConfigurationNoTlsVerify() { this.dockerSpec.getHost().set("docker.example.com"); BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); DockerConnectionConfiguration.Host host = (DockerConnectionConfiguration.Host) dockerConfiguration.connection(); assertThat(host).isNotNull(); assertThat(host.address()).isEqualTo("docker.example.com"); assertThat(host.secure()).isFalse(); assertThat(host.certificatePath()).isNull(); assertThat(dockerConfiguration.bindHostToBuilder()).isFalse(); DockerRegistryAuthentication builderRegistryAuthentication = dockerConfiguration .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); assertThat(builderRegistryAuthentication.getAuthHeader()).isNull(); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); assertThat(decoded(publishRegistryAuthentication.getAuthHeader())).contains("\"username\" : \"\"") .contains("\"password\" : \"\"") .contains("\"email\" : \"\"") .contains("\"serveraddress\" : \"\""); } @Test void asDockerConfigurationWithContextConfiguration() { this.dockerSpec.getContext().set("test-context"); BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); DockerConnectionConfiguration.Context host = (DockerConnectionConfiguration.Context) dockerConfiguration .connection(); assertThat(host).isNotNull(); assertThat(host.context()).isEqualTo("test-context"); assertThat(dockerConfiguration.bindHostToBuilder()).isFalse(); DockerRegistryAuthentication builderRegistryAuthentication = dockerConfiguration .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); assertThat(builderRegistryAuthentication.getAuthHeader()).isNull(); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); assertThat(decoded(publishRegistryAuthentication.getAuthHeader())).contains("\"username\" : \"\"") .contains("\"password\" : \"\"") .contains("\"email\" : \"\"") .contains("\"serveraddress\" : \"\""); } @Test void asDockerConfigurationWithHostAndContextFails() { this.dockerSpec.getContext().set("test-context"); this.dockerSpec.getHost().set("docker.example.com"); assertThatExceptionOfType(GradleException.class).isThrownBy(this.dockerSpec::asDockerConfiguration) .withMessageContaining("Invalid Docker configuration"); } @Test void asDockerConfigurationWithBindHostToBuilder() { this.dockerSpec.getHost().set("docker.example.com"); this.dockerSpec.getBindHostToBuilder().set(true); BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); DockerConnectionConfiguration.Host host = (DockerConnectionConfiguration.Host) dockerConfiguration.connection(); assertThat(host).isNotNull(); assertThat(host.address()).isEqualTo("docker.example.com"); assertThat(host.secure()).isFalse(); assertThat(host.certificatePath()).isNull(); assertThat(dockerConfiguration.bindHostToBuilder()).isTrue(); DockerRegistryAuthentication builderRegistryAuthentication = dockerConfiguration .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); assertThat(builderRegistryAuthentication.getAuthHeader()).isNull(); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); assertThat(decoded(publishRegistryAuthentication.getAuthHeader())).contains("\"username\" : \"\"") .contains("\"password\" : \"\"") .contains("\"email\" : \"\"") .contains("\"serveraddress\" : \"\""); } @Test void asDockerConfigurationWithUserAuth() { this.dockerSpec.builderRegistry((registry) -> { registry.getUsername().set("user1"); registry.getPassword().set("secret1"); registry.getUrl().set("https://docker1.example.com"); registry.getEmail().set("docker1@example.com"); }); this.dockerSpec.publishRegistry((registry) -> { registry.getUsername().set("user2"); registry.getPassword().set("secret2"); registry.getUrl().set("https://docker2.example.com"); registry.getEmail().set("docker2@example.com"); }); BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); DockerRegistryAuthentication builderRegistryAuthentication = dockerConfiguration .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); assertThat(decoded(builderRegistryAuthentication.getAuthHeader())).contains("\"username\" : \"user1\"") .contains("\"password\" : \"secret1\"") .contains("\"email\" : \"docker1@example.com\"") .contains("\"serveraddress\" : \"https://docker1.example.com\""); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); assertThat(decoded(publishRegistryAuthentication.getAuthHeader())).contains("\"username\" : \"user2\"") .contains("\"password\" : \"secret2\"") .contains("\"email\" : \"docker2@example.com\"") .contains("\"serveraddress\" : \"https://docker2.example.com\""); assertThat(dockerConfiguration.connection()).isNull(); } @Test void asDockerConfigurationWithIncompleteBuilderUserAuthFails() { this.dockerSpec.builderRegistry((registry) -> { registry.getUsername().set("user1"); registry.getUrl().set("https://docker1.example.com"); registry.getEmail().set("docker1@example.com"); }); assertThatExceptionOfType(GradleException.class).isThrownBy(this.dockerSpec::asDockerConfiguration) .withMessageContaining("Invalid Docker builder registry configuration"); } @Test void asDockerConfigurationWithIncompletePublishUserAuthFails() { this.dockerSpec.publishRegistry((registry) -> { registry.getUsername().set("user2"); registry.getUrl().set("https://docker2.example.com"); registry.getEmail().set("docker2@example.com"); }); assertThatExceptionOfType(GradleException.class).isThrownBy(this.dockerSpec::asDockerConfiguration) .withMessageContaining("Invalid Docker publish registry configuration"); } @Test void asDockerConfigurationWithTokenAuth() { this.dockerSpec.builderRegistry((registry) -> registry.getToken().set("token1")); this.dockerSpec.publishRegistry((registry) -> registry.getToken().set("token2")); BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); DockerRegistryAuthentication builderRegistryAuthentication = dockerConfiguration .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); assertThat(decoded(builderRegistryAuthentication.getAuthHeader())).contains("\"identitytoken\" : \"token1\""); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); assertThat(decoded(publishRegistryAuthentication.getAuthHeader())).contains("\"identitytoken\" : \"token2\""); } @Test void asDockerConfigurationWithUserAndTokenAuthFails() { this.dockerSpec.builderRegistry((registry) -> { registry.getUsername().set("user"); registry.getPassword().set("secret"); registry.getToken().set("token"); }); assertThatExceptionOfType(GradleException.class).isThrownBy(this.dockerSpec::asDockerConfiguration) .withMessageContaining("Invalid Docker builder registry configuration"); } @Nullable String decoded(@Nullable String value) { return (value != null) ? new String(Base64.getDecoder().decode(value)) : value; } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/MavenPublishingIntegrationTests.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for publishing Boot jars and wars using Gradle's Maven Publish * plugin. * * @author Andy Wilkinson */ @GradleCompatibility class MavenPublishingIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void bootJarCanBePublished() { BuildResult result = this.gradleBuild.build("publish"); BuildTask task = result.task(":publish"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(artifactWithSuffix("jar")).isFile(); assertThat(artifactWithSuffix("pom")).is(pomWith().groupId("com.example") .artifactId(this.gradleBuild.getProjectDir().getName()) .version("1.0") .noPackaging() .noDependencies()); } @TestTemplate void bootWarCanBePublished() { BuildResult result = this.gradleBuild.build("publish"); BuildTask task = result.task(":publish"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(artifactWithSuffix("war")).isFile(); assertThat(artifactWithSuffix("pom")).is(pomWith().groupId("com.example") .artifactId(this.gradleBuild.getProjectDir().getName()) .version("1.0") .packaging("war") .noDependencies()); } private File artifactWithSuffix(String suffix) { String name = this.gradleBuild.getProjectDir().getName(); return new File(new File(this.gradleBuild.getProjectDir(), "build/repo"), String.format("com/example/%s/1.0/%s-1.0.%s", name, name, suffix)); } private PomCondition pomWith() { return new PomCondition(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/PomCondition.java ================================================ /* * 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.gradle.tasks.bundling; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.HashSet; import java.util.Set; import org.assertj.core.api.Condition; import org.assertj.core.description.Description; import org.assertj.core.description.TextDescription; import org.springframework.util.FileCopyUtils; /** * AssertJ {@link Condition} for asserting the contents of a pom file. * * @author Andy Wilkinson */ class PomCondition extends Condition { private final Set expectedContents; private final Set notExpectedContents; PomCondition() { this(new HashSet<>(), new HashSet<>()); } private PomCondition(Set expectedContents, Set notExpectedContents) { super(new TextDescription("Pom file containing %s and not containing %s", expectedContents, notExpectedContents)); this.expectedContents = expectedContents; this.notExpectedContents = notExpectedContents; } @Override public boolean matches(File pom) { try { String contents = FileCopyUtils.copyToString(new FileReader(pom)); for (String expected : this.expectedContents) { if (!contents.contains(expected)) { return false; } } for (String notExpected : this.notExpectedContents) { if (contents.contains(notExpected)) { return false; } } } catch (IOException ex) { throw new RuntimeException(ex); } return true; } @Override public Description description() { return new TextDescription("Pom file containing %s and not containing %s", this.expectedContents, this.notExpectedContents); } PomCondition groupId(String groupId) { this.expectedContents.add(String.format("%s", groupId)); return this; } PomCondition artifactId(String artifactId) { this.expectedContents.add(String.format("%s", artifactId)); return this; } PomCondition version(String version) { this.expectedContents.add(String.format("%s", version)); return this; } PomCondition packaging(String packaging) { this.expectedContents.add(String.format("%s", packaging)); return this; } PomCondition noDependencies() { this.notExpectedContents.add(""); return this; } PomCondition noPackaging() { this.notExpectedContents.add(""); return this; } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java ================================================ /* * 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.gradle.tasks.run; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.function.Consumer; import java.util.jar.Attributes; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import org.assertj.core.api.Assumptions; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.gradle.util.GradleVersion; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for the {@link BootRun} task. * * @author Andy Wilkinson */ @GradleCompatibility(configurationCache = true) class BootRunIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void basicExecution() throws IOException { copyClasspathApplication(); new File(this.gradleBuild.getProjectDir(), "src/main/resources").mkdirs(); BuildResult result = this.gradleBuild.build("bootRun"); BuildTask task = result.task(":bootRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("1. " + canonicalPathOf("build/classes/java/main")); assertThat(result.getOutput()).contains("2. " + canonicalPathOf("build/resources/main")); assertThat(result.getOutput()).doesNotContain(canonicalPathOf("src/main/resources")); } @TestTemplate void sourceResourcesCanBeUsed() throws IOException { copyClasspathApplication(); BuildResult result = this.gradleBuild.build("bootRun"); BuildTask task = result.task(":bootRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("1. " + canonicalPathOf("src/main/resources")); assertThat(result.getOutput()).contains("2. " + canonicalPathOf("build/classes/java/main")); assertThat(result.getOutput()).doesNotContain(canonicalPathOf("build/resources/main")); } @TestTemplate void springBootExtensionMainClassNameIsUsed() throws IOException { copyMainClassApplication(); BuildResult result = this.gradleBuild.build("bootRun"); BuildTask task = result.task(":bootRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("com.example.bootrun.main.CustomMainClass"); } @TestTemplate void applicationPluginMainClassNameIsUsed() throws IOException { copyMainClassApplication(); BuildResult result = this.gradleBuild.build("bootRun"); BuildTask task = result.task(":bootRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("com.example.bootrun.main.CustomMainClass"); } @TestTemplate void applicationPluginMainClassNameIsNotUsedWhenItIsNull() throws IOException { copyClasspathApplication(); BuildResult result = this.gradleBuild.build("bootRun"); BuildTask task = result.task(":bootRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()) .contains("Main class name = com.example.bootrun.classpath.BootRunClasspathApplication"); } @TestTemplate void defaultJvmArgs() throws IOException { copyJvmArgsApplication(); BuildResult result = this.gradleBuild.build("bootRun"); BuildTask task = result.task(":bootRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("-XX:TieredStopAtLevel=1"); } @TestTemplate void optimizedLaunchDisabledJvmArgs() throws IOException { copyJvmArgsApplication(); BuildResult result = this.gradleBuild.build("bootRun"); BuildTask task = result.task(":bootRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).doesNotContain("-Xverify:none").doesNotContain("-XX:TieredStopAtLevel=1"); } @TestTemplate void applicationPluginJvmArgumentsAreUsed() throws IOException { if (this.gradleBuild.isConfigurationCache()) { // https://github.com/gradle/gradle/pull/23924 GradleVersion gradleVersion = GradleVersion.version(this.gradleBuild.getGradleVersion()); Assumptions.assumeThat(gradleVersion) .isLessThan(GradleVersion.version("8.0")) .isGreaterThanOrEqualTo(GradleVersion.version("8.1-rc-1")); } copyJvmArgsApplication(); BuildResult result = this.gradleBuild.build("bootRun"); BuildTask task = result.task(":bootRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("-Dcom.bar=baz") .contains("-Dcom.foo=bar") .contains("-XX:TieredStopAtLevel=1"); } @TestTemplate void jarTypeFilteringIsAppliedToTheClasspath() throws IOException { copyClasspathApplication(); File flatDirRepository = new File(this.gradleBuild.getProjectDir(), "repository"); createDependenciesStarterJar(new File(flatDirRepository, "starter.jar")); createStandardJar(new File(flatDirRepository, "standard.jar")); BuildResult result = this.gradleBuild.build("bootRun"); BuildTask task = result.task(":bootRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("standard.jar").doesNotContain("starter.jar"); } @TestTemplate void classesFromASecondarySourceSetCanBeOnTheClasspath() throws IOException { File output = new File(this.gradleBuild.getProjectDir(), "src/secondary/java/com/example/bootrun/main"); output.mkdirs(); FileSystemUtils.copyRecursively(new File("src/test/resources/com/example/bootrun/main"), output); BuildResult result = this.gradleBuild.build("bootRun"); BuildTask task = result.task(":bootRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("com.example.bootrun.main.CustomMainClass"); } @TestTemplate void developmentOnlyDependenciesAreOnTheClasspath() throws IOException { copyClasspathApplication(); BuildResult result = this.gradleBuild.build("bootRun"); BuildTask task = result.task(":bootRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("commons-lang3-3.12.0.jar"); } @TestTemplate void testAndDevelopmentOnlyDependenciesAreOnTheClasspath() throws IOException { copyClasspathApplication(); BuildResult result = this.gradleBuild.build("bootRun"); BuildTask task = result.task(":bootRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("commons-lang3-3.12.0.jar"); } private void copyMainClassApplication() throws IOException { copyApplication("main"); } private void copyClasspathApplication() throws IOException { copyApplication("classpath"); } private void copyJvmArgsApplication() throws IOException { copyApplication("jvmargs"); } private void copyApplication(String name) throws IOException { File output = new File(this.gradleBuild.getProjectDir(), "src/main/java/com/example/bootrun/" + name); output.mkdirs(); FileSystemUtils.copyRecursively(new File("src/test/resources/com/example/bootrun/" + name), output); } private String canonicalPathOf(String path) throws IOException { return new File(this.gradleBuild.getProjectDir(), path).getCanonicalPath(); } private void createStandardJar(File location) throws IOException { createJar(location, (attributes) -> { }); } private void createDependenciesStarterJar(File location) throws IOException { createJar(location, (attributes) -> attributes.putValue("Spring-Boot-Jar-Type", "dependencies-starter")); } private void createJar(File location, Consumer attributesConfigurer) throws IOException { location.getParentFile().mkdirs(); Manifest manifest = new Manifest(); Attributes attributes = manifest.getMainAttributes(); attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); attributesConfigurer.accept(attributes); new JarOutputStream(new FileOutputStream(location), manifest).close(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests.java ================================================ /* * 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.gradle.tasks.run; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.function.Consumer; import java.util.jar.Attributes; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import org.assertj.core.api.Assumptions; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; import org.gradle.util.GradleVersion; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for the {@link BootRun} task configured to use the test source set. * * @author Andy Wilkinson */ @GradleCompatibility(configurationCache = true) class BootTestRunIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @TestTemplate void basicExecution() throws IOException { copyClasspathApplication(); BuildResult result = this.gradleBuild.build("bootTestRun"); BuildTask task = result.task(":bootTestRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("1. " + canonicalPathOf("build/classes/java/test")) .contains("2. " + canonicalPathOf("build/resources/test")) .contains("3. " + canonicalPathOf("build/classes/java/main")) .contains("4. " + canonicalPathOf("build/resources/main")); } @TestTemplate void defaultJvmArgs() throws IOException { copyJvmArgsApplication(); BuildResult result = this.gradleBuild.build("bootTestRun"); BuildTask task = result.task(":bootTestRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("-XX:TieredStopAtLevel=1"); } @TestTemplate void optimizedLaunchDisabledJvmArgs() throws IOException { copyJvmArgsApplication(); BuildResult result = this.gradleBuild.build("bootTestRun"); BuildTask task = result.task(":bootTestRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).doesNotContain("-Xverify:none").doesNotContain("-XX:TieredStopAtLevel=1"); } @TestTemplate void applicationPluginJvmArgumentsAreUsed() throws IOException { if (this.gradleBuild.isConfigurationCache()) { // https://github.com/gradle/gradle/pull/23924 GradleVersion gradleVersion = GradleVersion.version(this.gradleBuild.getGradleVersion()); Assumptions.assumeThat(gradleVersion) .isLessThan(GradleVersion.version("8.0")) .isGreaterThanOrEqualTo(GradleVersion.version("8.1-rc-1")); } copyJvmArgsApplication(); BuildResult result = this.gradleBuild.build("bootTestRun"); BuildTask task = result.task(":bootTestRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("-Dcom.bar=baz") .contains("-Dcom.foo=bar") .contains("-XX:TieredStopAtLevel=1"); } @TestTemplate void jarTypeFilteringIsAppliedToTheClasspath() throws IOException { copyClasspathApplication(); File flatDirRepository = new File(this.gradleBuild.getProjectDir(), "repository"); createDependenciesStarterJar(new File(flatDirRepository, "starter.jar")); createStandardJar(new File(flatDirRepository, "standard.jar")); BuildResult result = this.gradleBuild.build("bootTestRun"); BuildTask task = result.task(":bootTestRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("standard.jar").doesNotContain("starter.jar"); } @TestTemplate void failsGracefullyWhenNoTestMainMethodIsFound() throws IOException { copyApplication("nomain"); BuildResult result = this.gradleBuild.buildAndFail("bootTestRun"); BuildTask task = result.task(":bootTestRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.FAILED); if (this.gradleBuild.isConfigurationCache() && this.gradleBuild.gradleVersionIsAtLeast("8.0")) { assertThat(result.getOutput()) .contains("Main class name has not been configured and it could not be resolved from classpath"); } else { assertThat(result.getOutput()) .contains("Main class name has not been configured and it could not be resolved from classpath " + canonicalPathOf("build/classes/java/test")); } } @TestTemplate void developmentOnlyDependenciesAreNotOnTheClasspath() throws IOException { copyClasspathApplication(); BuildResult result = this.gradleBuild.build("bootTestRun"); BuildTask task = result.task(":bootTestRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).doesNotContain("commons-lang3-3.12.0.jar"); } @TestTemplate void testAndDevelopmentOnlyDependenciesAreOnTheClasspath() throws IOException { copyClasspathApplication(); BuildResult result = this.gradleBuild.build("bootTestRun"); BuildTask task = result.task(":bootTestRun"); assertThat(task).isNotNull(); assertThat(task.getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("commons-lang3-3.12.0.jar"); } private void copyClasspathApplication() throws IOException { copyApplication("classpath"); } private void copyJvmArgsApplication() throws IOException { copyApplication("jvmargs"); } private void copyApplication(String name) throws IOException { File output = new File(this.gradleBuild.getProjectDir(), "src/test/java/com/example/boottestrun/" + name); output.mkdirs(); FileSystemUtils.copyRecursively(new File("src/test/resources/com/example/boottestrun/" + name), output); } private String canonicalPathOf(String path) throws IOException { return new File(this.gradleBuild.getProjectDir(), path).getCanonicalPath(); } private void createStandardJar(File location) throws IOException { createJar(location, (attributes) -> { }); } private void createDependenciesStarterJar(File location) throws IOException { createJar(location, (attributes) -> attributes.putValue("Spring-Boot-Jar-Type", "dependencies-starter")); } private void createJar(File location, Consumer attributesConfigurer) throws IOException { location.getParentFile().mkdirs(); Manifest manifest = new Manifest(); Attributes attributes = manifest.getMainAttributes(); attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); attributesConfigurer.accept(attributes); new JarOutputStream(new FileOutputStream(location), manifest).close(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/PluginClasspathGradleBuild.java ================================================ /* * 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.gradle.testkit; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.annotation.JsonView; import com.google.protobuf.gradle.ProtobufPlugin; import com.sun.jna.Platform; import io.spring.gradle.dependencymanagement.DependencyManagementPlugin; import org.antlr.v4.runtime.Lexer; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.cyclonedx.gradle.CyclonedxPlugin; import org.gradle.testkit.runner.GradleRunner; import org.jetbrains.kotlin.gradle.fus.BuildUidService; import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin; import org.jetbrains.kotlin.project.model.LanguageSettings; import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion; import org.tomlj.Toml; import tools.jackson.core.JsonParser; import tools.jackson.databind.JacksonModule; import org.springframework.asm.ClassVisitor; import org.springframework.boot.buildpack.platform.build.BuildRequest; import org.springframework.boot.loader.tools.Layers; import org.springframework.boot.testsupport.BuildOutput; import org.springframework.boot.testsupport.gradle.testkit.Dsl; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; /** * Custom {@link GradleBuild} that configures the * {@link GradleRunner#withPluginClasspath(Iterable) plugin classpath}. * * @author Andy Wilkinson * @author Scott Frederick */ public class PluginClasspathGradleBuild extends GradleBuild { private boolean kotlin; public PluginClasspathGradleBuild(BuildOutput buildOutput) { super(buildOutput); } public PluginClasspathGradleBuild(BuildOutput buildOutput, Dsl dsl) { super(buildOutput, dsl); } public PluginClasspathGradleBuild kotlin() { this.kotlin = true; return this; } @Override public GradleRunner prepareRunner(String... arguments) throws IOException { return super.prepareRunner(arguments).withPluginClasspath(pluginClasspath()); } private List pluginClasspath() { List classpath = new ArrayList<>(); classpath.add(new File("bin/main")); classpath.add(new File("build/classes/java/main")); classpath.add(new File("build/resources/main")); classpath.add(new File(pathOfJarContaining(Layers.class))); classpath.add(new File(pathOfJarContaining(ClassVisitor.class))); classpath.add(new File(pathOfJarContaining(DependencyManagementPlugin.class))); if (this.kotlin) { classpath.add(new File(pathOfJarContaining("org.jetbrains.kotlin.cli.common.PropertiesKt"))); classpath.add(new File(pathOfJarContaining(KotlinToolingVersion.class))); classpath.add(new File(pathOfJarContaining("org.jetbrains.kotlin.build.report.metrics.BuildTimes"))); classpath.add(new File(pathOfJarContaining("org.jetbrains.kotlin.buildtools.api.CompilationService"))); classpath.add(new File(pathOfJarContaining("org.jetbrains.kotlin.daemon.client.KotlinCompilerClient"))); classpath.add(new File(pathOfJarContaining(KotlinCompilerPluginSupportPlugin.class))); classpath.add(new File(pathOfJarContaining(LanguageSettings.class))); classpath.add(new File(pathOfJarContaining(BuildUidService.class))); } classpath.add(new File(pathOfJarContaining("org.apache.commons.lang3.ArrayFill"))); classpath.add(new File(pathOfJarContaining("org.apache.commons.io.Charsets"))); classpath.add(new File(pathOfJarContaining(ArchiveEntry.class))); classpath.add(new File(pathOfJarContaining(BuildRequest.class))); classpath.add(new File(pathOfJarContaining(HttpClientConnectionManager.class))); classpath.add(new File(pathOfJarContaining(HttpRequest.class))); classpath.add(new File(pathOfJarContaining(HttpVersionPolicy.class))); classpath.add(new File(pathOfJarContaining(JacksonModule.class))); classpath.add(new File(pathOfJarContaining(JsonParser.class))); classpath.add(new File(pathOfJarContaining("com.github.openjson.JSONObject"))); classpath.add(new File(pathOfJarContaining(JsonView.class))); classpath.add(new File(pathOfJarContaining(Platform.class))); classpath.add(new File(pathOfJarContaining(Toml.class))); classpath.add(new File(pathOfJarContaining(Lexer.class))); classpath.add(new File(pathOfJarContaining("org.graalvm.buildtools.gradle.NativeImagePlugin"))); classpath.add(new File(pathOfJarContaining("org.graalvm.reachability.GraalVMReachabilityMetadataRepository"))); classpath.add(new File(pathOfJarContaining("org.graalvm.buildtools.utils.SharedConstants"))); // Cyclonedx dependencies classpath.add(new File(pathOfJarContaining(CyclonedxPlugin.class))); classpath.add(new File(pathOfJarContaining("com.ctc.wstx.api.WriterConfig"))); classpath.add(new File(pathOfJarContaining("com.fasterxml.jackson.core.Versioned"))); classpath.add(new File(pathOfJarContaining("com.fasterxml.jackson.databind.JsonSerializer"))); classpath.add(new File(pathOfJarContaining("com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator"))); classpath.add(new File(pathOfJarContaining("com.github.packageurl.MalformedPackageURLException"))); classpath.add(new File(pathOfJarContaining("com.google.common.collect.ImmutableMap"))); classpath.add(new File(pathOfJarContaining("com.networknt.schema.resource.SchemaMappers"))); classpath.add(new File(pathOfJarContaining("org.apache.commons.collections4.CollectionUtils"))); classpath.add(new File(pathOfJarContaining("org.apache.maven.model.building.ModelBuildingException"))); classpath.add(new File(pathOfJarContaining("org.codehaus.plexus.util.xml.pull.XmlPullParserException"))); classpath.add(new File(pathOfJarContaining("org.codehaus.stax2.ri.Stax2WriterAdapter"))); classpath.add(new File(pathOfJarContaining("org.cyclonedx.model.ExternalReference"))); // Protobuf dependencies classpath.add(new File(pathOfJarContaining(ProtobufPlugin.class))); classpath.add(new File(pathOfJarContaining("com.google.gradle.osdetector.OsDetectorPlugin"))); classpath.add(new File(pathOfJarContaining("kr.motd.maven.os.FileOperationProvider"))); return classpath; } private String pathOfJarContaining(String className) { try { return pathOfJarContaining(Class.forName(className)); } catch (ClassNotFoundException ex) { throw new IllegalArgumentException(ex); } } private String pathOfJarContaining(Class type) { return type.getProtectionDomain().getCodeSource().getLocation().getPath(); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/package-info.java ================================================ /* * 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. */ @NullMarked package org.springframework.boot.gradle.testkit; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/com/example/bootjar/classpath/BootJarClasspathApplication.java ================================================ /* * 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 com.example.bootjar.classpath; import java.net.URL; import java.net.URLClassLoader; /** * Application used for testing classpath handling with BootJar. * * @author Andy Wilkinson */ public class BootJarClasspathApplication { protected BootJarClasspathApplication() { } public static void main(String[] args) { int i = 1; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); for (URL url : ((URLClassLoader) classLoader).getURLs()) { System.out.println(i++ + ". " + url.getFile()); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/com/example/bootjar/main/CustomMainClass.java ================================================ /* * 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 com.example.bootjar.main; /** * Application used for testing {@code BootRun}'s main class configuration. * * @author Andy Wilkinson */ public class CustomMainClass { protected CustomMainClass() { } public static void main(String[] args) { System.out.println(CustomMainClass.class.getName()); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/com/example/bootrun/classpath/BootRunClasspathApplication.java ================================================ /* * 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 com.example.bootrun.classpath; import java.io.File; import java.lang.management.ManagementFactory; /** * Application used for testing {@code BootRun}'s classpath handling. * * @author Andy Wilkinson */ public class BootRunClasspathApplication { protected BootRunClasspathApplication() { } public static void main(String[] args) { System.out.println("Main class name = " + BootRunClasspathApplication.class.getName()); int i = 1; for (String entry : ManagementFactory.getRuntimeMXBean().getClassPath().split(File.pathSeparator)) { System.out.println(i++ + ". " + entry); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/com/example/bootrun/jvmargs/BootRunJvmArgsApplication.java ================================================ /* * 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 com.example.bootrun.jvmargs; import java.lang.management.ManagementFactory; /** * Application used for testing {@code BootRun}'s JVM argument handling. * * @author Andy Wilkinson */ public class BootRunJvmArgsApplication { protected BootRunJvmArgsApplication() { } public static void main(String[] args) { int i = 1; for (String entry : ManagementFactory.getRuntimeMXBean().getInputArguments()) { System.out.println(i++ + ". " + entry); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/com/example/bootrun/main/CustomMainClass.java ================================================ /* * 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 com.example.bootrun.main; /** * Application used for testing {@code BootRun}'s main class configuration. * * @author Andy Wilkinson */ public class CustomMainClass { protected CustomMainClass() { } public static void main(String[] args) { System.out.println(CustomMainClass.class.getName()); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/com/example/boottestrun/classpath/BootTestRunClasspathApplication.java ================================================ /* * 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 com.example.boottestrun.classpath; import java.io.File; import java.lang.management.ManagementFactory; /** * Application used for testing {@code bootTestRun}'s classpath handling. * * @author Andy Wilkinson */ public class BootTestRunClasspathApplication { protected BootTestRunClasspathApplication() { } public static void main(String[] args) { System.out.println("Main class name = " + BootTestRunClasspathApplication.class.getName()); int i = 1; for (String entry : ManagementFactory.getRuntimeMXBean().getClassPath().split(File.pathSeparator)) { System.out.println(i++ + ". " + entry); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/com/example/boottestrun/jvmargs/BootTestRunJvmArgsApplication.java ================================================ /* * 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 com.example.boottestrun.jvmargs; import java.lang.management.ManagementFactory; /** * Application used for testing {@code bootTestRun}'s JVM argument handling. * * @author Andy Wilkinson */ public class BootTestRunJvmArgsApplication { protected BootTestRunJvmArgsApplication() { } public static void main(String[] args) { int i = 1; for (String entry : ManagementFactory.getRuntimeMXBean().getInputArguments()) { System.out.println(i++ + ". " + entry); } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/com/example/boottestrun/nomain/BootTestRunNoMain.java ================================================ /* * 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 com.example.boottestrun.nomain; /** * Application used for testing {@code bootTestRun}'s handling of no test main method * * @author Andy Wilkinson */ public class BootTestRunNoMain { } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/com/example/bootwar/main/CustomMainClass.java ================================================ /* * 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 com.example.bootwar.main; /** * Application used for testing {@code BootRun}'s main class configuration. * * @author Andy Wilkinson */ public class CustomMainClass { protected CustomMainClass() { } public static void main(String[] args) { System.out.println(CustomMainClass.class.getName()); } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/dsl/BuildInfoDslIntegrationTests-additionalProperties.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } group = 'com.example' version = '1.0' springBoot { buildInfo { properties { additional = [ 'a': 'alpha', 'b': providers.provider({'bravo'}) ] } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/dsl/BuildInfoDslIntegrationTests-basicJar.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } group = 'com.example' version = '1.0' springBoot { buildInfo() mainClass = "com.example.Main" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/dsl/BuildInfoDslIntegrationTests-basicWar.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } group = 'com.example' version = '1.0' springBoot { buildInfo() mainClass = "com.example.Main" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/dsl/BuildInfoDslIntegrationTests-classesDependency.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } springBoot { buildInfo() } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/dsl/BuildInfoDslIntegrationTests-jarWithCustomName.gradle ================================================ /* * 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. */ import org.gradle.util.GradleVersion plugins { id 'java' id 'org.springframework.boot' version '{version}' } group = 'com.example' version = '1.0' bootJar { if (GradleVersion.current().compareTo(GradleVersion.version('6.0.0')) < 0) { baseName = 'foo' } else { archiveBaseName = 'foo' } } springBoot { buildInfo() } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/dsl/BuildInfoDslIntegrationTests-warWithCustomName.gradle ================================================ /* * 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. */ import org.gradle.util.GradleVersion plugins { id 'war' id 'org.springframework.boot' version '{version}' } group = 'com.example' version = '1.0' bootWar { if (GradleVersion.current().compareTo(GradleVersion.version('6.0.0')) < 0) { baseName = 'foo' } else { archiveBaseName = 'foo' } } springBoot { buildInfo() } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests-applicationNameCanBeUsedToCustomizeDistributionName.gradle ================================================ /* * 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. */ plugins { id 'java' id 'application' id 'org.springframework.boot' version '{version}' } application { applicationName = 'custom' } bootJar { mainClass = 'com.example.ExampleApplication' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests-scriptsHaveCorrectPermissions.gradle ================================================ /* * 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. */ plugins { id 'java' id 'application' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.ExampleApplication' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests-tarDistributionForJarCanBeBuilt.gradle ================================================ /* * 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. */ plugins { id 'java' id 'application' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.ExampleApplication' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests-tarDistributionForWarCanBeBuilt.gradle ================================================ /* * 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. */ plugins { id 'war' id 'application' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.ExampleApplication' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests-taskConfigurationIsAvoided.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' id 'application' } tasks.configureEach { println "Configuring ${it.path}" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests-zipDistributionForJarCanBeBuilt.gradle ================================================ /* * 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. */ plugins { id 'java' id 'application' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.ExampleApplication' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests-zipDistributionForWarCanBeBuilt.gradle ================================================ /* * 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. */ plugins { id 'war' id 'application' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.ExampleApplication' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/ApplicationPluginActionIntegrationTests.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' } if (project.hasProperty('applyApplicationPlugin')) { apply plugin: 'application' application { applicationDefaultJvmArgs = ['-Dcom.example.a=alpha', '-Dcom.example.b=bravo'] } } task('taskExists') { doFirst { println "${taskName} exists = ${tasks.findByName(taskName) != null}" } } task('distributionExists') { def distributions = project.extensions.findByType(DistributionContainer) doFirst { boolean found = distributions != null && distributions.findByName(distributionName) != null println "${distributionName} exists = ${found}" } } task('javaCompileEncoding') { doFirst { tasks.withType(JavaCompile) { println "${name} = ${options.encoding}" } } } task('startScriptsDefaultJvmOpts') { doFirst { tasks.getByName("bootStartScripts") { println "$name defaultJvmOpts = $defaultJvmOpts" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/CyclonedxPluginActionIntegrationTests-sbomIsIncludedInUberJar.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } apply plugin: 'org.cyclonedx.bom' group = 'com.example' version = '0.0.1' tasks.named("bootJar") { mainClass = 'com.example.ExampleApplication' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/CyclonedxPluginActionIntegrationTests-sbomIsIncludedInUberWar.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'war' } apply plugin: 'org.cyclonedx.bom' group = 'com.example' version = '0.0.1' tasks.named("bootWar") { mainClass = 'com.example.ExampleApplication' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/DependencyManagementPluginActionIntegrationTests-helpfulErrorWhenVersionlessDependencyFailsToResolve.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } dependencies { implementation('org.springframework.boot:spring-boot-starter-web') } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/DependencyManagementPluginActionIntegrationTests.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } if (project.hasProperty('applyDependencyManagementPlugin')) { apply plugin: 'io.spring.dependency-management' dependencyManagement { resolutionStrategy { eachDependency { if (it.requested.group == 'org.springframework.boot') { it.useVersion project.bootVersion } } } } } repositories { maven { url = 'repository' } } tasks.register("doesNotHaveDependencyManagement") { def extensions = project.extensions doLast { if (extensions.findByName('dependencyManagement') != null) { throw new GradleException('Found dependency management extension') } } } tasks.register("hasDependencyManagement") { doLast { if (!dependencyManagement.managedVersions) { throw new GradleException('No managed versions have been configured') } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-additionalMetadataLocationsConfiguredWhenProcessorIsPresent.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } repositories { flatDir { dirs 'libs' } } dependencies { annotationProcessor(':spring-boot-configuration-processor:1.2.3') } compileJava { doLast { println "${name} compiler args: ${options.compilerArgs}" } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-additionalMetadataLocationsNotConfiguredWhenProcessorIsAbsent.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } compileJava { doLast { println "${name} compiler args: ${options.compilerArgs}" } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-applyingJavaPluginCreatesBootJarTask.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-applyingJavaPluginCreatesBootRunTask.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-applyingJavaPluginCreatesBootTestRunTask.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-applyingJavaPluginCreatesDevelopmentOnlyConfiguration.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } gradle.taskGraph.whenReady { println "developmentOnly exists = ${configurations.findByName('developmentOnly') != null}" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-applyingJavaPluginCreatesTestAndDevelopmentOnlyConfiguration.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } gradle.taskGraph.whenReady { println "testAndDevelopmentOnly exists = ${configurations.findByName('testAndDevelopmentOnly') != null}" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-assembleRunsBootJarAndJar.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-compileClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.12.0") } gradle.taskGraph.whenReady { configurations.compileClasspath.resolve().each { println it } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-compileClasspathDoesNotIncludeTestAndDevelopmentOnlyDependencies.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } repositories { mavenCentral() } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") } gradle.taskGraph.whenReady { configurations.compileClasspath.resolve().each { println it } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-errorMessageIsHelpfulWhenMainClassCannotBeResolved.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-javaCompileTasksCanOverrideDefaultParametersCompilerFlag.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } springBoot { mainClass = "com.example.Main" } tasks.withType(JavaCompile) { options.compilerArgs = ['-Xlint:all'] } gradle.taskGraph.whenReady { gradle.taskGraph.allTasks.each { if (it instanceof JavaCompile) { println "${it.name} compiler args: ${it.options.compilerArgs}" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-javaCompileTasksUseParametersAndAdditionalCompilerFlags.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } springBoot { mainClass = "com.example.Main" } tasks.withType(JavaCompile) { options.compilerArgs << '-Xlint:all' } gradle.taskGraph.whenReady { gradle.taskGraph.allTasks.each { if (it instanceof JavaCompile) { println "${it.name} compiler args: ${it.options.compilerArgs}" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-javaCompileTasksUseParametersCompilerFlagByDefault.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } springBoot { mainClass = "com.example.Main" } gradle.taskGraph.whenReady { gradle.taskGraph.allTasks.each { if (it instanceof JavaCompile) { println "${it.name} compiler args: ${it.options.compilerArgs}" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-javaCompileTasksUseUtf8Encoding.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } gradle.taskGraph.whenReady { gradle.taskGraph.allTasks.each { if (it instanceof JavaCompile) { println "${it.name} = ${it.options.encoding}" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-noBootJarTaskWithoutJavaPluginApplied.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-noBootRunTaskWithoutJavaPluginApplied.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-noBootTestRunTaskWithoutJavaPluginApplied.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-productionRuntimeClasspathIsConfiguredWithAttributesThatMatchRuntimeClasspath.gradle ================================================ /* * 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. */ def collectAttributes(String configurationName) { def attributes = configurations.findByName(configurationName).attributes def keys = new TreeSet<>((a1, a2) -> a1.name.compareTo(a2.name)) keys.addAll(attributes.keySet()) keys.collect { key -> "${key}: ${attributes.getAttribute(key)}" } } plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } gradle.taskGraph.whenReady { def runtimeClasspathAttributes = collectAttributes("runtimeClasspath") def productionRuntimeClasspathAttributes = collectAttributes("productionRuntimeClasspath") println("runtimeClasspath: ${runtimeClasspathAttributes}") println("productionRuntimeClasspath: ${productionRuntimeClasspathAttributes}") } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-productionRuntimeClasspathIsConfiguredWithResolvabilityAndConsumabilityThatMatchesRuntimeClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } gradle.taskGraph.whenReady { analyzeConfiguration('productionRuntimeClasspath') analyzeConfiguration('runtimeClasspath') } def analyzeConfiguration(String configurationName) { Configuration configuration = configurations.findByName(configurationName) println "$configurationName canBeResolved: ${configuration.canBeResolved}" println "$configurationName canBeConsumed: ${configuration.canBeConsumed}" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-runtimeClasspathIncludesDevelopmentOnlyDependencies.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.12.0") } gradle.taskGraph.whenReady { configurations.runtimeClasspath.resolve().each { println it } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-runtimeClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } repositories { mavenCentral() } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") } gradle.taskGraph.whenReady { configurations.runtimeClasspath.resolve().each { println it } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-taskConfigurationIsAvoided.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } tasks.configureEach { println "Configuring ${it.path}" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testCompileClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.12.0") } gradle.taskGraph.whenReady { configurations.testCompileClasspath.resolve().each { println it } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testCompileClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } repositories { mavenCentral() } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") } gradle.taskGraph.whenReady { configurations.testCompileClasspath.resolve().each { println it } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testRuntimeClasspathDoesNotIncludeDevelopmentOnlyDependencies.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.12.0") } gradle.taskGraph.whenReady { configurations.testRuntimeClasspath.resolve().each { println it } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testRuntimeClasspathIncludesTestAndDevelopmentOnlyDependencies.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } springBoot { mainClass = "com.example.Main" } repositories { mavenCentral() } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") } gradle.taskGraph.whenReady { configurations.testRuntimeClasspath.resolve().each { println it } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests-compileAotJavaHasTransitiveRuntimeDependenciesOnItsClasspathWhenUsingKotlin.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'org.springframework.boot.aot' id 'java' id 'org.jetbrains.kotlin.jvm' } repositories { mavenCentral() } dependencies { implementation "org.hibernate.orm:hibernate-core:6.1.1.Final" } task('compileAotJavaClasspath') { doFirst { tasks.findByName('compileAotJava').classpath.files.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests-compileAotTestJavaHasTransitiveRuntimeDependenciesOnItsClasspathWhenUsingKotlin.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'org.springframework.boot.aot' id 'java' id "org.jetbrains.kotlin.jvm" } repositories { mavenCentral() maven { url = 'repository' } } configurations.all { resolutionStrategy { eachDependency { if (it.requested.group == 'org.springframework.boot') { it.useVersion project.bootVersion } } } } dependencies { implementation "org.hibernate.orm:hibernate-core:6.1.1.Final" } task('compileAotTestJavaClasspath') { doFirst { tasks.findByName('compileAotTestJava').classpath.files.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests-kotlinCompileTasksCanOverrideDefaultJavaParametersFlag.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' } apply plugin: 'org.jetbrains.kotlin.jvm' import org.jetbrains.kotlin.gradle.tasks.KotlinCompile tasks.withType(KotlinCompile) { compilerOptions { javaParameters = false } } task('kotlinCompileTasksJavaParameters') { doFirst { tasks.withType(KotlinCompile) { println "${name} java parameters: ${compilerOptions.javaParameters.get()}" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests-kotlinCompileTasksUseJavaParametersFlagByDefault.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' } apply plugin: 'org.jetbrains.kotlin.jvm' import org.jetbrains.kotlin.gradle.tasks.KotlinCompile task('kotlinCompileTasksJavaParameters') { doFirst { tasks.withType(KotlinCompile) { println "${name} java parameters: ${compilerOptions.javaParameters.get()}" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests-kotlinVersionPropertyIsSet.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' } apply plugin: 'io.spring.dependency-management' apply plugin: 'org.jetbrains.kotlin.jvm' dependencyManagement { resolutionStrategy { eachDependency { if (it.requested.group == 'org.springframework.boot') { it.useVersion project.bootVersion } } } } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation('org.jetbrains.kotlin:kotlin-stdlib-jdk8') } tasks.register("kotlinVersion") { def properties = project.properties doLast { def kotlinVersion = properties.getOrDefault('kotlin.version', 'none') println "Kotlin version: ${kotlinVersion}" } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests-noKotlinVersionPropertyWithoutKotlinPlugin.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' } tasks.register("kotlinVersion") { def properties = project.properties doLast { def kotlinVersion = properties.getOrDefault('kotlin.version', 'none') println "Kotlin version: ${kotlinVersion}" } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests-taskConfigurationIsAvoided.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' } apply plugin: 'org.jetbrains.kotlin.jvm' tasks.configureEach { println "Configuring ${it.path}" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/MavenPluginActionIntegrationTests.gradle ================================================ /* * 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. */ plugins { id 'java' id 'maven' id 'org.springframework.boot' version '{version}' } task('conf2ScopeMappings') { doFirst { tasks.getByName('uploadBootArchives').repositories.withType(MavenResolver) { println "Conf2ScopeMappings = ${pom.scopeMappings.mappings.size()}" } } } uploadBootArchives { repositories { mavenDeployer { } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-applyingNativeImagePluginAppliesAotPlugin.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' } apply plugin: 'org.graalvm.buildtools.native' task('aotPluginApplied') { doFirst { println "org.springframework.boot.aot applied = ${plugins.hasPlugin('org.springframework.boot.aot')}" } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-classesGeneratedDuringAotProcessingAreOnTheNativeImageClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } apply plugin: 'org.graalvm.buildtools.native' repositories { mavenCentral() } task('checkNativeImageClasspath') { doFirst { tasks.nativeCompile.options.get().classpath.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-classesGeneratedDuringAotTestProcessingAreOnTheTestNativeImageClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } apply plugin: 'org.graalvm.buildtools.native' repositories { mavenCentral() } dependencies { testImplementation("org.junit.jupiter:junit-jupiter:{junitVersion}") } task('checkTestNativeImageClasspath') { doFirst { tasks.nativeTestCompile.options.get().classpath.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-developmentOnlyDependenciesDoNotAppearInNativeImageClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } apply plugin: 'org.graalvm.buildtools.native' repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.12.0") } task('checkNativeImageClasspath') { doFirst { tasks.nativeCompile.options.get().classpath.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-nativeEntryIsAddedToManifest.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' id 'org.springframework.boot.aot' } apply plugin: 'org.graalvm.buildtools.native' ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-reachabilityMetadataConfigurationFilesAreCopiedToJar.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' id 'org.springframework.boot.aot' } apply plugin: 'org.graalvm.buildtools.native' repositories { mavenCentral() } dependencies { implementation "ch.qos.logback:logback-classic:1.2.11" implementation "org.jline:jline:3.21.0" } // see https://github.com/graalvm/native-build-tools/issues/302 graalvmNative { agent { tasksToInstrumentPredicate = { t -> false } as java.util.function.Predicate } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-reachabilityMetadataConfigurationFilesFromFileRepositoryAreCopiedToJar.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' id 'org.springframework.boot.aot' } apply plugin: 'org.graalvm.buildtools.native' repositories { mavenCentral() } dependencies { implementation "ch.qos.logback:logback-classic:1.2.11" implementation "org.jline:jline:3.21.0" } graalvmNative { metadataRepository { uri(file("reachability-metadata-repository")) } // see https://github.com/graalvm/native-build-tools/issues/302 agent { tasksToInstrumentPredicate = { t -> false } as java.util.function.Predicate } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-testAndDevelopmentOnlyDependenciesDoNotAppearInNativeImageClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } apply plugin: 'org.graalvm.buildtools.native' repositories { mavenCentral() } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") } task('checkNativeImageClasspath') { doFirst { tasks.nativeCompile.options.get().classpath.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/OnlyDependencyManagementIntegrationTests.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' apply false id 'java' } apply plugin: 'io.spring.dependency-management' repositories { maven { url = 'repository' } } dependencyManagement { resolutionStrategy { eachDependency { if (it.requested.group == 'org.springframework.boot') { it.useVersion project.bootVersion } } } imports { mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/ProtobufPluginActionIntegrationTests-usesVersionOfGrpcPluginDependencyWhenSpecified.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } apply plugin: 'com.google.protobuf' group = 'com.example' version = '0.0.1' repositories { mavenCentral() } protobuf { plugins { grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.78.0" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/ProtobufPluginActionIntegrationTests-usesVersionOfProtocDependencyWhenSpecified.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } apply plugin: 'com.google.protobuf' group = 'com.example' version = '0.0.1' repositories { mavenCentral() } protobuf { protoc { artifact = "com.google.protobuf:protoc:4.33.5" } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/ProtobufPluginActionIntegrationTests.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } apply plugin: 'com.google.protobuf' group = 'com.example' version = '0.0.1' repositories { mavenCentral() } dependencies { implementation("com.google.protobuf:protobuf-java-util:4.34.0") implementation("io.grpc:grpc-util:1.79.0") } tasks.register("protocArtifact") { doFirst { protobuf { protoc { println "protoc artifact: '$artifact'" } } } } tasks.register("grpcPlugin") { doFirst { protobuf { plugins { grpc { println "grpc plugin artifact: '$artifact'" } } } } } tasks.register("generateProtoTasksGrpcPluginOptions") { doFirst { tasks.withType(com.google.protobuf.gradle.GenerateProtoTask).each { println "${it.name}: ${it.pluginsForCaching.collect { it.options }}" } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-applyingAotPluginCreatesProcessAotTask.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'org.springframework.boot.aot' id 'java' } task('taskExists') { doFirst { println "${taskName} exists = ${tasks.findByName(taskName) != null}" } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-applyingAotPluginCreatesProcessTestAotTask.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'org.springframework.boot.aot' id 'java' } task('taskExists') { doFirst { println "${taskName} exists = ${tasks.findByName(taskName) != null}" } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-applyingAotPluginDoesNotPreventConfigurationOfJavaToolchainLanguageVersion.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'org.springframework.boot.aot' id 'java' } java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-noProcessAotTaskWithoutAotPluginApplied.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'java' } task('taskExists') { doFirst { println "${taskName} exists = ${tasks.findByName(taskName) != null}" } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-noProcessTestAotTaskWithoutAotPluginApplied.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'java' } task('taskExists') { doFirst { println "${taskName} exists = ${tasks.findByName(taskName) != null}" } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processAotDoesNotHaveDevelopmentOnlyDependenciesOnItsClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } apply plugin: 'org.springframework.boot.aot' repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.12.0") } task('processAotClasspath') { doFirst { tasks.processAot.classpath.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processAotDoesNotHaveTestAndDevelopmentOnlyDependenciesOnItsClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } apply plugin: 'org.springframework.boot.aot' repositories { mavenCentral() } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") } task('processAotClasspath') { doFirst { tasks.processAot.classpath.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processAotHasLibraryResourcesOnItsClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'org.springframework.boot.aot' id 'java' } dependencies { implementation project(":library") } task('processAotClasspath') { doFirst { tasks.findByName('processAot').classpath.files.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processAotHasTransitiveRuntimeDependenciesOnItsClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'org.springframework.boot.aot' id 'java' } repositories { mavenCentral() } dependencies { implementation "org.hibernate.orm:hibernate-core:6.1.1.Final" } task('processAotClasspath') { doFirst { tasks.findByName('processAot').classpath.files.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processAotIsSkippedWhenProjectHasNoMainSource.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'org.springframework.boot.aot' id 'java' } repositories { mavenCentral() } springBoot { mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processAotRunsWhenProjectHasMainSource.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'org.springframework.boot.aot' id 'java' } repositories { mavenCentral() } springBoot { mainClass = 'com.example.Main' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processTestAotDoesNotHaveDevelopmentOnlyDependenciesOnItsClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } apply plugin: 'org.springframework.boot.aot' repositories { mavenCentral() maven { url = 'repository' } } configurations.all { resolutionStrategy { eachDependency { if (it.requested.group == 'org.springframework.boot') { it.useVersion project.bootVersion } } } } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.12.0") } task('processTestAotClasspath') { doFirst { tasks.processTestAot.classpath.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processTestAotHasLibraryResourcesOnItsClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'org.springframework.boot.aot' id 'java' } repositories { mavenCentral() maven { url = 'repository' } } configurations.all { resolutionStrategy { eachDependency { if (it.requested.group == 'org.springframework.boot') { it.useVersion project.bootVersion } } } } dependencies { implementation project(":library") } task('processTestAotClasspath') { dependsOn configurations.processTestAotClasspath doFirst { configurations.processTestAotClasspath.files.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processTestAotHasTestAndDevelopmentOnlyDependenciesOnItsClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'java' } apply plugin: 'org.springframework.boot.aot' repositories { mavenCentral() maven { url = 'repository' } } configurations.all { resolutionStrategy { eachDependency { if (it.requested.group == 'org.springframework.boot') { it.useVersion project.bootVersion } } } } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") } task('processTestAotClasspath') { doFirst { tasks.processTestAot.classpath.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processTestAotHasTransitiveRuntimeDependenciesOnItsClasspath.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'org.springframework.boot.aot' id 'java' } repositories { mavenCentral() maven { url = 'repository' } } configurations.all { resolutionStrategy { eachDependency { if (it.requested.group == 'org.springframework.boot') { it.useVersion project.bootVersion } } } } dependencies { implementation "org.hibernate.orm:hibernate-core:6.1.1.Final" } task('processTestAotClasspath') { dependsOn configurations.processTestAotClasspath doFirst { configurations.processTestAotClasspath.files.each { println it } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests-processTestAotIsSkippedWhenProjectHasNoTestSource.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' id 'org.springframework.boot.aot' id 'java' } repositories { mavenCentral() } springBoot { mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootPluginIntegrationTests-unresolvedDependenciesAreAnalyzedWhenDependencyResolutionFails.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } repositories { flatDir { dirs 'libs' } } dependencies { implementation('org.springframework.boot:spring-boot-starter') } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/SpringBootPluginIntegrationTests.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/WarPluginActionIntegrationTests-assembleRunsBootWarAndWar.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/WarPluginActionIntegrationTests-taskConfigurationIsAvoided.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' id 'war' } tasks.configureEach { println "Configuring ${it.path}" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/WarPluginActionIntegrationTests.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' } if (project.hasProperty('applyWarPlugin')) { apply plugin: 'war' } task('taskExists') { doFirst { println "${taskName} exists = ${tasks.findByName(taskName) != null}" } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-basicExecution.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' apply false } version = '0.1.0' tasks.register("buildInfo", org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) { properties { artifact = 'foo' group = 'foo' name = 'foo' additional = ['additional': 'foo'] } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-defaultValues.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' apply false } tasks.register("buildInfo", org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-excludeProperties.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' apply false } group = 'foo' version = '0.1.0' tasks.register("buildInfo", org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) { excludes = ['group', 'artifact', 'version', 'name'] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceAsTimeChanges.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' apply false } tasks.register("buildInfo", org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedGradlePropertiesProjectVersion.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' apply false } tasks.register("buildInfo", org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) { excludes = ["time"] properties { artifact = 'example' group = 'com.example' name = 'example' additional = ['additional': 'alpha'] } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' apply false } version = '{projectVersion}' tasks.register("buildInfo", org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) { excludes = ["time"] properties { artifact = 'example' group = 'com.example' name = 'example' additional = ['additional': 'alpha'] } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-reproducibleOutputWithFixedTime.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' apply false } tasks.register("buildInfo", org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) { excludes = ["time"] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-upToDateWhenExecutedTwiceWithFixedTime.gradle ================================================ /* * 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. */ plugins { id 'org.springframework.boot' version '{version}' apply false } tasks.register("buildInfo", org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) { excludes = ["time"] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-applicationPluginMainClassNameIsUsed.gradle ================================================ /* * 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. */ plugins { id 'java' id 'application' id 'org.springframework.boot' version '{version}' } application { mainClass = 'com.example.CustomMain' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classesFromASecondarySourceSetCanBeIncludedInTheArchive.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } sourceSets { secondary main { runtimeClasspath += secondary.output } } bootJar { mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-customLayers.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' layered { application { intoLayer("static") { include "META-INF/resources/**", "resources/**", "static/**", "public/**" } intoLayer("app") } dependencies { intoLayer("snapshot-dependencies") { include "*:*:*SNAPSHOT" } intoLayer("commons-dependencies") { include "org.apache.commons:*" } intoLayer("dependencies") } layerOrder = ["dependencies", "commons-dependencies", "snapshot-dependencies", "static", "app"] } } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation("com.example:library:1.0-SNAPSHOT") implementation("org.apache.commons:commons-lang3:3.9") implementation("org.springframework:spring-core:5.2.5.RELEASE") } tasks.register("listLayers", JavaExec) { classpath = bootJar.outputs.files systemProperties = [ "jarmode": "tools" ] args "list-layers" } tasks.register("extractLayers", JavaExec) { classpath = bootJar.outputs.files systemProperties = [ "jarmode": "tools" ] args "extract", "--layers", "--launcher", "--destination", ".", "--force" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchive.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.9") implementation("commons-io:commons-io:2.6") } bootJar { includeTools = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.9") developmentOnly("commons-io:commons-io:2.6") implementation("commons-io:commons-io:2.6") } bootJar { includeTools = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-developmentOnlyDependenciesCanBeIncludedInTheArchive.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.9") implementation("commons-io:commons-io:2.6") } bootJar { classpath configurations.developmentOnly } bootJar { includeTools = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-dirModeAndFileModeAreApplied.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } tasks.named("bootJar") { fileMode = 0400 dirMode = 0500 mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-duplicatesAreHandledGracefully.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.CustomMain' duplicatesStrategy = "exclude" } configurations { provided } sourceSets.all { compileClasspath += configurations.provided runtimeClasspath += configurations.provided } repositories { mavenCentral() } dependencies { implementation("org.apache.commons:commons-lang3:3.6") provided "org.apache.commons:commons-lang3:3.6" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-explodedApplicationClasspath.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation("com.example:library:1.0-SNAPSHOT") implementation("org.apache.commons:commons-lang3:3.9") } tasks.register("explode", Sync) { dependsOn(bootJar) destinationDir = layout.buildDirectory.dir("exploded").get().asFile from zipTree(files(bootJar).singleFile) } tasks.register("launch", JavaExec) { classpath = files(explode) mainClass = 'org.springframework.boot.loader.launch.JarLauncher' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-implicitLayers.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation("com.example:library:1.0-SNAPSHOT") implementation("org.apache.commons:commons-lang3:3.9") implementation("org.springframework:spring-core:5.2.5.RELEASE") implementation("org.springframework.boot:spring-boot-starter-logging:2.2.0.RELEASE") } tasks.register("listLayers", JavaExec) { classpath = bootJar.outputs.files systemProperties = [ "jarmode": "tools" ] args "list-layers" } tasks.register("extractLayers", JavaExec) { classpath = bootJar.outputs.files systemProperties = [ "jarmode": "tools" ] args "extract", "--layers", "--launcher", "--destination", ".", "--force" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-jarTypeFilteringIsApplied.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } repositories { flatDir { dirs 'repository' } } dependencies { implementation(":standard:") implementation(":starter:") } bootJar { includeTools = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-layersWithCustomSourceSet.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } sourceSets { custom } bootJar { mainClass = 'com.example.Application' } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation("com.example:library:1.0-SNAPSHOT") implementation("org.apache.commons:commons-lang3:3.9") implementation("org.springframework:spring-core:5.2.5.RELEASE") } tasks.register("listLayers", JavaExec) { classpath = bootJar.outputs.files systemProperties = [ "jarmode": "tools" ] args "list-layers" } tasks.register("extractLayers", JavaExec) { classpath = bootJar.outputs.files systemProperties = [ "jarmode": "tools" ] args "extract", "--layers", "--launcher", "--destination", ".", "--force" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-multiModuleCustomLayers.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } subprojects { apply plugin: 'java' group = 'org.example.projects' version = '1.2.3' if (it.name == 'bravo') { dependencies { implementation(project(':charlie')) } } } bootJar { mainClass = 'com.example.Application' layered { application { intoLayer("static") { include "META-INF/resources/**", "resources/**", "static/**", "public/**" } intoLayer("app") } dependencies { intoLayer("snapshot-dependencies") { include "*:*:*SNAPSHOT" excludeProjectDependencies() } intoLayer("subproject-dependencies") { includeProjectDependencies() } intoLayer("commons-dependencies") { include "org.apache.commons:*" } intoLayer("dependencies") } layerOrder = ["dependencies", "commons-dependencies", "snapshot-dependencies", "subproject-dependencies", "static", "app"] } } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation(project(':alpha')) implementation(project(':bravo')) implementation("com.example:library:1.0-SNAPSHOT") implementation("org.apache.commons:commons-lang3:3.9") implementation("org.springframework:spring-core:5.2.5.RELEASE") } tasks.register("listLayers", JavaExec) { classpath = bootJar.outputs.files systemProperties = [ "jarmode": "tools" ] args "list-layers" } tasks.register("extractLayers", JavaExec) { classpath = bootJar.outputs.files systemProperties = [ "jarmode": "tools" ] args "extract", "--layers", "--launcher", "--destination", ".", "--force" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-multiModuleImplicitLayers.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } subprojects { apply plugin: 'java' group = 'org.example.projects' version = '1.2.3' if (it.name == 'bravo') { dependencies { implementation(project(':charlie')) } } } bootJar { mainClass = 'com.example.Application' } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation(project(':alpha')) implementation(project(':bravo')) implementation("com.example:library:1.0-SNAPSHOT") implementation("org.apache.commons:commons-lang3:3.9") implementation("org.springframework:spring-core:5.2.5.RELEASE") } tasks.register("listLayers", JavaExec) { classpath = bootJar.outputs.files systemProperties = [ "jarmode": "tools" ] args "list-layers" } tasks.register("extractLayers", JavaExec) { classpath = bootJar.outputs.files systemProperties = [ "jarmode": "tools" ] args "extract", "--layers", "--launcher", "--destination", ".", "--force" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithToolsAndThenWithoutTools.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' {includeTools} } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithoutLayersAndThenWithLayers.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' layered { {layerEnablement} } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-packagedApplicationClasspath.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } tasks.register("launch", JavaExec) { classpath = files(bootJar) } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation("com.example:library:1.0-SNAPSHOT") implementation("org.apache.commons:commons-lang3:3.9") } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-reproducibleArchive.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' if (GradleVersion.current().compareTo(GradleVersion.version("9.0.0-rc-1")) < 0) { preserveFileTimestamps = false reproducibleFileOrder = true } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-signed.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation("org.bouncycastle:bcprov-jdk18on:1.78.1") } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle ================================================ /* * 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. */ plugins { id 'java' id 'application' id 'org.springframework.boot' version '{version}' } springBoot { mainClass = 'com.example.CustomMain' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle ================================================ /* * 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. */ plugins { id 'java' id 'application' id 'org.springframework.boot' version '{version}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-testAndDevelopmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } repositories { mavenCentral() } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.9") testAndDevelopmentOnly("commons-io:commons-io:2.6") implementation("commons-io:commons-io:2.6") } bootJar { includeTools = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-testAndDevelopmentOnlyDependenciesCanBeIncludedInTheArchive.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } repositories { mavenCentral() } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.9") implementation("commons-io:commons-io:2.6") } bootJar { classpath configurations.testAndDevelopmentOnly } bootJar { includeTools = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltTwice.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' {layered} } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-versionMismatchBetweenTransitiveDevelopmentOnlyImplementationDependenciesDoesNotRemoveDependencyFromTheArchive.gradle ================================================ plugins { id 'java' id 'org.springframework.boot' version '{version}' } repositories { mavenCentral() maven { url = 'repository' } } dependencies { developmentOnly("commons-io-consumer:one:1.0") implementation("commons-io-consumer:two:1.0") } bootJar { includeTools = false mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-whenAResolvableCopyOfAnUnresolvableConfigurationIsResolvedThenResolutionSucceeds.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } gradle.taskGraph.whenReady { def copy = configurations.implementation.copyRecursive() copy.canBeResolved = true copy.resolve() } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-applicationPluginMainClassNameIsUsed.gradle ================================================ /* * 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. */ plugins { id 'war' id 'application' id 'org.springframework.boot' version '{version}' } application { mainClass = 'com.example.CustomMain' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classesFromASecondarySourceSetCanBeIncludedInTheArchive.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } sourceSets { secondary main { runtimeClasspath += secondary.output } } bootWar { mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-customLayers.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' id 'war' } bootWar { mainClass = 'com.example.Application' layered { application { intoLayer("static") { include "META-INF/resources/**", "resources/**", "static/**", "public/**" } intoLayer("app") } dependencies { intoLayer("snapshot-dependencies") { include "*:*:*SNAPSHOT" } intoLayer("commons-dependencies") { include "org.apache.commons:*" } intoLayer("dependencies") } layerOrder = ["dependencies", "commons-dependencies", "snapshot-dependencies", "static", "app"] } } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation("com.example:library:1.0-SNAPSHOT") implementation("org.apache.commons:commons-lang3:3.9") implementation("org.springframework:spring-core:5.2.5.RELEASE") } tasks.register("listLayers", JavaExec) { classpath = bootWar.outputs.files systemProperties = [ "jarmode": "tools" ] args "list-layers" } tasks.register("extractLayers", JavaExec) { classpath = bootWar.outputs.files systemProperties = [ "jarmode": "tools" ] args "extract", "--layers", "--launcher", "--destination", ".", "--force" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-developmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.Application' } repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.9") developmentOnly("commons-io:commons-io:2.6") implementation("commons-io:commons-io:2.6") } bootWar { includeTools = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-developmentOnlyDependenciesCanBeIncludedInTheArchive.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.Application' } repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.9") implementation("commons-io:commons-io:2.6") } bootWar { classpath configurations.developmentOnly } bootWar { includeTools = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-dirModeAndFileModeAreApplied.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } tasks.named("bootWar") { fileMode = 0400 dirMode = 0500 mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-duplicatesAreHandledGracefully.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.CustomMain' duplicatesStrategy = "exclude" } configurations { provided } sourceSets.all { compileClasspath += configurations.provided runtimeClasspath += configurations.provided } repositories { mavenCentral() } dependencies { implementation("org.apache.commons:commons-lang3:3.6") provided "org.apache.commons:commons-lang3:3.6" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-implicitLayers.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' id 'war' } bootWar { mainClass = 'com.example.Application' } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation("com.example:library:1.0-SNAPSHOT") implementation("org.apache.commons:commons-lang3:3.9") implementation("org.springframework:spring-core:5.2.5.RELEASE") implementation("org.springframework.boot:spring-boot-starter-logging:2.2.0.RELEASE") } tasks.register("listLayers", JavaExec) { classpath = bootWar.outputs.files systemProperties = [ "jarmode": "tools" ] args "list-layers" } tasks.register("extractLayers", JavaExec) { classpath = bootWar.outputs.files systemProperties = [ "jarmode": "tools" ] args "extract", "--layers", "--launcher", "--destination", ".", "--force" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-jarTypeFilteringIsApplied.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.Application' } repositories { flatDir { dirs 'repository' } } dependencies { implementation(":standard:") implementation(":starter:") } bootWar { includeTools = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-layersWithCustomSourceSet.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' id 'war' } sourceSets { custom } bootWar { mainClass = 'com.example.Application' } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation("com.example:library:1.0-SNAPSHOT") implementation("org.apache.commons:commons-lang3:3.9") implementation("org.springframework:spring-core:5.2.5.RELEASE") } tasks.register("listLayers", JavaExec) { classpath = bootWar.outputs.files systemProperties = [ "jarmode": "tools" ] args "list-layers" } tasks.register("extractLayers", JavaExec) { classpath = bootWar.outputs.files systemProperties = [ "jarmode": "tools" ] args "extract", "--layers", "--launcher", "--destination", ".", "--force" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-multiModuleCustomLayers.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' id 'war' } subprojects { apply plugin: 'java' group = 'org.example.projects' version = '1.2.3' if (it.name == 'bravo') { dependencies { implementation(project(':charlie')) } } } bootWar { mainClass = 'com.example.Application' layered { application { intoLayer("static") { include "META-INF/resources/**", "resources/**", "static/**", "public/**" } intoLayer("app") } dependencies { intoLayer("snapshot-dependencies") { include "*:*:*SNAPSHOT" excludeProjectDependencies() } intoLayer("subproject-dependencies") { includeProjectDependencies() } intoLayer("commons-dependencies") { include "org.apache.commons:*" } intoLayer("dependencies") } layerOrder = ["dependencies", "commons-dependencies", "snapshot-dependencies", "subproject-dependencies", "static", "app"] } } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation(project(':alpha')) implementation(project(':bravo')) implementation("com.example:library:1.0-SNAPSHOT") implementation("org.apache.commons:commons-lang3:3.9") implementation("org.springframework:spring-core:5.2.5.RELEASE") } tasks.register("listLayers", JavaExec) { classpath = bootWar.outputs.files systemProperties = [ "jarmode": "tools" ] args "list-layers" } tasks.register("extractLayers", JavaExec) { classpath = bootWar.outputs.files systemProperties = [ "jarmode": "tools" ] args "extract", "--layers", "--launcher", "--destination", ".", "--force" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-multiModuleImplicitLayers.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' id 'war' } subprojects { apply plugin: 'java' group = 'org.example.projects' version = '1.2.3' if (it.name == 'bravo') { dependencies { implementation(project(':charlie')) } } } bootWar { mainClass = 'com.example.Application' } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation(project(':alpha')) implementation(project(':bravo')) implementation("com.example:library:1.0-SNAPSHOT") implementation("org.apache.commons:commons-lang3:3.9") implementation("org.springframework:spring-core:5.2.5.RELEASE") } tasks.register("listLayers", JavaExec) { classpath = bootWar.outputs.files systemProperties = [ "jarmode": "tools" ] args "list-layers" } tasks.register("extractLayers", JavaExec) { classpath = bootWar.outputs.files systemProperties = [ "jarmode": "tools" ] args "extract", "--layers", "--launcher", "--destination", ".", "--force" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenBuiltWithToolsAndThenWithoutTools.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' id 'war' } bootWar { mainClass = 'com.example.Application' {includeTools} } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenBuiltWithoutLayersAndThenWithLayers.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' id 'war' } bootWar { mainClass = 'com.example.Application' layered { {layerEnablement} } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-reproducibleArchive.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.Application' if (GradleVersion.current().compareTo(GradleVersion.version("9.0.0-rc-1")) < 0) { preserveFileTimestamps = false reproducibleFileOrder = true } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-signed.gradle ================================================ /* * 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. */ plugins { id 'war' id 'java' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.Application' } repositories { mavenCentral() maven { url = 'repository' } } dependencies { implementation("org.bouncycastle:bcprov-jdk18on:1.78.1") } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle ================================================ /* * 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. */ plugins { id 'war' id 'application' id 'org.springframework.boot' version '{version}' } springBoot { mainClass = 'com.example.CustomMain' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle ================================================ /* * 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. */ plugins { id 'war' id 'application' id 'org.springframework.boot' version '{version}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-testAndDevelopmentOnlyDependenciesAreNotIncludedInTheArchiveByDefault.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.Application' } repositories { mavenCentral() } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.9") testAndDevelopmentOnly("commons-io:commons-io:2.6") implementation("commons-io:commons-io:2.6") } bootWar { includeTools = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-testAndDevelopmentOnlyDependenciesCanBeIncludedInTheArchive.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.Application' } repositories { mavenCentral() } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.9") implementation("commons-io:commons-io:2.6") } bootWar { classpath configurations.testAndDevelopmentOnly } bootWar { includeTools = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-upToDateWhenBuiltTwice.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' id 'war' } bootWar { mainClass = 'com.example.Application' {layered} } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-versionMismatchBetweenTransitiveDevelopmentOnlyImplementationDependenciesDoesNotRemoveDependencyFromTheArchive.gradle ================================================ plugins { id 'war' id 'org.springframework.boot' version '{version}' } repositories { mavenCentral() maven { url = 'repository' } } dependencies { developmentOnly("commons-io-consumer:one:1.0") implementation("commons-io-consumer:two:1.0") } bootWar { includeTools = false mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.gradle ================================================ /* * 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. */ plugins { id 'war' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.Application' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/MavenIntegrationTests-bootJarCanBeUploaded.gradle ================================================ /* * 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. */ plugins { id 'java' id 'maven' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } group = 'com.example' version = '1.0' uploadBootArchives { repositories { mavenDeployer { repository(url: "file:${layout.buildDirectory.dir("repo").get().asFile}") } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/MavenIntegrationTests-bootWarCanBeUploaded.gradle ================================================ /* * 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. */ plugins { id 'war' id 'maven' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.Application' } group = 'com.example' version = '1.0' uploadBootArchives { repositories { mavenDeployer { repository(url: "file:${layout.buildDirectory.dir("repo").get().asFile}") } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/MavenPublishingIntegrationTests-bootJarCanBePublished.gradle ================================================ /* * 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. */ plugins { id 'java' id 'maven-publish' id 'org.springframework.boot' version '{version}' } bootJar { mainClass = 'com.example.Application' } group = 'com.example' version = '1.0' publishing { repositories { maven { url = layout.buildDirectory.dir("repo") } } publications { bootJava(MavenPublication) { artifact bootJar } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/MavenPublishingIntegrationTests-bootWarCanBePublished.gradle ================================================ /* * 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. */ plugins { id 'war' id 'maven-publish' id 'org.springframework.boot' version '{version}' } bootWar { mainClass = 'com.example.Application' } group = 'com.example' version = '1.0' publishing { repositories { maven { url = layout.buildDirectory.dir("repo") } } publications { bootWeb(MavenPublication) { artifact bootWar } } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-applicationPluginJvmArgumentsAreUsed.gradle ================================================ /* * 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. */ plugins { id 'application' id 'org.springframework.boot' version '{version}' } application { applicationDefaultJvmArgs = ['-Dcom.foo=bar', '-Dcom.bar=baz'] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-applicationPluginMainClassNameIsNotUsedWhenItIsNull.gradle ================================================ /* * 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. */ plugins { id 'application' id 'org.springframework.boot' version '{version}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-applicationPluginMainClassNameIsUsed.gradle ================================================ /* * 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. */ plugins { id 'application' id 'org.springframework.boot' version '{version}' } application { mainClass = 'com.example.bootrun.main.CustomMainClass' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-basicExecution.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-classesFromASecondarySourceSetCanBeOnTheClasspath.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } sourceSets { secondary main { runtimeClasspath += secondary.output } } springBoot { mainClass = 'com.example.bootrun.main.CustomMainClass' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-defaultJvmArgs.gradle ================================================ /* * 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. */ plugins { id 'application' id 'org.springframework.boot' version '{version}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-developmentOnlyDependenciesAreOnTheClasspath.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.12.0") } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-jarTypeFilteringIsAppliedToTheClasspath.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } repositories { flatDir { dirs 'repository' } } dependencies { implementation(":standard:") implementation(":starter:") } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-optimizedLaunchDisabledJvmArgs.gradle ================================================ /* * 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. */ plugins { id 'application' id 'org.springframework.boot' version '{version}' } bootRun { optimizedLaunch = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-sourceResourcesCanBeUsed.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } bootRun { sourceResources sourceSets.main } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } springBoot { mainClass = 'com.example.bootrun.main.CustomMainClass' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-testAndDevelopmentOnlyDependenciesAreOnTheClasspath.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } repositories { mavenCentral() } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-applicationPluginJvmArgumentsAreUsed.gradle ================================================ /* * 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. */ plugins { id 'application' id 'org.springframework.boot' version '{version}' } application { applicationDefaultJvmArgs = ['-Dcom.foo=bar', '-Dcom.bar=baz'] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-basicExecution.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-defaultJvmArgs.gradle ================================================ /* * 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. */ plugins { id 'application' id 'org.springframework.boot' version '{version}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-developmentOnlyDependenciesAreNotOnTheClasspath.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } repositories { mavenCentral() } dependencies { developmentOnly("org.apache.commons:commons-lang3:3.12.0") } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-failsGracefullyWhenNoTestMainMethodIsFound.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-jarTypeFilteringIsAppliedToTheClasspath.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } repositories { flatDir { dirs 'repository' } } dependencies { implementation(":standard:") implementation(":starter:") } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-optimizedLaunchDisabledJvmArgs.gradle ================================================ /* * 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. */ plugins { id 'application' id 'org.springframework.boot' version '{version}' } bootTestRun { optimizedLaunch = false } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootTestRunIntegrationTests-testAndDevelopmentOnlyDependenciesAreOnTheClasspath.gradle ================================================ /* * 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. */ plugins { id 'java' id 'org.springframework.boot' version '{version}' } repositories { mavenCentral() } dependencies { testAndDevelopmentOnly("org.apache.commons:commons-lang3:3.12.0") } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/ch.qos.logback/logback-classic/1.2.11/index.json ================================================ [ "reflect-config.json" ] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/ch.qos.logback/logback-classic/1.2.11/reflect-config.json ================================================ [ { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.CallerDataConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.CallerDataConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.ClassOfCallerConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.ContextNameConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.DateConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.ExtendedThrowableProxyConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.FileOfCallerConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.LevelConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.LineOfCallerConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.LineSeparatorConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.LocalSequenceNumberConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.LoggerConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.MDCConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.MarkerConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.MessageConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.MethodOfCallerConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.NopThrowableInformationConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.PrefixCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.PropertyConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.RelativeTimeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.RootCauseFirstThrowableProxyConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.ThreadConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.ThrowableProxyConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.classic.pattern.color.HighlightingCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.IdentityCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.ReplacingCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.BlackCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.BlueCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.BoldBlueCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.BoldCyanCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.BoldGreenCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.BoldMagentaCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.BoldRedCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.BoldWhiteCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.BoldYellowCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.CyanCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.GrayCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.GreenCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.MagentaCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.RedCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.WhiteCompositeConverter", "allPublicConstructors": true }, { "condition": { "typeReachable": "ch.qos.logback.classic.LoggerContext" }, "name": "ch.qos.logback.core.pattern.color.YellowCompositeConverter", "allPublicConstructors": true }, { "name": "org.slf4j.impl.StaticLoggerBinder" } ] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/ch.qos.logback/logback-classic/index.json ================================================ [ { "latest": true, "metadata-version": "1.2.11", "module": "ch.qos.logback:logback-classic", "tested-versions": [ "1.2.11" ] } ] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/index.json ================================================ [ { "directory": "org.jline/jline", "module": "org.jline:jline" }, { "directory": "ch.qos.logback/logback-classic", "module": "ch.qos.logback:logback-classic" } ] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/org.jline/jline/3.21.0/index.json ================================================ [ "jni-config.json", "proxy-config.json", "reflect-config.json", "resource-config.json" ] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/org.jline/jline/3.21.0/jni-config.json ================================================ [ { "condition": { "typeReachable": "org.jline.terminal.impl.jansi.JansiNativePty" }, "fields": [ { "name": "HAVE_ISATTY" }, { "name": "HAVE_TTYNAME" }, { "name": "TCSADRAIN" }, { "name": "TCSAFLUSH" }, { "name": "TCSANOW" }, { "name": "TIOCGETD" }, { "name": "TIOCGWINSZ" }, { "name": "TIOCSETD" }, { "name": "TIOCSWINSZ" } ], "name": "org.fusesource.jansi.internal.CLibrary" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.CLibrary$WinSize" }, "name": "org.fusesource.jansi.internal.CLibrary$WinSize" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.CLibrary$Termios" }, "name": "org.fusesource.jansi.internal.CLibrary$Termios" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.Kernel32" }, "name": "org.fusesource.jansi.internal.Kernel32" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.Kernel32$SMALL_RECT" }, "name": "org.fusesource.jansi.internal.Kernel32$SMALL_RECT" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.Kernel32$COORD" }, "name": "org.fusesource.jansi.internal.Kernel32$COORD" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.Kernel32$CONSOLE_SCREEN_BUFFER_INFO" }, "name": "org.fusesource.jansi.internal.Kernel32$CONSOLE_SCREEN_BUFFER_INFO" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.Kernel32$CHAR_INFO" }, "name": "org.fusesource.jansi.internal.Kernel32$CHAR_INFO" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.Kernel32$KEY_EVENT_RECORD" }, "name": "org.fusesource.jansi.internal.Kernel32$KEY_EVENT_RECORD" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.Kernel32$MOUSE_EVENT_RECORD" }, "name": "org.fusesource.jansi.internal.Kernel32$MOUSE_EVENT_RECORD" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.Kernel32$WINDOW_BUFFER_SIZE_RECORD" }, "name": "org.fusesource.jansi.internal.Kernel32$WINDOW_BUFFER_SIZE_RECORD" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.Kernel32$FOCUS_EVENT_RECORD" }, "name": "org.fusesource.jansi.internal.Kernel32$FOCUS_EVENT_RECORD" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.Kernel32$MENU_EVENT_RECORD" }, "name": "org.fusesource.jansi.internal.Kernel32$MENU_EVENT_RECORD" }, { "allDeclaredFields": true, "condition": { "typeReachable": "org.fusesource.jansi.internal.Kernel32$INPUT_RECORD" }, "name": "org.fusesource.jansi.internal.Kernel32$INPUT_RECORD" } ] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/org.jline/jline/3.21.0/proxy-config.json ================================================ [ { "condition": { "typeReachable": "org.jline.utils.Signals" }, "interfaces": [ "sun.misc.SignalHandler" ] } ] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/org.jline/jline/3.21.0/reflect-config.json ================================================ [ { "condition": { "typeReachable": "org.jline.terminal.impl.jansi.JansiNativePty" }, "name": "java.io.FileDescriptor", "queriedMethods": [ { "name": "", "parameterTypes": [ "int" ] } ] }, { "condition": { "typeReachable": "org.jline.terminal.TerminalBuilder" }, "methods": [ { "name": "current", "parameterTypes": [] }, { "name": "info", "parameterTypes": [] }, { "name": "parent", "parameterTypes": [] } ], "name": "java.lang.ProcessHandle" }, { "condition": { "typeReachable": "org.jline.terminal.TerminalBuilder" }, "methods": [ { "name": "command", "parameterTypes": [] } ], "name": "java.lang.ProcessHandle$Info" }, { "condition": { "typeReachable": "org.jline.builtins.Styles" }, "methods": [ { "name": "get", "parameterTypes": [] } ], "name": "org.jline.console.SystemRegistry" }, { "condition": { "typeReachable": "org.jline.utils.Signals" }, "methods": [ { "name": "", "parameterTypes": [ "java.lang.String" ] }, { "name": "handle", "parameterTypes": [ "sun.misc.Signal", "sun.misc.SignalHandler" ] } ], "name": "sun.misc.Signal" }, { "condition": { "typeReachable": "org.jline.utils.Signals" }, "fields": [ { "name": "SIG_DFL" } ], "name": "sun.misc.SignalHandler" }, { "allDeclaredClasses": true, "allDeclaredConstructors": true, "allDeclaredMethods": true, "allPublicClasses": true, "allPublicConstructors": true, "allPublicMethods": true, "condition": { "typeReachable": "org.jline.terminal.TerminalBuilder" }, "name": "sun.misc.SignalHandler" } ] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/org.jline/jline/3.21.0/resource-config.json ================================================ { "bundles": [], "resources": { "includes": [ { "condition": { "typeReachable": "org.jline.terminal.TerminalBuilder" }, "pattern": "\\QMETA-INF/services/org.jline.terminal.spi.JansiSupport\\E" }, { "condition": { "typeReachable": "org.jline.terminal.TerminalBuilder" }, "pattern": "\\QMETA-INF/services/org.jline.terminal.spi.JnaSupport\\E" }, { "condition": { "typeReachable": "org.jline.terminal.impl.jansi.JansiNativePty" }, "pattern": "\\Qorg/fusesource/jansi/internal/native/Linux/x86_64/libjansi.so\\E" }, { "condition": { "typeReachable": "org.jline.terminal.impl.jansi.JansiNativePty" }, "pattern": "\\QMETA-INF/native/windows64/jansi.dll\\E" }, { "condition": { "typeReachable": "org.jline.terminal.impl.jansi.JansiNativePty" }, "pattern": "\\Qorg/fusesource/jansi/jansi.properties\\E" }, { "condition": { "typeReachable": "org.jline.terminal.impl.jansi.JansiSupportImpl" }, "pattern": "\\Qorg/fusesource/jansi/jansi.properties\\E" }, { "condition": { "typeReachable": "org.jline.utils.InfoCmp" }, "pattern": "\\Qorg/jline/utils/capabilities.txt\\E" }, { "condition": { "typeReachable": "org.jline.utils.Colors" }, "pattern": "\\Qorg/jline/utils/colors.txt\\E" }, { "condition": { "typeReachable": "org.jline.utils.InfoCmp" }, "pattern": "\\Qorg/jline/utils/ansi.caps\\E" }, { "condition": { "typeReachable": "org.jline.terminal.impl.DumbTerminal" }, "pattern": "\\Qorg/jline/utils/dumb.caps\\E" }, { "condition": { "typeReachable": "org.jline.terminal.impl.DumbTerminal" }, "pattern": "\\Qorg/jline/utils/dumb-color.caps\\E" }, { "condition": { "typeReachable": "org.jline.utils.InfoCmp" }, "pattern": "\\Qorg/jline/utils/rxvt.caps\\E" }, { "condition": { "typeReachable": "org.jline.utils.InfoCmp" }, "pattern": "\\Qorg/jline/utils/rxvt-basic.caps\\E" }, { "condition": { "typeReachable": "org.jline.utils.InfoCmp" }, "pattern": "\\Qorg/jline/utils/rxvt-unicode.caps\\E" }, { "condition": { "typeReachable": "org.jline.utils.InfoCmp" }, "pattern": "\\Qorg/jline/utils/rxvt-unicode-256color.caps\\E" }, { "condition": { "typeReachable": "org.jline.utils.InfoCmp" }, "pattern": "\\Qorg/jline/utils/screen.caps\\E" }, { "condition": { "typeReachable": "org.jline.utils.InfoCmp" }, "pattern": "\\Qorg/jline/utils/screen-256color.caps\\E" }, { "condition": { "typeReachable": "org.jline.terminal.impl.AbstractWindowsTerminal" }, "pattern": "\\Qorg/jline/utils/windows.caps\\E" }, { "condition": { "typeReachable": "org.jline.terminal.impl.AbstractWindowsTerminal" }, "pattern": "\\Qorg/jline/utils/windows-256color.caps\\E" }, { "condition": { "typeReachable": "org.jline.terminal.impl.AbstractWindowsTerminal" }, "pattern": "\\Qorg/jline/utils/windows-conemu.caps\\E" }, { "condition": { "typeReachable": "org.jline.utils.InfoCmp" }, "pattern": "\\Qorg/jline/utils/xterm.caps\\E" }, { "condition": { "typeReachable": "org.jline.utils.InfoCmp" }, "pattern": "\\Qorg/jline/utils/xterm-256color.caps\\E" } ] } } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/org.jline/jline/index.json ================================================ [ { "latest": true, "metadata-version": "3.21.0", "module": "org.jline:jline", "tested-versions": [ "3.21.0" ] } ] ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/schemas/library-and-framework-list-schema-v1.0.0.json ================================================ { "$id": "https://raw.githubusercontent.com/oracle/graalvm-reachability-metadata/master/metadata/schemas/library-and-framework-support-schema-v1.0.0.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "default": [], "examples": [ { "artifact": "io.netty:netty5-parent", "details": [ { "minimum_version": "4.1", "metadata_locations": [ "https://github.com/netty/netty/tree/main/common/src/main/resources/META-INF/native-image" ], "tests_locations": [ "https://github.com/netty/netty/actions" ], "test_level": "fully-tested" } ] } ], "items": { "properties": { "artifact": { "default": "", "description": "The artifact name in the groupId:artifactId format", "pattern": "^[a-zA-Z0-9._-]+:[a-zA-Z0-9._*-]+$", "title": "The name of the artifact", "type": "string" }, "description": { "default": "", "title": "Short description of the library or framework", "type": "string" }, "details": { "default": [], "items": { "default": {}, "properties": { "maximal_version": { "default": "", "description": "Maximum version for which this entry applies. If not defined, it is assumed to be supported for all versions starting from the minimum_version", "title": "Maximal version for which this entry applies", "type": "string" }, "metadata_locations": { "default": [], "items": { "default": "", "type": "string" }, "title": "Web URLs of provided metadata", "type": "array" }, "minimum_version": { "default": "", "title": "Minimal version for which this entry applies", "type": "string" }, "test_level": { "default": "Untested", "enum": [ "untested", "community-tested", "fully-tested" ], "title": "Testing level of reachability metadata for the library or framework", "type": "string" }, "tests_locations": { "default": [], "items": { "default": "", "type": "string" }, "title": "Web URLs to tests (sources, CI dashboards/configurations, ...)", "type": "array" } }, "required": [ "minimum_version", "metadata_locations", "tests_locations", "test_level" ], "type": "object" }, "type": "array" } }, "required": [ "artifact", "details" ], "type": "object" }, "title": "Schema for the 'library-and-framework-list.json'", "type": "array" } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/schemas/metadata-library-index-schema-v1.0.0.json ================================================ { "$id": "https://raw.githubusercontent.com/oracle/graalvm-reachability-metadata/master/metadata/schemas/metadata-library-index-schema-v1.0.0.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "description": "Schema for metadata///index.json. Each entry describes a metadata bundle for a range of library versions.", "type": "array", "minItems": 1, "items": { "type": "object", "additionalProperties": false, "required": [ "module", "metadata-version", "tested-versions" ], "properties": { "module": { "$ref": "#/$defs/moduleCoordinate", "description": "Maven coordinates in the form ':'." }, "metadata-version": { "type": "string", "minLength": 1, "description": "Subdirectory name where the metadata files for this entry reside, e.g. '7.1.0.Final'." }, "tested-versions": { "type": "array", "description": "Explicitly tested upstream library versions that this metadata is known to support.", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }, "test-version": { "type": "string", "minLength": 1, "description": "Overrides 'metadata-version' for use in test directories. This allows one metadata entry to share tests defined for a different version's metadata directory." }, "latest": { "type": "boolean", "description": "Marks this entry as the latest/default metadata for currently supported versions." }, "default-for": { "type": "string", "description": "Java regular expression describing the version range for which this entry should be used by default (e.g. '7\\\\.1\\\\..*')." }, "override": { "type": "boolean", "description": "When true, expresses the intent to exclude outdated built-in metadata shipped with Native Image for the matched versions." }, "skipped-versions": { "type": "array", "description": "Versions explicitly excluded from support, each with a reason.", "minItems": 1, "items": { "type": "object", "additionalProperties": false, "required": [ "version", "reason" ], "properties": { "version": { "type": "string", "minLength": 1 }, "reason": { "type": "string", "minLength": 1 } } } } } }, "$defs": { "moduleCoordinate": { "type": "string", "pattern": "^[^:]+:[^:]+$" } }, "examples": [ [ { "metadata-version": "0.0.1", "module": "org.example:library", "tested-versions": [ "0.0.1", "0.0.2" ], "default-for": "0\\.0\\..*", "test-version": "0.0.1" }, { "latest": true, "metadata-version": "1.0.0", "module": "org.example:library", "tested-versions": [ "1.0.0", "1.1.0" , "1.2.0"] }, { "metadata-version": "1.19.0", "module": "io.opentelemetry:opentelemetry-sdk-trace", "tested-versions": [ "1.19.0" ], "override": true, "skipped-versions": [ { "version": "1.34.0", "reason": "Dependency io.opentelemetry:opentelemetry-api:1.34.0 provides reflect-config.json which does not parse." } ] } ] ] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/reachability-metadata-repository/schemas/metadata-root-index-schema-v1.0.0.json ================================================ { "$id": "https://raw.githubusercontent.com/oracle/graalvm-reachability-metadata/master/metadata/schemas/metadata-root-index-schema-v1.0.0.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "description": "Schema for metadata/index.json. This file lists modules known to the repository and optionally the directory where their metadata is stored, the allowed package prefixes for metadata, and inter-module requirements. See docs/CONTRIBUTING.md (Metadata structure).", "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["module"], "properties": { "module": { "$ref": "#/$defs/moduleCoordinate", "description": "Maven coordinates for the module in the form ':'." }, "directory": { "type": "string", "minLength": 1, "pattern": "^[^\\s].*$", "description": "Repository-relative path under 'metadata/' containing this module's metadata (e.g. 'org.example/library'). If omitted, the entry may reference requirements only." }, "allowed-packages": { "type": "array", "description": "List of package (or fully-qualified name) prefixes considered valid sources of metadata entries for this module. Used to filter-in relevant JSON entries.", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }, "requires": { "type": "array", "description": "Optional list of module coordinates this module depends on. Each item is ':'.", "minItems": 1, "uniqueItems": true, "items": { "$ref": "#/$defs/moduleCoordinate" } } } }, "$defs": { "moduleCoordinate": { "type": "string", "pattern": "^[^:]+:[^:]+$" } }, "examples": [ [ { "directory": "org.example/library", "module": "org.example:library" }, { "allowed-packages": ["org.package.name"], "module": "org.example:dependant-library", "requires": ["org.example:library"] }, { "allowed-packages" : [ "org.hibernate", "jakarta" ], "directory" : "org.hibernate.orm/hibernate-envers", "module" : "org.hibernate.orm:hibernate-envers", "requires" : [ "org.hibernate.orm:hibernate-core" ] } ] ] } ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/repository/com/example/library/1.0-SNAPSHOT/library-1.0-SNAPSHOT.pom ================================================ 4.0.0 com.example library 1.0-SNAPSHOT ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/repository/commons-io-consumer/one/1.0/one-1.0.pom ================================================ 4.0.0 commons-io-consumer one 1.0 commons-io commons-io 2.19.0 ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/repository/commons-io-consumer/two/1.0/two-1.0.pom ================================================ 4.0.0 commons-io-consumer two 1.0 commons-io commons-io 2.18.0 ================================================ FILE: build-plugin/spring-boot-gradle-plugin/src/test/resources/repository/org/springframework/boot/spring-boot-dependencies/TEST-SNAPSHOT/spring-boot-dependencies-TEST-SNAPSHOT.pom ================================================ 4.0.0 org.springframework.boot spring-boot-dependencies TEST-SNAPSHOT pom 1.7.25 org.slf4j slf4j-api ${slf4j.version} org.springframework.boot spring-boot-starter TEST-SNAPSHOT org.junit.platform junit-platform-launcher 1.9.1 ================================================ FILE: build-plugin/spring-boot-maven-plugin/build.gradle ================================================ /* * 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. */ plugins { id "org.springframework.boot.antora-contributor" id "org.springframework.boot.maven-plugin" id "org.springframework.boot.optional-dependencies" id "org.springframework.boot.docker-test" } description = "Spring Boot Maven Plugin" configurations { dependenciesBom } dependencies { compileOnly("org.apache.maven.plugin-tools:maven-plugin-annotations") compileOnly("org.apache.maven:maven-core") { exclude(group: "javax.annotation", module: "javax.annotation-api") } compileOnly("org.apache.maven:maven-plugin-api") { exclude(group: "javax.annotation", module: "javax.annotation-api") exclude(group: "javax.enterprise", module: "cdi-api") } dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) dockerTestImplementation("org.apache.maven.shared:maven-invoker") { exclude(group: "javax.inject", module: "javax.inject") } dockerTestImplementation("org.assertj:assertj-core") dockerTestImplementation("org.junit.jupiter:junit-jupiter") dockerTestImplementation("org.testcontainers:testcontainers-junit-jupiter") implementation(project(":buildpack:spring-boot-buildpack-platform")) implementation(project(":loader:spring-boot-loader-tools")) implementation("org.apache.maven.shared:maven-common-artifact-filters") { exclude(group: "javax.annotation", module: "javax.annotation-api") exclude(group: "javax.enterprise", module: "cdi-api") exclude(group: "javax.inject", module: "javax.inject") } implementation("org.sonatype.plexus:plexus-build-api") { exclude(group: "org.codehaus.plexus", module: "plexus-utils") } implementation("org.springframework:spring-core") implementation("org.springframework:spring-context") optional("org.apache.maven.plugins:maven-shade-plugin") { exclude(group: "javax.annotation", module: "javax.annotation-api") exclude(group: "javax.enterprise", module: "cdi-api") exclude(group: "javax.inject", module: "javax.inject") } testImplementation("org.apache.maven:maven-core") { exclude(group: "javax.annotation", module: "javax.annotation-api") exclude(group: "javax.inject", module: "javax.inject") } testImplementation("org.apache.maven.shared:maven-common-artifact-filters") { exclude(group: "javax.annotation", module: "javax.annotation-api") exclude(group: "javax.enterprise", module: "cdi-api") exclude(group: "javax.inject", module: "javax.inject") } testImplementation("org.assertj:assertj-core") testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-junit-jupiter") testImplementation("org.springframework:spring-core") intTestImplementation(project(":buildpack:spring-boot-buildpack-platform")) intTestImplementation(project(":loader:spring-boot-loader-tools")) intTestImplementation(project(":test-support:spring-boot-test-support")) intTestImplementation("org.apache.maven.shared:maven-invoker") { exclude(group: "javax.inject", module: "javax.inject") } intTestImplementation("org.assertj:assertj-core") intTestImplementation("org.junit.jupiter:junit-jupiter") mavenRepository(project(path: ":core:spring-boot", configuration: "mavenRepository")) mavenRepository(project(path: ":platform:spring-boot-dependencies", configuration: "mavenRepository")) mavenRepository(project(path: ":core:spring-boot-test", configuration: "mavenRepository")) mavenRepository(project(path: ":module:spring-boot-devtools", configuration: "mavenRepository")) mavenRepository(project(path: ":core:spring-boot-docker-compose", configuration: "mavenRepository")) mavenRepository(project(path: ":starter:spring-boot-starter-parent", configuration: "mavenRepository")) mavenRepository("org.springframework:spring-test") versionProperties(project(path: ":platform:spring-boot-dependencies", configuration: "resolvedBom")) } ext { versionElements = version.split("\\.") xsdVersion = versionElements[0] + "." + versionElements[1] } tasks.named("checkCompileClasspathForProhibitedDependencies") { permittedGroups = ["javax.inject"] } sourceSets { main { output.dir(layout.buildDirectory.dir("generated/resources/xsd"), builtBy: "xsdResources") } intTest { output.dir(layout.buildDirectory.dir("generated-resources"), builtBy: ["extractVersionProperties"]) } dockerTest { output.dir(layout.buildDirectory.dir("generated-resources"), builtBy: "extractVersionProperties") } } javadoc { options { author = true docTitle = "Spring Boot Maven Plugin ${project.version} API" encoding = "UTF-8" memberLevel = "protected" outputLevel = "quiet" splitIndex = true use = true windowTitle = "Spring Boot Maven Plugin ${project.version} API" } } tasks.register("xsdResources", Sync) { from "src/main/xsd/layers-${project.ext.xsdVersion}.xsd" into layout.buildDirectory.dir("generated/resources/xsd/org/springframework/boot/maven") rename { fileName -> "layers.xsd" } } prepareMavenBinaries { versions.add(providers.gradleProperty('mavenVersion')) versions.add("3.6.3") } tasks.named("documentPluginGoals") { goalSections = [ "build-image": "build-image", "build-image-no-fork": "build-image", "build-info": "build-info", "help": "help", "process-aot": "aot", "process-test-aot": "aot", "repackage": "packaging", "run": "run", "start": "integration-tests", "stop": "integration-tests", "test-run": "run" ] } antoraContributions { 'maven-plugin' { aggregateContent { from(documentPluginGoals) { into "modules/maven-plugin/partials/goals" } } catalogContent { from(javadoc) { into "api/java" } } localAggregateContent { from(tasks.named("generateAntoraYml")) { into "modules" } } source() } } tasks.named("generateAntoraPlaybook") { antoraExtensions.xref.stubs = ["appendix:.*", "api:.*", "reference:.*", "how-to:.*"] asciidocExtensions.excludeJavadocExtension = true } tasks.named("dockerTest").configure { dependsOn tasks.named("prepareMavenBinaries") } tasks.named("compileTestJava") { options.nullability.checking = "tests" } tasks.named("compileIntTestJava") { options.nullability.checking = "tests" } tasks.named("compileDockerTestJava") { options.nullability.checking = "tests" } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/java/org/springframework/boot/maven/BuildImageRegistryIntegrationTests.java ================================================ /* * 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 com.github.dockerjava.api.DockerClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.boot.buildpack.platform.docker.DockerApi; import org.springframework.boot.buildpack.platform.docker.UpdateListener; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.testsupport.container.RegistryContainer; import org.springframework.boot.testsupport.container.TestImage; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for the Maven plugin's image support using a Docker image registry. * * @author Scott Frederick */ @ExtendWith(MavenBuildExtension.class) @Testcontainers(disabledWithoutDocker = true) @Disabled("Disabled until differences between running locally and in CI can be diagnosed") class BuildImageRegistryIntegrationTests extends AbstractArchiveIntegrationTests { @Container static final RegistryContainer registry = TestImage.container(RegistryContainer.class); DockerClient dockerClient; String registryAddress; @BeforeEach void setUp() { assertThat(registry.isRunning()).isTrue(); this.dockerClient = registry.getDockerClient(); this.registryAddress = registry.getHost() + ":" + registry.getFirstMappedPort(); } @TestTemplate void whenBuildImageIsInvokedWithPublish(MavenBuild mavenBuild) { String repoName = "test-image"; String imageName = this.registryAddress + "/" + repoName; mavenBuild.project("dockerTest", "build-image-publish") .goals("package") .systemProperty("spring-boot.build-image.imageName", imageName) .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("Successfully built image") .contains("Pushing image '" + imageName + ":latest" + "'") .contains("Pushed image '" + imageName + ":latest" + "'"); ImageReference imageReference = ImageReference.of(imageName); DockerApi.ImageApi imageApi = new DockerApi().image(); Image pulledImage = imageApi.pull(imageReference, null, UpdateListener.none()); assertThat(pulledImage).isNotNull(); imageApi.remove(imageReference, false); }); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/java/org/springframework/boot/maven/BuildImageTests.java ================================================ /* * 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.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.OffsetDateTime; import java.util.Random; import java.util.stream.IntStream; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.buildpack.platform.docker.DockerApi; 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.transport.DockerEngineException; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageName; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for the Maven plugin's image support. * * @author Stephane Nicoll * @author Scott Frederick * @author Rafael Ceccone */ @ExtendWith(MavenBuildExtension.class) @DisabledIfDockerUnavailable class BuildImageTests extends AbstractArchiveIntegrationTests { @TestTemplate void whenBuildImageIsInvokedWithoutRepackageTheArchiveIsRepackagedOnTheFly(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .prepare(this::writeLongNameResource) .execute((project) -> { File jar = new File(project, "target/build-image-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar).isFile(); File original = new File(project, "target/build-image-0.0.1.BUILD-SNAPSHOT.jar.original"); assertThat(original).doesNotExist(); assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image:0.0.1.BUILD-SNAPSHOT") .contains("Running detector") .contains("Running builder") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("build-image", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedOnTheCommandLineWithoutRepackageTheArchiveIsRepackagedOnTheFly(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-cmd-line") .goals("spring-boot:build-image") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .prepare(this::writeLongNameResource) .execute((project) -> { File jar = new File(project, "target/build-image-cmd-line-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar).isFile(); File original = new File(project, "target/build-image-cmd-line-0.0.1.BUILD-SNAPSHOT.jar.original"); assertThat(original).doesNotExist(); assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-cmd-line:0.0.1.BUILD-SNAPSHOT") .contains("Running detector") .contains("Running builder") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("build-image-cmd-line", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenPackageIsInvokedWithClassifierTheOriginalArchiveIsFound(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-classifier") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .prepare(this::writeLongNameResource) .execute((project) -> { File jar = new File(project, "target/build-image-classifier-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar).isFile(); File classifier = new File(project, "target/build-image-classifier-0.0.1.BUILD-SNAPSHOT-test.jar"); assertThat(classifier).doesNotExist(); assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-classifier:0.0.1.BUILD-SNAPSHOT") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("build-image-classifier", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithClassifierAndRepackageTheOriginalArchiveIsFound(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-fork-classifier") .goals("spring-boot:build-image") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .prepare(this::writeLongNameResource) .execute((project) -> { File jar = new File(project, "target/build-image-fork-classifier-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar).isFile(); File classifier = new File(project, "target/build-image-fork-classifier-0.0.1.BUILD-SNAPSHOT-exec.jar"); assertThat(classifier).exists(); assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-fork-classifier:0.0.1.BUILD-SNAPSHOT") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("build-image-fork-classifier", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithClassifierSourceWithoutRepackageTheArchiveIsRepackagedOnTheFly( MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-classifier-source") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .prepare(this::writeLongNameResource) .execute((project) -> { File jar = new File(project, "target/build-image-classifier-source-0.0.1.BUILD-SNAPSHOT-test.jar"); assertThat(jar).isFile(); File original = new File(project, "target/build-image-classifier-source-0.0.1.BUILD-SNAPSHOT-test.jar.original"); assertThat(original).doesNotExist(); assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-classifier-source:0.0.1.BUILD-SNAPSHOT") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("build-image-classifier-source", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithRepackageTheExistingArchiveIsUsed(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-with-repackage") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .prepare(this::writeLongNameResource) .execute((project) -> { File jar = new File(project, "target/build-image-with-repackage-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar).isFile(); File original = new File(project, "target/build-image-with-repackage-0.0.1.BUILD-SNAPSHOT.jar.original"); assertThat(original).isFile(); assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-with-repackage:0.0.1.BUILD-SNAPSHOT") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("build-image-with-repackage", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithClassifierAndRepackageTheExistingArchiveIsUsed(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-classifier-with-repackage") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .prepare(this::writeLongNameResource) .execute((project) -> { File jar = new File(project, "target/build-image-classifier-with-repackage-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar).isFile(); File original = new File(project, "target/build-image-classifier-with-repackage-0.0.1.BUILD-SNAPSHOT-test.jar"); assertThat(original).isFile(); assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-classifier-with-repackage:0.0.1.BUILD-SNAPSHOT") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("build-image-classifier-with-repackage", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithClassifierSourceAndRepackageTheExistingArchiveIsUsed(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-classifier-source-with-repackage") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .prepare(this::writeLongNameResource) .execute((project) -> { File jar = new File(project, "target/build-image-classifier-source-with-repackage-0.0.1.BUILD-SNAPSHOT-test.jar"); assertThat(jar).isFile(); File original = new File(project, "target/build-image-classifier-source-with-repackage-0.0.1.BUILD-SNAPSHOT-test.jar.original"); assertThat(original).isFile(); assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-classifier-source-with-repackage:0.0.1.BUILD-SNAPSHOT") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("build-image-classifier-source-with-repackage", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithWarPackaging(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-war-packaging") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .prepare(this::writeLongNameResource) .execute((project) -> { File war = new File(project, "target/build-image-war-packaging-0.0.1.BUILD-SNAPSHOT.war"); assertThat(war).isFile(); File original = new File(project, "target/build-image-war-packaging-0.0.1.BUILD-SNAPSHOT.war.original"); assertThat(original).doesNotExist(); assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-war-packaging:0.0.1.BUILD-SNAPSHOT") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("build-image-war-packaging", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithCustomImageName(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-custom-name") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .systemProperty("spring-boot.build-image.imageName", "example.com/test/property-ignored:pom-preferred") .execute((project) -> { File jar = new File(project, "target/build-image-custom-name-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar).isFile(); File original = new File(project, "target/build-image-custom-name-0.0.1.BUILD-SNAPSHOT.jar.original"); assertThat(original).doesNotExist(); assertThat(buildLog(project)).contains("Building image") .contains("example.com/test/build-image:0.0.1.BUILD-SNAPSHOT") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("example.com/test/build-image", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithCommandLineParameters(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .systemProperty("spring-boot.build-image.imageName", "example.com/test/cmd-property-name:v1") .systemProperty("spring-boot.build-image.builder", "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2") .systemProperty("spring-boot.build-image.trustBuilder", "true") .systemProperty("spring-boot.build-image.runImage", "paketobuildpacks/run-noble-tiny") .systemProperty("spring-boot.build-image.createdDate", "2020-07-01T12:34:56Z") .systemProperty("spring-boot.build-image.applicationDirectory", "/application") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("example.com/test/cmd-property-name:v1") .contains("Running creator") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); Image image = new DockerApi().image() .inspect(ImageReference.of("example.com/test/cmd-property-name:v1")); assertThat(image.getCreated()).isEqualTo("2020-07-01T12:34:56Z"); removeImage("example.com/test/cmd-property-name", "v1"); }); } @TestTemplate void whenBuildImageIsInvokedWithCustomBuilderImageAndRunImage(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-custom-builder") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-v2-builder:0.0.1.BUILD-SNAPSHOT") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("docker.io/library/build-image-v2-builder", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithTrustBuilder(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-trust-builder") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-v2-trust-builder:0.0.1.BUILD-SNAPSHOT") .contains("Running creator") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("docker.io/library/build-image-v2-trust-builder", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithEmptyEnvEntry(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-empty-env-entry") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .prepare(this::writeLongNameResource) .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-empty-env-entry:0.0.1.BUILD-SNAPSHOT") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("build-image-empty-env-entry", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithZipPackaging(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-zip-packaging") .goals("package") .prepare(this::writeLongNameResource) .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .execute((project) -> { File jar = new File(project, "target/build-image-zip-packaging-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar).isFile(); assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-zip-packaging:0.0.1.BUILD-SNAPSHOT") .contains("Main-Class: org.springframework.boot.loader.launch.PropertiesLauncher") .contains("Successfully built image"); removeImage("build-image-zip-packaging", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithBuildpacks(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-custom-buildpacks") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-custom-buildpacks:0.0.1.BUILD-SNAPSHOT") .contains("Successfully built image"); removeImage("build-image-custom-buildpacks", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithBinding(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-bindings") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-bindings:0.0.1.BUILD-SNAPSHOT") .contains("binding: ca-certificates/type=ca-certificates") .contains("binding: ca-certificates/test.crt=---certificate one---") .contains("Successfully built image"); removeImage("build-image-bindings", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithNetworkModeNone(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-network") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-network:0.0.1.BUILD-SNAPSHOT") .contains("Network status: curl failed") .contains("Successfully built image"); removeImage("build-image-network", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedOnMultiModuleProjectWithPackageGoal(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-multi-module") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-multi-module-app:0.0.1.BUILD-SNAPSHOT") .contains("Successfully built image"); removeImage("build-image-multi-module-app", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithTags(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-tags") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-tags:0.0.1.BUILD-SNAPSHOT") .contains("Successfully built image") .contains("docker.io/library/build-image-tags:latest") .contains("Successfully created image tag"); removeImage("build-image-tags", "0.0.1.BUILD-SNAPSHOT"); removeImage("build-image-tags", "latest"); }); } @TestTemplate void whenBuildImageIsInvokedWithVolumeCaches(MavenBuild mavenBuild) { String testBuildId = randomString(); mavenBuild.project("dockerTest", "build-image-volume-caches") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .systemProperty("test-build-id", testBuildId) .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-volume-caches:0.0.1.BUILD-SNAPSHOT") .contains("Successfully built image"); removeImage("build-image-volume-caches", "0.0.1.BUILD-SNAPSHOT"); deleteVolumes("cache-" + testBuildId + ".build", "cache-" + testBuildId + ".launch"); }); } @TestTemplate @EnabledOnOs(value = OS.LINUX, disabledReason = "Works with Docker Engine on Linux but is not reliable with " + "Docker Desktop on other OSs") void whenBuildImageIsInvokedWithBindCaches(MavenBuild mavenBuild) { String testBuildId = randomString(); mavenBuild.project("dockerTest", "build-image-bind-caches") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .systemProperty("test-build-id", testBuildId) .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-bind-caches:0.0.1.BUILD-SNAPSHOT") .contains("Successfully built image"); removeImage("build-image-bind-caches", "0.0.1.BUILD-SNAPSHOT"); String tempDir = System.getProperty("java.io.tmpdir"); Path buildCachePath = Paths.get(tempDir, "junit-image-cache-" + testBuildId + "-build"); Path launchCachePath = Paths.get(tempDir, "junit-image-cache-" + testBuildId + "-launch"); assertThat(buildCachePath).exists().isDirectory(); assertThat(launchCachePath).exists().isDirectory(); cleanupCache(buildCachePath); cleanupCache(launchCachePath); }); } private static void cleanupCache(Path cachePath) { try { FileSystemUtils.deleteRecursively(cachePath); } catch (Exception ex) { // ignore } } @TestTemplate void whenBuildImageIsInvokedWithCreatedDate(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-created-date") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-created-date:0.0.1.BUILD-SNAPSHOT") .contains("Successfully built image"); Image image = new DockerApi().image() .inspect(ImageReference.of("docker.io/library/build-image-created-date:0.0.1.BUILD-SNAPSHOT")); assertThat(image.getCreated()).isEqualTo("2020-07-01T12:34:56Z"); removeImage("build-image-created-date", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithCurrentCreatedDate(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-current-created-date") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-current-created-date:0.0.1.BUILD-SNAPSHOT") .contains("Successfully built image"); Image image = new DockerApi().image() .inspect(ImageReference .of("docker.io/library/build-image-current-created-date:0.0.1.BUILD-SNAPSHOT")); OffsetDateTime createdDateTime = OffsetDateTime.parse(image.getCreated()); OffsetDateTime current = OffsetDateTime.now().withOffsetSameInstant(createdDateTime.getOffset()); assertThat(createdDateTime.getYear()).isEqualTo(current.getYear()); assertThat(createdDateTime.getMonth()).isEqualTo(current.getMonth()); assertThat(createdDateTime.getDayOfMonth()).isEqualTo(current.getDayOfMonth()); removeImage("build-image-current-created-date", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithApplicationDirectory(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-app-dir") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-app-dir:0.0.1.BUILD-SNAPSHOT") .contains("Successfully built image"); removeImage("build-image-app-dir", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate void whenBuildImageIsInvokedWithEmptySecurityOptions(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-security-opts") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-security-opts:0.0.1.BUILD-SNAPSHOT") .contains("Successfully built image"); removeImage("build-image-security-opts", "0.0.1.BUILD-SNAPSHOT"); }); } @TestTemplate @EnabledOnOs(value = { OS.LINUX, OS.MAC }, architectures = "aarch64", disabledReason = "Lifecycle will only run on ARM architecture") void whenBuildImageIsInvokedOnLinuxArmWithImagePlatformLinuxArm(MavenBuild mavenBuild) throws IOException { String builderImage = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2"; String runImage = "docker.io/paketobuildpacks/run-noble-tiny:latest"; String buildpackImage = "ghcr.io/spring-io/spring-boot-test-info:0.0.2"; removeImages(builderImage, runImage, buildpackImage); mavenBuild.project("dockerTest", "build-image-platform-linux-arm").goals("package").execute((project) -> { File jar = new File(project, "target/build-image-platform-linux-arm-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar).isFile(); assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-platform-linux-arm:0.0.1.BUILD-SNAPSHOT") .contains("Pulling builder image '" + builderImage + "' for platform 'linux/arm64'") .contains("Pulling run image '" + runImage + "' for platform 'linux/arm64'") .contains("Pulling buildpack image '" + buildpackImage + "' for platform 'linux/arm64'") .contains("---> Test Info buildpack building") .contains("---> Test Info buildpack done") .contains("Successfully built image"); removeImage("docker.io/library/build-image-platform-linux-arm", "0.0.1.BUILD-SNAPSHOT"); }); removeImages(builderImage, runImage, buildpackImage); } @TestTemplate @EnabledOnOs(value = { OS.LINUX, OS.MAC }, architectures = "amd64", disabledReason = "The expected failure condition will not fail on ARM architectures") void failsWhenBuildImageIsInvokedOnLinuxAmdWithImagePlatformLinuxArm(MavenBuild mavenBuild) throws IOException { String builderImage = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2"; String runImage = "docker.io/paketobuildpacks/run-noble-tiny:latest"; String buildpackImage = "ghcr.io/spring-io/spring-boot-test-info:0.0.2"; removeImages(buildpackImage, runImage, buildpackImage); mavenBuild.project("dockerTest", "build-image-platform-linux-arm") .goals("package") .executeAndFail((project) -> assertThat(buildLog(project)).contains("Building image") .contains("docker.io/library/build-image-platform-linux-arm:0.0.1.BUILD-SNAPSHOT") .contains("Pulling builder image '" + builderImage + "' for platform 'linux/arm64'") .contains("Pulling run image '" + runImage + "' for platform 'linux/arm64'") .contains("Pulling buildpack image '" + buildpackImage + "' for platform 'linux/arm64'") .contains("exec format error")); removeImages(builderImage, runImage, buildpackImage); } @TestTemplate void failsWhenBuildImageIsInvokedOnMultiModuleProjectWithBuildImageGoal(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-multi-module") .goals("spring-boot:build-image") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .executeAndFail((project) -> assertThat(buildLog(project)).contains("Error packaging archive for image")); } @TestTemplate void failsWhenBuilderFails(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-builder-error") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .executeAndFail((project) -> assertThat(buildLog(project)).contains("Building image") .contains("---> Test Info buildpack building") .contains("Forced builder failure") .containsPattern("Builder lifecycle '.*' failed with status code")); } @TestTemplate void failsWithBuildpackNotInBuilder(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-bad-buildpack") .goals("package") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .executeAndFail((project) -> assertThat(buildLog(project)) .contains("'urn:cnb:builder:example/does-not-exist:0.0.1' not found in builder")); } @TestTemplate void failsWhenFinalNameIsMisconfigured(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-final-name") .goals("package") .executeAndFail((project) -> assertThat(buildLog(project)).contains("final-name.jar.original") .contains("is required for building an image")); } @TestTemplate void failsWhenCachesAreConfiguredTwice(MavenBuild mavenBuild) { mavenBuild.project("dockerTest", "build-image-caches-multiple") .goals("package") .executeAndFail((project) -> assertThat(buildLog(project)) .contains("Each image building cache can be configured only once")); } private void writeLongNameResource(File project) { StringBuilder name = new StringBuilder(); new Random().ints('a', 'z' + 1).limit(128).forEach((i) -> name.append((char) i)); try { Path path = project.toPath().resolve(Paths.get("src", "main", "resources", name.toString())); Files.createDirectories(path.getParent()); Files.createFile(path); } catch (IOException ex) { throw new RuntimeException(ex); } } private void removeImages(String... names) throws IOException { ImageApi imageApi = new DockerApi().image(); for (String name : names) { try { imageApi.remove(ImageReference.of(name), false); } catch (DockerEngineException ex) { // ignore image remove failures } } } private void removeImage(String name, String version) { ImageReference imageReference = ImageReference.of(ImageName.of(name), version); try { new DockerApi().image().remove(imageReference, false); } catch (IOException ex) { throw new IllegalStateException("Failed to remove docker image " + imageReference, ex); } } private void deleteVolumes(String... names) throws IOException { VolumeApi volumeApi = new DockerApi().volume(); for (String name : names) { volumeApi.delete(VolumeName.of(name), false); } } private String randomString() { IntStream chars = new Random().ints('a', 'z' + 1).limit(10); return chars.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString(); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-app-dir/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-app-dir 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 /application ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-app-dir/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-bad-buildpack/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-custom-buildpacks 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 urn:cnb:builder:example/does-not-exist:0.0.1 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-bad-buildpack/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-bind-caches/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-bind-caches 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 ${java.io.tmpdir}/junit-image-cache-${test-build-id}-work ${java.io.tmpdir}/junit-image-cache-${test-build-id}-build ${java.io.tmpdir}/junit-image-cache-${test-build-id}-launch ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-bind-caches/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-bindings/bindings/ca-certificates/test.crt ================================================ ---certificate one--- ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-bindings/bindings/ca-certificates/type ================================================ ca-certificates ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-bindings/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-bindings 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 ${basedir}/bindings/ca-certificates:/platform/bindings/ca-certificates ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-bindings/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-builder-error/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-builder-error 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-builder-error/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-caches-multiple/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-caches-multiple 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 build-cache-volume1 build-cache-volume2 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-caches-multiple/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-classifier/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-classifier 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 test ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-classifier/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-classifier-source/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-classifier-source 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ org.apache.maven.plugins maven-jar-plugin 3.2.0 package jar test @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 test ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-classifier-source/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-classifier-source-with-repackage/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-classifier-source-with-repackage 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ org.apache.maven.plugins maven-jar-plugin 3.2.0 package jar test @project.groupId@ @project.artifactId@ @project.version@ repackage repackage build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 test ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-classifier-source-with-repackage/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-classifier-with-repackage/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-classifier-with-repackage 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage repackage build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 test ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-classifier-with-repackage/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-cmd-line/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-cmd-line 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-cmd-line/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-created-date/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-created-date 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 2020-07-01T12:34:56Z ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-created-date/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-current-created-date/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-current-created-date 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 now ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-current-created-date/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-custom-builder/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-v2-builder 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 paketobuildpacks/run-noble-tiny ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-custom-builder/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-custom-buildpacks/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-custom-buildpacks 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 urn:cnb:builder:spring-boot/spring-boot-test-info ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-custom-buildpacks/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-custom-name/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-custom-name 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 example.com/test/build-image:${project.version} ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-custom-name/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-empty-env-entry/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-empty-env-entry 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-empty-env-entry/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-final-name/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-final-name 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage build-image-no-fork final-name ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-final-name/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-fork-classifier/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-fork-classifier 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage repackage exec ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-fork-classifier/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-multi-module/app/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-multi-module 0.0.1.BUILD-SNAPSHOT build-image-multi-module-app app org.springframework.boot.maven.it build-image-multi-module-library 0.0.1.BUILD-SNAPSHOT @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-multi-module/app/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.test.SampleLibrary; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println(SampleLibrary.getMessage()); synchronized (args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-multi-module/library/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-multi-module 0.0.1.BUILD-SNAPSHOT build-image-multi-module-library library ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-multi-module/library/src/main/java/org/test/SampleLibrary.java ================================================ /* * 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.test; public class SampleLibrary { public static String getMessage() { return "Launched"; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-multi-module/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-multi-module 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ library app @project.groupId@ @project.artifactId@ @project.version@ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-network/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-network 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 none ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-network/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-platform-linux-arm/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-platform-linux-arm 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 paketobuildpacks/run-noble-tiny ghcr.io/spring-io/spring-boot-test-info:0.0.2 linux/arm64 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-platform-linux-arm/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-publish/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-publish/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-security-opts/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-security-opts 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-security-opts/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-tags/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-tags 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 ${project.artifactId}:latest ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-tags/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-trust-builder/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-v2-trust-builder 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-trust-builder/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-volume-caches/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-volume-caches 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 cache-${test-build-id}.work cache-${test-build-id}.build cache-${test-build-id}.launch ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-volume-caches/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-war-packaging/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-war-packaging 0.0.1.BUILD-SNAPSHOT war UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 org.apache.maven.plugins maven-war-plugin @maven-war-plugin.version@ Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-war-packaging/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-with-repackage/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-with-repackage 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage repackage build-image-no-fork ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-with-repackage/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-zip-packaging/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-image-zip-packaging 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-image-no-fork ZIP ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.2 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/dockerTest/projects/build-image-zip-packaging/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Launched"); synchronized(args) { args.wait(); // Prevent exit" } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/antora.yml ================================================ name: boot version: true ext: zip_contents_collector: include: - name: maven-plugin classifier: aggregate-content - name: maven-plugin classifier: catalog-content module: maven-plugin destination: content-catalog ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/local-nav.adoc ================================================ include::maven-plugin:partial$nav-maven-plugin.adoc[] ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/aot/pom.xml ================================================ 4.0.0 aot org.springframework.boot spring-boot-maven-plugin process-aot process-aot ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/aot-native/pom.xml ================================================ 4.0.0 aot-native org.graalvm.buildtools native-maven-plugin org.springframework.boot spring-boot-maven-plugin ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/aot-native-profile-buildpacks/pom.xml ================================================ 4.0.0 aot-native-profile-buildpacks native org.springframework.boot spring-boot-maven-plugin build-image build-image-no-fork ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/aot-native-profile-nbt/pom.xml ================================================ 4.0.0 aot-native-nbt native org.graalvm.buildtools native-maven-plugin build-image compile-no-fork ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/build-info/pom.xml ================================================ 4.0.0 build-info org.springframework.boot spring-boot-maven-plugin build-info UTF-8 UTF-8 ${java.version} ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/getting-started/plugin-repositories-pom.xml ================================================ 4.0.0 getting-started org.springframework.boot spring-boot-maven-plugin build-info UTF-8 UTF-8 ${maven.compiler.source} ${maven.compiler.target} spring-snapshots https://repo.spring.io/snapshot spring-milestones https://repo.spring.io/milestone ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/getting-started/pom.xml ================================================ 4.0.0 getting-started org.springframework.boot spring-boot-maven-plugin ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/integration-tests/customize-jmx-port-pom.xml ================================================ 4.0.0 integration-tests org.springframework.boot spring-boot-maven-plugin 9009 pre-integration-test start post-integration-test stop ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/integration-tests/failsafe-pom.xml ================================================ 4.0.0 integration-tests org.apache.maven.plugins maven-failsafe-plugin ${project.build.outputDirectory} ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/integration-tests/pom.xml ================================================ 4.0.0 integration-tests org.springframework.boot spring-boot-maven-plugin pre-integration-test start post-integration-test stop ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/integration-tests/random-port-pom.xml ================================================ 4.0.0 integration-tests org.codehaus.mojo build-helper-maven-plugin reserve-tomcat-port reserve-network-port process-resources tomcat.http.port org.springframework.boot spring-boot-maven-plugin pre-integration-test start --server.port=${tomcat.http.port} post-integration-test stop org.apache.maven.plugins maven-failsafe-plugin ${tomcat.http.port} ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/integration-tests/skip-integration-tests-pom.xml ================================================ false org.springframework.boot spring-boot-maven-plugin pre-integration-test start ${skip.it} post-integration-test stop ${skip.it} org.apache.maven.plugins maven-failsafe-plugin ${skip.it} ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/classified-artifact-pom.xml ================================================ org.apache.maven.plugins maven-jar-plugin jar package task org.springframework.boot spring-boot-maven-plugin repackage repackage task ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/custom-layers-classpath-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin true custom com.example layers-configuration 1.0.0 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/custom-layers-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin true ${project.basedir}/src/layers.xml ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/custom-layout-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin repackage repackage value com.example custom-layout 0.0.1.BUILD-SNAPSHOT ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/custom-name-pom.xml ================================================ my-app org.springframework.boot spring-boot-maven-plugin repackage repackage ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/different-classifier-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin repackage repackage exec ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/disable-layers-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin false ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/exclude-artifact-group-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin com.example ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/exclude-artifact-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin com.example module1 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/exclude-dependency-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin false ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/jar-plugin-first-pom.xml ================================================ org.apache.maven.plugins maven-jar-plugin default-jar task org.springframework.boot spring-boot-maven-plugin repackage task ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/layers-configuration.xml ================================================ org/springframework/boot/loader/** *:*:*SNAPSHOT com.acme:* dependencies spring-boot-loader snapshot-dependencies company-dependencies application ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/layers.xml ================================================ org/springframework/boot/loader/** *:*:*SNAPSHOT dependencies spring-boot-loader snapshot-dependencies application ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/local-repackaged-artifact-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin repackage repackage false ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/non-default-pom.xml ================================================ 4.0.0 packaging org.springframework.boot spring-boot-maven-plugin ${start.class} ZIP repackage ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/repackage-configuration-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin repackage exec ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging/repackage-pom.xml ================================================ 4.0.0 packaging org.springframework.boot spring-boot-maven-plugin repackage ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/bind-caches-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin /tmp/cache-${project.artifactId}.work /tmp/cache-${project.artifactId}.build /tmp/cache-${project.artifactId}.launch ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/build-image-example-builder-configuration-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin 17 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/buildpacks-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin file:///path/to/example-buildpack.tgz urn:cnb:builder:paketo-buildpacks/java ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/caches-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin cache-${project.artifactId}.build cache-${project.artifactId}.launch ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/custom-image-builder-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin mine/java-cnb-builder mine/java-cnb-run ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/custom-image-name-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin example.com/library/${project.artifactId} ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/docker-colima-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin unix:///${user.home}/.colima/docker.sock ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/docker-minikube-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin tcp://192.168.99.100:2376 true /home/user/.minikube/certs ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/docker-podman-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin unix:///run/user/1000/podman/podman.sock true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/docker-pom-authentication-command-line.xml ================================================ org.springframework.boot spring-boot-maven-plugin ${docker.publishRegistry.url} ${docker.publishRegistry.username} ${docker.publishRegistry.password} ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/docker-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin docker.example.com/library/${project.artifactId} true user secret ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/docker-registry-authentication-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin user secret https://docker.example.com/v1/ user@example.com ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/docker-token-authentication-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin 9cbaf023786cd7... ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/paketo-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin http://proxy.example.com https://proxy.example.com ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/pom.xml ================================================ 4.0.0 packaging-oci-image org.springframework.boot spring-boot-maven-plugin build-image-no-fork ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/packaging-oci-image/runtime-jvm-configuration-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin -XX:+HeapDumpOnOutOfMemoryError ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/running/active-profiles-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin local dev ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/running/application-arguments-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin property1 property2=${my.value} ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/running/debug-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/running/devtools-pom.xml ================================================ 4.0.0 running org.springframework.boot spring-boot-maven-plugin org.springframework.boot spring-boot-devtools true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/running/environment-variables-pom.xml ================================================ org.springframework.boot spring-boot-maven-plugin 5000 Some Text ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/running/hot-refresh-pom.xml ================================================ 4.0.0 getting-started org.springframework.boot spring-boot-maven-plugin true org.springframework.boot spring-boot-devtools true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/running/system-properties-pom.xml ================================================ 42 org.springframework.boot spring-boot-maven-plugin test ${my.value} ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/using/default-and-override-pom.xml ================================================ local,dev org.springframework.boot spring-boot-maven-plugin ${app.profiles} ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/using/different-versions-pom.xml ================================================ 4.0.0 getting-started 1.7.30 2024.1.10 org.springframework.boot spring-boot-maven-plugin ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/using/no-starter-parent-override-dependencies-pom.xml ================================================ 4.0.0 getting-started org.springframework.boot spring-boot-maven-plugin org.slf4j slf4j-api 1.7.30 org.springframework.data spring-data-bom 2024.1.10 pom import org.springframework.boot spring-boot-dependencies {version-spring-boot} pom import ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/examples/using/no-starter-parent-pom.xml ================================================ 4.0.0 using org.springframework.boot spring-boot-maven-plugin org.springframework.boot spring-boot-dependencies {version-spring-boot} pom import ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/aot.adoc ================================================ [[aot]] = Ahead-of-Time Processing Spring AOT is a process that analyzes your application at build-time and generate an optimized version of it. It is a mandatory step to run a Spring `ApplicationContext` in a native image. NOTE: For an overview of GraalVM Native Images support in Spring Boot, check the xref:reference:packaging/native-image/index.adoc[reference documentation]. The Spring Boot Maven plugin offers goals that can be used to perform AOT processing on both application and test code. [[aot.processing-applications]] == Processing Applications To configure your application to use this feature, add an execution for the `process-aot` goal, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$aot/pom.xml[tags=aot] ---- As the `BeanFactory` is fully prepared at build-time, conditions are also evaluated. This has an important difference compared to what a regular Spring Boot application does at runtime. For instance, if you want to opt-in or opt-out for certain features, you need to configure the environment used at build time to do so. The `process-aot` goal shares a number of properties with the xref:run.adoc[run goal] for that reason. [[aot.processing-applications.using-the-native-profile]] === Using the Native Profile If you use `spring-boot-starter-parent` as the `parent` of your project, a `native` profile can be used to streamline the steps required to build a native image. The `native` profile configures the following: * Execution of `process-aot` when the Spring Boot Maven Plugin is applied on a project. * Suitable settings so that xref:build-image.adoc[build-image] generates a native image. * Sensible defaults for the {url-native-build-tools-docs-maven-plugin}[Native Build Tools Maven Plugin], in particular: ** Making sure the plugin uses the raw classpath, and not the main jar file as it does not understand our repackaged jar format. ** Validate that a suitable GraalVM version is available. ** Download third-party reachability metadata. [WARNING] ==== The use of the raw classpath means that native image does not know about the generated `MANIFEST.MF`. If you need to read the content of the manifest in a native image, for instance to get the implementation version of your application, configure the `classesDirectory` option to use the regular jar. ==== To benefit from the `native` profile, a module that represents an application should define two plugins, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$aot-native/pom.xml[tags=aot-native] ---- A single project can trigger the generation of a native image on the command-line using either xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.buildpacks.maven[Cloud Native Buildpacks] or xref:how-to:native-image/developing-your-first-application.adoc#howto.native-image.developing-your-first-application.native-build-tools.maven[Native Image Build Tools]. To use the `native` profile with a multi-modules project, you can create a customization of the `native` profile so that it invokes your preferred technique. To bind Cloud Native Buildpacks during the `package` phase, add the following to the root POM of your multi-modules project: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$aot-native-profile-buildpacks/pom.xml[tags=profile] ---- The example below does the same for Native Build Tools: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$aot-native-profile-nbt/pom.xml[tags=profile] ---- Once the above is in place, you can build your multi-modules project and generate a native image in the relevant sub-modules, as shown in the following example: [source,shell] ---- $ mvn package -Pnative ---- NOTE: A "relevant" sub-module is a module that represents a Spring Boot application. Such module must define the Native Build Tools and Spring Boot plugins as described above. include::partial$goals/process-aot.adoc[leveloffset=+1] [[aot.processing-tests]] == Processing Tests The AOT engine can be applied to JUnit 5 tests that use Spring's Test Context Framework. Those tests are processed by the AOT engine and are then executed in a native image. NOTE: Tests are not processed when the regular tests are skipped using the `maven.test.skip` property. Just like <>, the `spring-boot-starter-parent` defines a `nativeTest` profile that can be used to streamline the steps required to execute your tests in a native image. The `nativeTest` profile configures the following: * Execution of `process-test-aot` when the Spring Boot Maven Plugin is applied on a project. * Execution of `test` when the {url-native-build-tools-docs-maven-plugin}[Native Build Tools Maven Plugin] is applied on a project. The execution defines sensible defaults, in particular: ** Making sure the plugin uses the raw classpath, and not the main jar file as it does not understand our repackaged jar format. ** Validate that a suitable GraalVM version is available. ** Download third-party reachability metadata. To benefit from the `nativeTest` profile, a module that represents an application should define two plugins, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$aot-native/pom.xml[tags=aot-native] ---- Once the above is in place for each module that needs this feature, you can build your multi-modules project and execute your tests in a native image in the relevant sub-modules, as shown in the following example: [source,shell] ---- $ mvn test -PnativeTest ---- NOTE: As with application AOT processing, the `BeanFactory` is fully prepared at build-time. include::partial$goals/process-test-aot.adoc[leveloffset=+1] ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/build-image.adoc ================================================ [[build-image]] = Packaging OCI Images The plugin can create an https://github.com/opencontainers/image-spec[OCI image] from a jar or war file using https://buildpacks.io/[Cloud Native Buildpacks] (CNB). Images can be built on the command-line using the `build-image` goal. This makes sure that the package lifecycle has run before the image is created. NOTE: For security reasons, images build and run as non-root users. See the {url-buildpacks-docs}/reference/spec/platform-api/#users[CNB specification] for more details. The easiest way to get started is to invoke `mvn spring-boot:build-image` on a project. It is possible to automate the creation of an image whenever the `package` phase is invoked, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/pom.xml[tags=packaging-oci-image] ---- NOTE: Use `build-image-no-fork` when binding the goal to the package lifecycle. This goal is similar to `build-image` but does not fork the lifecycle to make sure `package` has run. In the rest of this section, `build-image` is used to refer to either the `build-image` or `build-image-no-fork` goals. TIP: While the buildpack runs from an xref:packaging.adoc[executable archive], it is not necessary to execute the `repackage` goal first as the executable archive is created automatically if necessary. When the `build-image` repackages the application, it applies the same settings as the `repackage` goal would, that is dependencies can be excluded using one of the exclude options. The `spring-boot-devtools` and `spring-boot-docker-compose` modules are automatically excluded by default (you can control this using the `excludeDevtools` and `excludeDockerCompose` properties). Pay also attention that optional dependencies are not included by default. If you have defined those modules as optional, you also need to set the `includeOptional` property to `true`. [[build-image.docker-daemon]] == Docker Daemon The `build-image` goal requires access to a Docker daemon. The goal will inspect local Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] to determine the current https://docs.docker.com/engine/context/working-with-contexts/[context] and use the context connection information to communicate with a Docker daemon. If the current context can not be determined or the context does not have connection information, then the goal will use a default local connection. This works with https://docs.docker.com/install/[Docker Engine] on all supported platforms without configuration. Environment variables can be set to configure the `build-image` goal to use an alternative local or remote connection. The following table shows the environment variables and their values: |=== | Environment variable | Description | DOCKER_CONFIG | Location of Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] used to determine the current context (defaults to `$HOME/.docker`) | DOCKER_CONTEXT | Name of a https://docs.docker.com/engine/context/working-with-contexts/[context] that should be used to retrieve host information from Docker CLI configuration files (overrides `DOCKER_HOST`) | DOCKER_HOST | URL containing the host and port for the Docker daemon - for example `tcp://192.168.99.100:2376` | DOCKER_TLS_VERIFY | Enable secure HTTPS protocol when set to `1` (optional) | DOCKER_CERT_PATH | Path to certificate and key files for HTTPS (required if `DOCKER_TLS_VERIFY=1`, ignored otherwise) |=== Docker daemon connection information can also be provided using `docker` parameters in the plugin configuration. The following table summarizes the available parameters: |=== | Parameter | Description | `context` | Name of a https://docs.docker.com/engine/context/working-with-contexts/[context] that should be used to retrieve host information from Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] | `host` | URL containing the host and port for the Docker daemon - for example `tcp://192.168.99.100:2376` | `tlsVerify` | Enable secure HTTPS protocol when set to `true` (optional) | `certPath` | Path to certificate and key files for HTTPS (required if `tlsVerify` is `true`, ignored otherwise) | `bindHostToBuilder` | When `true`, the value of the `host` property will be provided to the container that is created for the CNB builder (optional) |=== For more details, see also xref:build-image.adoc#build-image.examples.docker[examples]. [[build-image.docker-registry]] == Docker Registry If the Docker images specified by the `builder` or `runImage` parameters are stored in a private Docker image registry that requires authentication, the authentication credentials can be provided using `docker.builderRegistry` parameters. If the generated Docker image is to be published to a Docker image registry, the authentication credentials can be provided using `docker.publishRegistry` parameters. Parameters are provided for user authentication or identity token authentication. Consult the documentation for the Docker registry being used to store images for further information on supported authentication methods. The following table summarizes the available parameters for `docker.builderRegistry` and `docker.publishRegistry`: |=== | Parameter | Description | `username` | Username for the Docker image registry user. Required for user authentication. | `password` | Password for the Docker image registry user. Required for user authentication. | `url` | Address of the Docker image registry. Optional for user authentication. | `email` | E-mail address for the Docker image registry user. Optional for user authentication. | `token` | Identity token for the Docker image registry user. Required for token authentication. |=== For more details, see also xref:build-image.adoc#build-image.examples.docker[examples]. [NOTE] ==== If credentials are not provided, the plugin reads the user's existing Docker configuration file (typically located at `$HOME/.docker/config.json`) to determine authentication methods. Using these methods, the plugin attempts to provide authentication credentials for the requested image. The plugin supports the following authentication methods: - *Credential Helpers*: External tools configured in the Docker configuration file to provide credentials for specific registries. For example, tools like `osxkeychain` or `ecr-login` handle authentication for certain registries. - *Credential Store*: A default fallback mechanism that securely stores and retrieves credentials (e.g., `desktop` for Docker Desktop). - *Static Credentials*: Credentials that are stored directly in the Docker configuration file under the `auths` section. ==== [[build-image.customization]] == Image Customizations The plugin invokes a {url-buildpacks-docs}/for-app-developers/concepts/builder/[builder] to orchestrate the generation of an image. The builder includes multiple {url-buildpacks-docs}/for-app-developers/concepts/buildpack/[buildpacks] that can inspect the application to influence the generated image. By default, the plugin chooses a builder image. The name of the generated image is deduced from project properties. The `image` parameter allows configuration of the builder and how it should operate on the project. The following table summarizes the available parameters and their default values: [cols="1,4,1"] |=== | Parameter / (User Property)| Description | Default value | `builder` + (`spring-boot.build-image.builder`) | Name of the builder image to use. | `paketobuildpacks/builder-noble-java-tiny:latest` | `trustBuilder` + (`spring-boot.build-image.trustBuilder`) | Whether to treat the builder as {url-buildpacks-docs}/for-platform-operators/how-to/integrate-ci/pack/concepts/trusted_builders/#what-is-a-trusted-builder[trusted]. | `true` if the builder is one of `paketobuildpacks/builder-noble-java-tiny`, `paketobuildpacks/builder-jammy-java-tiny`, `paketobuildpacks/builder-jammy-tiny`, `paketobuildpacks/builder-jammy-base`, `paketobuildpacks/builder-jammy-full`, `paketobuildpacks/builder-jammy-buildpackless-tiny`, `paketobuildpacks/builder-jammy-buildpackless-base`, `paketobuildpacks/builder-jammy-buildpackless-full`, `gcr.io/buildpacks/builder`, `heroku/builder`; `false` otherwise. | `imagePlatform` + (`spring-boot.build-image.imagePlatform`) a|The platform (operating system and architecture) of any builder, run, and buildpack images that are pulled. Must be in the form of `OS[/architecture[/variant]]`, such as `linux/amd64`, `linux/arm64`, or `linux/arm/v5`. Refer to documentation of the builder being used to determine the image OS and architecture options available. | No default value, indicating that the platform of the host machine should be used. | `runImage` + (`spring-boot.build-image.runImage`) | Name of the run image to use. | No default value, indicating the run image specified in Builder metadata should be used. | `name` + (`spring-boot.build-image.imageName`) | javadoc:org.springframework.boot.buildpack.platform.docker.type.ImageName#of(java.lang.String)[Image name] for the generated image. | `docker.io/library/` + `${project.artifactId}:${project.version}` | `pullPolicy` + (`spring-boot.build-image.pullPolicy`) | javadoc:org.springframework.boot.buildpack.platform.build.PullPolicy[Policy] used to determine when to pull the builder and run images from the registry. Acceptable values are `ALWAYS`, `NEVER`, and `IF_NOT_PRESENT`. | `ALWAYS` | `env` | Environment variables that should be passed to the builder. | | `buildpacks` a|Buildpacks that the builder should use when building the image. Only the specified buildpacks will be used, overriding the default buildpacks included in the builder. Buildpack references must be in one of the following forms: * Buildpack in the builder - `[urn:cnb:builder:][@]` * Buildpack in a directory on the file system - `[file://]` * Buildpack in a gzipped tar (.tgz) file on the file system - `[file://]/` * Buildpack in an OCI image - `[docker://]/[:][@]` | None, indicating the builder should use the buildpacks included in it. | `bindings` a|https://docs.docker.com/storage/bind-mounts/[Volume bind mounts] that should be mounted to the builder container when building the image. The bindings will be passed unparsed and unvalidated to Docker when creating the builder container. Bindings must be in one of the following forms: * `:[:]` * `:[:]` Where `` can contain: * `ro` to mount the volume as read-only in the container * `rw` to mount the volume as readable and writable in the container * `volume-opt=key=value` to specify key-value pairs consisting of an option name and its value | | `network` + (`spring-boot.build-image.network`) | The https://docs.docker.com/network/#network-drivers[network driver] the builder container will be configured to use. The value supplied will be passed unvalidated to Docker when creating the builder container. | | `cleanCache` + (`spring-boot.build-image.cleanCache`) | Whether to clean the cache before building. | `false` | `verboseLogging` | Enables verbose logging of builder operations. | `false` | `publish` + (`spring-boot.build-image.publish`) | Whether to publish the generated image to a Docker registry. | `false` | `tags` | One or more additional tags to apply to the generated image. The values provided to the `tags` option should be *full* image references. See xref:build-image.adoc#build-image.customization.tags[the tags section] for more details. | | `buildWorkspace` | A temporary workspace that will be used by the builder and buildpacks to store files during image building. The value can be a named volume or a bind mount location. | A named volume in the Docker daemon, with a name derived from the image name. | `buildCache` | A cache containing layers created by buildpacks and used by the image building process. The value can be a named volume or a bind mount location. | A named volume in the Docker daemon, with a name derived from the image name. | `launchCache` | A cache containing layers created by buildpacks and used by the image launching process. The value can be a named volume or a bind mount location. | A named volume in the Docker daemon, with a name derived from the image name. | `createdDate` + (`spring-boot.build-image.createdDate`) | A date that will be used to set the `Created` field in the generated image's metadata. The value must be a string in the ISO 8601 instant format, or `now` to use the current date and time. | A fixed date that enables {url-buildpacks-docs}/for-app-developers/concepts/reproducibility/[build reproducibility]. | `applicationDirectory` + (`spring-boot.build-image.applicationDirectory`) | The path to a directory that application contents will be uploaded to in the builder image. Application contents will also be in this location in the generated image. | `/workspace` | `securityOptions` | https://docs.docker.com/reference/cli/docker/container/run/#security-opt[Security options] that will be applied to the builder container, provided as an array of string values | `["label=disable"]` on Linux and macOS, `[]` on Windows |=== NOTE: The plugin detects the target Java compatibility of the project using the compiler's plugin configuration or the `maven.compiler.target` property. When using the default Paketo builder and buildpacks, the plugin instructs the buildpacks to install the same Java version. You can override this behaviour as shown in the xref:build-image.adoc#build-image.examples.builder-configuration[builder configuration] examples. For more details, see also xref:build-image.adoc#build-image.examples[examples]. [[build-image.customization.tags]] === Tags Format The values provided to the `tags` option should be *full* image references. The accepted format is `[domainHost:port/][path/]name[:tag][@digest]`. If the domain is missing, it defaults to `docker.io`. If the path is missing, it defaults to `library`. If the tag is missing, it defaults to `latest`. Some examples: * `my-image` leads to the image reference `docker.io/library/my-image:latest` * `my-repository/my-image` leads to `docker.io/my-repository/my-image:latest` * `example.com/my-repository/my-image:1.0.0` will be used as is include::partial$goals/build-image.adoc[leveloffset=+1] include::partial$goals/build-image-no-fork.adoc[leveloffset=+1] [[build-image.examples]] == Examples [[build-image.examples.custom-image-builder]] === Custom Image Builder If you need to customize the builder used to create the image or the run image used to launch the built image, configure the plugin as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/custom-image-builder-pom.xml[tags=custom-image-builder] ---- This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`, and the run image named `mine/java-cnb-run` and the tag `latest`. The builder and run image can be specified on the command line as well, as shown in this example: [source,shell] ---- $ mvn spring-boot:build-image -Dspring-boot.build-image.builder=mine/java-cnb-builder -Dspring-boot.build-image.runImage=mine/java-cnb-run ---- [[build-image.examples.builder-configuration]] === Builder Configuration If the builder exposes configuration options using environment variables, those can be set using the `env` attributes. The following is an example of {url-paketo-docs-java-buildpack}/#configuring-the-jvm-version[configuring the JVM version] used by the Paketo Java buildpacks at build time: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/build-image-example-builder-configuration-pom.xml[tags=build-image-example-builder-configuration] ---- If there is a network proxy between the Docker daemon the builder runs in and network locations that buildpacks download artifacts from, you will need to configure the builder to use the proxy. When using the Paketo builder, this can be accomplished by setting the `HTTPS_PROXY` and/or `HTTP_PROXY` environment variables as show in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/paketo-pom.xml[tags=paketo] ---- [[build-image.examples.runtime-jvm-configuration]] === Runtime JVM Configuration Paketo Java buildpacks {url-paketo-docs-java-buildpack}/#runtime-jvm-configuration[configure the JVM runtime environment] by setting the `JAVA_TOOL_OPTIONS` environment variable. The buildpack-provided `JAVA_TOOL_OPTIONS` value can be modified to customize JVM runtime behavior when the application image is launched in a container. Environment variable modifications that should be stored in the image and applied to every deployment can be set as described in the {url-paketo-docs}/buildpacks/configuration/#environment-variables[Paketo documentation] and shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/runtime-jvm-configuration-pom.xml[tags=runtime-jvm-configuration] ---- [[build-image.examples.custom-image-name]] === Custom Image Name By default, the image name is inferred from the `artifactId` and the `version` of the project, something like `docker.io/library/${project.artifactId}:${project.version}`. You can take control over the name, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/custom-image-name-pom.xml[tags=custom-image-name] ---- NOTE: This configuration does not provide an explicit tag so `latest` is used. It is possible to specify a tag as well, either using `${project.version}`, any property available in the build or a hardcoded version. The image name can be specified on the command line as well, as shown in this example: [source,shell] ---- $ mvn spring-boot:build-image -Dspring-boot.build-image.imageName=example.com/library/my-app:v1 ---- [[build-image.examples.buildpacks]] === Buildpacks By default, the builder will use buildpacks included in the builder image and apply them in a pre-defined order. An alternative set of buildpacks can be provided to apply buildpacks that are not included in the builder, or to change the order of included buildpacks. When one or more buildpacks are provided, only the specified buildpacks will be applied. The following example instructs the builder to use a custom buildpack packaged in a `.tgz` file, followed by a buildpack included in the builder. [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/buildpacks-pom.xml[tags=buildpacks] ---- Buildpacks can be specified in any of the forms shown below. A buildpack located in a CNB Builder (version may be omitted if there is only one buildpack in the builder matching the `buildpack-id`): * `urn:cnb:builder:buildpack-id` * `urn:cnb:builder:buildpack-id@0.0.1` * `buildpack-id` * `buildpack-id@0.0.1` A path to a directory containing buildpack content (not supported on Windows): * `\file:///path/to/buildpack/` * `/path/to/buildpack/` A path to a gzipped tar file containing buildpack content: * `\file:///path/to/buildpack.tgz` * `/path/to/buildpack.tgz` An OCI image containing a {url-buildpacks-docs}/for-buildpack-authors/how-to/distribute-buildpacks/package-buildpack/[packaged buildpack]: * `docker://example/buildpack` * `docker:///example/buildpack:latest` * `docker:///example/buildpack@sha256:45b23dee08...` * `example/buildpack` * `example/buildpack:latest` * `example/buildpack@sha256:45b23dee08...` [[build-image.examples.publish]] === Image Publishing The generated image can be published to a Docker registry by enabling a `publish` option. If the Docker registry requires authentication, the credentials can be configured using `docker.publishRegistry` parameters. If the Docker registry does not require authentication, the `docker.publishRegistry` configuration can be omitted. NOTE: The registry that the image will be published to is determined by the registry part of the image name (`docker.example.com` in these examples). If `docker.publishRegistry` credentials are configured and include a `url` parameter, this value is passed to the registry but is not used to determine the publishing registry location. [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/docker-pom.xml[tags=docker] ---- The `publish` option can be specified on the command line as well, as shown in this example: [source,shell] ---- $ mvn spring-boot:build-image -Dspring-boot.build-image.imageName=docker.example.com/library/my-app:v1 -Dspring-boot.build-image.publish=true ---- When using the `publish` option on the command line with authentication, you can provide credentials using properties as in this example: [source,shell] ---- $ mvn spring-boot:build-image \ -Ddocker.publishRegistry.username=user \ -Ddocker.publishRegistry.password=secret \ -Ddocker.publishRegistry.url=docker.example.com \ -Dspring-boot.build-image.publish=true \ -Dspring-boot.build-image.imageName=docker.example.com/library/my-app:v1 ---- and reference the properties in the XML configuration: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/docker-pom-authentication-command-line.xml[tags=docker] ---- [[build-image.examples.caches]] === Builder Cache and Workspace Configuration The CNB builder caches layers that are used when building and launching an image. By default, these caches are stored as named volumes in the Docker daemon with names that are derived from the full name of the target image. If the image name changes frequently, for example when the project version is used as a tag in the image name, then the caches can be invalidated frequently. The cache volumes can be configured to use alternative names to give more control over cache lifecycle as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/caches-pom.xml[tags=caches] ---- Builders and buildpacks need a location to store temporary files during image building. By default, this temporary build workspace is stored in a named volume. The caches and the build workspace can be configured to use bind mounts instead of named volumes, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/bind-caches-pom.xml[tags=caches] ---- [[build-image.examples.docker]] === Docker Configuration [[build-image.examples.docker.minikube]] ==== Docker Configuration for minikube The plugin can communicate with the https://minikube.sigs.k8s.io/docs/tasks/docker_daemon/[Docker daemon provided by minikube] instead of the default local connection. On Linux and macOS, environment variables can be set using the command `eval $(minikube docker-env)` after minikube has been started. The plugin can also be configured to use the minikube daemon by providing connection details similar to those shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/docker-minikube-pom.xml[tags=docker-minikube] ---- [[build-image.examples.docker.podman]] ==== Docker Configuration for podman The plugin can communicate with a https://podman.io/[podman container engine]. The plugin can be configured to use podman local connection by providing connection details similar to those shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/docker-podman-pom.xml[tags=docker-podman] ---- TIP: With the `colima` CLI installed, the command `podman info --format='{{.Host.RemoteSocket.Path}}'` can be used to get the value for the `docker.host` configuration property shown in this example. [[build-image.examples.docker.colima]] ==== Docker Configuration for Colima The plugin can communicate with the Docker daemon provided by https://github.com/abiosoft/colima[Colima]. The `DOCKER_HOST` environment variable can be set by using the following command: [source,shell,subs="verbatim,attributes"] ---- $ export DOCKER_HOST=$(docker context inspect colima -f '{{.Endpoints.docker.Host}}') ---- The plugin can also be configured to use Colima daemon by providing connection details similar to those shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/docker-colima-pom.xml[tags=docker-colima] ---- [[build-image.examples.docker.auth]] ==== Docker Configuration for Authentication If the builder or run image are stored in a private Docker registry that supports user authentication, authentication details can be provided using `docker.builderRegistry` parameters as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/docker-registry-authentication-pom.xml[tags=docker-registry-authentication] ---- If the builder or run image is stored in a private Docker registry that supports token authentication, the token value can be provided using `docker.builderRegistry` parameters as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging-oci-image/docker-token-authentication-pom.xml[tags=docker-token-authentication] ---- ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/build-info.adoc ================================================ [[build-info]] = Integrating with Actuator Spring Boot Actuator displays build-related information if a `META-INF/build-info.properties` file is present. The `build-info` goal generates such file with the coordinates of the project and the build time. It also allows you to add an arbitrary number of additional properties, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$build-info/pom.xml[tags=build-info] ---- This configuration will generate a `build-info.properties` at the expected location with three additional keys. NOTE: `java.version` is expected to be a regular property available in the project. It will be interpolated as you would expect. include::partial$goals/build-info.adoc[leveloffset=+1] ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/getting-started.adoc ================================================ [[getting-started]] = Getting Started To use the Spring Boot Maven Plugin, include the appropriate XML in the `plugins` section of your `pom.xml`, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/pom.xml[tags=getting-started] ---- ifeval::["{build-type}" == "commercial"] The plugin is published to the Spring Commercial repository. You will have to configure your build to access this repository. This is usually done through a local artifact repository that mirrors the content of the Spring Commercial repository. Alternatively, while it is not recommended, the Spring Commercial repository can also be accessed directly. In either case, see https://docs.vmware.com/en/Tanzu-Spring-Runtime/Commercial/Tanzu-Spring-Runtime/spring-enterprise-subscription.html[the Tanzu Spring Runtime documentation] for further details. endif::[] ifeval::["{build-type}" == "opensource"] If you use a milestone or snapshot release, you also need to add the appropriate `pluginRepository` elements, as shown in the following listing: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$getting-started/plugin-repositories-pom.xml[tags=plugin-repositories] ---- endif::[] ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/goals.adoc ================================================ [[goals]] = Goals The Spring Boot Plugin has the following goals: include::partial$goals/overview.adoc[] ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/help.adoc ================================================ [[help]] = Help Information The `help` goal is a standard goal that displays information on the capabilities of the plugin. include::partial$goals/help.adoc[leveloffset=+1] ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/index.adoc ================================================ [[maven-plugin]] = Maven Plugin The Spring Boot Maven Plugin provides Spring Boot support in https://maven.org[Apache Maven]. It allows you to package executable jar or war archives, run Spring Boot applications, generate build information and start your Spring Boot application prior to running integration tests. To use it, you must use Maven 3.6.3 or later. In addition to this user guide, xref:api/java/index.html[API documentation] is also available. ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/integration-tests.adoc ================================================ [[integration-tests]] = Running Integration Tests While you may start your Spring Boot application very easily from your test (or test suite) itself, it may be desirable to handle that in the build itself. To make sure that the lifecycle of your Spring Boot application is properly managed around your integration tests, you can use the `start` and `stop` goals, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$integration-tests/pom.xml[tags=integration-tests] ---- Such setup can now use the https://maven.apache.org/surefire/maven-failsafe-plugin[failsafe-plugin] to run your integration tests as you would expect. NOTE: The application is started in a separate process and JMX is used to communicate with the application. By default, the plugin uses port `9001`. If you need to configure the JMX port, see xref:integration-tests.adoc#integration-tests.examples.jmx-port[the dedicated example]. You could also configure a more advanced setup to skip the integration tests when a specific property has been set, see xref:integration-tests.adoc#integration-tests.examples.skip[the dedicated example]. [[integration-tests.no-starter-parent]] == Using Failsafe Without Spring Boot's Parent POM Spring Boot's Parent POM, `spring-boot-starter-parent`, configures Failsafe's `` to be `${project.build.outputDirectory}`. Without this configuration, which causes Failsafe to use the compiled classes rather than the repackaged jar, Failsafe cannot load your application's classes. If you are not using the parent POM, you should configure Failsafe in the same way, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$integration-tests/failsafe-pom.xml[tags=failsafe] ---- include::partial$goals/start.adoc[leveloffset=+1] include::partial$goals/stop.adoc[leveloffset=+1] [[integration-tests.examples]] == Examples [[integration-tests.examples.random-port]] === Random Port for Integration Tests One nice feature of the Spring Boot test integration is that it can allocate a free port for the web application. When the `start` goal of the plugin is used, the Spring Boot application is started separately, making it difficult to pass the actual port to the integration test itself. The example below showcases how you could achieve the same feature using the https://www.mojohaus.org/build-helper-maven-plugin[Build Helper Maven Plugin]: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$integration-tests/random-port-pom.xml[tags=random-port] ---- You can now retrieve the `test.server.port` system property in any of your integration test to create a proper `URL` to the server. [[integration-tests.examples.jmx-port]] === Customize JMX Port The `jmxPort` property allows to customize the port the plugin uses to communicate with the Spring Boot application. This example shows how you can customize the port in case `9001` is already used: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$integration-tests/customize-jmx-port-pom.xml[tags=customize-jmx-port] ---- TIP: If you need to configure the JMX port, make sure to do so in the global configuration as shown above so that it is shared by both goals. [[integration-tests.examples.skip]] === Skip Integration Tests The `skip` property allows to skip the execution of the Spring Boot maven plugin altogether. This example shows how you can skip integration tests with a command-line property and still make sure that the `repackage` goal runs: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$integration-tests/skip-integration-tests-pom.xml[tags=skip-integration-tests] ---- By default, the integration tests will run but this setup allows you to easily disable them on the command-line as follows: [source,shell] ---- $ mvn verify -Dskip.it=true ---- ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/packaging.adoc ================================================ [[packaging]] = Packaging Executable Archives The plugin can create executable archives (jar files and war files) that contain all of an application's dependencies and can then be run with `java -jar`. Packaging an executable archive is performed by the `repackage` goal, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/repackage-pom.xml[tags=repackage] ---- WARNING: The `repackage` goal is not meant to be used alone on the command-line as it operates on the source `jar` (or `war`) produced by the `package` phase. To use this goal on the command-line, you must include the `package` phase: `mvn package spring-boot:repackage`. TIP: If you are using `spring-boot-starter-parent`, such execution is already pre-configured with a `repackage` execution ID so that only the plugin definition should be added. The example above repackages a `jar` or `war` archive that is built during the package phase of the Maven lifecycle, including any `provided` dependencies that are defined in the project. If some of these dependencies need to be excluded, you can use one of the `exclude` options; see the xref:packaging.adoc#packaging.examples.exclude-dependency[dependency exclusion] for more details. The original (that is non-executable) artifact is renamed to `.original` by default but it is also possible to keep the original artifact using a custom classifier. NOTE: The `outputFileNameMapping` feature of the `maven-war-plugin` is currently not supported. The `spring-boot-devtools` and `spring-boot-docker-compose` modules are automatically excluded by default (you can control this using the `excludeDevtools` and `excludeDockerCompose` properties). In order to make that work with `war` packaging, the `spring-boot-devtools` and `spring-boot-docker-compose` dependencies must be set as `optional` or with the `provided` scope. Pay also attention that optional dependencies are not included by default. If you have defined those modules as optional, you also need to set the `includeOptional` property to `true`. The plugin rewrites your manifest, and in particular it manages the `Main-Class` and `Start-Class` entries. If the defaults don't work you have to configure the values in the Spring Boot plugin, not in the jar plugin. The `Main-Class` in the manifest is controlled by the `layout` property of the Spring Boot plugin, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/non-default-pom.xml[tags=non-default] ---- The `layout` property defaults to a value determined by the archive type (`jar` or `war`). The following layouts are available: * `JAR`: regular executable JAR layout. * `WAR`: executable WAR layout. `provided` dependencies are placed in `WEB-INF/lib-provided` to avoid any clash when the `war` is deployed in a servlet container. * `ZIP` (alias to `DIR`): similar to the `JAR` layout using `PropertiesLauncher`. * `NONE`: Bundle all dependencies and project resources. Does not bundle a bootstrap loader. [[packaging.layers]] == Layered Jar or War A repackaged jar contains the application's classes and dependencies in `BOOT-INF/classes` and `BOOT-INF/lib` respectively. Similarly, an executable war contains the application's classes in `WEB-INF/classes` and dependencies in `WEB-INF/lib` and `WEB-INF/lib-provided`. For cases where a docker image needs to be built from the contents of a jar or war, it's useful to be able to separate these directories further so that they can be written into distinct layers. Layered archives use the same layout as a regular repackaged jar or war, but include an additional meta-data file that describes each layer. By default, the following layers are defined: * `dependencies` for any dependency whose version does not contain `SNAPSHOT`. * `spring-boot-loader` for the loader classes. * `snapshot-dependencies` for any dependency whose version contains `SNAPSHOT`. * `application` for local module dependencies, application classes, and resources. Module dependencies are identified by looking at all of the modules that are part of the current build. If a module dependency can only be resolved because it has been installed into Maven's local cache and it is not part of the current build, it will be identified as regular dependency. The layers order is important as it determines how likely previous layers can be cached when part of the application changes. The default order is `dependencies`, `spring-boot-loader`, `snapshot-dependencies`, `application`. Content that is least likely to change should be added first, followed by layers that are more likely to change. The repackaged archive includes the `layers.idx` file by default. To disable this feature, you can do so in the following manner: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/disable-layers-pom.xml[tags=disable-layers] ---- [[packaging.layers.configuration]] === Custom Layers Configuration Depending on your application, you may want to tune how layers are created and add new ones. This can be done using a configuration loaded from a file or from the classpath. To use a configuration file, register it as shown below: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/custom-layers-pom.xml[tags=custom-layers] ---- Alternatively, the configuration can be loaded from the classpath by specifying its name. A matching file is expected at `META-INF/spring/layers/.xml` on the plugin's classpath. The jar that contains the configuration can be added as a plugin dependency, as shown below: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/custom-layers-classpath-pom.xml[tags=custom-layers-classpath] ---- In the example above, `META-INF/spring/layers/custom.xml` is expected to be available on the plugin's classpath. The configuration file describes how an archive can be separated into layers, and the order of those layers. The following example shows how the default ordering described above can be defined explicitly: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/layers.xml[tags=layers] ---- The `layers` XML format is defined in three sections: * The `` block defines how the application classes and resources should be layered. * The `` block defines how dependencies should be layered. * The `` block defines the order that the layers should be written. Nested `` blocks are used within `` and `` sections to claim content for a layer. The blocks are evaluated in the order that they are defined, from top to bottom. Any content not claimed by an earlier block remains available for subsequent blocks to consider. The `` block claims content using nested `` and `` elements. The `` section uses Ant-style path matching for include/exclude expressions. The `` section uses `group:artifact[:version]` patterns. It also provides `` and `` elements that can be used to include or exclude local module dependencies. If no `` is defined, then all content (not claimed by an earlier block) is considered. If no `` is defined, then no exclusions are applied. Looking at the `` example above, we can see that the first `` will claim all module dependencies for the `application.layer`. The next `` will claim all SNAPSHOT dependencies for the `snapshot-dependencies` layer. The final `` will claim anything left (in this case, any dependency that is not a SNAPSHOT) for the `dependencies` layer. The `` block has similar rules. First claiming `org/springframework/boot/loader/**` content for the `spring-boot-loader` layer. Then claiming any remaining classes and resources for the `application` layer. NOTE: The order that `` blocks are defined is often different from the order that the layers are written. For this reason the `` element must always be included and _must_ cover all layers referenced by the `` blocks. include::partial$goals/repackage.adoc[leveloffset=+1] [[packaging.examples]] == Examples [[packaging.examples.custom-classifier]] === Custom Classifier By default, the `repackage` goal replaces the original artifact with the repackaged one. That is a sane behavior for modules that represent an application but if your module is used as a dependency of another module, you need to provide a classifier for the repackaged one. The reason for that is that application classes are packaged in `BOOT-INF/classes` so that the dependent module cannot load a repackaged jar's classes. If that is the case or if you prefer to keep the original artifact and attach the repackaged one with a different classifier, configure the plugin as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/different-classifier-pom.xml[tags=different-classifier] ---- If you are using `spring-boot-starter-parent`, the `repackage` goal is executed automatically in an execution with id `repackage`. In that setup, only the configuration should be specified, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/repackage-configuration-pom.xml[tags=repackage-configuration] ---- This configuration will generate two artifacts: the original one and the repackaged counter part produced by the repackage goal. Both will be installed/deployed transparently. You can also use the same configuration if you want to repackage a secondary artifact the same way the main artifact is replaced. The following configuration installs/deploys a single `task` classified artifact with the repackaged application: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/classified-artifact-pom.xml[tags=classified-artifact] ---- As both the `maven-jar-plugin` and the `spring-boot-maven-plugin` run at the same phase, it is important that the jar plugin is defined first (so that it runs before the repackage goal). Again, if you are using `spring-boot-starter-parent`, this can be simplified as follows: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/jar-plugin-first-pom.xml[tags=jar-plugin-first] ---- [[packaging.examples.custom-name]] === Custom Name If you need the repackaged jar to have a different local name than the one defined by the `artifactId` attribute of the project, use the standard `finalName`, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/custom-name-pom.xml[tags=custom-name] ---- This configuration will generate the repackaged artifact in `target/my-app.jar`. [[packaging.examples.local-artifact]] === Local Repackaged Artifact By default, the `repackage` goal replaces the original artifact with the executable one. If you need to only deploy the original jar and yet be able to run your app with the regular file name, configure the plugin as follows: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/local-repackaged-artifact-pom.xml[tags=local-repackaged-artifact] ---- This configuration generates two artifacts: the original one and the executable counter part produced by the `repackage` goal. Only the original one will be installed/deployed. [[packaging.examples.custom-layout]] === Custom Layout Spring Boot repackages the jar file for this project using a custom layout factory defined in the additional jar file, provided as a dependency to the build plugin: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/custom-layout-pom.xml[tags=custom-layout] ---- The layout factory is provided as an implementation of `LayoutFactory` (from `spring-boot-loader-tools`) explicitly specified in the pom. If there is only one custom `LayoutFactory` on the plugin classpath and it is listed in `META-INF/spring.factories` then it is unnecessary to explicitly set it in the plugin configuration. Layout factories are always ignored if an explicit xref:#packaging.repackage-goal.parameter-details.layout-factory[layout] is set. [[packaging.examples.exclude-dependency]] === Dependency Exclusion By default, both the `repackage` and the `run` goals will include any `provided` dependencies that are defined in the project. A Spring Boot project should consider `provided` dependencies as "container" dependencies that are required to run the application. Generally speaking, Spring Boot projects are not used as dependencies and are therefore unlikely to have any `optional` dependencies. When a project does have optional dependencies they too will be included by the `repackage` and `run` goals. Some of these dependencies may not be required at all and should be excluded from the executable jar. For consistency, they should not be present either when running the application. There are two ways one can exclude a dependency from being packaged/used at runtime: * Exclude a specific artifact identified by `groupId` and `artifactId`, optionally with a `classifier` if needed. * Exclude any artifact belonging to a given `groupId`. The following example excludes `com.example:module1`, and only that artifact: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/exclude-artifact-pom.xml[tags=exclude-artifact] ---- This example excludes any artifact belonging to the `com.example` group: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/exclude-artifact-group-pom.xml[tags=exclude-artifact-group] ---- [[packaging.examples.layered-archive-tools]] === JAR Tools When a layered jar or war is created, the `spring-boot-jarmode-tools` jar will be added as a dependency to your archive. With this jar on the classpath, you can launch your application in a special mode which allows the bootstrap code to run something entirely different from your application, for example, something that extracts the layers. If you wish to exclude this dependency, you can do so in the following manner: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/exclude-dependency-pom.xml[tags=exclude-dependency] ---- [[packaging.examples.custom-layers-configuration]] === Custom Layers Configuration The default setup splits dependencies into snapshot and non-snapshot, however, you may have more complex rules. For example, you may want to isolate company-specific dependencies of your project in a dedicated layer. The following `layers.xml` configuration shown one such setup: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$packaging/layers-configuration.xml[tags=layers-configuration] ---- The configuration above creates an additional `company-dependencies` layer with all libraries with the `com.acme` groupId. ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/run.adoc ================================================ [[run]] = Running your Application with Maven The plugin includes a run goal which can be used to launch your application from the command line, as shown in the following example: [source,shell] ---- $ mvn spring-boot:run ---- Application arguments can be specified using the `arguments` parameter, see xref:run.adoc#run.examples.using-application-arguments[using application arguments] for more details. The application is executed in a forked process and setting properties on the command-line will not affect the application. If you need to specify some JVM arguments (that is for debugging purposes), you can use the `jvmArguments` parameter, see xref:run.adoc#run.examples.debug[Debug the application] for more details. There is also explicit support for xref:run.adoc#run.examples.system-properties[system properties] and xref:run.adoc#run.examples.environment-variables[environment variables]. As enabling a profile is quite common, there is dedicated `profiles` property that offers a shortcut for `-Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"`, see xref:run.adoc#run.examples.specify-active-profiles[Specify active profiles]. Spring Boot `devtools` is a module to improve the development-time experience when working on Spring Boot applications. To enable it, just add the following dependency to your project: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$running/devtools-pom.xml[tags=devtools] ---- When `devtools` is running, it detects changes when you recompile your application and automatically refreshes it. This works for not only resources but code as well. It also provides a LiveReload server so that it can automatically trigger a browser refresh whenever things change. Devtools can also be configured to only refresh the browser whenever a static resource has changed (and ignore any change in the code). Just include the following property in your project: [source,properties] ---- spring.devtools.remote.restart.enabled=false ---- Prior to `devtools`, the plugin supported hot refreshing of resources by default which has now been disabled in favour of the solution described above. You can restore it at any time by configuring your project: [source,xml,subs="verbatim,attributes"] ---- include::example$running/hot-refresh-pom.xml[tags=hot-refresh] ---- When `addResources` is enabled, any `src/main/resources` directory will be added to the application classpath when you run the application and any duplicate found in the classes output will be removed. This allows hot refreshing of resources which can be very useful when developing web applications. For example, you can work on HTML, CSS or JavaScript files and see your changes immediately without recompiling your application. It is also a helpful way of allowing your front end developers to work without needing to download and install a Java IDE. NOTE: A side effect of using this feature is that filtering of resources at build time will not work. In order to be consistent with the `repackage` goal, the `run` goal builds the classpath in such a way that any dependency that is excluded in the plugin's configuration gets excluded from the classpath as well. For more details, see xref:packaging.adoc#packaging.examples.exclude-dependency[the dedicated example]. Sometimes it is useful to run a test variant of your application. For example, if you want to xref:reference:features/dev-services.adoc#features.dev-services.testcontainers.at-development-time[use Testcontainers at development time] or make use of some test stubs. Use the `test-run` goal with many of the same features and configuration options as `run` for this purpose. include::partial$goals/run.adoc[leveloffset=+1] include::partial$goals/test-run.adoc[leveloffset=+1] [[run.examples]] == Examples [[run.examples.debug]] === Debug the Application The `run` and `test-run` goals run your application in a forked process. If you need to debug it, you should add the necessary JVM arguments to enable remote debugging. The following configuration suspend the process until a debugger has joined on port 5005: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$running/debug-pom.xml[tags=debug] ---- These arguments can be specified on the command line as well: [source,shell] ---- $ mvn spring-boot:run -Dspring-boot.run.jvmArguments=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005 ---- [[run.examples.system-properties]] === Using System Properties System properties can be specified using the `systemPropertyVariables` attribute. The following example sets `property1` to `test` and `property2` to 42: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$running/system-properties-pom.xml[tags=system-properties] ---- If the value is empty or not defined (that is `), the system property is set with an empty String as the value. Maven trims values specified in the pom, so it is not possible to specify a System property which needs to start or end with a space through this mechanism: consider using `jvmArguments` instead. Any String typed Maven variable can be passed as system properties. Any attempt to pass any other Maven variable type (for example a `List` or a `URL` variable) will cause the variable expression to be passed literally (unevaluated). The `jvmArguments` parameter takes precedence over system properties defined with the mechanism above. In the following example, the value for `property1` is `overridden`: [source,shell] ---- $ mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dproperty1=overridden" ---- [[run.examples.environment-variables]] === Using Environment Variables Environment variables can be specified using the `environmentVariables` attribute. The following example sets the 'ENV1', 'ENV2', 'ENV3', 'ENV4' env variables: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$running/environment-variables-pom.xml[tags=environment-variables] ---- If the value is empty or not defined (that is `), the env variable is set with an empty String as the value. Maven trims values specified in the pom so it is not possible to specify an env variable which needs to start or end with a space. Any String typed Maven variable can be passed as system properties. Any attempt to pass any other Maven variable type (for example a `List` or a `URL` variable) will cause the variable expression to be passed literally (unevaluated). Environment variables defined this way take precedence over existing values. [[run.examples.using-application-arguments]] === Using Application Arguments Application arguments can be specified using the `arguments` attribute. The following example sets two arguments: `property1` and `property2=42`: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$running/application-arguments-pom.xml[tags=application-arguments] ---- On the command-line, arguments are separated by a space the same way `jvmArguments` are. If an argument contains a space, make sure to quote it. In the following example, two arguments are available: `property1` and `property2=Hello World`: [source,shell] ---- $ mvn spring-boot:run -Dspring-boot.run.arguments="property1 'property2=Hello World'" ---- [[run.examples.specify-active-profiles]] === Specify Active Profiles The active profiles to use for a particular application can be specified using the `profiles` argument. The following configuration enables the `local` and `dev` profiles: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$running/active-profiles-pom.xml[tags=active-profiles] ---- The profiles to enable can be specified on the command line as well, make sure to separate them with a comma, as shown in the following example: [source,shell] ---- $ mvn spring-boot:run -Dspring-boot.run.profiles=local,dev ---- ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/using.adoc ================================================ [[using]] = Using the Plugin Maven users can inherit from the `spring-boot-starter-parent` project to obtain sensible defaults. The parent project provides the following features: * Java 17 as the default compiler level. * UTF-8 source encoding. * Compilation with `-parameters`. * A dependency management section, inherited from the `spring-boot-dependencies` POM, that manages the versions of common dependencies. This dependency management lets you omit `` tags for those dependencies when used in your own POM. * An execution of the xref:maven-plugin:packaging.adoc#packaging.repackage-goal[`repackage` goal] with a `repackage` execution id. * A `native` profile that configures the build to be able to generate a Native image. * Sensible https://maven.apache.org/plugins/maven-resources-plugin/examples/filter.html[resource filtering]. * Sensible plugin configuration ({url-git-commit-id-maven-plugin}[`Git Commit Id Plugin`], and https://maven.apache.org/plugins/maven-shade-plugin/[shade]). * Sensible resource filtering for `application.properties` and `application.yml` including profile-specific files (for example, `application-dev.properties` and `application-dev.yml`) NOTE: Since the `application.properties` and `application.yml` files accept Spring style placeholders (`${...}`), the Maven filtering is changed to use `@..@` placeholders. (You can override that by setting a Maven property called `resource.delimiter`.) [NOTE] ==== The `spring-boot-starter-parent` sets the `maven.compiler.release` property, which restricts the `--add-exports`, `--add-reads`, and `--patch-module` options https://openjdk.org/jeps/247[if they modify system modules]. In case you need to use those options, unset `maven.compiler.release`: [source,xml,indent=0,subs="verbatim,quotes,attributes"] ---- ---- and then configure the source and the target options instead: [source,xml,indent=0,subs="verbatim,quotes,attributes"] ---- ${java.version} ${java.version} ---- ==== [[using.parent-pom]] == Inheriting the Starter Parent POM To configure your project to inherit from the `spring-boot-starter-parent`, set the `parent` as follows: [source,xml,subs="verbatim,quotes,attributes"] ---- org.springframework.boot spring-boot-starter-parent {version-spring-boot} ---- NOTE: You should need to specify only the Spring Boot version number on this dependency. If you import additional starters, you can safely omit the version number. With that setup, you can also override individual dependencies by overriding a property in your own project. For instance, to use a different version of the SLF4J library and the Spring Data release train, you would add the following to your `pom.xml`: [source,xml,indent=0,subs="verbatim,attributes"] ---- 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. 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 Spring Boot without the Parent POM There may be reasons for you not to inherit from the `spring-boot-starter-parent` POM. You may have your own corporate standard parent that you need to use or you may prefer to explicitly declare all your Maven configuration. If you do not want to use the `spring-boot-starter-parent`, you can still keep the benefit of the dependency management (but not the plugin management) by using an `import` scoped dependency, as follows: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$using/no-starter-parent-pom.xml[tags=no-starter-parent] ---- The preceding sample setup does not let you override individual dependencies by using properties, as explained above. To achieve the same result, you need to add entries in the `dependencyManagement` section of your project **before** the `spring-boot-dependencies` entry. For instance, to use a different version of the SLF4J library and the Spring Data release train, you could add the following elements to your `pom.xml`: [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$using/no-starter-parent-override-dependencies-pom.xml[tags=no-starter-parent-override-dependencies] ---- [[using.overriding-command-line]] == Overriding Settings on the Command Line The plugin offers a number of user properties, starting with `spring-boot`, to let you customize the configuration from the command line. For instance, you could tune the profiles to enable when running the application as follows: [source,shell] ---- $ mvn spring-boot:run -Dspring-boot.run.profiles=dev,local ---- If you want to both have a default while allowing it to be overridden on the command line, you should use a combination of a user-provided project property and MOJO configuration. [source,xml,indent=0,subs="verbatim,attributes"] ---- include::example$using/default-and-override-pom.xml[tags=default-and-override] ---- The above makes sure that `local` and `dev` are enabled by default. Now a dedicated property has been exposed, this can be overridden on the command line as well: [source,shell] ---- $ mvn spring-boot:run -Dapp.profiles=test ---- ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/partials/nav-maven-plugin.adoc ================================================ * xref:maven-plugin:index.adoc[] ** xref:maven-plugin:getting-started.adoc[] ** xref:maven-plugin:using.adoc[] ** xref:maven-plugin:goals.adoc[] ** xref:maven-plugin:packaging.adoc[] ** xref:maven-plugin:build-image.adoc[] ** xref:maven-plugin:run.adoc[] ** xref:maven-plugin:aot.adoc[] ** xref:maven-plugin:integration-tests.adoc[] ** xref:maven-plugin:build-info.adoc[] ** xref:maven-plugin:help.adoc[] ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AbstractArchiveIntegrationTests.java ================================================ /* * 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.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Consumer; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.stream.Stream; import java.util.zip.ZipEntry; import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.AssertProvider; import org.assertj.core.api.ListAssert; import org.jspecify.annotations.Nullable; import org.springframework.lang.CheckReturnValue; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; /** * Base class for archive (jar or war) related Maven plugin integration tests. * * @author Andy Wilkinson * @author Scott Frederick */ abstract class AbstractArchiveIntegrationTests { protected String buildLog(File project) { return contentOf(new File(project, "target/build.log")); } protected AssertProvider jar(File file) { return new AssertProvider<>() { @Override @Deprecated(since = "2.3.0", forRemoval = false) public JarAssert assertThat() { return new JarAssert(file); } }; } protected Map> readLayerIndex(JarFile jarFile) throws IOException { if (getLayersIndexLocation() == null) { return Collections.emptyMap(); } Map> index = new LinkedHashMap<>(); String layerPrefix = "- "; String entryPrefix = " - "; ZipEntry indexEntry = jarFile.getEntry(getLayersIndexLocation()); try (BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(indexEntry)))) { String line = reader.readLine(); String layer = null; while (line != null) { if (line.startsWith(layerPrefix)) { layer = line.substring(layerPrefix.length() + 1, line.length() - 2); index.put(layer, new ArrayList<>()); } else if (line.startsWith(entryPrefix)) { index.computeIfAbsent(layer, (key) -> new ArrayList<>()) .add(line.substring(entryPrefix.length() + 1, line.length() - 1)); } line = reader.readLine(); } return index; } } protected @Nullable String getLayersIndexLocation() { return null; } protected List readClasspathIndex(JarFile jarFile, String location) throws IOException { List index = new ArrayList<>(); String entryPrefix = "- "; ZipEntry indexEntry = jarFile.getEntry(location); try (BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(indexEntry)))) { String line = reader.readLine(); while (line != null) { if (line.startsWith(entryPrefix)) { index.add(line.substring(entryPrefix.length() + 1, line.length() - 1)); } line = reader.readLine(); } } return index; } static final class JarAssert extends AbstractAssert { private JarAssert(File actual) { super(actual, JarAssert.class); assertThat(actual).exists(); } JarAssert doesNotHaveEntryWithName(String name) { withJarFile((jarFile) -> { withEntries(jarFile, (entries) -> { Optional match = entries.filter((entry) -> entry.getName().equals(name)).findFirst(); assertThat(match).isNotPresent(); }); }); return this; } JarAssert hasEntryWithName(String name) { withJarFile((jarFile) -> { withEntries(jarFile, (entries) -> { Optional match = entries.filter((entry) -> entry.getName().equals(name)).findFirst(); assertThat(match).hasValueSatisfying((entry) -> assertThat(entry.getComment()).isNull()); }); }); return this; } JarAssert hasEntryWithNameStartingWith(String prefix) { withJarFile((jarFile) -> { withEntries(jarFile, (entries) -> { Optional match = entries.filter((entry) -> entry.getName().startsWith(prefix)) .findFirst(); assertThat(match).hasValueSatisfying((entry) -> assertThat(entry.getComment()).isNull()); }); }); return this; } JarAssert hasUnpackEntryWithNameStartingWith(String prefix) { withJarFile((jarFile) -> { withEntries(jarFile, (entries) -> { Optional match = entries.filter((entry) -> entry.getName().startsWith(prefix)) .findFirst(); assertThat(match).as("Name starting with %s", prefix) .hasValueSatisfying((entry) -> assertThat(entry.getComment()).isEqualTo("UNPACK")); }); }); return this; } JarAssert doesNotHaveEntryWithNameStartingWith(String prefix) { withJarFile((jarFile) -> { withEntries(jarFile, (entries) -> { Optional match = entries.filter((entry) -> entry.getName().startsWith(prefix)) .findFirst(); assertThat(match).isNotPresent(); }); }); return this; } @CheckReturnValue ListAssert entryNamesInPath(String path) { List matches = new ArrayList<>(); withJarFile((jarFile) -> withEntries(jarFile, (entries) -> matches.addAll(entries.map(ZipEntry::getName) .filter((name) -> name.startsWith(path) && name.length() > path.length()) .toList()))); return new ListAssert<>(matches); } JarAssert manifest(Consumer consumer) { withJarFile((jarFile) -> { try { consumer.accept(new ManifestAssert(jarFile.getManifest())); } catch (IOException ex) { throw new RuntimeException(ex); } }); return this; } void withJarFile(Consumer consumer) { try (JarFile jarFile = new JarFile(this.actual)) { consumer.accept(jarFile); } catch (Exception ex) { throw new RuntimeException(ex); } } void withEntries(JarFile jarFile, Consumer> entries) { entries.accept(Collections.list(jarFile.entries()).stream()); } static final class ManifestAssert extends AbstractAssert { private ManifestAssert(Manifest actual) { super(actual, ManifestAssert.class); } ManifestAssert hasStartClass(String expected) { assertThat(this.actual.getMainAttributes().getValue("Start-Class")).isEqualTo(expected); return this; } ManifestAssert hasMainClass(String expected) { assertThat(this.actual.getMainAttributes().getValue("Main-Class")).isEqualTo(expected); return this; } ManifestAssert hasAttribute(String name, String value) { assertThat(this.actual.getMainAttributes().getValue(name)).isEqualTo(value); return this; } ManifestAssert doesNotHaveAttribute(String name) { assertThat(this.actual.getMainAttributes().getValue(name)).isNull(); return this; } } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AotTests.java ================================================ /* * 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.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.testsupport.junit.EnabledOnLocale; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; /** * Integration tests for the Maven plugin's AOT support. * * @author Stephane Nicoll * @author Andy Wilkinson * @author Scott Frederick * @author Moritz Halbritter */ @ExtendWith(MavenBuildExtension.class) class AotTests { @TestTemplate void whenAotRunsSourcesAreGenerated(MavenBuild mavenBuild) { mavenBuild.project("aot").goals("package").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/main"); assertThat(collectRelativePaths(aotDirectory.resolve("sources"))) .contains(Path.of("org", "test", "SampleApplication__ApplicationContextInitializer.java")); }); } @TestTemplate void whenAotRunsResourcesAreGeneratedAndCopiedToTargetClasses(MavenBuild mavenBuild) { mavenBuild.project("aot-resource-generation").goals("package").execute((project) -> { Path targetClasses = project.toPath().resolve("target/classes"); assertThat(collectRelativePaths(targetClasses)).contains( Path.of("META-INF", "native-image", "org.springframework.boot.maven.it", "aot-resource-generation", "reachability-metadata.json"), Path.of("META-INF", "native-image", "org.springframework.boot.maven.it", "aot-resource-generation", "native-image.properties"), Path.of("generated-resource"), Path.of("nested/generated-resource")); }); } @TestTemplate void whenAotRunsWithJdkProxyResourcesIncludeProxyConfig(MavenBuild mavenBuild) { mavenBuild.project("aot-jdk-proxy").goals("package").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/main"); assertThat(collectRelativePaths(aotDirectory.resolve("resources"))).contains( Path.of("META-INF", "native-image", "org.springframework.boot.maven.it", "aot-jdk-proxy", "reachability-metadata.json"), Path.of("META-INF", "native-image", "org.springframework.boot.maven.it", "aot-jdk-proxy", "native-image.properties")); }); } @TestTemplate void whenAotRunsWithClassProxyClassesAreGenerated(MavenBuild mavenBuild) { mavenBuild.project("aot-class-proxy").goals("package").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/main"); assertThat(collectRelativePaths(aotDirectory.resolve("classes"))) .contains(Path.of("org", "test", "SampleRunner$$SpringCGLIB$$0.class")); }); } @TestTemplate void whenAotRunsWithProfilesSourcesAreGenerated(MavenBuild mavenBuild) { mavenBuild.project("aot-profile").goals("package").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/main"); assertThat(collectRelativePaths(aotDirectory.resolve("sources"))) .contains(Path.of("org", "test", "TestProfileConfiguration__BeanDefinitions.java")); }); } @TestTemplate void whenAotRunsWithArgumentsSourcesAreGenerated(MavenBuild mavenBuild) { mavenBuild.project("aot-arguments").goals("package").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/main"); assertThat(collectRelativePaths(aotDirectory.resolve("sources"))) .contains(Path.of("org", "test", "TestProfileConfiguration__BeanDefinitions.java")); }); } @TestTemplate void whenAotRunsWithSystemPropertiesSourcesAreGenerated(MavenBuild mavenBuild) { mavenBuild.project("aot-system-properties").goals("package").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/main"); assertThat(collectRelativePaths(aotDirectory.resolve("sources"))) .contains(Path.of("org", "test", "TestProfileConfiguration__BeanDefinitions.java")); }); } @TestTemplate void whenAotRunsWithJvmArgumentsSourcesAreGenerated(MavenBuild mavenBuild) { mavenBuild.project("aot-jvm-arguments").goals("package").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/main"); assertThat(collectRelativePaths(aotDirectory.resolve("sources"))) .contains(Path.of("org", "test", "TestProfileConfiguration__BeanDefinitions.java")); }); } @TestTemplate void whenAotRunsWithReleaseSourcesAreGenerated(MavenBuild mavenBuild) { mavenBuild.project("aot-release").goals("package").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/main"); assertThat(collectRelativePaths(aotDirectory.resolve("sources"))) .contains(Path.of("org", "test", "SampleApplication__ApplicationContextInitializer.java")); }); } @TestTemplate @EnabledOnLocale(language = "en") void whenAotRunsWithInvalidCompilerArgumentsCompileFails(MavenBuild mavenBuild) { mavenBuild.project("aot-compiler-arguments") .goals("package") .executeAndFail( (project) -> assertThat(buildLog(project)).contains("invalid flag: --invalid-compiler-arg")); } @TestTemplate void whenAotRunsSourcesAreCompiledAndMovedToTargetClasses(MavenBuild mavenBuild) { mavenBuild.project("aot").goals("package").execute((project) -> { Path classesDirectory = project.toPath().resolve("target/classes"); assertThat(collectRelativePaths(classesDirectory)) .contains(Path.of("org", "test", "SampleApplication__ApplicationContextInitializer.class")); }); } @TestTemplate void whenAotRunsWithModuleInfoSourcesAreCompiledAndMovedToTargetClass(MavenBuild mavenBuild) { mavenBuild.project("aot-module-info").goals("package").execute((project) -> { Path classesDirectory = project.toPath().resolve("target/classes"); assertThat(collectRelativePaths(classesDirectory)) .contains(Path.of("org", "test", "SampleApplication__ApplicationContextInitializer.class")); }); } @TestTemplate void whenAotRunsResourcesAreCopiedToTargetClasses(MavenBuild mavenBuild) { mavenBuild.project("aot-jdk-proxy").goals("package").execute((project) -> { Path classesDirectory = project.toPath().resolve("target/classes/META-INF/native-image"); assertThat(collectRelativePaths(classesDirectory)).contains( Path.of("org.springframework.boot.maven.it", "aot-jdk-proxy", "reachability-metadata.json"), Path.of("org.springframework.boot.maven.it", "aot-jdk-proxy", "native-image.properties")); }); } @TestTemplate void whenAotRunsWithClassProxyClassesAreCopiedToTargetClasses(MavenBuild mavenBuild) { mavenBuild.project("aot-class-proxy").goals("package").execute((project) -> { Path classesDirectory = project.toPath().resolve("target/classes/"); assertThat(collectRelativePaths(classesDirectory)) .contains(Path.of("org", "test", "SampleRunner$$SpringCGLIB$$0.class")); }); } @TestTemplate void whenAotRunsWithDevtoolsInClasspathItIsExcluded(MavenBuild mavenBuild) { mavenBuild.project("aot-exclude-devtools").goals("package").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/main"); assertThat(aotDirectory).exists(); Path sourcesDirectory = aotDirectory.resolve("sources"); assertThat(sourcesDirectory).exists(); assertThat(collectRelativePaths(sourcesDirectory)).isNotEmpty(); }); } @TestTemplate void whenAotTestRunsSourcesAndResourcesAreGenerated(MavenBuild mavenBuild) { mavenBuild.project("aot-test").goals("test").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/test"); assertThat(collectRelativePaths(aotDirectory.resolve("sources"))).contains(Path.of("org", "test", "SampleApplicationTests__TestContext001_ApplicationContextInitializer.java")); Path testClassesDirectory = project.toPath().resolve("target/test-classes"); assertThat(collectRelativePaths(testClassesDirectory)).contains(Path.of("META-INF", "native-image", "org.springframework.boot.maven.it", "aot-test", "reachability-metadata.json")); assertThat(collectRelativePaths(testClassesDirectory)).contains(Path.of("org", "test", "SampleApplicationTests__TestContext001_ApplicationContextInitializer.class")); }); } @TestTemplate void whenTestAotRunsWithTestSkipItIsAlsoSkipped(MavenBuild mavenBuild) { mavenBuild.project("aot-test-skip").goals("test").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/test"); assertThat(aotDirectory).doesNotExist(); Path testClassesDirectory = project.toPath().resolve("target/test-classes"); assertThat(testClassesDirectory.resolve("META-INF").resolve("native-image")).doesNotExist(); }); } @TestTemplate void whenTestAotRunsWithDevtoolsInClasspathItIsExcluded(MavenBuild mavenBuild) { mavenBuild.project("aot-test-exclude-devtools").goals("process-test-classes").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/test"); assertThat(aotDirectory).exists(); Path sourcesDirectory = aotDirectory.resolve("sources"); assertThat(sourcesDirectory).exists(); assertThat(collectRelativePaths(sourcesDirectory)).isNotEmpty(); }); } List collectRelativePaths(Path sourceDirectory) { try (Stream pathStream = Files.walk(sourceDirectory)) { return pathStream.filter(Files::isRegularFile) .map((path) -> path.subpath(sourceDirectory.getNameCount(), path.getNameCount())) .toList(); } catch (IOException ex) { throw new IllegalStateException(ex); } } protected String buildLog(File project) { return contentOf(new File(project, "target/build.log")); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildInfoIntegrationTests.java ================================================ /* * 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.io.File; import java.io.FileReader; import java.io.IOException; import java.time.Instant; import java.util.Properties; import java.util.function.Consumer; import org.assertj.core.api.AbstractMapAssert; import org.assertj.core.api.AssertProvider; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.maven.MavenBuild.ProjectCallback; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for the Maven plugin's build info support. * * @author Andy Wilkinson * @author Vedran Pavic */ @ExtendWith(MavenBuildExtension.class) class BuildInfoIntegrationTests { @TestTemplate void buildInfoPropertiesAreGenerated(MavenBuild mavenBuild) { mavenBuild.project("build-info") .execute(buildInfo((buildInfo) -> assertThat(buildInfo).hasBuildGroup("org.springframework.boot.maven.it") .hasBuildArtifact("build-info") .hasBuildName("Generate build info") .hasBuildVersion("0.0.1.BUILD-SNAPSHOT") .containsBuildTime())); } @TestTemplate void generatedBuildInfoIncludesAdditionalProperties(MavenBuild mavenBuild) { mavenBuild.project("build-info-additional-properties") .execute(buildInfo((buildInfo) -> assertThat(buildInfo).hasBuildGroup("org.springframework.boot.maven.it") .hasBuildArtifact("build-info-additional-properties") .hasBuildName("Generate build info with additional properties") .hasBuildVersion("0.0.1.BUILD-SNAPSHOT") .containsBuildTime() .containsEntry("build.foo", "bar") .containsEntry("build.encoding", "UTF-8") .containsEntry("build.java.source", "1.8"))); } @TestTemplate void generatedBuildInfoUsesCustomBuildTime(MavenBuild mavenBuild) { mavenBuild.project("build-info-custom-build-time") .execute(buildInfo((buildInfo) -> assertThat(buildInfo).hasBuildGroup("org.springframework.boot.maven.it") .hasBuildArtifact("build-info-custom-build-time") .hasBuildName("Generate build info with custom build time") .hasBuildVersion("0.0.1.BUILD-SNAPSHOT") .hasBuildTime("2019-07-08T08:00:00Z"))); } @TestTemplate void generatedBuildInfoReproducible(MavenBuild mavenBuild) { mavenBuild.project("build-info-reproducible") .execute(buildInfo((buildInfo) -> assertThat(buildInfo).hasBuildGroup("org.springframework.boot.maven.it") .hasBuildArtifact("build-reproducible") .hasBuildName("Generate build info with build time from project.build.outputTimestamp") .hasBuildVersion("0.0.1.BUILD-SNAPSHOT") .hasBuildTime("2021-04-21T11:22:33Z"))); } @TestTemplate void generatedBuildInfoReproducibleEpochSeconds(MavenBuild mavenBuild) { mavenBuild.project("build-info-reproducible-epoch-seconds") .execute(buildInfo((buildInfo) -> assertThat(buildInfo).hasBuildGroup("org.springframework.boot.maven.it") .hasBuildArtifact("build-reproducible-epoch-seconds") .hasBuildName("Generate build info with build time from project.build.outputTimestamp") .hasBuildVersion("0.0.1.BUILD-SNAPSHOT") .hasBuildTime(Instant.ofEpochSecond(1619004153).toString()))); } @TestTemplate void buildInfoPropertiesAreGeneratedToCustomOutputLocation(MavenBuild mavenBuild) { mavenBuild.project("build-info-custom-file") .execute(buildInfo("target/build.info", (buildInfo) -> assertThat(buildInfo).hasBuildGroup("org.springframework.boot.maven.it") .hasBuildArtifact("build-info-custom-file") .hasBuildName("Generate custom build info") .hasBuildVersion("0.0.1.BUILD-SNAPSHOT") .containsBuildTime())); } @TestTemplate void whenBuildTimeIsDisabledIfDoesNotAppearInGeneratedBuildInfo(MavenBuild mavenBuild) { mavenBuild.project("build-info-disable-build-time") .execute(buildInfo((buildInfo) -> assertThat(buildInfo).hasBuildGroup("org.springframework.boot.maven.it") .hasBuildArtifact("build-info-disable-build-time") .hasBuildName("Generate build info with disabled build time") .hasBuildVersion("0.0.1.BUILD-SNAPSHOT") .doesNotContainBuildTime())); } @TestTemplate void whenBuildTimeIsExcludedIfDoesNotAppearInGeneratedBuildInfo(MavenBuild mavenBuild) { mavenBuild.project("build-info-exclude-build-time") .execute(buildInfo((buildInfo) -> assertThat(buildInfo).hasBuildGroup("org.springframework.boot.maven.it") .hasBuildArtifact("build-info-exclude-build-time") .hasBuildName("Generate build info with excluded build time") .hasBuildVersion("0.0.1.BUILD-SNAPSHOT") .doesNotContainBuildTime())); } @TestTemplate void whenBuildPropertiesAreExcludedTheyDoNotAppearInGeneratedBuildInfo(MavenBuild mavenBuild) { mavenBuild.project("build-info-exclude-build-properties") .execute(buildInfo((buildInfo) -> assertThat(buildInfo).doesNotContainBuildGroup() .doesNotContainBuildArtifact() .doesNotContainBuildName() .doesNotContainBuildVersion() .containsBuildTime())); } private ProjectCallback buildInfo(Consumer> buildInfo) { return buildInfo("target/classes/META-INF/build-info.properties", buildInfo); } private ProjectCallback buildInfo(String location, Consumer> buildInfo) { return (project) -> buildInfo.accept((buildInfo(project, location))); } private AssertProvider buildInfo(File project, String buildInfo) { return new AssertProvider<>() { @Override @Deprecated(since = "2.3.0", forRemoval = false) public BuildInfoAssert assertThat() { return new BuildInfoAssert(new File(project, buildInfo)); } }; } private static final class BuildInfoAssert extends AbstractMapAssert { private BuildInfoAssert(File actual) { super(loadProperties(actual), BuildInfoAssert.class); } private static Properties loadProperties(File file) { try (FileReader reader = new FileReader(file)) { Properties properties = new Properties(); properties.load(reader); return properties; } catch (IOException ex) { throw new RuntimeException(ex); } } BuildInfoAssert hasBuildGroup(String expected) { return containsEntry("build.group", expected); } BuildInfoAssert doesNotContainBuildGroup() { return doesNotContainKey("build.group"); } BuildInfoAssert hasBuildArtifact(String expected) { return containsEntry("build.artifact", expected); } BuildInfoAssert doesNotContainBuildArtifact() { return doesNotContainKey("build.artifact"); } BuildInfoAssert hasBuildName(String expected) { return containsEntry("build.name", expected); } BuildInfoAssert doesNotContainBuildName() { return doesNotContainKey("build.name"); } BuildInfoAssert hasBuildVersion(String expected) { return containsEntry("build.version", expected); } BuildInfoAssert doesNotContainBuildVersion() { return doesNotContainKey("build.version"); } BuildInfoAssert containsBuildTime() { return containsKey("build.time"); } BuildInfoAssert doesNotContainBuildTime() { return doesNotContainKey("build.time"); } BuildInfoAssert hasBuildTime(String expected) { return containsEntry("build.time", expected); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/EclipseM2eIntegrationTests.java ================================================ /* * 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.io.File; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Comparator; import org.junit.jupiter.api.Test; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Tests to check that our plugin works well with Eclipse m2e. * * @author Phillip Webb */ class EclipseM2eIntegrationTests { @Test // gh-21992 void pluginPomIncludesOptionalShadeDependency() throws Exception { String version = new Versions().get("project.version"); File repository = new File("build/test-maven-repository"); File pluginDirectory = new File(repository, "org/springframework/boot/spring-boot-maven-plugin/" + version); File[] pomFiles = pluginDirectory.listFiles(this::isPomFile); Arrays.sort(pomFiles, Comparator.comparing(File::getName)); File pomFile = pomFiles[pomFiles.length - 1]; String pomContent = new String(FileCopyUtils.copyToByteArray(pomFile), StandardCharsets.UTF_8); assertThat(pomContent).contains("maven-shade-plugin"); } private boolean isPomFile(File file) { return file.getName().endsWith(".pom"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java ================================================ /* * 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.io.File; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.concurrent.atomic.AtomicReference; import java.util.jar.JarFile; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.loader.tools.JarModeLibrary; import org.springframework.boot.loader.tools.LibraryCoordinates; import org.springframework.boot.testsupport.FileUtils; import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for the Maven plugin's jar support. * * @author Andy Wilkinson * @author Madhura Bhave * @author Scott Frederick * @author Moritz Halbritter */ @ExtendWith(MavenBuildExtension.class) class JarIntegrationTests extends AbstractArchiveIntegrationTests { @Override protected String getLayersIndexLocation() { return "BOOT-INF/layers.idx"; } @TestTemplate void whenJarIsRepackagedInPlaceOnlyRepackagedJarIsInstalled(MavenBuild mavenBuild) { mavenBuild.project("jar").goals("install").execute((project) -> { File original = new File(project, "target/jar-0.0.1.BUILD-SNAPSHOT.jar.original"); assertThat(original).isFile(); File repackaged = new File(project, "target/jar-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(repackaged)).manifest((manifest) -> { manifest.hasMainClass("org.springframework.boot.loader.launch.JarLauncher"); manifest.hasStartClass("some.random.Main"); manifest.hasAttribute("Not-Used", "Foo"); }) .hasEntryWithNameStartingWith("BOOT-INF/lib/spring-context") .hasEntryWithNameStartingWith("BOOT-INF/lib/spring-core") .hasEntryWithNameStartingWith("BOOT-INF/lib/commons-logging") .hasEntryWithNameStartingWith("BOOT-INF/lib/jakarta.servlet-api-6") .hasEntryWithName("BOOT-INF/classes/org/test/SampleApplication.class") .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 void whenAttachIsDisabledOnlyTheOriginalJarIsInstalled(MavenBuild mavenBuild) { mavenBuild.project("jar-attach-disabled").goals("install").execute((project) -> { File original = new File(project, "target/jar-attach-disabled-0.0.1.BUILD-SNAPSHOT.jar.original"); assertThat(original).isFile(); File main = new File(project, "target/jar-attach-disabled-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(main).isFile(); assertThat(buildLog(project)).contains("Updating main artifact " + main + " to " + original) .contains("Installing " + original + " to") .doesNotContain("Installing " + main + " to"); }); } @TestTemplate void whenAClassifierIsConfiguredTheRepackagedJarHasAClassifierAndBothItAndTheOriginalAreInstalled( MavenBuild mavenBuild) { mavenBuild.project("jar-classifier-main").goals("install").execute((project) -> { assertThat(new File(project, "target/jar-classifier-main-0.0.1.BUILD-SNAPSHOT.jar.original")) .doesNotExist(); File main = new File(project, "target/jar-classifier-main-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(main).isFile(); File repackaged = new File(project, "target/jar-classifier-main-0.0.1.BUILD-SNAPSHOT-test.jar"); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/"); assertThat(buildLog(project)) .contains("Attaching repackaged archive " + repackaged + " with classifier test") .doesNotContain("Creating repackaged archive " + repackaged + " with classifier test") .contains("Installing " + main + " to") .contains("Installing " + repackaged + " to"); }); } @TestTemplate void whenBothJarsHaveTheSameClassifierRepackagingIsDoneInPlaceAndOnlyRepackagedJarIsInstalled( MavenBuild mavenBuild) { mavenBuild.project("jar-classifier-source").goals("install").execute((project) -> { File original = new File(project, "target/jar-classifier-source-0.0.1.BUILD-SNAPSHOT-test.jar.original"); assertThat(original).isFile(); File repackaged = new File(project, "target/jar-classifier-source-0.0.1.BUILD-SNAPSHOT-test.jar"); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/"); assertThat(buildLog(project)) .contains("Replacing artifact with classifier test " + repackaged + " with repackaged archive,") .contains("The original artifact has been renamed to " + original) .doesNotContain("Installing " + original + " to") .contains("Installing " + repackaged + " to"); }); } @TestTemplate void whenBothJarsHaveTheSameClassifierAndAttachIsDisabledOnlyTheOriginalJarIsInstalled(MavenBuild mavenBuild) { mavenBuild.project("jar-classifier-source-attach-disabled").goals("install").execute((project) -> { File original = new File(project, "target/jar-classifier-source-attach-disabled-0.0.1.BUILD-SNAPSHOT-test.jar.original"); assertThat(original).isFile(); File repackaged = new File(project, "target/jar-classifier-source-attach-disabled-0.0.1.BUILD-SNAPSHOT-test.jar"); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/"); assertThat(buildLog(project)) .doesNotContain("Attaching repackaged archive " + repackaged + " with classifier test") .contains("Updating artifact with classifier test " + repackaged + " to " + original) .contains("Installing " + original + " to") .doesNotContain("Installing " + repackaged + " to"); }); } @TestTemplate void whenAClassifierAndAnOutputDirectoryAreConfiguredTheRepackagedJarHasAClassifierAndIsWrittenToTheOutputDirectory( MavenBuild mavenBuild) { mavenBuild.project("jar-create-dir").goals("install").execute((project) -> { File repackaged = new File(project, "target/foo/jar-create-dir-0.0.1.BUILD-SNAPSHOT-foo.jar"); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/"); assertThat(buildLog(project)).contains("Installing " + repackaged + " to"); }); } @TestTemplate void whenAnOutputDirectoryIsConfiguredTheRepackagedJarIsWrittenToIt(MavenBuild mavenBuild) { mavenBuild.project("jar-custom-dir").goals("install").execute((project) -> { File repackaged = new File(project, "target/foo/jar-custom-dir-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/"); assertThat(buildLog(project)).contains("Installing " + repackaged + " to"); }); } @TestTemplate void whenAnEntryIsExcludedItDoesNotAppearInTheRepackagedJar(MavenBuild mavenBuild) { mavenBuild.project("jar-exclude-entry").goals("install").execute((project) -> { File repackaged = new File(project, "target/jar-exclude-entry-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/commons-logging") .doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/servlet-api-"); }); } @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-"); }); } @TestTemplate void whenAnEntryIsExcludedWithPropertyItDoesNotAppearInTheRepackagedJar(MavenBuild mavenBuild) { mavenBuild.project("jar") .systemProperty("spring-boot.excludes", "jakarta.servlet:jakarta.servlet-api") .goals("install") .execute((project) -> { File repackaged = new File(project, "target/jar-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/commons-logging") .doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/jakarta.servlet-api-"); }); } @TestTemplate void whenAnEntryIsIncludedOnlyIncludedEntriesAppearInTheRepackagedJar(MavenBuild mavenBuild) { mavenBuild.project("jar-include-entry").goals("install").execute((project) -> { File repackaged = new File(project, "target/jar-include-entry-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/") .hasEntryWithNameStartingWith("BOOT-INF/lib/jakarta.servlet-api-") .doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/spring-context") .doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/spring-core") .doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/commons-logging"); }); } @TestTemplate void whenAnIncludeIsSpecifiedAsAPropertyOnlyIncludedEntriesAppearInTheRepackagedJar(MavenBuild mavenBuild) { mavenBuild.project("jar") .systemProperty("spring-boot.includes", "jakarta.servlet:jakarta.servlet-api") .goals("install") .execute((project) -> { File repackaged = new File(project, "target/jar-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/") .hasEntryWithNameStartingWith("BOOT-INF/lib/jakarta.servlet-api-") .doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/spring-context") .doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/spring-core") .doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/commons-logging"); }); } @TestTemplate void whenAGroupIsExcludedNoEntriesInThatGroupAppearInTheRepackagedJar(MavenBuild mavenBuild) { mavenBuild.project("jar-exclude-group").goals("install").execute((project) -> { File repackaged = new File(project, "target/jar-exclude-group-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/") .hasEntryWithNameStartingWith("BOOT-INF/lib/spring-context") .hasEntryWithNameStartingWith("BOOT-INF/lib/commons-logging") .doesNotHaveEntryWithName("BOOT-INF/lib/log4j-api-"); }); } @TestTemplate void whenAJarIsBuiltWithLibrariesWithConflictingNamesTheyAreMadeUniqueUsingTheirGroupIds(MavenBuild mavenBuild) { mavenBuild.project("jar-lib-name-conflict").execute((project) -> { File repackaged = new File(project, "test-project/target/test-project-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/") .hasEntryWithName("BOOT-INF/lib/org.springframework.boot.maven.it-acme-lib-0.0.1.BUILD-SNAPSHOT.jar") .hasEntryWithName( "BOOT-INF/lib/org.springframework.boot.maven.it.another-acme-lib-0.0.1.BUILD-SNAPSHOT.jar"); }); } @TestTemplate void whenAProjectUsesPomPackagingRepackagingIsSkipped(MavenBuild mavenBuild) { mavenBuild.project("jar-pom").execute((project) -> { File target = new File(project, "target"); assertThat(target.listFiles()).containsExactly(new File(target, "build.log")); }); } @TestTemplate void whenRepackagingIsSkippedTheJarIsNotRepackaged(MavenBuild mavenBuild) { mavenBuild.project("jar-skip").execute((project) -> { File main = new File(project, "target/jar-skip-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(main)).doesNotHaveEntryWithNameStartingWith("org/springframework/boot"); assertThat(new File(project, "target/jar-skip-0.0.1.BUILD-SNAPSHOT.jar.original")).doesNotExist(); }); } @TestTemplate void whenADependencyHasSystemScopeAndInclusionOfSystemScopeDependenciesIsEnabledItIsIncludedInTheRepackagedJar( MavenBuild mavenBuild) { mavenBuild.project("jar-system-scope").execute((project) -> { File main = new File(project, "target/jar-system-scope-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(main)).hasEntryWithName("BOOT-INF/lib/sample-1.0.0.jar"); }); } @TestTemplate void whenADependencyHasSystemScopeItIsNotIncludedInTheRepackagedJar(MavenBuild mavenBuild) { mavenBuild.project("jar-system-scope-default").execute((project) -> { File main = new File(project, "target/jar-system-scope-default-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(main)).doesNotHaveEntryWithName("BOOT-INF/lib/sample-1.0.0.jar"); }); } @TestTemplate void whenADependencyHasTestScopeItIsNotIncludedInTheRepackagedJar(MavenBuild mavenBuild) { mavenBuild.project("jar-test-scope").execute((project) -> { File main = new File(project, "target/jar-test-scope-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(main)).doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/log4j") .hasEntryWithNameStartingWith("BOOT-INF/lib/spring-"); }); } @TestTemplate void whenAProjectUsesKotlinItsModuleMetadataIsRepackagedIntoBootInfClasses(MavenBuild mavenBuild) { mavenBuild.project("jar-with-kotlin-module").execute((project) -> { File main = new File(project, "target/jar-with-kotlin-module-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(main)).hasEntryWithName("BOOT-INF/classes/META-INF/jar-with-kotlin-module.kotlin_module"); }); } @TestTemplate void whenAProjectIsBuiltWithALayoutPropertyTheSpecifiedLayoutIsUsed(MavenBuild mavenBuild) { mavenBuild.project("jar-with-layout-property") .goals("package", "-Dspring-boot.repackage.layout=ZIP") .execute((project) -> { File main = new File(project, "target/jar-with-layout-property-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(main)).manifest( (manifest) -> manifest.hasMainClass("org.springframework.boot.loader.launch.PropertiesLauncher") .hasStartClass("org.test.SampleApplication")); assertThat(buildLog(project)).contains("Layout: ZIP"); }); } @TestTemplate void whenALayoutIsConfiguredTheSpecifiedLayoutIsUsed(MavenBuild mavenBuild) { mavenBuild.project("jar-with-zip-layout").execute((project) -> { File main = new File(project, "target/jar-with-zip-layout-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(main)).manifest( (manifest) -> manifest.hasMainClass("org.springframework.boot.loader.launch.PropertiesLauncher") .hasStartClass("org.test.SampleApplication")); assertThat(buildLog(project)).contains("Layout: ZIP"); }); } @TestTemplate void whenRequiresUnpackConfigurationIsProvidedItIsReflectedInTheRepackagedJar(MavenBuild mavenBuild) { mavenBuild.project("jar-with-unpack").execute((project) -> { File main = new File(project, "target/jar-with-unpack-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(main)).hasUnpackEntryWithNameStartingWith("BOOT-INF/lib/spring-core-") .hasEntryWithNameStartingWith("BOOT-INF/lib/spring-context-"); }); } @TestTemplate void whenJarIsRepackagedWithACustomLayoutTheJarUsesTheLayout(MavenBuild mavenBuild) { mavenBuild.project("jar-custom-layout").execute((project) -> { assertThat(jar(new File(project, "custom/target/custom-0.0.1.BUILD-SNAPSHOT.jar"))) .hasEntryWithName("custom"); assertThat(jar(new File(project, "default/target/default-0.0.1.BUILD-SNAPSHOT.jar"))) .hasEntryWithName("sample"); }); } @TestTemplate void repackagedJarContainsTheLayersIndexByDefault(MavenBuild mavenBuild) { mavenBuild.project("jar-layered").execute((project) -> { File repackaged = new File(project, "jar/target/jar-layered-0.0.1.BUILD-SNAPSHOT.jar"); LibraryCoordinates coordinates = JarModeLibrary.TOOLS.getCoordinates(); assertThat(coordinates).isNotNull(); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/") .hasEntryWithNameStartingWith("BOOT-INF/lib/jar-release") .hasEntryWithNameStartingWith("BOOT-INF/lib/jar-snapshot") .hasEntryWithNameStartingWith("BOOT-INF/lib/" + coordinates.getArtifactId()); try (JarFile jarFile = new JarFile(repackaged)) { Map> layerIndex = readLayerIndex(jarFile); assertThat(layerIndex.keySet()).containsExactly("dependencies", "spring-boot-loader", "snapshot-dependencies", "application"); 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"); assertThat(layerIndex.get("dependencies")) .anyMatch((dependency) -> dependency.startsWith("BOOT-INF/lib/log4j-api-")); } catch (IOException ex) { // Ignore } }); } @TestTemplate void whenJarIsRepackagedWithTheLayersDisabledDoesNotContainLayersIndex(MavenBuild mavenBuild) { mavenBuild.project("jar-layered-disabled").execute((project) -> { File repackaged = new File(project, "jar/target/jar-layered-0.0.1.BUILD-SNAPSHOT.jar"); LibraryCoordinates coordinates = JarModeLibrary.TOOLS.getCoordinates(); assertThat(coordinates).isNotNull(); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/") .hasEntryWithNameStartingWith("BOOT-INF/lib/jar-release") .hasEntryWithNameStartingWith("BOOT-INF/lib/jar-snapshot") .hasEntryWithNameStartingWith("BOOT-INF/lib/" + coordinates.getArtifactId()) .doesNotHaveEntryWithName("BOOT-INF/layers.idx"); }); } @TestTemplate void whenJarIsRepackagedWithToolsExclude(MavenBuild mavenBuild) { mavenBuild.project("jar-no-tools").execute((project) -> { File repackaged = new File(project, "jar/target/jar-no-tools-0.0.1.BUILD-SNAPSHOT.jar"); LibraryCoordinates coordinates = JarModeLibrary.TOOLS.getCoordinates(); assertThat(coordinates).isNotNull(); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/") .hasEntryWithNameStartingWith("BOOT-INF/lib/jar-release") .hasEntryWithNameStartingWith("BOOT-INF/lib/jar-snapshot") .doesNotHaveEntryWithNameStartingWith("BOOT-INF/lib/" + coordinates.getArtifactId()); }); } @TestTemplate void whenJarIsRepackagedWithTheCustomLayers(MavenBuild mavenBuild) { mavenBuild.project("jar-layered-custom").execute((project) -> { File repackaged = new File(project, "jar/target/jar-layered-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/") .hasEntryWithNameStartingWith("BOOT-INF/lib/jar-release") .hasEntryWithNameStartingWith("BOOT-INF/lib/jar-snapshot"); try (JarFile jarFile = new JarFile(repackaged)) { Map> layerIndex = readLayerIndex(jarFile); assertThat(layerIndex.keySet()).containsExactly("my-dependencies-name", "snapshot-dependencies", "configuration", "application"); 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-classifier-0.0.1-bravo.jar") .doesNotContain("BOOT-INF/lib/jar-classifier-0.0.1-alpha.jar"); } }); } @TestTemplate void whenJarIsRepackagedWithTheCustomLayersFromClasspath(MavenBuild mavenBuild) { mavenBuild.project("jar-layered-custom-name").execute((project) -> { File repackaged = new File(project, "jar/target/jar-layered-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("BOOT-INF/classes/") .hasEntryWithNameStartingWith("BOOT-INF/lib/jar-release") .hasEntryWithNameStartingWith("BOOT-INF/lib/jar-snapshot"); try (JarFile jarFile = new JarFile(repackaged)) { Map> layerIndex = readLayerIndex(jarFile); assertThat(layerIndex.keySet()).containsExactly("my-dependencies-name", "snapshot-dependencies", "configuration", "application"); 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-classifier-0.0.1-bravo.jar") .doesNotContain("BOOT-INF/lib/jar-classifier-0.0.1-alpha.jar", "BOOT-INF/lib/jar-layers-configuration-0.0.1.jar"); } }); } @TestTemplate void repackagedJarContainsClasspathIndex(MavenBuild mavenBuild) { mavenBuild.project("jar").execute((project) -> { File repackaged = new File(project, "target/jar-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(repackaged)) .manifest((manifest) -> manifest.hasAttribute("Spring-Boot-Classpath-Index", "BOOT-INF/classpath.idx")); assertThat(jar(repackaged)).hasEntryWithName("BOOT-INF/classpath.idx"); try (JarFile jarFile = new JarFile(repackaged)) { List index = readClasspathIndex(jarFile, "BOOT-INF/classpath.idx"); assertThat(index).allMatch((entry) -> entry.startsWith("BOOT-INF/lib/")); } }); } @TestTemplate void whenJarIsRepackagedWithOutputTimestampConfiguredThenJarIsReproducible(MavenBuild mavenBuild) throws InterruptedException { String firstHash = buildJarWithOutputTimestamp(mavenBuild); Thread.sleep(1500); String secondHash = buildJarWithOutputTimestamp(mavenBuild); assertThat(firstHash).isEqualTo(secondHash); } private String buildJarWithOutputTimestamp(MavenBuild mavenBuild) { AtomicReference jarHash = new AtomicReference<>(); mavenBuild.project("jar-output-timestamp").execute((project) -> { File repackaged = new File(project, "target/jar-output-timestamp-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(repackaged).isFile(); long expectedModified = 1584352800000L; long offsetExpectedModified = expectedModified - TimeZone.getDefault().getOffset(expectedModified); assertThat(repackaged.lastModified()).isEqualTo(expectedModified); try (JarFile jar = new JarFile(repackaged)) { List unreproducibleEntries = jar.stream() .filter((entry) -> entry.getLastModifiedTime().toMillis() != offsetExpectedModified) .map((entry) -> entry.getName() + ": " + entry.getLastModifiedTime()) .toList(); assertThat(unreproducibleEntries).isEmpty(); jarHash.set(FileUtils.sha1Hash(repackaged)); FileSystemUtils.deleteRecursively(project); } catch (IOException ex) { throw new RuntimeException(ex); } }); String hash = jarHash.get(); assertThat(hash).isNotNull(); return hash; } @TestTemplate void whenJarIsRepackagedWithOutputTimestampConfiguredThenLibrariesAreSorted(MavenBuild mavenBuild) { mavenBuild.project("jar-output-timestamp").execute((project) -> { File repackaged = new File(project, "target/jar-output-timestamp-0.0.1.BUILD-SNAPSHOT.jar"); LibraryCoordinates coordinates = JarModeLibrary.TOOLS.getCoordinates(); assertThat(coordinates).isNotNull(); List sortedLibs = Arrays.asList("BOOT-INF/lib/commons-logging", "BOOT-INF/lib/jakarta.servlet-api", "BOOT-INF/lib/jspecify", "BOOT-INF/lib/micrometer-commons", "BOOT-INF/lib/micrometer-observation", "BOOT-INF/lib/spring-aop", "BOOT-INF/lib/spring-beans", "BOOT-INF/lib/" + coordinates.getArtifactId(), "BOOT-INF/lib/spring-context", "BOOT-INF/lib/spring-core", "BOOT-INF/lib/spring-expression"); assertThat(jar(repackaged)).entryNamesInPath("BOOT-INF/lib/") .zipSatisfy(sortedLibs, (String jarLib, String expectedLib) -> assertThat(jarLib).startsWith(expectedLib)); }); } @TestTemplate void whenSigned(MavenBuild mavenBuild) { mavenBuild.project("jar-signed").execute((project) -> { File repackaged = new File(project, "target/jar-signed-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(jar(repackaged)).hasEntryWithName("META-INF/BOOT.SF"); }); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/MavenBuild.java ================================================ /* * 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.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import org.apache.maven.shared.invoker.DefaultInvocationRequest; import org.apache.maven.shared.invoker.DefaultInvoker; import org.apache.maven.shared.invoker.InvocationRequest; import org.apache.maven.shared.invoker.InvocationResult; import org.apache.maven.shared.invoker.Invoker; import org.apache.maven.shared.invoker.MavenInvocationException; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; /** * Helper class for executing a Maven build. * * @author Andy Wilkinson * @author Scott Frederick */ class MavenBuild { private final File home; private final File temp; private final Map pomReplacements; private final List goals = new ArrayList<>(); private final Properties properties = new Properties(); private @Nullable ProjectCallback preparation; private @Nullable File projectDir; MavenBuild(File home) { this.home = home; this.temp = createTempDirectory(); this.pomReplacements = getPomReplacements(); } private File createTempDirectory() { try { return Files.createTempDirectory("maven-build").toFile().getCanonicalFile(); } catch (IOException ex) { throw new IllegalStateException(ex); } } private Map getPomReplacements() { Map replacements = new HashMap<>(); replacements.put("java.version", "17"); replacements.put("project.groupId", "org.springframework.boot"); replacements.put("project.artifactId", "spring-boot-maven-plugin"); replacements.putAll(new Versions().asMap()); return Collections.unmodifiableMap(replacements); } MavenBuild project(String project) { return project("intTest", project); } MavenBuild project(String root, String project) { this.projectDir = new File("src/" + root + "/projects/" + project); return this; } MavenBuild goals(String... goals) { this.goals.addAll(Arrays.asList(goals)); return this; } MavenBuild systemProperty(String name, String value) { this.properties.setProperty(name, value); return this; } MavenBuild prepare(ProjectCallback callback) { this.preparation = callback; return this; } void execute(ProjectCallback callback) { execute(callback, 0); } void executeAndFail(ProjectCallback callback) { execute(callback, 1); } private void execute(ProjectCallback callback, int expectedExitCode) { Invoker invoker = new DefaultInvoker(); invoker.setMavenHome(this.home); InvocationRequest request = new DefaultInvocationRequest(); try { Path destination = this.temp.toPath(); Assert.notNull(this.projectDir, "'projectDir' must not be null"); Path source = this.projectDir.toPath(); Files.walkFileTree(source, new SimpleFileVisitor<>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { Files.createDirectories(destination.resolve(source.relativize(dir))); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file.toFile().getName().equals("pom.xml")) { String pomXml = Files.readString(file); for (Entry replacement : MavenBuild.this.pomReplacements.entrySet()) { pomXml = pomXml.replace("@" + replacement.getKey() + "@", replacement.getValue()); } Files.writeString(destination.resolve(source.relativize(file)), pomXml, StandardOpenOption.CREATE_NEW); } else { Files.copy(file, destination.resolve(source.relativize(file)), StandardCopyOption.REPLACE_EXISTING); } return FileVisitResult.CONTINUE; } }); String settingsXml = Files.readString(Paths.get("src", "intTest", "projects", "settings.xml")) .replace("@localCentralUrl@", new File("build/test-maven-repository").toURI().toURL().toString()) .replace("@localRepositoryPath@", new File("build/local-maven-repository").getAbsolutePath()); Files.writeString(destination.resolve("settings.xml"), settingsXml, StandardOpenOption.CREATE_NEW); request.setBaseDirectory(this.temp); request.setJavaHome(new File(System.getProperty("java.home"))); request.setProperties(this.properties); request.addArgs(this.goals.isEmpty() ? Collections.singletonList("package") : this.goals); request.setUserSettingsFile(new File(this.temp, "settings.xml")); request.setUpdateSnapshots(true); request.setBatchMode(true); // request.setMavenOpts("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000"); File target = new File(this.temp, "target"); target.mkdirs(); if (this.preparation != null) { this.preparation.doWith(this.temp); } File buildLogFile = new File(target, "build.log"); try (PrintWriter buildLog = new PrintWriter(new FileWriter(buildLogFile))) { request.setOutputHandler((line) -> { buildLog.println(line); buildLog.flush(); }); try { InvocationResult result = invoker.execute(request); assertThat(result.getExitCode()).as(contentOf(buildLogFile)).isEqualTo(expectedExitCode); } catch (MavenInvocationException ex) { throw new RuntimeException(ex); } } callback.doWith(this.temp); } catch (Exception ex) { throw new RuntimeException(ex); } finally { FileSystemUtils.deleteRecursively(this.temp); } } /** * Action to take on a maven project directory. */ @FunctionalInterface public interface ProjectCallback { /** * Take the action on the given project. * @param project the project directory * @throws Exception on error */ void doWith(File project) throws Exception; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/MavenBuildExtension.java ================================================ /* * 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.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; /** * An {@link Extension} for templated tests that use {@link MavenBuild}. Each templated * test is run against multiple versions of Maven. * * @author Andy Wilkinson */ class MavenBuildExtension implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { try { // Returning a stream which must be closed here is fine, as JUnit will take // care of closing it return Files.list(Paths.get("build/maven-binaries")).map(MavenVersionTestTemplateInvocationContext::new); } catch (IOException ex) { throw new RuntimeException(ex); } } private static final class MavenVersionTestTemplateInvocationContext implements TestTemplateInvocationContext { private final Path mavenHome; private MavenVersionTestTemplateInvocationContext(Path mavenHome) { this.mavenHome = mavenHome; } @Override public String getDisplayName(int invocationIndex) { return this.mavenHome.getFileName().toString(); } @Override public List getAdditionalExtensions() { return Arrays.asList(new MavenBuildParameterResolver(this.mavenHome)); } } private static final class MavenBuildParameterResolver implements ParameterResolver { private final Path mavenHome; private MavenBuildParameterResolver(Path mavenHome) { this.mavenHome = mavenHome; } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType().equals(MavenBuild.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return new MavenBuild(this.mavenHome.toFile()); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/RunIntegrationTests.java ================================================ /* * 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.io.File; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.ExtendWith; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; /** * Integration tests for the Maven plugin's run goal. * * @author Andy Wilkinson * @author Stephane Nicoll */ @ExtendWith(MavenBuildExtension.class) class RunIntegrationTests { @TestTemplate void whenTheRunGoalIsExecutedTheApplicationIsForkedWithOptimizedJvmArguments(MavenBuild mavenBuild) { mavenBuild.project("run").goals("spring-boot:run", "-X").execute((project) -> { String jvmArguments = "JVM argument: -XX:TieredStopAtLevel=1"; assertThat(buildLog(project)).contains("I haz been run").contains(jvmArguments); }); } @TestTemplate void whenEnvironmentVariablesAreConfiguredTheyAreAvailableToTheApplication(MavenBuild mavenBuild) { mavenBuild.project("run-envargs") .goals("spring-boot:run") .execute((project) -> assertThat(buildLog(project)).contains("I haz been run")); } @TestTemplate void whenExclusionsAreConfiguredExcludedDependenciesDoNotAppearOnTheClasspath(MavenBuild mavenBuild) { mavenBuild.project("run-exclude") .goals("spring-boot:run") .execute((project) -> assertThat(buildLog(project)).contains("I haz been run")); } @TestTemplate void whenSystemPropertiesAndJvmArgumentsAreConfiguredTheyAreAvailableToTheApplication(MavenBuild mavenBuild) { mavenBuild.project("run-jvm-system-props") .goals("spring-boot:run") .execute((project) -> assertThat(buildLog(project)).contains("I haz been run")); } @TestTemplate void whenJvmArgumentsAreConfiguredTheyAreAvailableToTheApplication(MavenBuild mavenBuild) { mavenBuild.project("run-jvmargs") .goals("spring-boot:run") .execute((project) -> assertThat(buildLog(project)).contains("I haz been run")); } @TestTemplate void whenCommandLineSpecifiesJvmArgumentsTheyAreAvailableToTheApplication(MavenBuild mavenBuild) { mavenBuild.project("run-jvmargs-commandline") .goals("spring-boot:run") .systemProperty("spring-boot.run.jvmArguments", "-Dfoo=value-from-cmd") .execute((project) -> assertThat(buildLog(project)).contains("I haz been run")); } @TestTemplate void whenPomAndCommandLineSpecifyJvmArgumentsThenPomOverrides(MavenBuild mavenBuild) { mavenBuild.project("run-jvmargs") .goals("spring-boot:run") .systemProperty("spring-boot.run.jvmArguments", "-Dfoo=value-from-cmd") .execute((project) -> assertThat(buildLog(project)).contains("I haz been run")); } @TestTemplate void whenProfilesAreConfiguredTheyArePassedToTheApplication(MavenBuild mavenBuild) { mavenBuild.project("run-profiles") .goals("spring-boot:run", "-X") .execute((project) -> assertThat(buildLog(project)).contains("I haz been run with profile(s) 'foo,bar'")); } @TestTemplate void whenUseTestClasspathIsEnabledTheApplicationHasTestDependenciesOnItsClasspath(MavenBuild mavenBuild) { mavenBuild.project("run-use-test-classpath") .goals("spring-boot:run") .execute((project) -> assertThat(buildLog(project)).contains("I haz been run")); } @TestTemplate void whenAWorkingDirectoryIsConfiguredTheApplicationIsRunFromThatDirectory(MavenBuild mavenBuild) { mavenBuild.project("run-working-directory") .goals("spring-boot:run") .execute((project) -> assertThat(buildLog(project)).containsPattern("I haz been run from.*src.main.java")); } @TestTemplate void whenAdditionalClasspathDirectoryIsConfiguredItsResourcesAreAvailableToTheApplication(MavenBuild mavenBuild) { mavenBuild.project("run-additional-classpath-directory") .goals("spring-boot:run") .execute((project) -> assertThat(buildLog(project)).contains("I haz been run")); } @TestTemplate void whenAdditionalClasspathFileIsConfiguredItsContentIsAvailableToTheApplication(MavenBuild mavenBuild) { mavenBuild.project("run-additional-classpath-jar") .goals("spring-boot:run") .execute((project) -> assertThat(buildLog(project)).contains("I haz been run")); } @TestTemplate @DisabledOnOs(OS.WINDOWS) void whenAToolchainIsConfiguredItIsUsedToRunTheApplication(MavenBuild mavenBuild) { mavenBuild.project("run-toolchains") .goals("verify", "-t", "toolchains.xml") .execute((project) -> assertThat(buildLog(project)).contains("The Maven Toolchains is awesome!")); } @TestTemplate void whenPomSpecifiesRunArgumentsContainingCommasTheyArePassedToTheApplicationCorrectly(MavenBuild mavenBuild) { mavenBuild.project("run-arguments") .goals("spring-boot:run") .execute((project) -> assertThat(buildLog(project)) .contains("I haz been run with profile(s) 'foo,bar' and endpoint(s) 'prometheus,info'")); } @TestTemplate void whenCommandLineSpecifiesRunArgumentsContainingCommasTheyArePassedToTheApplicationCorrectly( MavenBuild mavenBuild) { mavenBuild.project("run-arguments-commandline") .goals("spring-boot:run") .systemProperty("spring-boot.run.arguments", "--management.endpoints.web.exposure.include=prometheus,info,health,metrics --spring.profiles.active=foo,bar") .execute((project) -> assertThat(buildLog(project)) .contains("I haz been run with profile(s) 'foo,bar' and endpoint(s) 'prometheus,info,health,metrics'")); } @TestTemplate void whenPomAndCommandLineSpecifyRunArgumentsThenPomOverrides(MavenBuild mavenBuild) { mavenBuild.project("run-arguments") .goals("spring-boot:run") .systemProperty("spring-boot.run.arguments", "--management.endpoints.web.exposure.include=one,two,three --spring.profiles.active=test") .execute((project) -> assertThat(buildLog(project)) .contains("I haz been run with profile(s) 'foo,bar' and endpoint(s) 'prometheus,info'")); } private String buildLog(File project) { return contentOf(new File(project, "target/build.log")); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/StartStopIntegrationTests.java ================================================ /* * 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.io.File; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; /** * Integration tests for the Maven plugin's war support. * * @author Andy Wilkinson */ @ExtendWith(MavenBuildExtension.class) class StartStopIntegrationTests { @TestTemplate void startStopWaitsForApplicationToBeReadyAndThenRequestsShutdown(MavenBuild mavenBuild) { mavenBuild.project("start-stop") .goals("verify") .execute((project) -> assertThat(buildLog(project)).contains("isReady: true") .contains("Shutdown requested")); } @TestTemplate void whenSkipIsTrueStartAndStopAreSkipped(MavenBuild mavenBuild) { mavenBuild.project("start-stop-skip") .goals("verify") .execute((project) -> assertThat(buildLog(project)).doesNotContain("Ooops, I haz been run") .doesNotContain("Stopping application")); } private String buildLog(File project) { return contentOf(new File(project, "target/build.log")); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/TestRunIntegrationTests.java ================================================ /* * 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.io.File; import java.io.IOException; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; /** * Integration tests for the Maven plugin's {@code test-run} goal. * * @author Andy Wilkinson */ @ExtendWith(MavenBuildExtension.class) class TestRunIntegrationTests { @TestTemplate void whenTheTestRunGoalIsExecutedTheApplicationIsRunWithTestAndMainClassesAndTestClasspath(MavenBuild mavenBuild) { mavenBuild.project("test-run") .goals("spring-boot:test-run", "-X") .execute((project) -> assertThat(buildLog(project)) .contains("Main class name = org.test.TestSampleApplication") .contains("1. " + canonicalPathOf(project, "target/test-classes")) .contains("2. " + canonicalPathOf(project, "target/classes")) .containsPattern("3\\. .*spring-core") .containsPattern("4\\. .*commons-logging")); } private String canonicalPathOf(File project, String path) throws IOException { return new File(project, path).getCanonicalPath(); } private String buildLog(File project) { return contentOf(new File(project, "target/build.log")); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/Versions.java ================================================ /* * 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.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; import org.jspecify.annotations.Nullable; /** * Provides access to various versions. * * @author Andy Wilkinson */ class Versions { private final Map versions; Versions() { this.versions = loadVersions(); } private static Map loadVersions() { try (InputStream input = Versions.class.getClassLoader().getResourceAsStream("extracted-versions.properties")) { Properties properties = new Properties(); properties.load(input); Map versions = new HashMap<>(); properties.forEach((key, value) -> versions.put((String) key, (String) value)); return versions; } catch (IOException ex) { throw new RuntimeException(ex); } } @Nullable String get(String name) { return this.versions.get(name); } Map asMap() { return Collections.unmodifiableMap(this.versions); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java ================================================ /* * 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.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.concurrent.atomic.AtomicReference; import java.util.jar.JarFile; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.loader.tools.JarModeLibrary; import org.springframework.boot.loader.tools.LibraryCoordinates; import org.springframework.boot.testsupport.FileUtils; import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for the Maven plugin's war support. * * @author Andy Wilkinson * @author Scott Frederick * @author Moritz Halbritter */ @ExtendWith(MavenBuildExtension.class) class WarIntegrationTests extends AbstractArchiveIntegrationTests { @Override protected String getLayersIndexLocation() { return "WEB-INF/layers.idx"; } @TestTemplate void warRepackaging(MavenBuild mavenBuild) { mavenBuild.project("war") .execute((project) -> assertThat(jar(new File(project, "target/war-0.0.1.BUILD-SNAPSHOT.war"))) .hasEntryWithNameStartingWith("WEB-INF/lib/spring-context") .hasEntryWithNameStartingWith("WEB-INF/lib/spring-core") .hasEntryWithNameStartingWith("WEB-INF/lib/commons-logging") .hasEntryWithNameStartingWith("WEB-INF/lib-provided/jakarta.servlet-api-6") .hasEntryWithName("org/springframework/boot/loader/launch/WarLauncher.class") .hasEntryWithName("WEB-INF/classes/org/test/SampleApplication.class") .hasEntryWithName("index.html") .manifest((manifest) -> manifest.hasMainClass("org.springframework.boot.loader.launch.WarLauncher") .hasStartClass("org.test.SampleApplication") .hasAttribute("Not-Used", "Foo"))); } @TestTemplate void jarDependencyWithCustomFinalNameBuiltInSameReactorIsPackagedUsingArtifactIdAndVersion(MavenBuild mavenBuild) { mavenBuild.project("war-reactor") .execute(((project) -> assertThat(jar(new File(project, "war/target/war-0.0.1.BUILD-SNAPSHOT.war"))) .hasEntryWithName("WEB-INF/lib/jar-0.0.1.BUILD-SNAPSHOT.jar") .doesNotHaveEntryWithName("WEB-INF/lib/jar.jar"))); } @TestTemplate void whenRequiresUnpackConfigurationIsProvidedItIsReflectedInTheRepackagedWar(MavenBuild mavenBuild) { mavenBuild.project("war-with-unpack") .execute((project) -> assertThat(jar(new File(project, "target/war-with-unpack-0.0.1.BUILD-SNAPSHOT.war"))) .hasUnpackEntryWithNameStartingWith("WEB-INF/lib/spring-core-") .hasEntryWithNameStartingWith("WEB-INF/lib/spring-context-") .hasEntryWithNameStartingWith("WEB-INF/lib/commons-logging-")); } @TestTemplate void whenWarIsRepackagedWithOutputTimestampConfiguredThenWarIsReproducible(MavenBuild mavenBuild) throws InterruptedException { String firstHash = buildWarWithOutputTimestamp(mavenBuild); Thread.sleep(1500); String secondHash = buildWarWithOutputTimestamp(mavenBuild); assertThat(firstHash).isEqualTo(secondHash); } private String buildWarWithOutputTimestamp(MavenBuild mavenBuild) { AtomicReference warHash = new AtomicReference<>(); mavenBuild.project("war-output-timestamp").execute((project) -> { File repackaged = new File(project, "target/war-output-timestamp-0.0.1.BUILD-SNAPSHOT.war"); assertThat(repackaged).isFile(); long expectedModified = 1584352800000L; assertThat(repackaged.lastModified()).isEqualTo(expectedModified); long offsetExpectedModified = expectedModified - TimeZone.getDefault().getOffset(expectedModified); try (JarFile jar = new JarFile(repackaged)) { List unreproducibleEntries = jar.stream() .filter((entry) -> entry.getLastModifiedTime().toMillis() != offsetExpectedModified) .map((entry) -> entry.getName() + ": " + entry.getLastModifiedTime()) .toList(); assertThat(unreproducibleEntries).isEmpty(); warHash.set(FileUtils.sha1Hash(repackaged)); FileSystemUtils.deleteRecursively(project); } catch (IOException ex) { throw new RuntimeException(ex); } }); String hash = warHash.get(); assertThat(hash).isNotNull(); return hash; } @TestTemplate void whenWarIsRepackagedWithOutputTimestampConfiguredThenLibrariesAreSorted(MavenBuild mavenBuild) { mavenBuild.project("war-output-timestamp").execute((project) -> { File repackaged = new File(project, "target/war-output-timestamp-0.0.1.BUILD-SNAPSHOT.war"); LibraryCoordinates coordinates = JarModeLibrary.TOOLS.getCoordinates(); assertThat(coordinates).isNotNull(); List sortedLibs = Arrays.asList( // these libraries are copied from the original war, sorted when // packaged by Maven "WEB-INF/lib/commons-logging", "WEB-INF/lib/jspecify", "WEB-INF/lib/micrometer-commons", "WEB-INF/lib/micrometer-observation", "WEB-INF/lib/spring-aop", "WEB-INF/lib/spring-beans", "WEB-INF/lib/spring-context", "WEB-INF/lib/spring-core", "WEB-INF/lib/spring-expression", // these libraries are contributed by Spring Boot repackaging, and // sorted separately "WEB-INF/lib/" + coordinates.getArtifactId()); assertThat(jar(repackaged)).entryNamesInPath("WEB-INF/lib/") .zipSatisfy(sortedLibs, (String jarLib, String expectedLib) -> assertThat(jarLib).startsWith(expectedLib)); }); } @TestTemplate void whenADependencyHasSystemScopeAndInclusionOfSystemScopeDependenciesIsEnabledItIsIncludedInTheRepackagedJar( MavenBuild mavenBuild) { mavenBuild.project("war-system-scope").execute((project) -> { File main = new File(project, "target/war-system-scope-0.0.1.BUILD-SNAPSHOT.war"); assertThat(jar(main)).hasEntryWithName("WEB-INF/lib-provided/sample-1.0.0.jar"); }); } @TestTemplate void repackagedWarContainsTheLayersIndexByDefault(MavenBuild mavenBuild) { mavenBuild.project("war-layered").execute((project) -> { File repackaged = new File(project, "war/target/war-layered-0.0.1.BUILD-SNAPSHOT.war"); LibraryCoordinates coordinates = JarModeLibrary.TOOLS.getCoordinates(); assertThat(coordinates).isNotNull(); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("WEB-INF/classes/") .hasEntryWithNameStartingWith("WEB-INF/lib/jar-release") .hasEntryWithNameStartingWith("WEB-INF/lib/jar-snapshot") .hasEntryWithNameStartingWith("WEB-INF/lib/" + coordinates.getArtifactId()); try (JarFile jarFile = new JarFile(repackaged)) { Map> layerIndex = readLayerIndex(jarFile); assertThat(layerIndex.keySet()).containsExactly("dependencies", "spring-boot-loader", "snapshot-dependencies", "application"); List dependenciesAndSnapshotDependencies = new ArrayList<>(); dependenciesAndSnapshotDependencies.addAll(layerIndex.get("dependencies")); dependenciesAndSnapshotDependencies.addAll(layerIndex.get("snapshot-dependencies")); assertThat(layerIndex.get("application")).contains("WEB-INF/lib/jar-release-0.0.1.RELEASE.jar", "WEB-INF/lib/jar-snapshot-0.0.1.BUILD-SNAPSHOT.jar"); assertThat(dependenciesAndSnapshotDependencies) .anyMatch((dependency) -> dependency.startsWith("WEB-INF/lib/spring-context")); assertThat(layerIndex.get("dependencies")) .anyMatch((dependency) -> dependency.startsWith("WEB-INF/lib-provided/")); } catch (IOException ex) { // Ignore } }); } @TestTemplate void whenWarIsRepackagedWithTheLayersDisabledDoesNotContainLayersIndex(MavenBuild mavenBuild) { mavenBuild.project("war-layered-disabled").execute((project) -> { File repackaged = new File(project, "war/target/war-layered-0.0.1.BUILD-SNAPSHOT.war"); LibraryCoordinates coordinates = JarModeLibrary.TOOLS.getCoordinates(); assertThat(coordinates).isNotNull(); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("WEB-INF/classes/") .hasEntryWithNameStartingWith("WEB-INF/lib/jar-release") .hasEntryWithNameStartingWith("WEB-INF/lib/jar-snapshot") .hasEntryWithNameStartingWith("WEB-INF/lib/" + coordinates.getArtifactId()) .doesNotHaveEntryWithName("WEB-INF/layers.idx"); }); } @TestTemplate void whenWarIsRepackagedWithToolsExclude(MavenBuild mavenBuild) { mavenBuild.project("war-no-tools").execute((project) -> { File repackaged = new File(project, "war/target/war-no-tools-0.0.1.BUILD-SNAPSHOT.war"); LibraryCoordinates coordinates = JarModeLibrary.TOOLS.getCoordinates(); assertThat(coordinates).isNotNull(); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("WEB-INF/classes/") .hasEntryWithNameStartingWith("WEB-INF/lib/jar-release") .hasEntryWithNameStartingWith("WEB-INF/lib/jar-snapshot") .doesNotHaveEntryWithNameStartingWith("WEB-INF/lib/" + coordinates.getArtifactId()); }); } @TestTemplate void whenWarIsRepackagedWithTheCustomLayers(MavenBuild mavenBuild) { mavenBuild.project("war-layered-custom").execute((project) -> { File repackaged = new File(project, "war/target/war-layered-0.0.1.BUILD-SNAPSHOT.war"); assertThat(jar(repackaged)).hasEntryWithNameStartingWith("WEB-INF/classes/") .hasEntryWithNameStartingWith("WEB-INF/lib/jar-release") .hasEntryWithNameStartingWith("WEB-INF/lib/jar-snapshot"); try (JarFile jarFile = new JarFile(repackaged)) { Map> layerIndex = readLayerIndex(jarFile); assertThat(layerIndex.keySet()).containsExactly("my-dependencies-name", "snapshot-dependencies", "configuration", "application"); assertThat(layerIndex.get("application")) .contains("WEB-INF/lib/jar-release-0.0.1.RELEASE.jar", "WEB-INF/lib/jar-snapshot-0.0.1.BUILD-SNAPSHOT.jar", "WEB-INF/lib/jar-classifier-0.0.1-bravo.jar") .doesNotContain("WEB-INF/lib/jar-classifier-0.0.1-alpha.jar"); } }); } @TestTemplate void repackagedWarContainsClasspathIndex(MavenBuild mavenBuild) { mavenBuild.project("war").execute((project) -> { File repackaged = new File(project, "target/war-0.0.1.BUILD-SNAPSHOT.war"); assertThat(jar(repackaged)) .manifest((manifest) -> manifest.hasAttribute("Spring-Boot-Classpath-Index", "WEB-INF/classpath.idx")); assertThat(jar(repackaged)).hasEntryWithName("WEB-INF/classpath.idx"); try (JarFile jarFile = new JarFile(repackaged)) { List index = readClasspathIndex(jarFile, "WEB-INF/classpath.idx"); assertThat(index) .allMatch((entry) -> entry.startsWith("WEB-INF/lib/") || entry.startsWith("WEB-INF/lib-provided/")); } }); } @TestTemplate void whenEntryIsExcludedItShouldNotBePresentInTheRepackagedWar(MavenBuild mavenBuild) { mavenBuild.project("war-exclude-entry").execute((project) -> { File war = new File(project, "target/war-exclude-entry-0.0.1.BUILD-SNAPSHOT.war"); assertThat(jar(war)).hasEntryWithNameStartingWith("WEB-INF/lib/spring-context") .doesNotHaveEntryWithNameStartingWith("WEB-INF/lib/spring-core"); }); } @TestTemplate void whenSigned(MavenBuild mavenBuild) { mavenBuild.project("war-signed").execute((project) -> { File repackaged = new File(project, "target/war-signed-0.0.1.BUILD-SNAPSHOT.war"); assertThat(jar(repackaged)).hasEntryWithName("META-INF/BOOT.SF"); }); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-aot org.springframework.boot spring-boot @project.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-arguments/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-aot --spring.profiles.active=abc org.springframework.boot spring-boot @project.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-arguments/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration(proxyBeanMethods = false) @Import(TestProfileConfiguration.class) public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-arguments/src/main/java/org/test/TestProfileConfiguration.java ================================================ /* * 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.test; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @Configuration(proxyBeanMethods = false) @Profile("abc") class TestProfileConfiguration { @Bean public String abc() { return "abc"; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-class-proxy/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot-class-proxy 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-aot org.springframework.boot spring-boot @project.version@ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-class-proxy/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; @Configuration(proxyBeanMethods = false) @ComponentScan @EnableAsync public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-class-proxy/src/main/java/org/test/SampleRunner.java ================================================ /* * 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.test; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component public class SampleRunner { @Async public void run() { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-compiler-arguments/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-aot -parameters --invalid-compiler-arg org.springframework.boot spring-boot @project.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-compiler-arguments/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration(proxyBeanMethods = false) public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-exclude-devtools/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot-exclude-devtools 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-aot org.springframework.boot spring-boot @project.version@ org.springframework.boot spring-boot-devtools @project.version@ true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-exclude-devtools/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-jdk-proxy/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot-jdk-proxy 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-aot org.springframework.boot spring-boot @project.version@ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-jdk-proxy/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.test.SampleApplication.SampleApplicationRuntimeHints; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.stereotype.Service; @Configuration(proxyBeanMethods = false) @ImportRuntimeHints(SampleApplicationRuntimeHints.class) public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } static class SampleApplicationRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { // Force creation of at least one JDK proxy hints.proxies().registerJdkProxy(AopProxyUtils.completeJdkProxyInterfaces(Service.class)); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-jvm-arguments/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-aot -Dspring.profiles.active=abc org.springframework.boot spring-boot @project.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-jvm-arguments/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration(proxyBeanMethods = false) @Import(TestProfileConfiguration.class) public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-jvm-arguments/src/main/java/org/test/TestProfileConfiguration.java ================================================ /* * 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.test; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @Configuration(proxyBeanMethods = false) @Profile("abc") class TestProfileConfiguration { @Bean public String abc() { return "abc"; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-module-info/pom.xml ================================================ 4.0.0 org.springframework.boot spring-boot-starter-parent @project.version@ org.springframework.boot.maven.it aot-module-info 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ process-aot repackage true org.springframework.boot spring-boot ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-module-info/src/main/java/module-info.java ================================================ /* * 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. */ module sampleApp { requires spring.boot; requires spring.context; } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-module-info/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-profile/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-aot abc org.springframework.boot spring-boot @project.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-profile/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration(proxyBeanMethods = false) @Import(TestProfileConfiguration.class) public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-profile/src/main/java/org/test/TestProfileConfiguration.java ================================================ /* * 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.test; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @Configuration(proxyBeanMethods = false) @Profile("abc") class TestProfileConfiguration { @Bean public String abc() { return "abc"; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-release/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-aot org.springframework.boot spring-boot @project.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-release/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration(proxyBeanMethods = false) public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-resource-generation/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot-resource-generation 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-aot org.springframework.boot spring-boot @project.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-resource-generation/src/main/java/org/test/ResourceRegisteringAotProcessor.java ================================================ /* * 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.test; import org.springframework.aot.generate.GenerationContext; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; class ResourceRegisteringAotProcessor implements BeanFactoryInitializationAotProcessor { @Override public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { return new BeanFactoryInitializationAotContribution() { @Override public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { generationContext.getGeneratedFiles().addResourceFile("generated-resource", "content"); generationContext.getGeneratedFiles().addResourceFile("nested/generated-resource", "nested content"); } }; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-resource-generation/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-resource-generation/src/main/resources/META-INF/spring/aot.factories ================================================ org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\ org.test.ResourceRegisteringAotProcessor ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-system-properties/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot-system-properties 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-aot abc org.springframework.boot spring-boot @project.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-system-properties/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration(proxyBeanMethods = false) @Import(TestProfileConfiguration.class) public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-system-properties/src/main/java/org/test/TestProfileConfiguration.java ================================================ /* * 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.test; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @Configuration(proxyBeanMethods = false) @Profile("abc") class TestProfileConfiguration { @Bean public String abc() { return "abc"; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot-test 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-test-aot org.springframework.boot spring-boot @project.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided org.springframework spring-test @spring-framework.version@ test org.springframework.boot spring-boot-test @project.version@ test org.assertj assertj-core @assertj.version@ test org.junit.jupiter junit-jupiter @junit-jupiter.version@ test ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test/src/test/java/org/test/SampleApplicationTests.java ================================================ /* * 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.test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @SpringJUnitConfig class SampleApplicationTests { @Autowired private MyBean myBean; @Test void contextLoads() { assertThat(this.myBean).isNotNull(); } @Configuration static class MyConfig { @Bean MyBean myBean() { return new MyBean(); } } static class MyBean { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot-test-exclude-devtools 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ process-test-aot org.springframework.boot spring-boot @project.version@ org.springframework.boot spring-boot-devtools @project.version@ true org.springframework.boot spring-boot-test @project.version@ test org.junit.jupiter junit-jupiter @junit-jupiter.version@ test ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/src/test/java/org/test/SampleApplicationTests.java ================================================ /* * 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.test; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class SampleApplicationTests { @Test void contextLoads() { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-skip/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aot-test-skip 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ true @project.groupId@ @project.artifactId@ @project.version@ process-test-aot org.springframework.boot spring-boot @project.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided org.springframework spring-test @spring-framework.version@ test org.springframework.boot spring-boot-test @project.version@ test org.assertj assertj-core @assertj.version@ test org.junit.jupiter junit-jupiter @junit-jupiter.version@ test ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-skip/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-skip/src/test/java/org/test/SampleApplicationTests.java ================================================ /* * 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.test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @SpringJUnitConfig class SampleApplicationTests { @Autowired private MyBean myBean; @Test void contextLoads() { assertThat(this.myBean).isNotNull(); } @Configuration static class MyConfig { @Bean MyBean myBean() { return new MyBean(); } } static class MyBean { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-info 0.0.1.BUILD-SNAPSHOT Generate build info UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-info ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-additional-properties/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-info-additional-properties 0.0.1.BUILD-SNAPSHOT Generate build info with additional properties UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-info bar ${project.build.sourceEncoding} 1.8 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-additional-properties/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-custom-build-time/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-info-custom-build-time 0.0.1.BUILD-SNAPSHOT Generate build info with custom build time UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-info ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-custom-build-time/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-custom-file/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-info-custom-file 0.0.1.BUILD-SNAPSHOT Generate custom build info UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-info ${project.build.directory}/build.info ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-custom-file/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-disable-build-time/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-info-disable-build-time 0.0.1.BUILD-SNAPSHOT Generate build info with disabled build time UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ build-info org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-disable-build-time/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-exclude-build-properties/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-info-exclude-build-properties 0.0.1.BUILD-SNAPSHOT Generate build info with excluded build properties UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ group artifact version name build-info ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-exclude-build-properties/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-exclude-build-time/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-info-exclude-build-time 0.0.1.BUILD-SNAPSHOT Generate build info with excluded build time UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ time build-info org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-exclude-build-time/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-reproducible/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-reproducible 0.0.1.BUILD-SNAPSHOT Generate build info with build time from project.build.outputTimestamp UTF-8 @java.version@ @java.version@ 2021-04-21T11:22:33Z @project.groupId@ @project.artifactId@ @project.version@ build-info ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-reproducible/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-reproducible-epoch-seconds/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it build-reproducible-epoch-seconds 0.0.1.BUILD-SNAPSHOT Generate build info with build time from project.build.outputTimestamp UTF-8 @java.version@ @java.version@ 1619004153 @project.groupId@ @project.artifactId@ @project.version@ build-info ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/build-info-reproducible-epoch-seconds/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.apache.maven.plugins maven-jar-plugin @maven-jar-plugin.version@ some.random.Main Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-attach-disabled/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-attach-disabled 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage false ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-attach-disabled/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-classifier-main/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-classifier-main 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage test ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-classifier-main/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-classifier-main-attach-disabled/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-classifier-main-attach-disabled 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage test false ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-classifier-main-attach-disabled/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-classifier-source/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-classifier-source 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ org.apache.maven.plugins maven-jar-plugin @maven-jar-plugin.version@ jar package test @project.groupId@ @project.artifactId@ @project.version@ repackage test ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-classifier-source/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-classifier-source-attach-disabled/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-classifier-source-attach-disabled 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ org.apache.maven.plugins maven-jar-plugin @maven-jar-plugin.version@ jar package test @project.groupId@ @project.artifactId@ @project.version@ repackage test false ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-classifier-source-attach-disabled/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-create-dir/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-create-dir 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage ${project.build.directory}/foo foo ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-create-dir/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-custom-dir/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-custom-dir 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage ${project.build.directory}/foo ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-custom-dir/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-custom-layout/custom/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it custom 0.0.1.BUILD-SNAPSHOT 1.8 1.8 UTF-8 org.springframework.boot spring-boot-maven-plugin @project.version@ repackage custom org.springframework.boot.maven.it layout 0.0.1.BUILD-SNAPSHOT ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-custom-layout/custom/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-custom-layout/default/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it default 0.0.1.BUILD-SNAPSHOT 1.8 1.8 UTF-8 org.springframework.boot spring-boot-maven-plugin @project.version@ repackage org.springframework.boot.maven.it layout 0.0.1.BUILD-SNAPSHOT ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-custom-layout/default/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-custom-layout/layout/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-custom-layout 0.0.1.BUILD-SNAPSHOT jar layout org.springframework.boot spring-boot-loader-tools @project.version@ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-custom-layout/layout/src/main/java/smoketest/layout/SampleLayout.java ================================================ /* * 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 smoketest.layout; import java.io.ByteArrayInputStream; import java.io.IOException; import org.springframework.boot.loader.tools.CustomLoaderLayout; import org.springframework.boot.loader.tools.Layouts; import org.springframework.boot.loader.tools.LoaderClassesWriter; /** * An example layout. * * @author Phillip Webb */ public class SampleLayout extends Layouts.Jar implements CustomLoaderLayout { private String name; public SampleLayout(String name) { this.name = name; } @Override public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException { writer.writeEntry(this.name, new ByteArrayInputStream("test".getBytes())); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-custom-layout/layout/src/main/java/smoketest/layout/SampleLayoutFactory.java ================================================ /* * 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 smoketest.layout; import java.io.File; import org.springframework.boot.loader.tools.Layout; import org.springframework.boot.loader.tools.LayoutFactory; public class SampleLayoutFactory implements LayoutFactory { private String name = "sample"; public SampleLayoutFactory() { } public SampleLayoutFactory(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return this.name; } @Override public Layout getLayout(File source) { return new SampleLayout(this.name); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-custom-layout/layout/src/main/resources/META-INF/spring.factories ================================================ # Layout Factories org.springframework.boot.loader.tools.LayoutFactory=\ smoketest.layout.SampleLayoutFactory ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-custom-layout/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-custom-layout 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ layout custom default ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-exclude-entry/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-exclude-entry 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage javax.servlet servlet-api org.springframework spring-context @spring-framework.version@ javax.servlet servlet-api 2.5 provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-exclude-entry/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-exclude-group/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-exclude-group 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.apache.logging.log4j org.apache.maven.plugins maven-jar-plugin @maven-jar-plugin.version@ Foo org.springframework spring-context @spring-framework.version@ org.apache.logging.log4j log4j-api @log4j2.version@ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-exclude-group/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-include-entry/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-include-entry 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage jakarta.servlet jakarta.servlet-api org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-include-entry/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered/jar/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-layered 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.apache.logging.log4j log4j-api @log4j2.version@ org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT org.springframework.boot.maven.it jar-release 0.0.1.RELEASE ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered/jar/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered/jar-release/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-release 0.0.1.RELEASE jar jar Release Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered/jar-snapshot/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT jar jar Snapshot Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ jar-snapshot jar-release jar ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom/jar/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-layered 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage true ${project.basedir}/src/layers.xml org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT org.springframework.boot.maven.it jar-release 0.0.1.RELEASE org.springframework.boot.maven.it jar-classifier 0.0.1 bravo org.apache.logging.log4j log4j-api @log4j2.version@ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom/jar/src/layers.xml ================================================ **/application*.* *:*:*-SNAPSHOT my-dependencies-name snapshot-dependencies configuration application ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom/jar/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom/jar/src/main/resources/application.yml ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom/jar-classifier/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-classifier 0.0.1 jar jar Classifier Jar dependency maven-jar-plugin alpha package jar alpha bravo package jar bravo ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom/jar-release/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-release 0.0.1.RELEASE jar jar Release Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom/jar-snapshot/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT jar jar Snapshot Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ jar-classifier jar-release jar-snapshot jar ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom-name/jar/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-layered 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage true custom org.springframework.boot.maven.it jar-layers-configuration 0.0.1 org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT org.springframework.boot.maven.it jar-release 0.0.1.RELEASE org.springframework.boot.maven.it jar-classifier 0.0.1 bravo org.apache.logging.log4j log4j-api @log4j2.version@ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom-name/jar/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom-name/jar/src/main/resources/application.yml ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom-name/jar-classifier/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-classifier 0.0.1 jar jar Classifier Jar dependency maven-jar-plugin alpha package jar alpha bravo package jar bravo ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom-name/jar-layers-configuration/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-layers-configuration 0.0.1 jar jar Layers configuration dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom-name/jar-layers-configuration/src/main/resources/META-INF/spring/layers/custom.xml ================================================ **/application*.* *:*:*-SNAPSHOT my-dependencies-name snapshot-dependencies configuration application ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom-name/jar-release/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-release 0.0.1.RELEASE jar jar Release Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom-name/jar-snapshot/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT jar jar Snapshot Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-custom-name/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ jar-classifier jar-layers-configuration jar-release jar-snapshot jar ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-disabled/jar/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-layered 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage false org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT org.springframework.boot.maven.it jar-release 0.0.1.RELEASE ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-disabled/jar/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-disabled/jar-release/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-release 0.0.1.RELEASE jar jar Release Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-disabled/jar-snapshot/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT jar jar Snapshot Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-layered-disabled/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ jar-snapshot jar-release jar ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-lib-name-conflict/acme-lib/pom.xml ================================================ 4.0.0 acme-lib org.springframework.boot.maven.it jar-lib-name-conflict 0.0.1.BUILD-SNAPSHOT ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-lib-name-conflict/another-acme-lib/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it.another acme-lib org.springframework.boot.maven.it jar-lib-name-conflict 0.0.1.BUILD-SNAPSHOT ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-lib-name-conflict/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-lib-name-conflict 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ acme-lib another-acme-lib test-project ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-lib-name-conflict/test-project/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it test-project 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.springframework.boot.maven.it acme-lib 0.0.1.BUILD-SNAPSHOT org.springframework.boot.maven.it.another acme-lib 0.0.1.BUILD-SNAPSHOT ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-lib-name-conflict/test-project/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-no-tools/jar/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-no-tools 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage false org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT org.springframework.boot.maven.it jar-release 0.0.1.RELEASE ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-no-tools/jar/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-no-tools/jar-release/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-release 0.0.1.RELEASE jar jar Release Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-no-tools/jar-snapshot/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT jar jar Snapshot Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-no-tools/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ jar-snapshot jar-release jar ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-optional-default/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-optional-default 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.apache.maven.plugins maven-jar-plugin @maven-jar-plugin.version@ org.springframework spring-context @spring-framework.version@ org.apache.logging.log4j log4j-api @log4j2.version@ true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-optional-default/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-optional-exclude/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-optional-exclude 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage false org.apache.maven.plugins maven-jar-plugin @maven-jar-plugin.version@ org.springframework spring-context @spring-framework.version@ org.apache.logging.log4j log4j-api @log4j2.version@ true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-optional-exclude/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-optional-include/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-optional-include 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage true org.apache.maven.plugins maven-jar-plugin @maven-jar-plugin.version@ org.springframework spring-context @spring-framework.version@ org.apache.logging.log4j log4j-api @log4j2.version@ true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-optional-include/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-output-timestamp/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-output-timestamp 0.0.1.BUILD-SNAPSHOT UTF-8 2020-03-16T02:00:00-08:00 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.apache.maven.plugins maven-jar-plugin @maven-jar-plugin.version@ some.random.Main Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-output-timestamp/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-pom/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-pom pom 0.0.1.BUILD-SNAPSHOT UTF-8 @project.groupId@ @project.artifactId@ @project.version@ repackage ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-signed/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-signed 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.apache.maven.plugins maven-jar-plugin @maven-jar-plugin.version@ some.random.Main Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided org.bouncycastle bcprov-jdk18on 1.78.1 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-signed/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-skip/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-skip 0.0.1.BUILD-SNAPSHOT UTF-8 @project.groupId@ @project.artifactId@ @project.version@ repackage true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-system-scope/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-system-scope 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage true com.example sample 1.0.0 system ${project.basedir}/sample-1.0.0.jar ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-system-scope/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-system-scope-default/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-system-scope-default 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage com.example sample 1.0.0 system ${project.basedir}/sample-1.0.0.jar ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-system-scope-default/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-test-scope/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-test-scope 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.springframework spring-context @spring-framework.version@ org.apache.logging.log4j log4j-api @log4j2.version@ test ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-test-scope/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-kotlin-module/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-with-kotlin-module 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ ${project.basedir}/src/main/kotlin ${project.basedir}/src/test/kotlin @project.groupId@ @project.artifactId@ @project.version@ repackage org.jetbrains.kotlin kotlin-maven-plugin @kotlin.version@ compile process-resources compile test-compile process-test-resources compile org.jetbrains.kotlin kotlin-stdlib-jdk8 @kotlin.version@ org.jetbrains.kotlin kotlin-reflect @kotlin.version@ org.jetbrains.kotlin kotlin-compiler @kotlin.version@ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-kotlin-module/src/main/kotlin/org/test/SampleApplication.kt ================================================ /* * 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. */ @file:JvmName("SampleApplication") package org.test; fun main(args: Array) { } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-layout-property/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-with-layout-property 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ package repackage ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-layout-property/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-unpack/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-with-unpack 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.springframework spring-core org.apache.maven.plugins maven-jar-plugin @maven-jar-plugin.version@ Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided org.apache.logging.log4j log4j-api @log4j2.version@ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-unpack/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-zip-layout/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-with-zip-layout 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage ZIP ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-zip-layout/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { System.out.println("I haz been run"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-additional-classpath-directory/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-additional-classpath-directory 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ src/main/additional-elements/ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-additional-classpath-directory/src/main/additional-elements/another/two.txt ================================================ 2 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-additional-classpath-directory/src/main/additional-elements/one.txt ================================================ 1 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-additional-classpath-directory/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Scanner; public class SampleApplication { public static void main(String[] args) { if (!readContent("one.txt").contains("1")) { throw new IllegalArgumentException("Invalid content for one.txt"); } if (!readContent("another/two.txt").contains("2")) { throw new IllegalArgumentException("Invalid content for another/two.txt"); } System.out.println("I haz been run"); } private static String readContent(String location) { InputStream in = SampleApplication.class.getClassLoader().getResourceAsStream(location); if (in == null) { throw new IllegalArgumentException("Not found: '" + location + "'"); } try (Scanner scanner = new Scanner(in, StandardCharsets.UTF_8)) { return scanner.useDelimiter("\\A").next(); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-additional-classpath-jar/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-additional-classpath-directory 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ src/main/additional-jar/resources-1.0.0.jar ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-additional-classpath-jar/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Scanner; public class SampleApplication { public static void main(String[] args) { if (!readContent("one.txt").contains("1")) { throw new IllegalArgumentException("Invalid content for one.txt"); } if (!readContent("another/two.txt").contains("2")) { throw new IllegalArgumentException("Invalid content for another/two.txt"); } System.out.println("I haz been run"); } private static String readContent(String location) { InputStream in = SampleApplication.class.getClassLoader().getResourceAsStream(location); if (in == null) { throw new IllegalArgumentException("Not found: '" + location + "'"); } try (Scanner scanner = new Scanner(in, StandardCharsets.UTF_8)) { return scanner.useDelimiter("\\A").next(); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-arguments/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-arguments 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ --management.endpoints.web.exposure.include=prometheus,info --spring.profiles.active=foo,bar ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-arguments/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import java.util.Arrays; public class SampleApplication { public static void main(String[] args) { if (args.length < 2) { throw new IllegalArgumentException("Missing arguments " + Arrays.toString(args)); } if (!args[0].startsWith("--management.endpoints.web.exposure.include=")) { throw new IllegalArgumentException("Invalid argument " + args[0]); } if (!args[1].startsWith("--spring.profiles.active=")) { throw new IllegalArgumentException("Invalid argument " + args[1]); } String endpoints = args[0].split("=")[1]; String profile = args[1].split("=")[1]; System.out.println("I haz been run with profile(s) '" + profile + "' and endpoint(s) '" + endpoints + "'"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-arguments-commandline/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-arguments-commandline 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-arguments-commandline/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import java.util.Arrays; public class SampleApplication { public static void main(String[] args) { if (args.length < 2) { throw new IllegalArgumentException("Missing arguments " + Arrays.toString(args)); } if (!args[0].startsWith("--management.endpoints.web.exposure.include=")) { throw new IllegalArgumentException("Invalid argument " + args[0]); } if (!args[1].startsWith("--spring.profiles.active=")) { throw new IllegalArgumentException("Invalid argument " + args[1]); } String endpoints = args[0].split("=")[1]; String profile = args[1].split("=")[1]; System.out.println("I haz been run with profile(s) '" + profile + "' and endpoint(s) '" + endpoints + "'"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-envargs/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-envargs 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ 5000 Some Text ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-envargs/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { assertEnvValue("ENV1", "5000"); assertEnvValue("ENV2", "Some Text"); assertEnvValue("ENV3", ""); assertEnvValue("ENV4", ""); System.out.println("I haz been run"); } private static void assertEnvValue(String envKey, String expectedValue) { String actual = System.getenv(envKey); if (!expectedValue.equals(actual)) { throw new IllegalStateException("env property [" + envKey + "] mismatch " + "(got [" + actual + "], expected [" + expectedValue + "]"); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-exclude/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-exclude 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ org.apache.logging.log4j log4j-api jakarta.servlet,javax.servlet org.apache.logging.log4j log4j-api @log4j2.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided javax.servlet servlet-api 2.5 provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-exclude/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { if (isClassPresent("org.apache.log4j.Logger")) { throw new IllegalStateException("Log4j was present and should not"); } if (isClassPresent("jakarta.servlet.Servlet")) { throw new IllegalStateException("servlet-api was present and should not"); } System.out.println("I haz been run"); } private static boolean isClassPresent(String className) { try { ClassLoader classLoader = SampleApplication.class.getClassLoader(); Class.forName(className, false, classLoader); return true; } catch (ClassNotFoundException e) { return false; } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-fork/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-fork 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ package run ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-fork/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import java.io.File; public class SampleApplication { public static void main(String[] args) { System.out.println("I haz been run from '" + new File("").getAbsolutePath() + "'"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-jvm-system-props/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-jvmargs 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ -Dfoo="value 1" -Dbar=value2 value1 ${project.artifactId} should-be-ignored ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-jvm-system-props/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { String foo = System.getProperty("foo"); if (!"value 1".equals(foo)) { throw new IllegalStateException("foo system property mismatch (got [" + foo + "]"); } String bar = System.getProperty("bar"); if (!"value2".equals(bar)) { throw new IllegalStateException("bar system property mismatch (got [" + bar + "]"); } String property1 = System.getProperty("property1"); if (!"value1".equals(property1)) { throw new IllegalStateException("property1 system property mismatch (got [" + property1 + "]"); } String property2 = System.getProperty("property2"); if (!"".equals(property2)) { throw new IllegalStateException("property2 system property mismatch (got [" + property2 + "]"); } String property3 = System.getProperty("property3"); if (!"run-jvmargs".equals(property3)) { throw new IllegalStateException("property3 system property mismatch (got [" + property3 + "]"); } System.out.println("I haz been run"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-jvmargs/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-jvmargs 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ -Dfoo="value 1" -Dbar=value2 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-jvmargs/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { String foo = System.getProperty("foo"); if (!"value 1".equals(foo)) { throw new IllegalStateException("foo system property mismatch (got [" + foo + "]"); } String bar = System.getProperty("bar"); if (!"value2".equals(bar)) { throw new IllegalStateException("bar system property mismatch (got [" + bar + "]"); } System.out.println("I haz been run"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-jvmargs-commandline/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-jvmargs-commandline 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-jvmargs-commandline/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { String foo = System.getProperty("foo"); if (!"value-from-cmd".equals(foo)) { throw new IllegalStateException("foo system property mismatch (got [" + foo + "]"); } System.out.println("I haz been run"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-profiles/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-profiles 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ foo bar ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-profiles/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import java.util.Arrays; public class SampleApplication { public static void main(String[] args) { if (args.length < 1) { throw new IllegalArgumentException("Missing active profile argument " + Arrays.toString(args)); } String argument = args[0]; if (!argument.startsWith("--spring.profiles.active=")) { throw new IllegalArgumentException("Invalid argument " + argument); } int index = args[0].indexOf('='); String profile = argument.substring(index + 1); System.out.println("I haz been run with profile(s) '" + profile + "'"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-toolchains/jdkHome/bin/java ================================================ #!/bin/bash echo 'The Maven Toolchains is awesome!' ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-toolchains/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-toolchains 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ org.apache.maven.plugins maven-toolchains-plugin 3.0.0 toolchain 42 test @project.groupId@ @project.artifactId@ @project.version@ package run ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-toolchains/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { throw new IllegalStateException("Should not be called!"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-toolchains/toolchains.xml ================================================ jdk 42 test jdkHome ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-use-test-classpath/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-use-test-classpath 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ true org.springframework spring-context @spring-framework.version@ test ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-use-test-classpath/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { Class appContext = null; try { appContext = Class.forName("org.springframework.context.ApplicationContext"); } catch (ClassNotFoundException e) { throw new IllegalStateException("Test dependencies not added to classpath", e); } System.out.println("I haz been run"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-working-directory/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it run-working-directory 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ ${project.build.sourceDirectory} ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/run-working-directory/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { String workingDirectory = System.getProperty("user.dir"); System.out.println(String.format("I haz been run from %s", workingDirectory)); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/settings.xml ================================================ @localRepositoryPath@ spring-commercial-release ${env.COMMERCIAL_REPO_USERNAME} ${env.COMMERCIAL_REPO_PASSWORD} spring-commercial-snapshot ${env.COMMERCIAL_REPO_USERNAME} ${env.COMMERCIAL_REPO_PASSWORD} it-repo true local.central @localCentralUrl@ true true local.central @localCentralUrl@ true true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/start-stop/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it start-stop-fork 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ org.codehaus.mojo build-helper-maven-plugin @build-helper-maven-plugin.version@ reserve-jmx-port reserve-network-port process-resources jmx.port @project.groupId@ @project.artifactId@ @project.version@ pre-integration-test start post-integration-test stop ${jmx.port} ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/start-stop/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; import java.lang.management.ManagementFactory; import javax.management.MBeanServer; import javax.management.ObjectName; /** * This sample app simulates the JMX Mbean that is exposed by the Spring Boot application. */ public class SampleApplication { private static final Object lock = new Object(); public static void main(String[] args) throws Exception { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); ObjectName name = new ObjectName( "org.springframework.boot:type=Admin,name=SpringApplication"); SpringApplicationAdmin mbean = new SpringApplicationAdmin(); mbs.registerMBean(mbean, name); // Flag the app as ready mbean.ready = true; int waitAttempts = 0; while (!mbean.shutdownInvoked) { if (waitAttempts > 30) { throw new IllegalStateException( "Shutdown should have been invoked by now"); } synchronized (lock) { lock.wait(250); } waitAttempts++; } } public interface SpringApplicationAdminMXBean { boolean isReady(); void shutdown(); } static final class SpringApplicationAdmin implements SpringApplicationAdminMXBean { private boolean ready; private boolean shutdownInvoked; @Override public boolean isReady() { System.out.println("isReady: " + this.ready); return this.ready; } @Override public void shutdown() { this.shutdownInvoked = true; System.out.println("Shutdown requested"); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/start-stop-skip/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it start-stop-skip 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ pre-integration-test start post-integration-test stop true ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/start-stop-skip/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; /** * This sample should not run at all */ public class SampleApplication { public static void main(String[] args) throws Exception { System.out.println("Ooops, I haz been run"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/test-run/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it test-run 0.0.1.BUILD-SNAPSHOT UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ org.springframework spring-core @spring-framework.version@ test ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/test-run/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { System.out.println("I haz been run"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/test-run/src/test/java/org/test/TestSampleApplication.java ================================================ /* * 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.test; import java.io.File; import java.lang.management.ManagementFactory; public class TestSampleApplication { public static void main(String[] args) { System.out.println("Main class name = " + TestSampleApplication.class.getName()); int i = 1; for (String entry : ManagementFactory.getRuntimeMXBean().getClassPath().split(File.pathSeparator)) { System.out.println(i++ + ". " + entry); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it war 0.0.1.BUILD-SNAPSHOT war UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.apache.maven.plugins maven-war-plugin @maven-war-plugin.version@ Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war/src/main/webapp/index.html ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it war-exclude-entry 0.0.1.BUILD-SNAPSHOT war UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.springframework spring-core org.apache.maven.plugins maven-war-plugin @maven-war-plugin.version@ Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/webapp/index.html ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered/jar-release/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-release 0.0.1.RELEASE jar jar Release Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered/jar-snapshot/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT jar jar Snapshot Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ jar-snapshot jar-release war ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered/war/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT war-layered war war @project.groupId@ @project.artifactId@ @project.version@ repackage org.apache.maven.plugins maven-war-plugin @maven-war-plugin.version@ Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT org.springframework.boot.maven.it jar-release 0.0.1.RELEASE ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered/war/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered/war/src/main/webapp/index.html ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-custom/jar-classifier/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-classifier 0.0.1 jar jar Classifier Jar dependency maven-jar-plugin alpha package jar alpha bravo package jar bravo ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-custom/jar-release/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-release 0.0.1.RELEASE jar jar Release Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-custom/jar-snapshot/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT jar jar Snapshot Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-custom/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ jar-classifier jar-release jar-snapshot war ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-custom/war/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT war-layered war war @project.groupId@ @project.artifactId@ @project.version@ repackage true ${project.basedir}/src/layers.xml org.apache.maven.plugins maven-war-plugin @maven-war-plugin.version@ Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT org.springframework.boot.maven.it jar-classifier 0.0.1 bravo org.springframework.boot.maven.it jar-release 0.0.1.RELEASE ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-custom/war/src/layers.xml ================================================ **/application*.* *:*:*-SNAPSHOT my-dependencies-name snapshot-dependencies configuration application ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-custom/war/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-custom/war/src/main/webapp/index.html ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-disabled/jar-release/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-release 0.0.1.RELEASE jar jar Release Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-disabled/jar-snapshot/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT jar jar Snapshot Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-disabled/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ jar-snapshot jar-release war ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-disabled/war/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT war-layered war war @project.groupId@ @project.artifactId@ @project.version@ repackage false org.apache.maven.plugins maven-war-plugin @maven-war-plugin.version@ Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT org.springframework.boot.maven.it jar-release 0.0.1.RELEASE ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-disabled/war/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-layered-disabled/war/src/main/webapp/index.html ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-no-tools/jar-release/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-release 0.0.1.RELEASE jar jar Release Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-no-tools/jar-snapshot/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT jar jar Snapshot Jar dependency ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-no-tools/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ jar-snapshot jar-release war ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-no-tools/war/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it aggregator 0.0.1.BUILD-SNAPSHOT war-no-tools war war @project.groupId@ @project.artifactId@ @project.version@ repackage false org.apache.maven.plugins maven-war-plugin @maven-war-plugin.version@ Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided org.springframework.boot.maven.it jar-snapshot 0.0.1.BUILD-SNAPSHOT org.springframework.boot.maven.it jar-release 0.0.1.RELEASE ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-no-tools/war/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-no-tools/war/src/main/webapp/index.html ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-output-timestamp/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it war-output-timestamp 0.0.1.BUILD-SNAPSHOT war UTF-8 2020-03-16T02:00:00-08:00 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.apache.maven.plugins maven-war-plugin @maven-war-plugin.version@ Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-output-timestamp/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-output-timestamp/src/main/webapp/index.html ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-reactor/jar/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it war-reactor 0.0.1.BUILD-SNAPSHOT jar jar jar Jar dependency jar ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-reactor/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it war-reactor 0.0.1.BUILD-SNAPSHOT pom UTF-8 @java.version@ @java.version@ jar war ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-reactor/war/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it war-reactor 0.0.1.BUILD-SNAPSHOT war war war @project.groupId@ @project.artifactId@ @project.version@ repackage org.apache.maven.plugins maven-war-plugin @maven-war-plugin.version@ Foo org.springframework.boot.maven.it jar 0.0.1.BUILD-SNAPSHOT org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-reactor/war/src/main/java/com/example/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-reactor/war/src/main/webapp/index.html ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-signed/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it war-signed 0.0.1.BUILD-SNAPSHOT war UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.apache.maven.plugins maven-war-plugin @maven-war-plugin.version@ some.random.Main Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided org.bouncycastle bcprov-jdk18on 1.78.1 ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-signed/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-system-scope/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it war-system-scope 0.0.1.BUILD-SNAPSHOT war UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage true org.apache.maven.plugins maven-war-plugin @maven-war-plugin.version@ Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided com.example sample 1.0.0 system ${project.basedir}/sample-1.0.0.jar ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-system-scope/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-system-scope/src/main/webapp/index.html ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-with-unpack/pom.xml ================================================ 4.0.0 org.springframework.boot.maven.it war-with-unpack 0.0.1.BUILD-SNAPSHOT war UTF-8 @java.version@ @java.version@ @project.groupId@ @project.artifactId@ @project.version@ repackage org.springframework spring-core org.apache.maven.plugins maven-war-plugin @maven-war-plugin.version@ Foo org.springframework spring-context @spring-framework.version@ jakarta.servlet jakarta.servlet-api @jakarta-servlet.version@ provided ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-with-unpack/src/main/java/org/test/SampleApplication.java ================================================ /* * 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.test; public class SampleApplication { public static void main(String[] args) { } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-with-unpack/src/main/webapp/index.html ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java ================================================ /* * 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.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.stream.Stream; import javax.tools.Diagnostic; import javax.tools.DiagnosticListener; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter; import org.apache.maven.toolchain.ToolchainManager; import org.jspecify.annotations.Nullable; /** * Abstract base class for AOT processing MOJOs. * * @author Phillip Webb * @author Scott Frederick * @author Omar YAYA * @since 3.0.0 */ public abstract class AbstractAotMojo extends AbstractDependencyFilterMojo { /** * The current Maven session. This is used for toolchain manager API calls. */ @Parameter(defaultValue = "${session}", readonly = true) @SuppressWarnings("NullAway.Init") private MavenSession session; /** * The toolchain manager to use to locate a custom JDK. */ private final ToolchainManager toolchainManager; /** * Skip the execution. */ @Parameter(property = "spring-boot.aot.skip", defaultValue = "false") private boolean skip; /** * List of JVM system properties to pass to the AOT process. */ @Parameter private @Nullable Map systemPropertyVariables; /** * JVM arguments that should be associated with the AOT process. On command line, make * sure to wrap multiple values between quotes. */ @Parameter(property = "spring-boot.aot.jvmArguments") private @Nullable String jvmArguments; /** * Arguments that should be provided to the AOT compile process. On command line, make * sure to wrap multiple values between quotes. */ @Parameter(property = "spring-boot.aot.compilerArguments") private @Nullable String compilerArguments; protected AbstractAotMojo(ToolchainManager toolchainManager) { this.toolchainManager = toolchainManager; } /** * Return Maven execution session. * @return session * @since 3.0.10 */ protected final MavenSession getSession() { return this.session; } @Override public void execute() throws MojoExecutionException, MojoFailureException { if (this.skip) { getLog().debug("Skipping AOT execution as per configuration"); return; } try { executeAot(); } catch (Exception ex) { throw new MojoExecutionException(ex.getMessage(), ex); } } protected abstract void executeAot() throws Exception; protected void generateAotAssets(URL[] classPath, String processorClassName, String... arguments) throws Exception { List command = CommandLineBuilder.forMainClass(processorClassName) .withSystemProperties(this.systemPropertyVariables) .withJvmArguments(new RunArguments(this.jvmArguments).asArray()) .withClasspath(classPath) .withArguments(arguments) .build(); if (getLog().isDebugEnabled()) { getLog().debug("Generating AOT assets using command: " + command); } JavaProcessExecutor processExecutor = new JavaProcessExecutor(this.session, this.toolchainManager); processExecutor.run(this.project.getBasedir(), command, Collections.emptyMap()); } protected final void compileSourceFiles(URL[] classPath, File sourcesDirectory, File outputDirectory) throws Exception { List sourceFiles; try (Stream pathStream = Files.walk(sourcesDirectory.toPath())) { sourceFiles = pathStream.filter(Files::isRegularFile).toList(); } if (sourceFiles.isEmpty()) { return; } JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) { JavaCompilerPluginConfiguration compilerConfiguration = new JavaCompilerPluginConfiguration(this.project); List args = new ArrayList<>(); args.addAll(ClassPath.of(classPath).args(false)); args.add("-d"); args.add(outputDirectory.toPath().toAbsolutePath().toString()); String releaseVersion = compilerConfiguration.getReleaseVersion(); if (releaseVersion != null) { args.add("--release"); args.add(releaseVersion); } else { String source = compilerConfiguration.getSourceMajorVersion(); if (source != null) { args.add("--source"); args.add(source); } String target = compilerConfiguration.getTargetMajorVersion(); if (target != null) { args.add("--target"); args.add(target); } } args.add("-parameters"); args.addAll(new RunArguments(this.compilerArguments).getArgs()); Iterable compilationUnits = fileManager.getJavaFileObjectsFromPaths(sourceFiles); Errors errors = new Errors(); CompilationTask task = compiler.getTask(null, fileManager, errors, args, null, compilationUnits); boolean result = task.call(); if (!result || errors.hasReportedErrors()) { throw new IllegalStateException("Unable to compile generated source" + errors); } } } protected final URL[] getClassPath(File[] directories, ArtifactsFilter... artifactFilters) throws MojoExecutionException { List urls = new ArrayList<>(); Arrays.stream(directories).map(this::toURL).forEach(urls::add); urls.addAll(getDependencyURLs(artifactFilters)); return urls.toArray(URL[]::new); } protected final void copyAll(Path from, Path to) throws IOException { if (!Files.exists(from)) { return; } List files; try (Stream pathStream = Files.walk(from)) { files = pathStream.filter(Files::isRegularFile).toList(); } for (Path file : files) { String relativeFileName = file.subpath(from.getNameCount(), file.getNameCount()).toString(); getLog().debug("Copying '" + relativeFileName + "' to " + to); Path target = to.resolve(relativeFileName); Files.createDirectories(target.getParent()); Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING); } } /** * {@link DiagnosticListener} used to collect errors. */ protected static class Errors implements DiagnosticListener { private final StringBuilder message = new StringBuilder(); @Override public void report(Diagnostic diagnostic) { if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { this.message.append("\n"); this.message.append(diagnostic.getMessage(Locale.getDefault())); if (diagnostic.getSource() != null) { this.message.append(" "); this.message.append(diagnostic.getSource().getName()); this.message.append(" "); this.message.append(diagnostic.getLineNumber()).append(":").append(diagnostic.getColumnNumber()); } } } boolean hasReportedErrors() { return !this.message.isEmpty(); } @Override public String toString() { return this.message.toString(); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractDependencyFilterMojo.java ================================================ /* * 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.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.resolver.filter.ArtifactFilter; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactFeatureFilter; import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException; import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter; import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts; import org.jspecify.annotations.Nullable; /** * A base mojo filtering the dependencies of the project. * * @author Stephane Nicoll * @author David Turanski * @since 1.1.0 */ public abstract class AbstractDependencyFilterMojo extends AbstractMojo { static final ExcludeFilter DEVTOOLS_EXCLUDE_FILTER; static { Exclude exclude = new Exclude(); exclude.setGroupId("org.springframework.boot"); exclude.setArtifactId("spring-boot-devtools"); DEVTOOLS_EXCLUDE_FILTER = new ExcludeFilter(exclude); } static final ExcludeFilter DOCKER_COMPOSE_EXCLUDE_FILTER; static { Exclude exclude = new Exclude(); exclude.setGroupId("org.springframework.boot"); exclude.setArtifactId("spring-boot-docker-compose"); DOCKER_COMPOSE_EXCLUDE_FILTER = new ExcludeFilter(exclude); } /** * The Maven project. * @since 3.0.0 */ @Parameter(defaultValue = "${project}", readonly = true, required = true) @SuppressWarnings("NullAway.Init") protected MavenProject project; /** * Collection of artifact definitions to include. The {@link Include} element defines * mandatory {@code groupId} and {@code artifactId} components and an optional * {@code classifier} component. When configured as a property, values should be * comma-separated with colon-separated components: * {@code groupId:artifactId,groupId:artifactId:classifier} * @since 1.2.0 */ @Parameter(property = "spring-boot.includes") private @Nullable List includes; /** * Collection of artifact definitions to exclude. The {@link Exclude} element defines * mandatory {@code groupId} and {@code artifactId} components and an optional * {@code classifier} component. When configured as a property, values should be * comma-separated with colon-separated components: * {@code groupId:artifactId,groupId:artifactId:classifier} * @since 1.1.0 */ @Parameter(property = "spring-boot.excludes") private @Nullable List excludes; /** * Comma separated list of groupId names to exclude (exact match). * @since 1.1.0 */ @Parameter(property = "spring-boot.excludeGroupIds", defaultValue = "") private @Nullable String excludeGroupIds; protected void setExcludes(@Nullable List excludes) { this.excludes = excludes; } protected void setIncludes(@Nullable List includes) { this.includes = includes; } protected void setExcludeGroupIds(String excludeGroupIds) { this.excludeGroupIds = excludeGroupIds; } protected List getDependencyURLs(ArtifactsFilter... additionalFilters) throws MojoExecutionException { Set artifacts = filterDependencies(this.project.getArtifacts(), additionalFilters); List urls = new ArrayList<>(); for (Artifact artifact : artifacts) { if (artifact.getFile() != null) { urls.add(toURL(artifact.getFile())); } } return urls; } protected final Set filterDependencies(Set dependencies, ArtifactsFilter... additionalFilters) throws MojoExecutionException { try { Set filtered = new LinkedHashSet<>(dependencies); filtered.retainAll(getFilters(additionalFilters).filter(dependencies)); return filtered; } catch (ArtifactFilterException ex) { throw new MojoExecutionException(ex.getMessage(), ex); } } protected URL toURL(File file) { try { return file.toURI().toURL(); } catch (MalformedURLException ex) { throw new IllegalStateException("Invalid URL for " + file, ex); } } /** * Return artifact filters configured for this MOJO. * @param additionalFilters optional additional filters to apply * @return the filters */ private FilterArtifacts getFilters(ArtifactsFilter... additionalFilters) { FilterArtifacts filters = new FilterArtifacts(); for (ArtifactsFilter additionalFilter : additionalFilters) { filters.addFilter(additionalFilter); } filters.addFilter(new MatchingGroupIdFilter(cleanFilterConfig(this.excludeGroupIds))); if (this.includes != null && !this.includes.isEmpty()) { filters.addFilter(new IncludeFilter(this.includes)); } if (this.excludes != null && !this.excludes.isEmpty()) { filters.addFilter(new ExcludeFilter(this.excludes)); } filters.addFilter(new JarTypeFilter()); return filters; } private String cleanFilterConfig(@Nullable String content) { if (content == null || content.trim().isEmpty()) { return ""; } StringBuilder cleaned = new StringBuilder(); StringTokenizer tokenizer = new StringTokenizer(content, ","); while (tokenizer.hasMoreElements()) { cleaned.append(tokenizer.nextToken().trim()); if (tokenizer.hasMoreElements()) { cleaned.append(","); } } return cleaned.toString(); } /** * {@link ArtifactFilter} to exclude test scope dependencies. */ protected static class ExcludeTestScopeArtifactFilter extends AbstractArtifactFeatureFilter { ExcludeTestScopeArtifactFilter() { super("", Artifact.SCOPE_TEST); } @Override protected String getArtifactFeature(Artifact artifact) { return artifact.getScope(); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java ================================================ /* * 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.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.function.Supplier; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.maven.artifact.Artifact; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Dependency; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter; import org.apache.maven.shared.artifact.filter.collection.ScopeFilter; import org.jspecify.annotations.Nullable; import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.springframework.boot.loader.tools.Layout; import org.springframework.boot.loader.tools.LayoutFactory; import org.springframework.boot.loader.tools.Layouts.Expanded; import org.springframework.boot.loader.tools.Layouts.Jar; import org.springframework.boot.loader.tools.Layouts.None; import org.springframework.boot.loader.tools.Layouts.War; import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Packager; import org.springframework.boot.loader.tools.layer.CustomLayers; /** * Abstract base class for classes that work with an {@link Packager}. * * @author Phillip Webb * @author Scott Frederick * @author Moritz Halbritter * @since 2.3.0 */ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo { private static final org.springframework.boot.loader.tools.Layers IMPLICIT_LAYERS = org.springframework.boot.loader.tools.Layers.IMPLICIT; /** * The Maven project. * @since 1.0.0 */ @Parameter(defaultValue = "${project}", readonly = true, required = true) @SuppressWarnings("NullAway.Init") protected MavenProject project; /** * The Maven session. * @since 2.4.0 */ @Parameter(defaultValue = "${session}", readonly = true, required = true) @SuppressWarnings("NullAway.Init") protected MavenSession session; /** * The plugin descriptor. * @since 4.1.0 */ @Parameter(defaultValue = "${plugin}", readonly = true, required = true) @SuppressWarnings("NullAway.Init") private PluginDescriptor pluginDescriptor; /** * Maven project helper utils. * @since 1.0.0 */ protected final MavenProjectHelper projectHelper; /** * The name of the main class. If not specified the first compiled class found that * contains a {@code main} method will be used. * @since 1.0.0 */ @Parameter private @Nullable String mainClass; /** * Exclude Spring Boot devtools from the repackaged archive. * @since 1.3.0 */ @Parameter(property = "spring-boot.repackage.excludeDevtools", defaultValue = "true") private boolean excludeDevtools = true; /** * Exclude Spring Boot dev services from the repackaged archive. * @since 3.1.0 */ @Parameter(property = "spring-boot.repackage.excludeDockerCompose", defaultValue = "true") private boolean excludeDockerCompose = true; /** * Include system scoped dependencies. * @since 1.4.0 */ @Parameter(defaultValue = "false") public boolean includeSystemScope; /** * Include optional dependencies. * @since 3.5.7 */ @Parameter(defaultValue = "false") public boolean includeOptional; /** * Include JAR tools. * @since 3.3.0 */ @Parameter(defaultValue = "true") public boolean includeTools = true; /** * Layer configuration with options to disable layer creation, exclude layer tools * jar, and provide a custom layers configuration file. * @since 2.3.0 */ @Parameter private Layers layers = new Layers(); protected AbstractPackagerMojo(MavenProjectHelper projectHelper) { this.projectHelper = projectHelper; } /** * Return the type of archive that should be packaged by this MOJO. * @return {@code null}, indicating a layout type will be chosen based on the original * archive type */ protected @Nullable LayoutType getLayout() { return null; } /** * Return the layout factory that will be used to determine the {@link LayoutType} if * no explicit layout is set. * @return {@code null}, indicating a default layout factory will be chosen */ protected @Nullable LayoutFactory getLayoutFactory() { return null; } /** * Return a {@link Packager} configured for this MOJO. * @param

the packager type * @param supplier a packager supplier * @return a configured packager */ protected

P getConfiguredPackager(Supplier

supplier) { P packager = supplier.get(); packager.setLayoutFactory(getLayoutFactory()); packager.addMainClassTimeoutWarningListener(new LoggingMainClassTimeoutWarningListener(this::getLog)); packager.setMainClass(this.mainClass); LayoutType layout = getLayout(); if (layout != null) { getLog().info("Layout: " + layout); packager.setLayout(layout.layout()); } if (this.layers.isEnabled()) { packager.setLayers(loadLayersConfiguration()); } packager.setIncludeRelevantJarModeJars(getIncludeRelevantJarModeJars()); return packager; } private boolean getIncludeRelevantJarModeJars() { return this.includeTools; } private org.springframework.boot.loader.tools.Layers loadLayersConfiguration() { File configuration = this.layers.getConfiguration(); if (configuration != null) { return getCustomLayers(configuration.getAbsolutePath(), () -> new FileInputStream(configuration)); } String configurationName = this.layers.getConfigurationName(); if (configurationName != null) { String location = "META-INF/spring/layers/%s.xml".formatted(configurationName); return getCustomLayers(location, () -> loadLayersConfigurationFromClasspath(configurationName, location)); } return IMPLICIT_LAYERS; } private InputStream loadLayersConfigurationFromClasspath(String name, String location) { InputStream in = this.pluginDescriptor.getClassRealm().getResourceAsStream(location); if (in == null) { throw new IllegalStateException( "Failed to load layers configuration with name '%s': '%s' not found".formatted(name, location)); } return in; } private CustomLayers getCustomLayers(String source, InputStreamSource inputStreamSource) { try { Document document = getDocumentIfAvailable(inputStreamSource); return new CustomLayersProvider().getLayers(document); } catch (Exception ex) { throw new IllegalStateException("Failed to process custom layers configuration " + source, ex); } } private Document getDocumentIfAvailable(InputStreamSource source) throws Exception { try (InputStream in = source.getInputStream()) { InputSource inputSource = new InputSource(in); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); return builder.parse(inputSource); } } /** * Return {@link Libraries} that the packager can use. * @param unpacks any libraries that require unpack * @return the libraries to use * @throws MojoExecutionException on execution error */ protected final Libraries getLibraries(@Nullable Collection unpacks) throws MojoExecutionException { Set artifacts = this.project.getArtifacts(); Set includedArtifacts = filterDependencies(artifacts, getAdditionalFilters()); return new ArtifactsLibraries(artifacts, includedArtifacts, this.session.getProjects(), unpacks, getLog()); } private ArtifactsFilter[] getAdditionalFilters() { List filters = new ArrayList<>(); if (this.excludeDevtools) { filters.add(DEVTOOLS_EXCLUDE_FILTER); } if (this.excludeDockerCompose) { filters.add(DOCKER_COMPOSE_EXCLUDE_FILTER); } if (!this.includeSystemScope) { filters.add(new ScopeFilter(null, Artifact.SCOPE_SYSTEM)); } if (!this.includeOptional) { filters.add(DependencyFilter.exclude(Artifact::isOptional)); } return filters.toArray(new ArtifactsFilter[0]); } /** * Return the source {@link Artifact} to repackage. If a classifier is specified and * an artifact with that classifier exists, it is used. Otherwise, the main artifact * is used. * @param classifier the artifact classifier * @return the source artifact to repackage */ protected Artifact getSourceArtifact(@Nullable String classifier) { Artifact sourceArtifact = getArtifact(classifier); return (sourceArtifact != null) ? sourceArtifact : this.project.getArtifact(); } private @Nullable Artifact getArtifact(@Nullable String classifier) { if (classifier != null) { for (Artifact attachedArtifact : this.project.getAttachedArtifacts()) { if (classifier.equals(attachedArtifact.getClassifier()) && attachedArtifact.getFile() != null && attachedArtifact.getFile().isFile()) { return attachedArtifact; } } } return null; } protected File getTargetFile(String finalName, @Nullable String classifier, File targetDirectory) { String classifierSuffix = (classifier != null) ? classifier.trim() : ""; if (!classifierSuffix.isEmpty() && !classifierSuffix.startsWith("-")) { classifierSuffix = "-" + classifierSuffix; } if (!targetDirectory.exists()) { targetDirectory.mkdirs(); } return new File(targetDirectory, finalName + classifierSuffix + "." + this.project.getArtifact().getArtifactHandler().getExtension()); } /** * Archive layout types. */ public enum LayoutType { /** * Jar Layout. */ JAR(new Jar()), /** * War Layout. */ WAR(new War()), /** * Zip Layout. */ ZIP(new Expanded()), /** * Directory Layout. */ DIR(new Expanded()), /** * No Layout. */ NONE(new None()); private final Layout layout; LayoutType(Layout layout) { this.layout = layout; } public Layout layout() { return this.layout; } } @FunctionalInterface private interface InputStreamSource { InputStream getInputStream() throws IOException; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java ================================================ /* * 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.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; import org.apache.maven.artifact.Artifact; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Resource; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.toolchain.ToolchainManager; import org.jspecify.annotations.Nullable; import org.springframework.boot.loader.tools.FileUtils; import org.springframework.util.StringUtils; /** * Base class to run a Spring Boot application. * * @author Phillip Webb * @author Stephane Nicoll * @author David Liu * @author Daniel Young * @author Dmytro Nosan * @author Moritz Halbritter * @since 1.3.0 * @see RunMojo * @see StartMojo */ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * The Maven project. * * @since 1.0.0 */ @Parameter(defaultValue = "${project}", readonly = true, required = true) @SuppressWarnings("NullAway.Init") private MavenProject project; /** * The current Maven session. This is used for toolchain manager API calls. * * @since 2.3.0 */ @Parameter(defaultValue = "${session}", readonly = true) @SuppressWarnings("NullAway.Init") private MavenSession session; /** * The toolchain manager to use to locate a custom JDK. * * @since 2.3.0 */ private final ToolchainManager toolchainManager; /** * Add maven resources to the classpath directly, this allows live in-place editing of * resources. Duplicate resources are removed from {@code target/classes} to prevent * them from appearing twice if {@code ClassLoader.getResources()} is called. Please * consider adding {@code spring-boot-devtools} to your project instead as it provides * this feature and many more. * * @since 1.0.0 */ @Parameter(property = "spring-boot.run.addResources", defaultValue = "false") private boolean addResources; /** * Path to agent jars. * * @since 2.2.0 */ @Parameter(property = "spring-boot.run.agents") @SuppressWarnings("NullAway") // maven-maven-plugin can't handle annotated arrays private File[] agents; /** * Flag to say that the agent requires -noverify. * * @since 1.0.0 */ @Parameter(property = "spring-boot.run.noverify") private boolean noverify; /** * Current working directory to use for the application. If not specified, basedir * will be used. * * @since 1.5.0 */ @Parameter(property = "spring-boot.run.workingDirectory") private @Nullable File workingDirectory; /** * JVM arguments that should be associated with the forked process used to run the * application. On command line, make sure to wrap multiple values between quotes. * * @since 1.1.0 */ @Parameter(property = "spring-boot.run.jvmArguments") private @Nullable String jvmArguments; /** * List of JVM system properties to pass to the process. * * @since 2.1.0 */ @Parameter private @Nullable Map systemPropertyVariables; /** * List of Environment variables that should be associated with the forked process * used to run the application. * * @since 2.1.0 */ @Parameter private @Nullable Map environmentVariables; /** * Arguments that should be passed to the application. * * @since 1.0.0 */ @Parameter @SuppressWarnings("NullAway") // maven-maven-plugin can't handle annotated arrays private String[] arguments; /** * Arguments from the command line that should be passed to the application. Use * spaces to separate multiple arguments and make sure to wrap multiple values between * quotes. When specified, takes precedence over {@link #arguments}. * * @since 2.2.3 */ @Parameter(property = "spring-boot.run.arguments") private @Nullable String commandlineArguments; /** * The spring profiles to activate. Convenience shortcut of specifying the * 'spring.profiles.active' argument. On command line use commas to separate multiple * profiles. * * @since 1.3.0 */ @Parameter(property = "spring-boot.run.profiles") @SuppressWarnings("NullAway") // maven-maven-plugin can't handle annotated arrays private String[] profiles; /** * The name of the main class. If not specified the first compiled class found that * contains a 'main' method will be used. * * @since 1.0.0 */ @Parameter(property = "spring-boot.run.main-class") private @Nullable String mainClass; /** * Additional classpath elements that should be added to the classpath. An element can * be a directory with classes and resources or a jar file. * * @since 3.2.0 */ @Parameter(property = "spring-boot.run.additional-classpath-elements") @SuppressWarnings("NullAway") // maven-maven-plugin can't handle annotated arrays private String[] additionalClasspathElements; /** * Directory containing the classes and resource files that should be used to run the * application. * * @since 1.0.0 */ @Parameter(defaultValue = "${project.build.outputDirectory}", required = true) @SuppressWarnings("NullAway.Init") private File classesDirectory; /** * Skip the execution. * * @since 1.3.2 */ @Parameter(property = "spring-boot.run.skip", defaultValue = "false") private boolean skip; protected AbstractRunMojo(ToolchainManager toolchainManager) { this.toolchainManager = toolchainManager; } @Override public void execute() throws MojoExecutionException, MojoFailureException { if (this.skip) { getLog().debug("skipping run as per configuration."); return; } run(determineMainClass()); } private String determineMainClass() throws MojoExecutionException { if (this.mainClass != null) { return this.mainClass; } return SpringBootApplicationClassFinder.findSingleClass(getClassesDirectories()); } /** * Returns the directories that contain the application's classes and resources. When * the application's main class has not been configured, each directory is searched in * turn for an appropriate main class. * @return the directories that contain the application's classes and resources * @since 3.1.0 */ protected List getClassesDirectories() { return List.of(this.classesDirectory); } protected abstract boolean isUseTestClasspath(); private void run(String startClassName) throws MojoExecutionException, MojoFailureException { List args = new ArrayList<>(); addAgents(args); addJvmArgs(args); addClasspath(args); args.add(startClassName); addArgs(args); JavaProcessExecutor processExecutor = new JavaProcessExecutor(this.session, this.toolchainManager); File workingDirectoryToUse = (this.workingDirectory != null) ? this.workingDirectory : this.project.getBasedir(); if (getLog().isDebugEnabled()) { getLog().debug("Working directory: " + workingDirectoryToUse); getLog().debug("Java arguments: " + String.join(" ", args)); } run(processExecutor, workingDirectoryToUse, args, determineEnvironmentVariables()); } /** * Run the application. * @param processExecutor the {@link JavaProcessExecutor} to use * @param workingDirectory the working directory of the forked JVM * @param args the arguments (JVM arguments and application arguments) * @param environmentVariables the environment variables * @throws MojoExecutionException in case of MOJO execution errors * @throws MojoFailureException in case of MOJO failures * @since 3.0.0 */ protected abstract void run(JavaProcessExecutor processExecutor, File workingDirectory, List args, Map environmentVariables) throws MojoExecutionException, MojoFailureException; /** * Resolve the application arguments to use. * @return a {@link RunArguments} defining the application arguments */ protected RunArguments resolveApplicationArguments() { RunArguments runArguments = (this.arguments != null) ? new RunArguments(this.arguments) : new RunArguments(this.commandlineArguments); addActiveProfileArgument(runArguments); return runArguments; } /** * Resolve the environment variables to use. * @return an {@link EnvVariables} defining the environment variables */ protected EnvVariables resolveEnvVariables() { return new EnvVariables(this.environmentVariables); } private void addArgs(List args) { RunArguments applicationArguments = resolveApplicationArguments(); Collections.addAll(args, applicationArguments.asArray()); logArguments("Application argument", applicationArguments.asArray()); } private Map determineEnvironmentVariables() { EnvVariables envVariables = resolveEnvVariables(); logArguments("Environment variable", envVariables.asArray()); return envVariables.asMap(); } /** * Resolve the JVM arguments to use. * @return a {@link RunArguments} defining the JVM arguments */ protected RunArguments resolveJvmArguments() { List<@Nullable String> arguments = new ArrayList<>(); if (this.systemPropertyVariables != null) { for (Entry systemProperty : this.systemPropertyVariables.entrySet()) { String argument = SystemPropertyFormatter.format(systemProperty.getKey(), systemProperty.getValue()); if (StringUtils.hasText(argument)) { arguments.add(argument); } } } if (this.jvmArguments != null) { String[] jvmArguments = RunArguments.parseArgs(this.jvmArguments); arguments.addAll(Arrays.asList(jvmArguments)); } return new RunArguments(arguments); } private void addJvmArgs(List args) { RunArguments jvmArguments = resolveJvmArguments(); Collections.addAll(args, jvmArguments.asArray()); logArguments("JVM argument", jvmArguments.asArray()); } private void addAgents(List args) { if (this.agents != null) { if (getLog().isInfoEnabled()) { getLog().info("Attaching agents: " + Arrays.asList(this.agents)); } for (File agent : this.agents) { args.add("-javaagent:" + agent); } } if (this.noverify) { args.add("-noverify"); } } private void addActiveProfileArgument(RunArguments arguments) { if (this.profiles != null && this.profiles.length > 0) { StringBuilder arg = new StringBuilder("--spring.profiles.active="); for (int i = 0; i < this.profiles.length; i++) { arg.append(this.profiles[i]); if (i < this.profiles.length - 1) { arg.append(","); } } arguments.getArgs().addFirst(arg.toString()); logArguments("Active profile", this.profiles); } } private void addClasspath(List args) throws MojoExecutionException { try { ClassPath classpath = ClassPath.of(getClassPathUrls()); if (getLog().isDebugEnabled()) { getLog().debug("Classpath for forked process: " + classpath); } args.addAll(classpath.args(true)); } catch (Exception ex) { throw new MojoExecutionException("Could not build classpath", ex); } } protected URL[] getClassPathUrls() throws MojoExecutionException { try { List urls = new ArrayList<>(); addAdditionalClasspathLocations(urls); addResources(urls); addProjectClasses(urls); addDependencies(urls); return urls.toArray(new URL[0]); } catch (IOException ex) { throw new MojoExecutionException("Unable to build classpath", ex); } } private void addAdditionalClasspathLocations(List urls) throws MalformedURLException { if (this.additionalClasspathElements != null) { for (String element : this.additionalClasspathElements) { urls.add(new File(element).toURI().toURL()); } } } private void addResources(List urls) throws IOException { if (this.addResources) { for (Resource resource : this.project.getResources()) { File directory = new File(resource.getDirectory()); urls.add(directory.toURI().toURL()); for (File classesDirectory : getClassesDirectories()) { FileUtils.removeDuplicatesFromOutputDirectory(classesDirectory, directory); } } } } private void addProjectClasses(List urls) throws MalformedURLException { for (File classesDirectory : getClassesDirectories()) { urls.add(classesDirectory.toURI().toURL()); } } private void addDependencies(List urls) throws MalformedURLException, MojoExecutionException { Set artifacts = (isUseTestClasspath()) ? filterDependencies(this.project.getArtifacts()) : filterDependencies(this.project.getArtifacts(), new ExcludeTestScopeArtifactFilter()); for (Artifact artifact : artifacts) { if (artifact.getFile() != null) { urls.add(artifact.getFile().toURI().toURL()); } } } private void logArguments(String name, String[] args) { if (getLog().isDebugEnabled()) { String message = (args.length == 1) ? name + ": " : name + "s: "; getLog().debug(Arrays.stream(args).collect(Collectors.joining(" ", message, ""))); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ArtifactsLibraries.java ================================================ /* * 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.io.File; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Dependency; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.jspecify.annotations.Nullable; import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Library; import org.springframework.boot.loader.tools.LibraryCallback; import org.springframework.boot.loader.tools.LibraryCoordinates; import org.springframework.boot.loader.tools.LibraryScope; /** * {@link Libraries} backed by Maven {@link Artifact}s. * * @author Phillip Webb * @author Andy Wilkinson * @author Stephane Nicoll * @author Scott Frederick * @since 1.0.0 */ public class ArtifactsLibraries implements Libraries { private static final Map SCOPES; static { Map libraryScopes = new HashMap<>(); libraryScopes.put(Artifact.SCOPE_COMPILE, LibraryScope.COMPILE); libraryScopes.put(Artifact.SCOPE_RUNTIME, LibraryScope.RUNTIME); libraryScopes.put(Artifact.SCOPE_PROVIDED, LibraryScope.PROVIDED); libraryScopes.put(Artifact.SCOPE_SYSTEM, LibraryScope.PROVIDED); SCOPES = Collections.unmodifiableMap(libraryScopes); } private final Set artifacts; private final Set includedArtifacts; private final Collection localProjects; private final @Nullable Collection unpacks; private final Log log; /** * Creates a new {@code ArtifactsLibraries} from the given {@code artifacts}. * @param artifacts the artifacts to represent as libraries * @param localProjects projects for which {@link Library#isLocal() local} libraries * should be created * @param unpacks artifacts that should be unpacked on launch * @param log the log * @since 2.4.0 */ public ArtifactsLibraries(Set artifacts, Collection localProjects, @Nullable Collection unpacks, Log log) { this(artifacts, artifacts, localProjects, unpacks, log); } /** * Creates a new {@code ArtifactsLibraries} from the given {@code artifacts}. * @param artifacts all artifacts that can be represented as libraries * @param includedArtifacts the actual artifacts to include in the uber jar * @param localProjects projects for which {@link Library#isLocal() local} libraries * should be created * @param unpacks artifacts that should be unpacked on launch * @param log the log * @since 2.4.8 */ public ArtifactsLibraries(Set artifacts, Set includedArtifacts, Collection localProjects, @Nullable Collection unpacks, Log log) { this.artifacts = artifacts; this.includedArtifacts = includedArtifacts; this.localProjects = localProjects; this.unpacks = unpacks; this.log = log; } @Override public void doWithLibraries(LibraryCallback callback) throws IOException { Set duplicates = getDuplicates(this.artifacts); for (Artifact artifact : this.artifacts) { String name = getFileName(artifact); File file = artifact.getFile(); LibraryScope scope = SCOPES.get(artifact.getScope()); if (scope == null || file == null) { continue; } if (duplicates.contains(name)) { this.log.debug("Duplicate found: " + name); name = artifact.getGroupId() + "-" + name; this.log.debug("Renamed to: " + name); } LibraryCoordinates coordinates = new ArtifactLibraryCoordinates(artifact); boolean unpackRequired = isUnpackRequired(artifact); boolean local = isLocal(artifact); boolean included = this.includedArtifacts.contains(artifact); callback.library(new Library(name, file, scope, coordinates, unpackRequired, local, included)); } } private Set getDuplicates(Set artifacts) { Set duplicates = new HashSet<>(); Set seen = new HashSet<>(); for (Artifact artifact : artifacts) { String fileName = getFileName(artifact); if (artifact.getFile() != null && !seen.add(fileName)) { duplicates.add(fileName); } } return duplicates; } private boolean isUnpackRequired(Artifact artifact) { if (this.unpacks != null) { for (Dependency unpack : this.unpacks) { if (artifact.getGroupId().equals(unpack.getGroupId()) && artifact.getArtifactId().equals(unpack.getArtifactId())) { return true; } } } return false; } private boolean isLocal(Artifact artifact) { for (MavenProject localProject : this.localProjects) { if (localProject.getArtifact().equals(artifact)) { return true; } for (Artifact attachedArtifact : localProject.getAttachedArtifacts()) { if (attachedArtifact.equals(artifact)) { return true; } } } return false; } private String getFileName(Artifact artifact) { StringBuilder sb = new StringBuilder(); sb.append(artifact.getArtifactId()).append("-").append(artifact.getBaseVersion()); String classifier = artifact.getClassifier(); if (classifier != null) { sb.append("-").append(classifier); } sb.append(".").append(artifact.getArtifactHandler().getExtension()); return sb.toString(); } /** * {@link LibraryCoordinates} backed by a Maven {@link Artifact}. */ private static class ArtifactLibraryCoordinates implements LibraryCoordinates { private final Artifact artifact; ArtifactLibraryCoordinates(Artifact artifact) { this.artifact = artifact; } @Override public String getGroupId() { return this.artifact.getGroupId(); } @Override public String getArtifactId() { return this.artifact.getArtifactId(); } @Override public String getVersion() { return this.artifact.getBaseVersion(); } @Override public String toString() { return this.artifact.toString(); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageForkMojo.java ================================================ /* * 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 javax.inject.Inject; import org.apache.maven.plugins.annotations.Execute; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProjectHelper; /** * Package an application into an OCI image using a buildpack, forking the lifecycle to * make sure that {@code package} ran. This goal is suitable for command-line invocation. * If you need to configure a goal {@code execution} in your build, use * {@code build-image-no-fork} instead. * * @author Stephane Nicoll * @since 3.0.0 */ @Mojo(name = "build-image", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME) @Execute(phase = LifecyclePhase.PACKAGE) public class BuildImageForkMojo extends BuildImageMojo { @Inject public BuildImageForkMojo(MavenProjectHelper projectHelper) { super(projectHelper); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java ================================================ /* * 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.io.File; import java.io.IOException; import java.io.OutputStream; import java.time.Duration; import java.util.Collections; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.zip.ZipEntry; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.archivers.tar.TarConstants; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProjectHelper; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.build.AbstractBuildLog; import org.springframework.boot.buildpack.platform.build.BuildLog; import org.springframework.boot.buildpack.platform.build.BuildRequest; import org.springframework.boot.buildpack.platform.build.Builder; import org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration; import org.springframework.boot.buildpack.platform.build.Creator; import org.springframework.boot.buildpack.platform.build.PullPolicy; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.loader.tools.EntryWriter; import org.springframework.boot.loader.tools.ImagePackager; import org.springframework.boot.loader.tools.LayoutFactory; import org.springframework.boot.loader.tools.Libraries; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Package an application into an OCI image using a buildpack. * * @author Phillip Webb * @author Scott Frederick * @author Jeroen Meijer * @since 2.3.0 */ public abstract class BuildImageMojo extends AbstractPackagerMojo { static { System.setProperty("org.slf4j.simpleLogger.log.org.apache.http.wire", "ERROR"); } /** * Directory containing the source archive. * @since 2.3.0 */ @Parameter(defaultValue = "${project.build.directory}", required = true) @SuppressWarnings("NullAway.Init") private File sourceDirectory; /** * Name of the source archive. * @since 2.3.0 */ @Parameter(defaultValue = "${project.build.finalName}", readonly = true, required = true) @SuppressWarnings("NullAway.Init") private String finalName; /** * Skip the execution. * @since 2.3.0 */ @Parameter(property = "spring-boot.build-image.skip", defaultValue = "false") private boolean skip; /** * Classifier used when finding the source archive. * @since 2.3.0 */ @Parameter private @Nullable String classifier; /** * Image configuration, with {@code builder}, {@code runImage}, {@code name}, * {@code env}, {@code cleanCache}, {@code verboseLogging}, {@code pullPolicy}, and * {@code publish} options. * @since 2.3.0 */ @Parameter private @Nullable Image image; /** * Alias for {@link Image#name} to support configuration through command-line * property. * @since 2.3.0 */ @Parameter(property = "spring-boot.build-image.imageName") @Nullable String imageName; /** * Alias for {@link Image#builder} to support configuration through command-line * property. * @since 2.3.0 */ @Parameter(property = "spring-boot.build-image.builder") @Nullable String imageBuilder; /** * Alias for {@link Image#trustBuilder} to support configuration through command-line * property. */ @Parameter(property = "spring-boot.build-image.trustBuilder") @Nullable Boolean trustBuilder; /** * Alias for {@link Image#runImage} to support configuration through command-line * property. * @since 2.3.1 */ @Parameter(property = "spring-boot.build-image.runImage") @Nullable String runImage; /** * Alias for {@link Image#cleanCache} to support configuration through command-line * property. * @since 2.4.0 */ @Parameter(property = "spring-boot.build-image.cleanCache") @Nullable Boolean cleanCache; /** * Alias for {@link Image#pullPolicy} to support configuration through command-line * property. */ @Parameter(property = "spring-boot.build-image.pullPolicy") @Nullable PullPolicy pullPolicy; /** * Alias for {@link Image#publish} to support configuration through command-line * property. */ @Parameter(property = "spring-boot.build-image.publish") @Nullable Boolean publish; /** * Alias for {@link Image#network} to support configuration through command-line * property. * @since 2.6.0 */ @Parameter(property = "spring-boot.build-image.network") @Nullable String network; /** * Alias for {@link Image#createdDate} to support configuration through command-line * property. * @since 3.1.0 */ @Parameter(property = "spring-boot.build-image.createdDate") @Nullable String createdDate; /** * Alias for {@link Image#applicationDirectory} to support configuration through * command-line property. * @since 3.1.0 */ @Parameter(property = "spring-boot.build-image.applicationDirectory") @Nullable String applicationDirectory; /** * Alias for {@link Image#imagePlatform} to support configuration through command-line * property. * @since 3.4.0 */ @Parameter(property = "spring-boot.build-image.imagePlatform") @Nullable String imagePlatform; /** * Docker configuration options. * @since 2.4.0 */ @Parameter private @Nullable Docker docker; /** * The type of archive (which corresponds to how the dependencies are laid out inside * it). Possible values are {@code JAR}, {@code WAR}, {@code ZIP}, {@code DIR}, * {@code NONE}. Defaults to a guess based on the archive type. * @since 2.3.11 */ @Parameter private @Nullable LayoutType layout; /** * 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 * parties. * @since 2.3.11 */ @Parameter private @Nullable LayoutFactory layoutFactory; protected BuildImageMojo(MavenProjectHelper projectHelper) { super(projectHelper); } /** * Return the type of archive that should be used when building the image. * @return the value of the {@code layout} parameter, or {@code null} if the parameter * is not provided */ @Override protected @Nullable LayoutType getLayout() { return this.layout; } /** * Return the layout factory that will be used to determine the * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set. * @return the value of the {@code layoutFactory} parameter, or {@code null} if the * parameter is not provided */ @Override protected @Nullable LayoutFactory getLayoutFactory() { return this.layoutFactory; } @Override public void execute() throws MojoExecutionException { if (this.project.getPackaging().equals("pom")) { getLog().debug("build-image goal could not be applied to pom project."); return; } if (this.skip) { getLog().debug("skipping build-image as per configuration."); return; } buildImage(); } private void buildImage() throws MojoExecutionException { Libraries libraries = getLibraries(Collections.emptySet()); try { BuildRequest request = getBuildRequest(libraries); Docker docker = (this.docker != null) ? this.docker : new Docker(); BuilderDockerConfiguration dockerConfiguration = docker.asDockerConfiguration(getLog(), request.isPublish()); Builder builder = new Builder(new MojoBuildLog(this::getLog), dockerConfiguration); builder.build(request); } catch (IOException ex) { throw new MojoExecutionException(ex.getMessage(), ex); } } private BuildRequest getBuildRequest(Libraries libraries) { ImagePackager imagePackager = new ImagePackager(getArchiveFile(), getBackupFile()); Function content = (owner) -> getApplicationContent(owner, libraries, imagePackager); Image image = (this.image != null) ? this.image : new Image(); if (image.name == null && this.imageName != null) { image.setName(this.imageName); } if (image.builder == null && this.imageBuilder != null) { image.setBuilder(this.imageBuilder); } if (image.trustBuilder == null && this.trustBuilder != null) { image.setTrustBuilder(this.trustBuilder); } if (image.runImage == null && this.runImage != null) { image.setRunImage(this.runImage); } if (image.cleanCache == null && this.cleanCache != null) { image.setCleanCache(this.cleanCache); } if (image.pullPolicy == null && this.pullPolicy != null) { image.setPullPolicy(this.pullPolicy); } if (image.publish == null && this.publish != null) { image.setPublish(this.publish); } if (image.network == null && this.network != null) { image.setNetwork(this.network); } if (image.createdDate == null && this.createdDate != null) { image.setCreatedDate(this.createdDate); } if (image.applicationDirectory == null && this.applicationDirectory != null) { image.setApplicationDirectory(this.applicationDirectory); } if (image.imagePlatform == null && this.imagePlatform != null) { image.setImagePlatform(this.imagePlatform); } return customize(image.getBuildRequest(this.project.getArtifact(), content)); } private TarArchive getApplicationContent(Owner owner, Libraries libraries, ImagePackager imagePackager) { ImagePackager packager = getConfiguredPackager(() -> imagePackager); return new PackagedTarArchive(owner, libraries, packager); } private File getArchiveFile() { // We can't use 'project.getArtifact().getFile()' because package can be done in a // forked lifecycle and will be null File archiveFile = getTargetFile(this.finalName, this.classifier, this.sourceDirectory); if (!archiveFile.exists()) { archiveFile = getSourceArtifact(this.classifier).getFile(); } if (!archiveFile.exists()) { throw new IllegalStateException("A jar or war file is required for building image"); } return archiveFile; } /** * Return the {@link File} to use to back up the original source. * @return the file to use to back up the original source */ private @Nullable File getBackupFile() { // We can't use 'project.getAttachedArtifacts()' because package can be done in a // forked lifecycle and will be null if (this.classifier != null) { File backupFile = getTargetFile(this.finalName, null, this.sourceDirectory); if (backupFile.exists()) { return backupFile; } Artifact source = getSourceArtifact(null); if (!this.classifier.equals(source.getClassifier())) { return source.getFile(); } } return null; } private BuildRequest customize(BuildRequest request) { request = customizeCreator(request); return request; } private BuildRequest customizeCreator(BuildRequest request) { String springBootVersion = VersionExtractor.forClass(BuildImageMojo.class); if (StringUtils.hasText(springBootVersion)) { request = request.withCreator(Creator.withVersion(springBootVersion)); } return request; } /** * {@link BuildLog} backed by Mojo logging. */ private static class MojoBuildLog extends AbstractBuildLog { private static final long THRESHOLD = Duration.ofSeconds(2).toMillis(); private final Supplier log; MojoBuildLog(Supplier log) { this.log = log; } @Override protected void log(String message) { this.log.get().info(message); } @Override protected Consumer getProgressConsumer(String message) { return new ProgressLog(message); } private class ProgressLog implements Consumer { private final String message; private long last; ProgressLog(String message) { this.message = message; this.last = System.currentTimeMillis(); } @Override public void accept(TotalProgressEvent progress) { log(progress.getPercent()); } private void log(int percent) { if (percent == 100 || (System.currentTimeMillis() - this.last) > THRESHOLD) { MojoBuildLog.this.log.get().info(this.message + " " + percent + "%"); this.last = System.currentTimeMillis(); } } } } /** * Adapter class to expose the packaged jar as a {@link TarArchive}. */ static class PackagedTarArchive implements TarArchive { static final long NORMALIZED_MOD_TIME = TarArchive.NORMALIZED_TIME.toEpochMilli(); private final Owner owner; private final Libraries libraries; private final ImagePackager packager; PackagedTarArchive(Owner owner, Libraries libraries, ImagePackager packager) { this.owner = owner; this.libraries = libraries; this.packager = packager; } @Override public void writeTo(OutputStream outputStream) throws IOException { TarArchiveOutputStream tar = new TarArchiveOutputStream(outputStream); tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); try { this.packager.packageImage(this.libraries, (entry, entryWriter) -> write(entry, entryWriter, tar)); } catch (RuntimeException ex) { outputStream.close(); throw new RuntimeException("Error packaging archive for image", ex); } } private void write(ZipEntry jarEntry, @Nullable EntryWriter entryWriter, TarArchiveOutputStream tar) { try { TarArchiveEntry tarEntry = convert(jarEntry); tar.putArchiveEntry(tarEntry); if (tarEntry.isFile()) { Assert.state(entryWriter != null, "'entryWriter' must not be null"); entryWriter.write(tar); } tar.closeArchiveEntry(); } catch (IOException ex) { throw new IllegalStateException(ex); } } private TarArchiveEntry convert(ZipEntry entry) { byte linkFlag = (entry.isDirectory()) ? TarConstants.LF_DIR : TarConstants.LF_NORMAL; TarArchiveEntry tarEntry = new TarArchiveEntry(entry.getName(), linkFlag, true); tarEntry.setUserId(this.owner.getUid()); tarEntry.setGroupId(this.owner.getGid()); tarEntry.setModTime(NORMALIZED_MOD_TIME); if (!entry.isDirectory()) { tarEntry.setSize(entry.getSize()); } return tarEntry; } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageNoForkMojo.java ================================================ /* * 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 javax.inject.Inject; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProjectHelper; /** * Package an application into an OCI image using a buildpack, but without forking the * lifecycle. This goal should be used when configuring a goal {@code execution} in your * build. To invoke the goal on the command-line, use {@code build-image} instead. * * @author Stephane Nicoll * @since 3.0.0 */ @Mojo(name = "build-image-no-fork", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME) public class BuildImageNoForkMojo extends BuildImageMojo { @Inject public BuildImageNoForkMojo(MavenProjectHelper projectHelper) { super(projectHelper); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildInfoMojo.java ================================================ /* * 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.io.File; import java.time.Instant; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.jspecify.annotations.Nullable; import org.sonatype.plexus.build.incremental.BuildContext; import org.springframework.boot.loader.tools.BuildPropertiesWriter; import org.springframework.boot.loader.tools.BuildPropertiesWriter.NullAdditionalPropertyValueException; import org.springframework.boot.loader.tools.BuildPropertiesWriter.ProjectDetails; /** * Generate a {@code build-info.properties} file based on the content of the current * {@link MavenProject}. * * @author Stephane Nicoll * @author Vedran Pavic * @since 1.4.0 */ @Mojo(name = "build-info", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true) public class BuildInfoMojo extends AbstractMojo { private final BuildContext buildContext; /** * The Maven session. */ @Parameter(defaultValue = "${session}", readonly = true, required = true) @SuppressWarnings("NullAway.Init") private MavenSession session; /** * The Maven project. */ @Parameter(defaultValue = "${project}", readonly = true, required = true) @SuppressWarnings("NullAway.Init") private MavenProject project; /** * The location of the generated {@code build-info.properties} file. */ @Parameter(defaultValue = "${project.build.outputDirectory}/META-INF/build-info.properties") @SuppressWarnings("NullAway.Init") private File outputFile; /** * The value used for the {@code build.time} property in a form suitable for * {@link Instant#parse(CharSequence)}. Defaults to * {@code project.build.outputTimestamp} or {@code session.request.startTime} if the * former is not set. To disable the {@code build.time} property entirely, use * {@code 'off'} or add it to {@code excludeInfoProperties}. * @since 2.2.0 */ @Parameter(defaultValue = "${project.build.outputTimestamp}") private @Nullable String time; /** * Additional properties to store in the {@code build-info.properties} file. Each * entry is prefixed by {@code build.} in the generated {@code build-info.properties}. */ @Parameter private @Nullable Map additionalProperties; /** * Properties that should be excluded {@code build-info.properties} file. Can be used * to exclude the standard {@code group}, {@code artifact}, {@code name}, * {@code version} or {@code time} properties as well as items from * {@code additionalProperties}. */ @Parameter private @Nullable List excludeInfoProperties; /** * Skip the execution. * @since 3.1.0 */ @Parameter(property = "spring-boot.build-info.skip", defaultValue = "false") private boolean skip; @Inject public BuildInfoMojo(BuildContext buildContext) { this.buildContext = buildContext; } @Override public void execute() throws MojoExecutionException, MojoFailureException { if (this.skip) { getLog().debug("skipping build-info as per configuration."); return; } try { ProjectDetails details = getProjectDetails(); new BuildPropertiesWriter(this.outputFile).writeBuildProperties(details); this.buildContext.refresh(this.outputFile); } catch (NullAdditionalPropertyValueException ex) { throw new MojoFailureException("Failed to generate build-info.properties. " + ex.getMessage(), ex); } catch (Exception ex) { throw new MojoExecutionException(ex.getMessage(), ex); } } private ProjectDetails getProjectDetails() { String group = getIfNotExcluded("group", this.project.getGroupId()); String artifact = getIfNotExcluded("artifact", this.project.getArtifactId()); String version = getIfNotExcluded("version", this.project.getVersion()); String name = getIfNotExcluded("name", this.project.getName()); Instant time = getIfNotExcluded("time", getBuildTime()); Map additionalProperties = applyExclusions(this.additionalProperties); return new ProjectDetails(group, artifact, version, name, time, additionalProperties); } private @Nullable T getIfNotExcluded(String name, @Nullable T value) { return (this.excludeInfoProperties == null || !this.excludeInfoProperties.contains(name)) ? value : null; } private @Nullable Map applyExclusions(@Nullable Map source) { if (source == null || this.excludeInfoProperties == null) { return source; } Map result = new LinkedHashMap<>(source); this.excludeInfoProperties.forEach(result::remove); return result; } private @Nullable Instant getBuildTime() { if (this.time == null || this.time.isEmpty()) { Date startTime = this.session.getRequest().getStartTime(); return (startTime != null) ? startTime.toInstant() : Instant.now(); } if ("off".equalsIgnoreCase(this.time)) { return null; } return new MavenBuildOutputTimestamp(this.time).toInstant(); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/CacheInfo.java ================================================ /* * 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 org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.build.Cache; import org.springframework.util.Assert; /** * Encapsulates configuration of an image building cache. * * @author Scott Frederick * @since 2.6.0 */ public class CacheInfo { private @Nullable Cache cache; public CacheInfo() { } private CacheInfo(Cache cache) { this.cache = cache; } public void setVolume(VolumeCacheInfo info) { Assert.state(this.cache == null, "Each image building cache can be configured only once"); String name = info.getName(); Assert.state(name != null, "'name' must not be null"); this.cache = Cache.volume(name); } public void setBind(BindCacheInfo info) { Assert.state(this.cache == null, "Each image building cache can be configured only once"); String source = info.getSource(); Assert.state(source != null, "'source' must not be null"); this.cache = Cache.bind(source); } @Nullable Cache asCache() { return this.cache; } static CacheInfo fromVolume(VolumeCacheInfo cacheInfo) { String name = cacheInfo.getName(); Assert.state(name != null, "'name' must not be null"); return new CacheInfo(Cache.volume(name)); } static CacheInfo fromBind(BindCacheInfo cacheInfo) { String source = cacheInfo.getSource(); Assert.state(source != null, "'source' must not be null"); return new CacheInfo(Cache.bind(source)); } /** * Encapsulates configuration of an image building cache stored in a volume. */ public static class VolumeCacheInfo { private @Nullable String name; public VolumeCacheInfo() { } VolumeCacheInfo(String name) { this.name = name; } public @Nullable String getName() { return this.name; } void setName(@Nullable String name) { this.name = name; } } /** * Encapsulates configuration of an image building cache stored in a bind mount. */ public static class BindCacheInfo { private @Nullable String source; public BindCacheInfo() { } BindCacheInfo(String name) { this.source = name; } public @Nullable String getSource() { return this.source; } void setSource(@Nullable String source) { this.source = source; } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ClassPath.java ================================================ /* * 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.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; import org.jspecify.annotations.Nullable; import org.springframework.util.StringUtils; /** * Encapsulates a class path and allows argument parameters to be created. On Windows an * argument file is used whenever possible since the maximum command line length is * limited. * * @author Stephane Nicoll * @author Dmytro Nosan * @author Phillip Webb */ final class ClassPath { private static final Collector JOIN_BY_PATH_SEPARATOR = Collectors .joining(File.pathSeparator); private final boolean preferArgFile; private final String path; private ClassPath(boolean preferArgFile, String path) { this.preferArgFile = preferArgFile; this.path = path; } /** * Return the args to append to a java command line call (including {@code -cp}). * @param allowArgFile if an arg file can be used * @return the command line arguments */ List args(boolean allowArgFile) { return (!this.path.isEmpty()) ? List.of("-cp", classPathArg(allowArgFile)) : Collections.emptyList(); } private String classPathArg(boolean allowArgFile) { if (this.preferArgFile && allowArgFile) { try { return "@" + createArgFile(); } catch (IOException ex) { return this.path; } } return this.path; } @Override public String toString() { return this.path; } private Path createArgFile() throws IOException { Path argFile = Files.createTempFile("spring-boot-", ".argfile"); argFile.toFile().deleteOnExit(); Files.writeString(argFile, "\"" + this.path.replace("\\", "\\\\") + "\"", charset()); return argFile; } private Charset charset() { try { String nativeEncoding = System.getProperty("native.encoding"); return (nativeEncoding != null) ? Charset.forName(nativeEncoding) : Charset.defaultCharset(); } catch (UnsupportedCharsetException ex) { return Charset.defaultCharset(); } } /** * Factory method to create a {@link ClassPath} of the given URLs. * @param urls the class path URLs * @return a new {@link ClassPath} instance */ static ClassPath of(URL... urls) { return of(Arrays.asList(urls)); } /** * Factory method to create a {@link ClassPath} of the given URLs. * @param urls the class path URLs * @return a new {@link ClassPath} instance */ static ClassPath of(List urls) { return of(System::getProperty, urls); } /** * Factory method to create a {@link ClassPath} of the given URLs. * @param getSystemProperty {@link UnaryOperator} allowing access to system properties * @param urls the class path URLs * @return a new {@link ClassPath} instance */ static ClassPath of(UnaryOperator<@Nullable String> getSystemProperty, List urls) { boolean preferArgFile = urls.size() > 1 && isWindows(getSystemProperty); return new ClassPath(preferArgFile, urls.stream().map(ClassPath::toPathString).collect(JOIN_BY_PATH_SEPARATOR)); } private static boolean isWindows(UnaryOperator<@Nullable String> getSystemProperty) { String os = getSystemProperty.apply("os.name"); return StringUtils.hasText(os) && os.toLowerCase(Locale.ROOT).contains("win"); } private static String toPathString(URL url) { try { return Paths.get(url.toURI()).toString(); } catch (URISyntaxException ex) { throw new IllegalArgumentException(ex); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/CommandLineBuilder.java ================================================ /* * 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.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import org.jspecify.annotations.Nullable; import org.springframework.util.StringUtils; /** * Helper class to build the command-line arguments of a java process. * * @author Stephane Nicoll */ final class CommandLineBuilder { private final List options = new ArrayList<>(); private final List classpathElements = new ArrayList<>(); private final String mainClass; private final List arguments = new ArrayList<>(); private CommandLineBuilder(String mainClass) { this.mainClass = mainClass; } static CommandLineBuilder forMainClass(String mainClass) { return new CommandLineBuilder(mainClass); } // Do not use String @Nullable ... jvmArguments, Maven can't deal with that CommandLineBuilder withJvmArguments(@Nullable String... jvmArguments) { if (jvmArguments != null) { this.options.addAll(Arrays.stream(jvmArguments).filter(Objects::nonNull).toList()); } return this; } CommandLineBuilder withSystemProperties(@Nullable Map systemProperties) { if (systemProperties != null) { for (Entry systemProperty : systemProperties.entrySet()) { String option = SystemPropertyFormatter.format(systemProperty.getKey(), systemProperty.getValue()); if (StringUtils.hasText(option)) { this.options.add(option); } } } return this; } CommandLineBuilder withClasspath(URL... elements) { this.classpathElements.addAll(Arrays.asList(elements)); return this; } // Do not use String @Nullable ... arguments, Maven can't deal with that CommandLineBuilder withArguments(@Nullable String... arguments) { if (arguments != null) { this.arguments.addAll(Arrays.stream(arguments).filter(Objects::nonNull).toList()); } return this; } List build() { List commandLine = new ArrayList<>(); if (!this.options.isEmpty()) { commandLine.addAll(this.options); } commandLine.addAll(ClassPath.of(this.classpathElements).args(true)); commandLine.add(this.mainClass); if (!this.arguments.isEmpty()) { commandLine.addAll(this.arguments); } return commandLine; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/CustomLayersProvider.java ================================================ /* * 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.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; import javax.xml.XMLConstants; import javax.xml.transform.dom.DOMSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import org.jspecify.annotations.Nullable; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.springframework.boot.loader.tools.Layer; import org.springframework.boot.loader.tools.Library; import org.springframework.boot.loader.tools.layer.ApplicationContentFilter; import org.springframework.boot.loader.tools.layer.ContentFilter; import org.springframework.boot.loader.tools.layer.ContentSelector; import org.springframework.boot.loader.tools.layer.CustomLayers; import org.springframework.boot.loader.tools.layer.IncludeExcludeContentSelector; import org.springframework.boot.loader.tools.layer.LibraryContentFilter; /** * Produces a {@link CustomLayers} based on the given {@link Document}. * * @author Madhura Bhave * @author Phillip Webb */ class CustomLayersProvider { CustomLayers getLayers(Document document) { validate(document); Element root = document.getDocumentElement(); List> applicationSelectors = getApplicationSelectors(root); List> librarySelectors = getLibrarySelectors(root); List layers = getLayers(root); return new CustomLayers(layers, applicationSelectors, librarySelectors); } private void validate(Document document) { Schema schema = loadSchema(); try { Validator validator = schema.newValidator(); validator.validate(new DOMSource(document)); } catch (SAXException | IOException ex) { throw new IllegalStateException("Invalid layers.xml configuration", ex); } } private Schema loadSchema() { try { SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); return factory.newSchema(getClass().getResource("layers.xsd")); } catch (SAXException ex) { throw new IllegalStateException("Unable to load layers XSD"); } } private List> getApplicationSelectors(Element root) { return getSelectors(root, "application", (element) -> getSelector(element, ApplicationContentFilter::new)); } private List> getLibrarySelectors(Element root) { return getSelectors(root, "dependencies", (element) -> getLibrarySelector(element, LibraryContentFilter::new)); } private List getLayers(Element root) { Element layerOrder = getChildElement(root, "layerOrder"); if (layerOrder == null) { return Collections.emptyList(); } return getChildNodeTextContent(layerOrder, "layer").stream().map(Layer::new).toList(); } private List> getSelectors(Element root, String elementName, Function> selectorFactory) { Element element = getChildElement(root, elementName); if (element == null) { return Collections.emptyList(); } List> selectors = new ArrayList<>(); NodeList children = element.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child instanceof Element childElement) { ContentSelector selector = selectorFactory.apply(childElement); selectors.add(selector); } } return selectors; } private ContentSelector getSelector(Element element, Function> filterFactory) { Layer layer = new Layer(element.getAttribute("layer")); List includes = getChildNodeTextContent(element, "include"); List excludes = getChildNodeTextContent(element, "exclude"); return new IncludeExcludeContentSelector<>(layer, includes, excludes, filterFactory); } private ContentSelector getLibrarySelector(Element element, Function> filterFactory) { Layer layer = new Layer(element.getAttribute("layer")); List includes = getChildNodeTextContent(element, "include"); List excludes = getChildNodeTextContent(element, "exclude"); Element includeModuleDependencies = getChildElement(element, "includeModuleDependencies"); Element excludeModuleDependencies = getChildElement(element, "excludeModuleDependencies"); List> includeFilters = includes.stream() .map(filterFactory) .collect(Collectors.toCollection(ArrayList::new)); if (includeModuleDependencies != null) { includeFilters.add(Library::isLocal); } List> excludeFilters = excludes.stream() .map(filterFactory) .collect(Collectors.toCollection(ArrayList::new)); if (excludeModuleDependencies != null) { excludeFilters.add(Library::isLocal); } return new IncludeExcludeContentSelector<>(layer, includeFilters, excludeFilters); } private List getChildNodeTextContent(Element element, String tagName) { List patterns = new ArrayList<>(); NodeList nodes = element.getElementsByTagName(tagName); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node instanceof Element) { patterns.add(node.getTextContent()); } } return patterns; } private @Nullable Element getChildElement(Element element, String tagName) { NodeList nodes = element.getElementsByTagName(tagName); if (nodes.getLength() == 0) { return null; } if (nodes.getLength() > 1) { throw new IllegalStateException("Multiple '" + tagName + "' nodes found"); } return (Element) nodes.item(0); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/DependencyFilter.java ================================================ /* * 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.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Predicate; import org.apache.maven.artifact.Artifact; import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactsFilter; import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException; import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter; /** * Base class for {@link ArtifactsFilter} based on a {@link FilterableDependency} list. * * @author Stephane Nicoll * @author David Turanski * @since 1.2.0 */ public abstract class DependencyFilter extends AbstractArtifactsFilter { private final List filters; /** * Create a new instance with the list of {@link FilterableDependency} instance(s) to * use. * @param dependencies the source dependencies */ public DependencyFilter(List dependencies) { this.filters = dependencies; } @Override public Set filter(Set artifacts) throws ArtifactFilterException { Set result = new HashSet<>(); for (Artifact artifact : artifacts) { if (!filter(artifact)) { result.add(artifact); } } return result; } protected abstract boolean filter(Artifact artifact); /** * Check if the specified {@link org.apache.maven.artifact.Artifact} matches the * specified {@link org.springframework.boot.maven.FilterableDependency}. Returns * {@code true} if it should be excluded * @param artifact the Maven {@link Artifact} * @param dependency the {@link FilterableDependency} * @return {@code true} if the artifact matches the dependency */ protected final boolean equals(Artifact artifact, FilterableDependency dependency) { if (!dependency.getGroupId().equals(artifact.getGroupId())) { return false; } if (!dependency.getArtifactId().equals(artifact.getArtifactId())) { return false; } return (dependency.getClassifier() == null || artifact.getClassifier() != null && dependency.getClassifier().equals(artifact.getClassifier())); } protected final List getFilters() { 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 filter) { return new DependencyFilter(Collections.emptyList()) { @Override protected boolean filter(Artifact artifact) { return filter.test(artifact); } }; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java ================================================ /* * 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 org.apache.maven.plugin.logging.Log; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication; import org.springframework.util.Assert; /** * Docker configuration options. * * @author Wei Jiang * @author Scott Frederick * @since 2.4.0 */ public class Docker { private @Nullable String host; private @Nullable String context; private boolean tlsVerify; private @Nullable String certPath; private boolean bindHostToBuilder; private @Nullable DockerRegistry builderRegistry; private @Nullable DockerRegistry publishRegistry; /** * The host address of the Docker daemon. * @return the Docker host */ public @Nullable String getHost() { return this.host; } void setHost(@Nullable String host) { this.host = host; } /** * The Docker context to use to retrieve host configuration. * @return the Docker context */ public @Nullable String getContext() { return this.context; } public void setContext(@Nullable String context) { this.context = context; } /** * Whether the Docker daemon requires TLS communication. * @return {@code true} to enable TLS */ public boolean isTlsVerify() { return this.tlsVerify; } void setTlsVerify(boolean tlsVerify) { this.tlsVerify = tlsVerify; } /** * The path to TLS certificate and key files required for TLS communication with the * Docker daemon. * @return the TLS certificate path */ public @Nullable String getCertPath() { return this.certPath; } void setCertPath(@Nullable String certPath) { this.certPath = certPath; } /** * Whether to use the configured Docker host in the builder container. * @return {@code true} to use the configured Docker host in the builder container */ public boolean isBindHostToBuilder() { return this.bindHostToBuilder; } void setBindHostToBuilder(boolean bindHostToBuilder) { this.bindHostToBuilder = bindHostToBuilder; } /** * Configuration of the Docker registry where builder and run images are stored. * @return the registry configuration */ @Nullable DockerRegistry getBuilderRegistry() { return this.builderRegistry; } /** * Sets the {@link DockerRegistry} that configures authentication to the builder * registry. * @param builderRegistry the registry configuration */ void setBuilderRegistry(@Nullable DockerRegistry builderRegistry) { this.builderRegistry = builderRegistry; } /** * Configuration of the Docker registry where the generated image will be published. * @return the registry configuration */ @Nullable DockerRegistry getPublishRegistry() { return this.publishRegistry; } /** * Sets the {@link DockerRegistry} that configures authentication to the publishing * registry. * @param builderRegistry the registry configuration */ void setPublishRegistry(@Nullable DockerRegistry builderRegistry) { this.publishRegistry = builderRegistry; } /** * Returns this configuration as a {@link BuilderDockerConfiguration} instance. This * method should only be called when the configuration is complete and will no longer * be changed. * @param log the output log * @param publish whether the image should be published * @return the Docker configuration */ BuilderDockerConfiguration asDockerConfiguration(Log log, boolean publish) { BuilderDockerConfiguration dockerConfiguration = new BuilderDockerConfiguration(); dockerConfiguration = customizeHost(dockerConfiguration); dockerConfiguration = dockerConfiguration.withBindHostToBuilder(this.bindHostToBuilder); dockerConfiguration = customizeBuilderAuthentication(log, dockerConfiguration); dockerConfiguration = customizePublishAuthentication(log, dockerConfiguration, publish); return dockerConfiguration; } private BuilderDockerConfiguration customizeHost(BuilderDockerConfiguration dockerConfiguration) { if (this.context != null && this.host != null) { throw new IllegalArgumentException( "Invalid Docker configuration, either context or host can be provided but not both"); } if (this.context != null) { return dockerConfiguration.withContext(this.context); } if (this.host != null) { return dockerConfiguration.withHost(this.host, this.tlsVerify, this.certPath); } return dockerConfiguration; } private BuilderDockerConfiguration customizeBuilderAuthentication(Log log, BuilderDockerConfiguration dockerConfiguration) { DockerRegistryAuthentication authentication = DockerRegistryAuthentication.configuration(null, (message, ex) -> log.warn(message)); return dockerConfiguration.withBuilderRegistryAuthentication( getRegistryAuthentication("builder", this.builderRegistry, authentication)); } private BuilderDockerConfiguration customizePublishAuthentication(Log log, BuilderDockerConfiguration dockerConfiguration, boolean publish) { if (!publish) { return dockerConfiguration; } DockerRegistryAuthentication authentication = DockerRegistryAuthentication .configuration(DockerRegistryAuthentication.EMPTY_USER, (message, ex) -> log.warn(message)); return dockerConfiguration.withPublishRegistryAuthentication( getRegistryAuthentication("publish", this.publishRegistry, authentication)); } private DockerRegistryAuthentication getRegistryAuthentication(String type, @Nullable DockerRegistry registry, DockerRegistryAuthentication fallback) { if (registry == null || registry.isEmpty()) { return fallback; } if (registry.hasTokenAuth() && !registry.hasUserAuth()) { String token = registry.getToken(); Assert.state(token != null, "'token' must not be null"); return DockerRegistryAuthentication.token(token); } if (registry.hasUserAuth() && !registry.hasTokenAuth()) { String username = registry.getUsername(); String password = registry.getPassword(); Assert.state(username != null, "'username' must not be null"); Assert.state(password != null, "'password' must not be null"); return DockerRegistryAuthentication.user(username, password, registry.getUrl(), registry.getEmail()); } throw new IllegalArgumentException("Invalid Docker " + type + " registry configuration, either token or username/password must be provided"); } /** * Encapsulates Docker registry authentication configuration options. */ public static class DockerRegistry { private @Nullable String username; private @Nullable String password; private @Nullable String url; private @Nullable String email; private @Nullable String token; public DockerRegistry() { } public DockerRegistry(@Nullable String username, @Nullable String password, @Nullable String url, @Nullable String email) { this.username = username; this.password = password; this.url = url; this.email = email; } public DockerRegistry(String token) { this.token = token; } /** * The username that will be used for user authentication to the registry. * @return the username */ public @Nullable String getUsername() { return this.username; } void setUsername(@Nullable String username) { this.username = username; } /** * The password that will be used for user authentication to the registry. * @return the password */ public @Nullable String getPassword() { return this.password; } void setPassword(@Nullable String password) { this.password = password; } /** * The email address that will be used for user authentication to the registry. * @return the email address */ public @Nullable String getEmail() { return this.email; } void setEmail(@Nullable String email) { this.email = email; } /** * The URL of the registry. * @return the registry URL */ @Nullable String getUrl() { return this.url; } void setUrl(@Nullable String url) { this.url = url; } /** * The token that will be used for token authentication to the registry. * @return the authentication token */ public @Nullable String getToken() { return this.token; } void setToken(@Nullable String token) { this.token = token; } boolean isEmpty() { return this.username == null && this.password == null && this.url == null && this.email == null && this.token == null; } boolean hasTokenAuth() { return this.token != null; } boolean hasUserAuth() { return this.username != null && this.password != null; } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/EnvVariables.java ================================================ /* * 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.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.jspecify.annotations.Nullable; /** * Utility class for working with Env variables. * * @author Dmytro Nosan */ class EnvVariables { private final Map variables; EnvVariables(@Nullable Map variables) { this.variables = parseEnvVariables(variables); } private static Map parseEnvVariables(@Nullable Map args) { if (args == null || args.isEmpty()) { return Collections.emptyMap(); } Map result = new LinkedHashMap<>(); for (Map.Entry e : args.entrySet()) { if (e.getKey() != null) { result.put(e.getKey(), getValue(e.getValue())); } } return result; } private static String getValue(@Nullable String value) { return (value != null) ? value : ""; } Map asMap() { return Collections.unmodifiableMap(this.variables); } String[] asArray() { List args = new ArrayList<>(this.variables.size()); for (Map.Entry arg : this.variables.entrySet()) { args.add(arg.getKey() + "=" + arg.getValue()); } return args.toArray(new String[0]); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Exclude.java ================================================ /* * 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; /** * A model for a dependency to exclude. * * @author Stephane Nicoll * @since 1.1.0 */ public class Exclude extends FilterableDependency { } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ExcludeFilter.java ================================================ /* * 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.Arrays; import java.util.List; import org.apache.maven.artifact.Artifact; /** * An {DependencyFilter} that filters out any artifact matching an {@link Exclude}. * * @author Stephane Nicoll * @author David Turanski * @since 1.1.0 */ public class ExcludeFilter extends DependencyFilter { public ExcludeFilter(Exclude... excludes) { this(Arrays.asList(excludes)); } public ExcludeFilter(List excludes) { super(excludes); } @Override protected boolean filter(Artifact artifact) { for (FilterableDependency dependency : getFilters()) { if (equals(artifact, dependency)) { return true; } } return false; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/FilterableDependency.java ================================================ /* * 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 org.apache.maven.plugins.annotations.Parameter; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** * A model for a dependency to include or exclude. * * @author Stephane Nicoll * @author David Turanski * @since 3.1.11 */ public abstract class FilterableDependency { /** * The groupId of the artifact to exclude. */ @Parameter(required = true) @SuppressWarnings("NullAway.Init") private String groupId; /** * The artifactId of the artifact to exclude. */ @Parameter(required = true) @SuppressWarnings("NullAway.Init") private String artifactId; /** * The classifier of the artifact to exclude. */ @Parameter private @Nullable String classifier; String getGroupId() { return this.groupId; } void setGroupId(String groupId) { this.groupId = groupId; } String getArtifactId() { return this.artifactId; } void setArtifactId(String artifactId) { this.artifactId = artifactId; } @Nullable String getClassifier() { return this.classifier; } void setClassifier(@Nullable String classifier) { this.classifier = classifier; } /** * Configures the include or exclude using a user-provided property in the form * {@code groupId:artifactId} or {@code groupId:artifactId:classifier}. * @param property the user-provided property */ public void set(String property) { String[] parts = property.split(":"); Assert.isTrue(parts.length == 2 || parts.length == 3, getClass().getSimpleName() + " 'property' must be in the form groupId:artifactId or groupId:artifactId:classifier"); setGroupId(parts[0]); setArtifactId(parts[1]); if (parts.length == 3) { setClassifier(parts[2]); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java ================================================ /* * 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.List; import java.util.Map; import java.util.function.Function; import org.apache.maven.artifact.Artifact; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.build.BuildRequest; import org.springframework.boot.buildpack.platform.build.BuildpackReference; import org.springframework.boot.buildpack.platform.build.Cache; import org.springframework.boot.buildpack.platform.build.PullPolicy; 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.ImageReference; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** * Image configuration options. * * @author Phillip Webb * @author Scott Frederick * @author Jeroen Meijer * @author Rafael Ceccone * @author Julian Liebig * @since 2.3.0 */ public class Image { @Nullable String name; @Nullable String builder; @Nullable Boolean trustBuilder; @Nullable String runImage; @Nullable Map env; @Nullable Boolean cleanCache; boolean verboseLogging; @Nullable PullPolicy pullPolicy; @Nullable Boolean publish; @Nullable List buildpacks; @Nullable List bindings; @Nullable String network; @Nullable List tags; @Nullable CacheInfo buildWorkspace; @Nullable CacheInfo buildCache; @Nullable CacheInfo launchCache; @Nullable String createdDate; @Nullable String applicationDirectory; @Nullable List securityOptions; @Nullable String imagePlatform; /** * The name of the created image. * @return the image name */ public @Nullable String getName() { return this.name; } void setName(@Nullable String name) { this.name = name; } /** * The name of the builder image to use to create the image. * @return the builder image name */ public @Nullable String getBuilder() { return this.builder; } void setBuilder(@Nullable String builder) { this.builder = builder; } /** * If the builder should be treated as trusted. * @return {@code true} if the builder should be treated as trusted */ public @Nullable Boolean getTrustBuilder() { return this.trustBuilder; } void setTrustBuilder(@Nullable Boolean trustBuilder) { this.trustBuilder = trustBuilder; } /** * The name of the run image to use to create the image. * @return the builder image name */ public @Nullable String getRunImage() { return this.runImage; } void setRunImage(@Nullable String runImage) { this.runImage = runImage; } /** * Environment properties that should be passed to the builder. * @return the environment properties */ public @Nullable Map getEnv() { return this.env; } /** * If the cache should be cleaned before building. * @return {@code true} if the cache should be cleaned */ public @Nullable Boolean getCleanCache() { return this.cleanCache; } void setCleanCache(@Nullable Boolean cleanCache) { this.cleanCache = cleanCache; } /** * If verbose logging is required. * @return {@code true} for verbose logging */ public boolean isVerboseLogging() { return this.verboseLogging; } /** * If images should be pulled from a remote repository during image build. * @return the pull policy */ public @Nullable PullPolicy getPullPolicy() { return this.pullPolicy; } void setPullPolicy(@Nullable PullPolicy pullPolicy) { this.pullPolicy = pullPolicy; } /** * If the built image should be pushed to a registry. * @return {@code true} if the image should be published */ public @Nullable Boolean getPublish() { return this.publish; } void setPublish(@Nullable Boolean publish) { this.publish = publish; } /** * Returns the network the build container will connect to. * @return the network */ public @Nullable String getNetwork() { return this.network; } public void setNetwork(@Nullable String network) { this.network = network; } /** * Returns the created date for the image. * @return the created date */ public @Nullable String getCreatedDate() { return this.createdDate; } public void setCreatedDate(@Nullable String createdDate) { this.createdDate = createdDate; } /** * Returns the application content directory for the image. * @return the application directory */ public @Nullable String getApplicationDirectory() { return this.applicationDirectory; } public void setApplicationDirectory(@Nullable String applicationDirectory) { this.applicationDirectory = applicationDirectory; } /** * Returns the platform (os/architecture/variant) that will be used for all pulled * images. When {@code null}, the system will choose a platform based on the host * operating system and architecture. * @return the image platform */ public @Nullable String getImagePlatform() { return this.imagePlatform; } public void setImagePlatform(@Nullable String imagePlatform) { this.imagePlatform = imagePlatform; } BuildRequest getBuildRequest(Artifact artifact, Function applicationContent) { return customize(BuildRequest.of(getOrDeduceName(artifact), applicationContent)); } private ImageReference getOrDeduceName(Artifact artifact) { if (StringUtils.hasText(this.name)) { return ImageReference.of(this.name); } ImageName imageName = ImageName.of(artifact.getArtifactId()); return ImageReference.of(imageName, artifact.getVersion()); } private BuildRequest customize(BuildRequest request) { if (StringUtils.hasText(this.builder)) { request = request.withBuilder(ImageReference.of(this.builder)); } if (this.trustBuilder != null) { request = request.withTrustBuilder(this.trustBuilder); } if (StringUtils.hasText(this.runImage)) { request = request.withRunImage(ImageReference.of(this.runImage)); } if (!CollectionUtils.isEmpty(this.env)) { request = request.withEnv(this.env); } if (this.cleanCache != null) { request = request.withCleanCache(this.cleanCache); } request = request.withVerboseLogging(this.verboseLogging); if (this.pullPolicy != null) { request = request.withPullPolicy(this.pullPolicy); } if (this.publish != null) { request = request.withPublish(this.publish); } if (!CollectionUtils.isEmpty(this.buildpacks)) { request = request.withBuildpacks(this.buildpacks.stream().map(BuildpackReference::of).toList()); } if (!CollectionUtils.isEmpty(this.bindings)) { request = request.withBindings(this.bindings.stream().map(Binding::of).toList()); } request = request.withNetwork(this.network); if (!CollectionUtils.isEmpty(this.tags)) { request = request.withTags(this.tags.stream().map(ImageReference::of).toList()); } if (this.buildWorkspace != null) { Cache cache = this.buildWorkspace.asCache(); Assert.state(cache != null, "'cache' must not be null"); request = request.withBuildWorkspace(cache); } if (this.buildCache != null) { Cache cache = this.buildCache.asCache(); Assert.state(cache != null, "'cache' must not be null"); request = request.withBuildCache(cache); } if (this.launchCache != null) { Cache cache = this.launchCache.asCache(); Assert.state(cache != null, "'cache' must not be null"); request = request.withLaunchCache(cache); } if (StringUtils.hasText(this.createdDate)) { request = request.withCreatedDate(this.createdDate); } if (StringUtils.hasText(this.applicationDirectory)) { request = request.withApplicationDirectory(this.applicationDirectory); } if (this.securityOptions != null) { request = request.withSecurityOptions(this.securityOptions); } if (StringUtils.hasText(this.imagePlatform)) { request = request.withImagePlatform(this.imagePlatform); } return request; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Include.java ================================================ /* * 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; /** * A model for a dependency to include. * * @author David Turanski * @since 1.2.0 */ public class Include extends FilterableDependency { } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/IncludeFilter.java ================================================ /* * 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.List; import org.apache.maven.artifact.Artifact; import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter; /** * An {@link ArtifactsFilter} that filters out any artifact not matching an * {@link Include}. * * @author David Turanski * @since 1.2.0 */ public class IncludeFilter extends DependencyFilter { public IncludeFilter(List includes) { super(includes); } @Override protected boolean filter(Artifact artifact) { for (FilterableDependency dependency : getFilters()) { if (equals(artifact, dependency)) { return false; } } return true; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/JarTypeFilter.java ================================================ /* * 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.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.apache.maven.artifact.Artifact; /** * A {@link DependencyFilter} that filters dependencies based on the jar type declared in * their manifest. * * @author Andy Wilkinson */ class JarTypeFilter extends DependencyFilter { private static final Set EXCLUDED_JAR_TYPES = Collections.unmodifiableSet( new HashSet<>(Arrays.asList("annotation-processor", "dependencies-starter", "development-tool"))); JarTypeFilter() { super(Collections.emptyList()); } @Override protected boolean filter(Artifact artifact) { try (JarFile jarFile = new JarFile(artifact.getFile())) { Manifest manifest = jarFile.getManifest(); if (manifest != null) { String jarType = manifest.getMainAttributes().getValue("Spring-Boot-Jar-Type"); if (jarType != null && EXCLUDED_JAR_TYPES.contains(jarType)) { return true; } } } catch (IOException ex) { // Continue } return false; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/JavaCompilerPluginConfiguration.java ================================================ /* * 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.Arrays; import org.apache.maven.model.Plugin; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.jspecify.annotations.Nullable; import org.springframework.lang.Contract; /** * Provides access to the Maven Java Compiler plugin configuration. * * @author Scott Frederick */ class JavaCompilerPluginConfiguration { private final MavenProject project; JavaCompilerPluginConfiguration(MavenProject project) { this.project = project; } @Nullable String getSourceMajorVersion() { String version = getConfigurationValue("source"); if (version == null) { version = getPropertyValue("maven.compiler.source"); } return majorVersionFor(version); } @Nullable String getTargetMajorVersion() { String version = getConfigurationValue("target"); if (version == null) { version = getPropertyValue("maven.compiler.target"); } return majorVersionFor(version); } @Nullable String getReleaseVersion() { String version = getConfigurationValue("release"); if (version == null) { version = getPropertyValue("maven.compiler.release"); } return majorVersionFor(version); } private @Nullable String getConfigurationValue(String propertyName) { Plugin plugin = this.project.getPlugin("org.apache.maven.plugins:maven-compiler-plugin"); if (plugin != null) { Object pluginConfiguration = plugin.getConfiguration(); if (pluginConfiguration instanceof Xpp3Dom dom) { return getNodeValue(dom, propertyName); } } return null; } private @Nullable String getPropertyValue(String propertyName) { if (this.project.getProperties().containsKey(propertyName)) { return this.project.getProperties().get(propertyName).toString(); } return null; } private @Nullable String getNodeValue(Xpp3Dom dom, String... childNames) { Xpp3Dom childNode = dom.getChild(childNames[0]); if (childNode == null) { return null; } if (childNames.length > 1) { return getNodeValue(childNode, Arrays.copyOfRange(childNames, 1, childNames.length)); } return childNode.getValue(); } @Contract("!null -> !null") private @Nullable String majorVersionFor(@Nullable String version) { if (version != null && version.startsWith("1.")) { return version.substring("1.".length()); } return version; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/JavaProcessExecutor.java ================================================ /* * 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.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.function.Consumer; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.toolchain.Toolchain; import org.apache.maven.toolchain.ToolchainManager; import org.jspecify.annotations.Nullable; import org.springframework.boot.loader.tools.JavaExecutable; import org.springframework.boot.loader.tools.RunProcess; /** * Ease the execution of a Java process using Maven's toolchain support. * * @author Stephane Nicoll */ class JavaProcessExecutor { private static final int EXIT_CODE_SIGINT = 130; private final MavenSession mavenSession; private final ToolchainManager toolchainManager; private final @Nullable Consumer runProcessCustomizer; JavaProcessExecutor(MavenSession mavenSession, ToolchainManager toolchainManager) { this(mavenSession, toolchainManager, null); } private JavaProcessExecutor(MavenSession mavenSession, ToolchainManager toolchainManager, @Nullable Consumer runProcessCustomizer) { this.mavenSession = mavenSession; this.toolchainManager = toolchainManager; this.runProcessCustomizer = runProcessCustomizer; } JavaProcessExecutor withRunProcessCustomizer(Consumer customizer) { Consumer combinedCustomizer = (this.runProcessCustomizer != null) ? this.runProcessCustomizer.andThen(customizer) : customizer; return new JavaProcessExecutor(this.mavenSession, this.toolchainManager, combinedCustomizer); } int run(File workingDirectory, List args, Map environmentVariables) throws MojoExecutionException { RunProcess runProcess = new RunProcess(workingDirectory, getJavaExecutable()); if (this.runProcessCustomizer != null) { this.runProcessCustomizer.accept(runProcess); } try { int exitCode = runProcess.run(true, args, environmentVariables); if (!hasTerminatedSuccessfully(exitCode)) { throw new MojoExecutionException("Process terminated with exit code: " + exitCode); } return exitCode; } catch (IOException ex) { throw new MojoExecutionException("Process execution failed", ex); } } RunProcess runAsync(File workingDirectory, List args, Map environmentVariables) throws MojoExecutionException { try { RunProcess runProcess = new RunProcess(workingDirectory, getJavaExecutable()); runProcess.run(false, args, environmentVariables); return runProcess; } catch (IOException ex) { throw new MojoExecutionException("Process execution failed", ex); } } private boolean hasTerminatedSuccessfully(int exitCode) { return (exitCode == 0 || exitCode == EXIT_CODE_SIGINT); } private String getJavaExecutable() { Toolchain toolchain = this.toolchainManager.getToolchainFromBuildContext("jdk", this.mavenSession); String javaExecutable = (toolchain != null) ? toolchain.findTool("java") : null; return (javaExecutable != null) ? javaExecutable : new JavaExecutable().toString(); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Layers.java ================================================ /* * 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.io.File; import org.jspecify.annotations.Nullable; /** * Layer configuration options. * * @author Madhura Bhave * @since 2.3.0 */ public class Layers { private boolean enabled = true; private @Nullable File configuration; private @Nullable String configurationName; /** * Whether a {@code layers.idx} file should be added to the jar. * @return true if a {@code layers.idx} file should be added. */ public boolean isEnabled() { return this.enabled; } /** * The location of the layers configuration file. If no file is provided, a default * configuration is used with four layers: {@code application}, {@code resources}, * {@code snapshot-dependencies} and {@code dependencies}. * @return the layers configuration file */ public @Nullable File getConfiguration() { return this.configuration; } public void setConfiguration(@Nullable File configuration) { this.configuration = configuration; } /** * The name of the layers configuration to load from the classpath. A matching file is * expected at {@code META-INF/spring/layers/.xml} * @return the layers configuration name to load from the classpath */ public @Nullable String getConfigurationName() { return this.configurationName; } public void setConfigurationName(@Nullable String configurationName) { this.configurationName = configurationName; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/LoggingMainClassTimeoutWarningListener.java ================================================ /* * 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.function.Supplier; import org.apache.maven.plugin.logging.Log; import org.jspecify.annotations.Nullable; import org.springframework.boot.loader.tools.Packager.MainClassTimeoutWarningListener; /** * {@link MainClassTimeoutWarningListener} backed by a supplied Maven {@link Log}. * * @author Phillip Webb */ class LoggingMainClassTimeoutWarningListener implements MainClassTimeoutWarningListener { private final Supplier log; LoggingMainClassTimeoutWarningListener(Supplier log) { this.log = log; } @Override public void handleTimeoutWarning(long duration, @Nullable String mainMethod) { this.log.get() .warn("Searching for the main-class is taking some time, " + "consider using the mainClass configuration parameter"); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/MatchingGroupIdFilter.java ================================================ /* * 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 org.apache.maven.artifact.Artifact; import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactFeatureFilter; /** * An {@link org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter * ArtifactsFilter} that filters by matching groupId. * * Preferred over the * {@link org.apache.maven.shared.artifact.filter.collection.GroupIdFilter} due to that * classes use of {@link String#startsWith} to match on prefix. * * @author Mark Ingram * @since 1.1.0 */ public class MatchingGroupIdFilter extends AbstractArtifactFeatureFilter { /** * Create a new instance with the CSV groupId values that should be excluded. * @param exclude the group values to exclude */ public MatchingGroupIdFilter(String exclude) { super("", exclude); } @Override protected String getArtifactFeature(Artifact artifact) { return artifact.getGroupId(); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/MavenBuildOutputTimestamp.java ================================================ /* * 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.nio.file.attribute.FileTime; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeParseException; import org.jspecify.annotations.Nullable; import org.springframework.util.StringUtils; /** * Parse output timestamp configured for Reproducible Builds' archive entries. *

* Either as {@link java.time.format.DateTimeFormatter#ISO_OFFSET_DATE_TIME} or as a * number representing seconds since the epoch (like SOURCE_DATE_EPOCH). * Implementation inspired by MavenArchiver. * * @author Niels Basjes * @author Moritz Halbritter */ class MavenBuildOutputTimestamp { private static final Instant DATE_MIN = Instant.parse("1980-01-01T00:00:02Z"); private static final Instant DATE_MAX = Instant.parse("2099-12-31T23:59:59Z"); private final @Nullable String timestamp; /** * Creates a new {@link MavenBuildOutputTimestamp}. * @param timestamp timestamp or {@code null} */ MavenBuildOutputTimestamp(@Nullable String timestamp) { this.timestamp = timestamp; } /** * Returns the parsed timestamp as an {@code FileTime}. * @return the parsed timestamp as an {@code FileTime}, or {@code null} * @throws IllegalArgumentException if the outputTimestamp is neither ISO 8601 nor an * integer, or it's not within the valid range 1980-01-01T00:00:02Z to * 2099-12-31T23:59:59Z */ @Nullable FileTime toFileTime() { Instant instant = toInstant(); if (instant == null) { return null; } return FileTime.from(instant); } /** * Returns the parsed timestamp as an {@code Instant}. * @return the parsed timestamp as an {@code Instant}, or {@code null} * @throws IllegalArgumentException if the outputTimestamp is neither ISO 8601 nor an * integer, or it's not within the valid range 1980-01-01T00:00:02Z to * 2099-12-31T23:59:59Z */ @Nullable Instant toInstant() { if (!StringUtils.hasLength(this.timestamp)) { return null; } if (isNumeric(this.timestamp)) { return Instant.ofEpochSecond(Long.parseLong(this.timestamp)); } if (this.timestamp.length() < 2) { return null; } try { Instant instant = OffsetDateTime.parse(this.timestamp).withOffsetSameInstant(ZoneOffset.UTC).toInstant(); if (instant.isBefore(DATE_MIN) || instant.isAfter(DATE_MAX)) { throw new IllegalArgumentException( String.format("'%s' is not within the valid range %s to %s", instant, DATE_MIN, DATE_MAX)); } return instant; } catch (DateTimeParseException pe) { throw new IllegalArgumentException(String.format("Can't parse '%s' to instant", this.timestamp)); } } private static boolean isNumeric(String str) { for (char c : str.toCharArray()) { if (!Character.isDigit(c)) { return false; } } return true; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java ================================================ /* * 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.io.File; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.toolchain.ToolchainManager; import org.jspecify.annotations.Nullable; import org.springframework.util.ObjectUtils; /** * Invoke the AOT engine on the application. * * @author Stephane Nicoll * @author Andy Wilkinson * @since 3.0.0 */ @Mojo(name = "process-aot", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME) public class ProcessAotMojo extends AbstractAotMojo { private static final String AOT_PROCESSOR_CLASS_NAME = "org.springframework.boot.SpringApplicationAotProcessor"; /** * Directory containing the classes and resource files that should be packaged into * the archive. */ @Parameter(defaultValue = "${project.build.outputDirectory}", required = true) @SuppressWarnings("NullAway.Init") private File classesDirectory; /** * Directory containing the generated sources. */ @Parameter(defaultValue = "${project.build.directory}/spring-aot/main/sources", required = true) @SuppressWarnings("NullAway.Init") private File generatedSources; /** * Directory containing the generated resources. */ @Parameter(defaultValue = "${project.build.directory}/spring-aot/main/resources", required = true) @SuppressWarnings("NullAway.Init") private File generatedResources; /** * Directory containing the generated classes. */ @Parameter(defaultValue = "${project.build.directory}/spring-aot/main/classes", required = true) @SuppressWarnings("NullAway.Init") private File generatedClasses; /** * Name of the main class to use as the source for the AOT process. If not specified * the first compiled class found that contains a 'main' method will be used. */ @Parameter(property = "spring-boot.aot.main-class") private @Nullable String mainClass; /** * Application arguments that should be taken into account for AOT processing. */ @Parameter @SuppressWarnings("NullAway") // maven-maven-plugin can't handle annotated arrays private String[] arguments; /** * Spring profiles to take into account for AOT processing. */ @Parameter @SuppressWarnings("NullAway") // maven-maven-plugin can't handle annotated arrays private String[] profiles; @Inject public ProcessAotMojo(ToolchainManager toolchainManager) { super(toolchainManager); } @Override protected void executeAot() throws Exception { if (this.project.getPackaging().equals("pom")) { getLog().debug("process-aot goal could not be applied to pom project."); return; } String applicationClass = (this.mainClass != null) ? this.mainClass : SpringBootApplicationClassFinder.findSingleClass(this.classesDirectory); URL[] classPath = getClassPath(); generateAotAssets(classPath, AOT_PROCESSOR_CLASS_NAME, getAotArguments(applicationClass)); compileSourceFiles(classPath, this.generatedSources, this.generatedClasses); copyAll(this.generatedResources.toPath(), this.classesDirectory.toPath()); copyAll(this.generatedClasses.toPath(), this.classesDirectory.toPath()); } private String[] getAotArguments(String applicationClass) { List aotArguments = new ArrayList<>(); aotArguments.add(applicationClass); aotArguments.add(this.generatedSources.toString()); aotArguments.add(this.generatedResources.toString()); aotArguments.add(this.generatedClasses.toString()); aotArguments.add(this.project.getGroupId()); aotArguments.add(this.project.getArtifactId()); aotArguments.addAll(resolveArguments().getArgs()); return aotArguments.toArray(String[]::new); } private URL[] getClassPath() throws Exception { File[] directories = new File[] { this.classesDirectory, this.generatedClasses }; return getClassPath(directories, new ExcludeTestScopeArtifactFilter(), DEVTOOLS_EXCLUDE_FILTER); } private RunArguments resolveArguments() { RunArguments runArguments = new RunArguments(this.arguments); if (!ObjectUtils.isEmpty(this.profiles)) { runArguments.getArgs().addFirst("--spring.profiles.active=" + String.join(",", this.profiles)); } return runArguments; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessTestAotMojo.java ================================================ /* * 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.io.File; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; import org.apache.maven.RepositoryUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.toolchain.ToolchainManager; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.collection.CollectRequest; import org.eclipse.aether.resolution.ArtifactResult; import org.eclipse.aether.resolution.DependencyRequest; import org.eclipse.aether.resolution.DependencyResult; import org.eclipse.aether.util.artifact.JavaScopes; import org.eclipse.aether.util.filter.DependencyFilterUtils; /** * Invoke the AOT engine on tests. * * @author Phillip Webb * @since 3.0.0 */ @Mojo(name = "process-test-aot", defaultPhase = LifecyclePhase.PROCESS_TEST_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST, requiresDependencyCollection = ResolutionScope.TEST) public class ProcessTestAotMojo extends AbstractAotMojo { private static final String JUNIT_PLATFORM_GROUP_ID = "org.junit.platform"; private static final String JUNIT_PLATFORM_COMMONS_ARTIFACT_ID = "junit-platform-commons"; private static final String JUNIT_PLATFORM_LAUNCHER_ARTIFACT_ID = "junit-platform-launcher"; private static final String AOT_PROCESSOR_CLASS_NAME = "org.springframework.boot.test.context.SpringBootTestAotProcessor"; /** * Directory containing the classes and resource files that should be packaged into * the archive. */ @Parameter(defaultValue = "${project.build.testOutputDirectory}", required = true) @SuppressWarnings("NullAway.Init") private File testClassesDirectory; /** * Directory containing the classes and resource files that should be used to run the * tests. */ @Parameter(defaultValue = "${project.build.outputDirectory}", required = true) @SuppressWarnings("NullAway.Init") private File classesDirectory; /** * Directory containing the generated sources. */ @Parameter(defaultValue = "${project.build.directory}/spring-aot/test/sources", required = true) @SuppressWarnings("NullAway.Init") private File generatedSources; /** * Directory containing the generated test resources. */ @Parameter(defaultValue = "${project.build.directory}/spring-aot/test/resources", required = true) @SuppressWarnings("NullAway.Init") private File generatedResources; /** * Directory containing the generated test classes. */ @Parameter(defaultValue = "${project.build.directory}/spring-aot/test/classes", required = true) @SuppressWarnings("NullAway.Init") private File generatedTestClasses; /** * Directory containing the generated test classes. */ @Parameter(defaultValue = "${project.build.directory}/spring-aot/main/classes", required = true) @SuppressWarnings("NullAway.Init") private File generatedClasses; private final RepositorySystem repositorySystem; @Inject public ProcessTestAotMojo(ToolchainManager toolchainManager, RepositorySystem repositorySystem) { super(toolchainManager); this.repositorySystem = repositorySystem; } @Override protected void executeAot() throws Exception { if (this.project.getPackaging().equals("pom")) { getLog().debug("process-test-aot goal could not be applied to pom project."); return; } if (Boolean.getBoolean("maven.test.skip")) { getLog().info("Skipping AOT test processing since tests are skipped"); return; } Path testOutputDirectory = Paths.get(this.project.getBuild().getTestOutputDirectory()); if (Files.notExists(testOutputDirectory)) { getLog().info("Skipping AOT test processing since no tests have been detected"); return; } generateAotAssets(getClassPath(true), AOT_PROCESSOR_CLASS_NAME, getAotArguments()); compileSourceFiles(getClassPath(false), this.generatedSources, this.generatedTestClasses); copyAll(this.generatedResources.toPath().resolve("META-INF/native-image"), this.testClassesDirectory.toPath().resolve("META-INF/native-image")); copyAll(this.generatedTestClasses.toPath(), this.testClassesDirectory.toPath()); } private String[] getAotArguments() { List aotArguments = new ArrayList<>(); aotArguments.add(this.testClassesDirectory.toPath().toAbsolutePath().normalize().toString()); aotArguments.add(this.generatedSources.toString()); aotArguments.add(this.generatedResources.toString()); aotArguments.add(this.generatedTestClasses.toString()); aotArguments.add(this.project.getGroupId()); aotArguments.add(this.project.getArtifactId()); return aotArguments.toArray(String[]::new); } protected URL[] getClassPath(boolean includeJUnitPlatformLauncher) throws Exception { File[] directories = new File[] { this.testClassesDirectory, this.generatedTestClasses, this.classesDirectory, this.generatedClasses }; URL[] classPath = getClassPath(directories, DEVTOOLS_EXCLUDE_FILTER); if (!includeJUnitPlatformLauncher || this.project.getArtifactMap() .containsKey(JUNIT_PLATFORM_GROUP_ID + ":" + JUNIT_PLATFORM_LAUNCHER_ARTIFACT_ID)) { return classPath; } return addJUnitPlatformLauncher(classPath); } private URL[] addJUnitPlatformLauncher(URL[] classPath) throws Exception { String version = getJUnitPlatformVersion(); DefaultArtifactHandler handler = new DefaultArtifactHandler("jar"); handler.setIncludesDependencies(true); Set artifacts = resolveArtifact(new DefaultArtifact(JUNIT_PLATFORM_GROUP_ID, JUNIT_PLATFORM_LAUNCHER_ARTIFACT_ID, version, null, "jar", null, handler)); Set fullClassPath = new LinkedHashSet<>(Arrays.asList(classPath)); for (Artifact artifact : artifacts) { fullClassPath.add(artifact.getFile().toURI().toURL()); } return fullClassPath.toArray(URL[]::new); } private String getJUnitPlatformVersion() throws MojoExecutionException { String id = JUNIT_PLATFORM_GROUP_ID + ":" + JUNIT_PLATFORM_COMMONS_ARTIFACT_ID; Artifact platformCommonsArtifact = this.project.getArtifactMap().get(id); String version = (platformCommonsArtifact != null) ? platformCommonsArtifact.getBaseVersion() : null; if (version == null) { throw new MojoExecutionException( "Unable to find '%s' dependency. Please ensure JUnit is correctly configured.".formatted(id)); } return version; } private Set resolveArtifact(Artifact artifact) throws Exception { CollectRequest collectRequest = new CollectRequest(); collectRequest.setRoot(RepositoryUtils.toDependency(artifact, null)); collectRequest.setRepositories(this.project.getRemotePluginRepositories()); DependencyRequest request = new DependencyRequest(); request.setCollectRequest(collectRequest); request.setFilter(DependencyFilterUtils.classpathFilter(JavaScopes.RUNTIME)); DependencyResult dependencyResult = this.repositorySystem .resolveDependencies(getSession().getRepositorySession(), request); return dependencyResult.getArtifactResults() .stream() .map(ArtifactResult::getArtifact) .map(RepositoryUtils::toArtifact) .collect(Collectors.toSet()); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/PropertiesMergingResourceTransformer.java ================================================ /* * 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.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Properties; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import org.apache.maven.plugins.shade.relocation.Relocator; import org.apache.maven.plugins.shade.resource.ReproducibleResourceTransformer; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** * Extension for the Maven * shade plugin to allow properties files (e.g. {@literal META-INF/spring.factories}) * to be merged without losing any information. * * @author Dave Syer * @author Andy Wilkinson * @since 1.0.0 */ public class PropertiesMergingResourceTransformer implements ReproducibleResourceTransformer { // Set this in pom configuration with ... private @Nullable String resource; private final Properties data = new Properties(); private long time; /** * Return the data the properties being merged. * @return the data */ public Properties getData() { return this.data; } @Override public boolean canTransformResource(String resource) { return this.resource != null && this.resource.equalsIgnoreCase(resource); } @Override @Deprecated(since = "2.4.0", forRemoval = false) public void processResource(String resource, InputStream inputStream, List relocators) throws IOException { processResource(resource, inputStream, relocators, 0); } @Override public void processResource(String resource, InputStream inputStream, List relocators, long time) throws IOException { Properties properties = new Properties(); properties.load(inputStream); properties.forEach((name, value) -> process((String) name, (String) value)); if (time > this.time) { this.time = time; } } private void process(String name, String value) { String existing = this.data.getProperty(name); this.data.setProperty(name, (existing != null) ? existing + "," + value : value); } @Override public boolean hasTransformedResource() { return !this.data.isEmpty(); } @Override public void modifyOutputStream(JarOutputStream os) throws IOException { Assert.state(this.resource != null, "'resource' must not be null"); JarEntry jarEntry = new JarEntry(this.resource); jarEntry.setTime(this.time); os.putNextEntry(jarEntry); this.data.store(os, "Merged by PropertiesMergingResourceTransformer"); os.flush(); this.data.clear(); } public @Nullable String getResource() { return this.resource; } public void setResource(@Nullable String resource) { this.resource = resource; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java ================================================ /* * 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.io.File; import java.io.IOException; import java.nio.file.attribute.FileTime; import java.util.List; import java.util.regex.Pattern; import javax.inject.Inject; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Dependency; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProjectHelper; import org.jspecify.annotations.Nullable; import org.springframework.boot.loader.tools.LayoutFactory; import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Repackager; import org.springframework.lang.Contract; /** * Repackage existing JAR and WAR archives so that they can be executed from the command * line using {@literal java -jar}. With layout=NONE can also be used simply * to package a JAR with nested dependencies (and no main class, so not executable). * * @author Phillip Webb * @author Dave Syer * @author Stephane Nicoll * @author Björn Lindström * @author Scott Frederick * @since 1.0.0 */ @Mojo(name = "repackage", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME) public class RepackageMojo extends AbstractPackagerMojo { private static final Pattern WHITE_SPACE_PATTERN = Pattern.compile("\\s+"); /** * Directory containing the generated archive. * @since 1.0.0 */ @Parameter(defaultValue = "${project.build.directory}", required = true) @SuppressWarnings("NullAway.Init") private File outputDirectory; /** * Name of the generated archive. * @since 1.0.0 */ @Parameter(defaultValue = "${project.build.finalName}", readonly = true, required = true) @SuppressWarnings("NullAway.Init") private String finalName; /** * Skip the execution. * @since 1.2.0 */ @Parameter(property = "spring-boot.repackage.skip", defaultValue = "false") private boolean skip; /** * Classifier to add to the repackaged archive. If not given, the main artifact will * be replaced by the repackaged archive. If given, the classifier will also be used * to determine the source archive to repackage: if an artifact with that classifier * already exists, it will be used as source and replaced. If no such artifact exists, * the main artifact will be used as source and the repackaged archive will be * attached as a supplemental artifact with that classifier. Attaching the artifact * allows to deploy it alongside to the original one, see the Maven documentation for more details. * @since 1.0.0 */ @Parameter private @Nullable String classifier; /** * Attach the repackaged archive to be installed into your local Maven repository or * deployed to a remote repository. If no classifier has been configured, it will * replace the normal jar. If a {@code classifier} has been configured such that the * normal jar and the repackaged jar are different, it will be attached alongside the * normal jar. When the property is set to {@code false}, the repackaged archive will * not be installed or deployed. * @since 1.4.0 */ @Parameter(defaultValue = "true") private boolean attach = true; /** * A list of the libraries that must be unpacked from uber jars in order to run. * Specify each library as a {@code } with a {@code } and a * {@code } and they will be unpacked at runtime. * @since 1.1.0 */ @Parameter private @Nullable List requiresUnpack; /** * Timestamp for reproducible output archive entries, either formatted as ISO 8601 * (yyyy-MM-dd'T'HH:mm:ssXXX) or an {@code int} representing seconds * since the epoch. * @since 2.3.0 */ @Parameter(defaultValue = "${project.build.outputTimestamp}") private @Nullable String outputTimestamp; /** * The type of archive (which corresponds to how the dependencies are laid out inside * it). Possible values are {@code JAR}, {@code WAR}, {@code ZIP}, {@code DIR}, * {@code NONE}. Defaults to a guess based on the archive type. * @since 1.0.0 */ @Parameter(property = "spring-boot.repackage.layout") private @Nullable LayoutType layout; /** * 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 * parties. * @since 1.5.0 */ @Parameter private @Nullable LayoutFactory layoutFactory; @Inject public RepackageMojo(MavenProjectHelper projectHelper) { super(projectHelper); } /** * Return the type of archive that should be packaged by this MOJO. * @return the value of the {@code layout} parameter, or {@code null} if the parameter * is not provided */ @Override protected @Nullable LayoutType getLayout() { return this.layout; } /** * Return the layout factory that will be used to determine the * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set. * @return the value of the {@code layoutFactory} parameter, or {@code null} if the * parameter is not provided */ @Override protected @Nullable LayoutFactory getLayoutFactory() { return this.layoutFactory; } @Override public void execute() throws MojoExecutionException, MojoFailureException { if (this.project.getPackaging().equals("pom")) { getLog().debug("repackage goal could not be applied to pom project."); return; } if (this.skip) { getLog().debug("skipping repackaging as per configuration."); return; } repackage(); } private void repackage() throws MojoExecutionException { Artifact source = getSourceArtifact(this.classifier); File target = getTargetFile(this.finalName, this.classifier, this.outputDirectory); if (source.getFile() == null) { throw new MojoExecutionException( "Source file is not available, make sure 'package' runs as part of the same lifecycle"); } Repackager repackager = getRepackager(source.getFile()); Libraries libraries = getLibraries(this.requiresUnpack); try { repackager.repackage(target, libraries, parseOutputTimestamp()); } catch (IOException ex) { throw new MojoExecutionException(ex.getMessage(), ex); } updateArtifact(source, target, repackager.getBackupFile()); } private @Nullable FileTime parseOutputTimestamp() throws MojoExecutionException { try { return new MavenBuildOutputTimestamp(this.outputTimestamp).toFileTime(); } catch (IllegalArgumentException ex) { throw new MojoExecutionException("Invalid value for parameter 'outputTimestamp'", ex); } } private Repackager getRepackager(File source) { return getConfiguredPackager(() -> new Repackager(source)); } @Contract("!null -> !null") private @Nullable String removeLineBreaks(@Nullable String description) { return (description != null) ? WHITE_SPACE_PATTERN.matcher(description).replaceAll(" ") : null; } private void updateArtifact(Artifact source, File target, File original) { if (this.attach) { attachArtifact(source, target, original); } else if (source.getFile().equals(target) && original.exists()) { String artifactId = (this.classifier != null) ? "artifact with classifier " + this.classifier : "main artifact"; getLog().info(String.format("Updating %s %s to %s", artifactId, source.getFile(), original)); source.setFile(original); } else if (this.classifier != null) { getLog().info("Creating repackaged archive " + target + " with classifier " + this.classifier); } } private void attachArtifact(Artifact source, File target, File original) { if (this.classifier != null && !source.getFile().equals(target)) { getLog().info("Attaching repackaged archive " + target + " with classifier " + this.classifier); this.projectHelper.attachArtifact(this.project, this.project.getPackaging(), this.classifier, target); } else { String artifactId = (this.classifier != null) ? "artifact with classifier " + this.classifier : "main artifact"; getLog() .info(String.format("Replacing %s %s with repackaged archive, adding nested dependencies in BOOT-INF/.", artifactId, source.getFile())); getLog().info("The original artifact has been renamed to " + original); source.setFile(target); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunArguments.java ================================================ /* * 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.Arrays; import java.util.Deque; import java.util.LinkedList; import org.codehaus.plexus.util.cli.CommandLineUtils; import org.jspecify.annotations.Nullable; /** * Parse and expose arguments specified in a single string. * * @author Stephane Nicoll */ class RunArguments { private static final String[] NO_ARGS = {}; private final Deque args = new LinkedList<>(); RunArguments(@Nullable String arguments) { this(parseArgs(arguments)); } @SuppressWarnings("NullAway") // Maven can't handle nullable arrays RunArguments(@Nullable String[] args) { this((args != null) ? Arrays.asList(args) : null); } RunArguments(@Nullable Iterable<@Nullable String> args) { if (args != null) { for (String arg : args) { if (arg != null) { this.args.add(arg); } } } } Deque getArgs() { return this.args; } String[] asArray() { return this.args.toArray(new String[0]); } static String[] parseArgs(@Nullable String arguments) { if (arguments == null || arguments.trim().isEmpty()) { return NO_ARGS; } try { arguments = arguments.replace('\n', ' ').replace('\t', ' '); return CommandLineUtils.translateCommandline(arguments); } catch (Exception ex) { throw new IllegalArgumentException("Failed to parse arguments [" + arguments + "]", ex); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java ================================================ /* * 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.io.File; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Execute; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.toolchain.ToolchainManager; import org.springframework.boot.loader.tools.RunProcess; /** * Run an application in place. * * @author Phillip Webb * @author Dmytro Nosan * @author Stephane Nicoll * @author Andy Wilkinson * @since 1.0.0 */ @Mojo(name = "run", requiresProject = true, defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyResolution = ResolutionScope.TEST) @Execute(phase = LifecyclePhase.TEST_COMPILE) public class RunMojo extends AbstractRunMojo { /** * Whether the JVM's launch should be optimized. * @since 2.2.0 */ @Parameter(property = "spring-boot.run.optimizedLaunch", defaultValue = "true") private boolean optimizedLaunch; /** * Flag to include the test classpath when running. * @since 1.3.0 */ @Parameter(property = "spring-boot.run.useTestClasspath", defaultValue = "false") private boolean useTestClasspath; @Inject public RunMojo(ToolchainManager toolchainManager) { super(toolchainManager); } @Override protected RunArguments resolveJvmArguments() { RunArguments jvmArguments = super.resolveJvmArguments(); if (this.optimizedLaunch) { jvmArguments.getArgs().addFirst("-XX:TieredStopAtLevel=1"); } return jvmArguments; } @Override protected void run(JavaProcessExecutor processExecutor, File workingDirectory, List args, Map environmentVariables) throws MojoExecutionException, MojoFailureException { processExecutor .withRunProcessCustomizer( (runProcess) -> Runtime.getRuntime().addShutdownHook(new Thread(new RunProcessKiller(runProcess)))) .run(workingDirectory, args, environmentVariables); } @Override protected boolean isUseTestClasspath() { return this.useTestClasspath; } private static final class RunProcessKiller implements Runnable { private final RunProcess runProcess; private RunProcessKiller(RunProcess runProcess) { this.runProcess = runProcess; } @Override public void run() { this.runProcess.kill(); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/SpringApplicationAdminClient.java ================================================ /* * 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.io.IOException; import javax.management.AttributeNotFoundException; import javax.management.InstanceNotFoundException; import javax.management.MBeanException; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import org.apache.maven.plugin.MojoExecutionException; /** * A JMX client for the {@code SpringApplicationAdmin} MBean. Permits to obtain * information about a given Spring application. * * @author Stephane Nicoll */ class SpringApplicationAdminClient { // Note: see SpringApplicationAdminJmxAutoConfiguration static final String DEFAULT_OBJECT_NAME = "org.springframework.boot:type=Admin,name=SpringApplication"; private final MBeanServerConnection connection; private final ObjectName objectName; SpringApplicationAdminClient(MBeanServerConnection connection, String jmxName) { this.connection = connection; this.objectName = toObjectName(jmxName); } /** * Check if the spring application managed by this instance is ready. Returns * {@code false} if the mbean is not yet deployed so this method should be repeatedly * called until a timeout is reached. * @return {@code true} if the application is ready to service requests * @throws MojoExecutionException if the JMX service could not be contacted */ boolean isReady() throws MojoExecutionException { try { return (Boolean) this.connection.getAttribute(this.objectName, "Ready"); } catch (InstanceNotFoundException ex) { return false; // Instance not available yet } catch (AttributeNotFoundException ex) { throw new IllegalStateException("Unexpected: attribute 'Ready' not available", ex); } catch (ReflectionException ex) { throw new MojoExecutionException("Failed to retrieve Ready attribute", ex.getCause()); } catch (MBeanException | IOException ex) { throw new MojoExecutionException(ex.getMessage(), ex); } } /** * Stop the application managed by this instance. * @throws MojoExecutionException if the JMX service could not be contacted * @throws IOException if an I/O error occurs * @throws InstanceNotFoundException if the lifecycle mbean cannot be found */ void stop() throws MojoExecutionException, IOException, InstanceNotFoundException { try { this.connection.invoke(this.objectName, "shutdown", null, null); } catch (ReflectionException ex) { throw new MojoExecutionException("Shutdown failed", ex.getCause()); } catch (MBeanException ex) { throw new MojoExecutionException("Could not invoke shutdown operation", ex); } } private ObjectName toObjectName(String name) { try { return new ObjectName(name); } catch (MalformedObjectNameException ex) { throw new IllegalArgumentException("Invalid jmx name '" + name + "'"); } } /** * Create a connector for an {@link javax.management.MBeanServer} exposed on the * current machine and the current port. Security should be disabled. * @param port the port on which the mbean server is exposed * @return a connection * @throws IOException if the connection to that server failed */ static JMXConnector connect(int port) throws IOException { String url = "service:jmx:rmi:///jndi/rmi://127.0.0.1:" + port + "/jmxrmi"; JMXServiceURL serviceUrl = new JMXServiceURL(url); return JMXConnectorFactory.connect(serviceUrl, null); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/SpringBootApplicationClassFinder.java ================================================ /* * 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.io.File; import java.io.IOException; import java.util.List; import org.apache.maven.plugin.MojoExecutionException; import org.springframework.boot.loader.tools.MainClassFinder; /** * Find a single Spring Boot Application class match based on directory. * * @author Stephane Nicoll * @see MainClassFinder */ abstract class SpringBootApplicationClassFinder { private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication"; static String findSingleClass(File classesDirectory) throws MojoExecutionException { return findSingleClass(List.of(classesDirectory)); } static String findSingleClass(List classesDirectories) throws MojoExecutionException { try { for (File classesDirectory : classesDirectories) { String mainClass = MainClassFinder.findSingleMainClass(classesDirectory, SPRING_BOOT_APPLICATION_CLASS_NAME); if (mainClass != null) { return mainClass; } } throw new MojoExecutionException("Unable to find a suitable main class, please add a 'mainClass' property"); } catch (IOException ex) { throw new MojoExecutionException(ex.getMessage(), ex); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/StartMojo.java ================================================ /* * 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.io.File; import java.io.IOException; import java.net.ConnectException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import javax.inject.Inject; import javax.management.MBeanServerConnection; import javax.management.ReflectionException; import javax.management.remote.JMXConnector; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.toolchain.ToolchainManager; import org.jspecify.annotations.Nullable; import org.springframework.boot.loader.tools.RunProcess; /** * Start a spring application. Contrary to the {@code run} goal, this does not block and * allows other goals to operate on the application. This goal is typically used in * integration test scenario where the application is started before a test suite and * stopped after. * * @author Stephane Nicoll * @since 1.3.0 * @see StopMojo */ @Mojo(name = "start", requiresProject = true, defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST, requiresDependencyResolution = ResolutionScope.TEST) public class StartMojo extends AbstractRunMojo { private static final String ENABLE_MBEAN_PROPERTY = "--spring.application.admin.enabled=true"; private static final String JMX_NAME_PROPERTY_PREFIX = "--spring.application.admin.jmx-name="; /** * The JMX name of the automatically deployed MBean managing the lifecycle of the * spring application. */ @Parameter(defaultValue = SpringApplicationAdminClient.DEFAULT_OBJECT_NAME) @SuppressWarnings("NullAway.Init") private String jmxName; /** * The port to use to expose the platform MBeanServer. */ @Parameter(defaultValue = "9001") private int jmxPort; /** * The number of milliseconds to wait between each attempt to check if the spring * application is ready. */ @Parameter(property = "spring-boot.start.wait", defaultValue = "500") private long wait; /** * The maximum number of attempts to check if the spring application is ready. * Combined with the "wait" argument, this gives a global timeout value (30 sec by * default) */ @Parameter(property = "spring-boot.start.maxAttempts", defaultValue = "60") private int maxAttempts; private final Object lock = new Object(); /** * Flag to include the test classpath when running. */ @Parameter(property = "spring-boot.run.useTestClasspath", defaultValue = "false") private boolean useTestClasspath; @Inject public StartMojo(ToolchainManager toolchainManager) { super(toolchainManager); } @Override protected void run(JavaProcessExecutor processExecutor, File workingDirectory, List args, Map environmentVariables) throws MojoExecutionException, MojoFailureException { RunProcess runProcess = processExecutor.runAsync(workingDirectory, args, environmentVariables); try { waitForSpringApplication(); } catch (MojoExecutionException | MojoFailureException ex) { runProcess.kill(); throw ex; } } @Override protected RunArguments resolveApplicationArguments() { RunArguments applicationArguments = super.resolveApplicationArguments(); applicationArguments.getArgs().addLast(ENABLE_MBEAN_PROPERTY); applicationArguments.getArgs().addLast(JMX_NAME_PROPERTY_PREFIX + this.jmxName); return applicationArguments; } @Override protected RunArguments resolveJvmArguments() { RunArguments jvmArguments = super.resolveJvmArguments(); List remoteJmxArguments = new ArrayList<>(); remoteJmxArguments.add("-Dcom.sun.management.jmxremote"); remoteJmxArguments.add("-Dcom.sun.management.jmxremote.port=" + this.jmxPort); remoteJmxArguments.add("-Dcom.sun.management.jmxremote.authenticate=false"); remoteJmxArguments.add("-Dcom.sun.management.jmxremote.ssl=false"); remoteJmxArguments.add("-Djava.rmi.server.hostname=127.0.0.1"); jvmArguments.getArgs().addAll(remoteJmxArguments); return jvmArguments; } private void waitForSpringApplication() throws MojoFailureException, MojoExecutionException { try { getLog().debug("Connecting to local MBeanServer at port " + this.jmxPort); try (JMXConnector connector = execute(this.wait, this.maxAttempts, new CreateJmxConnector(this.jmxPort))) { if (connector == null) { throw new MojoExecutionException("JMX MBean server was not reachable before the configured " + "timeout (" + (this.wait * this.maxAttempts) + "ms"); } getLog().debug("Connected to local MBeanServer at port " + this.jmxPort); MBeanServerConnection connection = connector.getMBeanServerConnection(); doWaitForSpringApplication(connection); } } catch (IOException ex) { throw new MojoFailureException("Could not contact Spring Boot application via JMX on port " + this.jmxPort + ". Please make sure that no other process is using that port", ex); } catch (Exception ex) { throw new MojoExecutionException("Failed to connect to MBean server at port " + this.jmxPort, ex); } } private void doWaitForSpringApplication(MBeanServerConnection connection) throws MojoExecutionException, MojoFailureException { final SpringApplicationAdminClient client = new SpringApplicationAdminClient(connection, this.jmxName); try { Callable<@Nullable Boolean> isReady = () -> (client.isReady() ? true : null); execute(this.wait, this.maxAttempts, isReady); } catch (ReflectionException ex) { throw new MojoExecutionException("Unable to retrieve 'ready' attribute", ex.getCause()); } catch (Exception ex) { throw new MojoFailureException("Could not invoke shutdown operation", ex); } } /** * Execute a task, retrying it on failure. * @param the result type * @param wait the wait time * @param maxAttempts the maximum number of attempts * @param callback the task to execute (possibly multiple times). The callback should * return {@code null} to indicate that another attempt should be made * @return the result * @throws Exception in case of execution errors */ public T execute(long wait, int maxAttempts, Callable<@Nullable T> callback) throws Exception { getLog().debug("Waiting for spring application to start..."); for (int i = 0; i < maxAttempts; i++) { T result = callback.call(); if (result != null) { return result; } String message = "Spring application is not ready yet, waiting " + wait + "ms (attempt " + (i + 1) + ")"; getLog().debug(message); synchronized (this.lock) { try { this.lock.wait(wait); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new IllegalStateException("Interrupted while waiting for Spring Boot app to start."); } } } throw new MojoExecutionException( "Spring application did not start before the configured timeout (" + (wait * maxAttempts) + "ms"); } @Override protected boolean isUseTestClasspath() { return this.useTestClasspath; } private class CreateJmxConnector implements Callable<@Nullable JMXConnector> { private final int port; CreateJmxConnector(int port) { this.port = port; } @Override public @Nullable JMXConnector call() throws Exception { try { return SpringApplicationAdminClient.connect(this.port); } catch (IOException ex) { if (hasCauseWithType(ex, ConnectException.class)) { String message = "MBean server at port " + this.port + " is not up yet..."; getLog().debug(message); return null; } throw ex; } } private boolean hasCauseWithType(Throwable t, Class type) { return type.isAssignableFrom(t.getClass()) || t.getCause() != null && hasCauseWithType(t.getCause(), type); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/StopMojo.java ================================================ /* * 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.io.IOException; import javax.management.InstanceNotFoundException; import javax.management.MBeanServerConnection; import javax.management.remote.JMXConnector; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; /** * Stop an application that has been started by the "start" goal. Typically invoked once a * test suite has completed. * * @author Stephane Nicoll * @since 1.3.0 */ @Mojo(name = "stop", requiresProject = true, defaultPhase = LifecyclePhase.POST_INTEGRATION_TEST) public class StopMojo extends AbstractMojo { /** * The Maven project. * @since 1.4.1 */ @Parameter(defaultValue = "${project}", readonly = true, required = true) @SuppressWarnings("NullAway.Init") private MavenProject project; /** * The JMX name of the automatically deployed MBean managing the lifecycle of the * application. */ @Parameter(defaultValue = SpringApplicationAdminClient.DEFAULT_OBJECT_NAME) @SuppressWarnings("NullAway.Init") private String jmxName; /** * The port to use to look up the platform MBeanServer. */ @Parameter(defaultValue = "9001") private int jmxPort; /** * Skip the execution. * @since 1.3.2 */ @Parameter(property = "spring-boot.stop.skip", defaultValue = "false") private boolean skip; @Override public void execute() throws MojoExecutionException, MojoFailureException { if (this.skip) { getLog().debug("skipping stop as per configuration."); return; } getLog().info("Stopping application..."); try (JMXConnector connector = SpringApplicationAdminClient.connect(this.jmxPort)) { MBeanServerConnection connection = connector.getMBeanServerConnection(); stop(connection); } catch (IOException ex) { // The response won't be received as the server has died - ignoring getLog().debug("Service is not reachable anymore (" + ex.getMessage() + ")"); } } private void stop(MBeanServerConnection connection) throws IOException, MojoExecutionException { try { new SpringApplicationAdminClient(connection, this.jmxName).stop(); } catch (InstanceNotFoundException ex) { throw new MojoExecutionException( "Spring application lifecycle JMX bean not found. Could not stop application gracefully", ex); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/SystemPropertyFormatter.java ================================================ /* * 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 org.jspecify.annotations.Nullable; /** * Format System properties. * * @author Phillip Webb * @author Stephane Nicoll */ final class SystemPropertyFormatter { private SystemPropertyFormatter() { } static String format(@Nullable String key, @Nullable String value) { if (key == null) { return ""; } if (value == null || value.isEmpty()) { return String.format("-D%s", key); } return String.format("-D%s=%s", key, value); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/TestRunMojo.java ================================================ /* * 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.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Execute; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.toolchain.ToolchainManager; import org.springframework.boot.loader.tools.RunProcess; /** * Run an application in place using the test runtime classpath. The main class that will * be used to launch the application is determined as follows: The configured main class, * if any. Then the main class found in the test classes directory, if any. Then the main * class found in the classes directory, if any. * * @author Phillip Webb * @author Dmytro Nosan * @author Stephane Nicoll * @author Andy Wilkinson * @since 3.1.0 */ @Mojo(name = "test-run", requiresProject = true, defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyResolution = ResolutionScope.TEST) @Execute(phase = LifecyclePhase.TEST_COMPILE) public class TestRunMojo extends AbstractRunMojo { /** * Whether the JVM's launch should be optimized. */ @Parameter(property = "spring-boot.test-run.optimizedLaunch", defaultValue = "true") private boolean optimizedLaunch; /** * Directory containing the test classes and resource files that should be used to run * the application. */ @Parameter(defaultValue = "${project.build.testOutputDirectory}", required = true) @SuppressWarnings("NullAway.Init") private File testClassesDirectory; @Inject public TestRunMojo(ToolchainManager toolchainManager) { super(toolchainManager); } @Override protected List getClassesDirectories() { ArrayList classesDirectories = new ArrayList<>(super.getClassesDirectories()); classesDirectories.add(0, this.testClassesDirectory); return classesDirectories; } @Override protected boolean isUseTestClasspath() { return true; } @Override protected RunArguments resolveJvmArguments() { RunArguments jvmArguments = super.resolveJvmArguments(); if (this.optimizedLaunch) { jvmArguments.getArgs().addFirst("-XX:TieredStopAtLevel=1"); } return jvmArguments; } @Override protected void run(JavaProcessExecutor processExecutor, File workingDirectory, List args, Map environmentVariables) throws MojoExecutionException, MojoFailureException { processExecutor .withRunProcessCustomizer( (runProcess) -> Runtime.getRuntime().addShutdownHook(new Thread(new RunProcessKiller(runProcess)))) .run(workingDirectory, args, environmentVariables); } private static final class RunProcessKiller implements Runnable { private final RunProcess runProcess; private RunProcessKiller(RunProcess runProcess) { this.runProcess = runProcess; } @Override public void run() { this.runProcess.kill(); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/VersionExtractor.java ================================================ /* * 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.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.jar.Attributes; import java.util.jar.JarFile; import org.jspecify.annotations.Nullable; /** * Extracts version information for a Class. * * @author Andy Wilkinson * @author Scott Frederick */ final class VersionExtractor { private VersionExtractor() { } /** * Return the version information for the provided {@link Class}. * @param cls the Class to retrieve the version for * @return the version, or {@code null} if a version can not be extracted */ static @Nullable String forClass(Class cls) { String implementationVersion = cls.getPackage().getImplementationVersion(); if (implementationVersion != null) { return implementationVersion; } URL codeSourceLocation = cls.getProtectionDomain().getCodeSource().getLocation(); try { URLConnection connection = codeSourceLocation.openConnection(); if (connection instanceof JarURLConnection jarURLConnection) { return getImplementationVersion(jarURLConnection.getJarFile()); } try (JarFile jarFile = new JarFile(new File(codeSourceLocation.toURI()))) { return getImplementationVersion(jarFile); } } catch (Exception ex) { return null; } } private static String getImplementationVersion(JarFile jarFile) throws IOException { return jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/package-info.java ================================================ /* * 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. */ /** * Maven plugin for Spring Boot. */ @NullMarked package org.springframework.boot.maven; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml ================================================ build-info true false ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/resources/META-INF/spring/layers/default.xml ================================================ org/springframework/boot/loader/** *:*:*SNAPSHOT dependencies spring-boot-loader snapshot-dependencies application ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-2.3.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-2.4.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-2.5.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-2.6.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-2.7.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-3.0.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-3.1.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-3.2.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-3.3.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-3.4.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-3.5.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-4.0.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/main/xsd/layers-4.1.xsd ================================================ ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/maven/resources/pom.xml ================================================ 4.0.0 org.springframework.boot spring-boot-maven-plugin {{version}} maven-plugin Spring Boot Maven Plugin https://projects.spring.io/spring-boot/# UTF-8 Apache License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0 https://github.com/spring-projects/spring-boot scm:git:git://github.com/spring-projects/spring-boot.git scm:git:ssh://git@github.com/spring-projects/spring-boot.git GitHub https://github.com/spring-projects/spring-boot/issues Spring https://spring.io org.apache.maven.plugins maven-plugin-plugin 3.6.0 org.springframework.boot.maven ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ArtifactsLibrariesTests.java ================================================ /* * 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.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.handler.ArtifactHandler; import org.apache.maven.model.Dependency; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.loader.tools.Library; import org.springframework.boot.loader.tools.LibraryCallback; import org.springframework.boot.loader.tools.LibraryCoordinates; import org.springframework.boot.loader.tools.LibraryScope; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.assertArg; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; /** * Tests for {@link ArtifactsLibraries}. * * @author Phillip Webb */ @ExtendWith(MockitoExtension.class) class ArtifactsLibrariesTests { @Mock @SuppressWarnings("NullAway.Init") private Artifact artifact; @Mock @SuppressWarnings("NullAway.Init") private ArtifactHandler artifactHandler; private Set artifacts; private final File file = new File("."); private ArtifactsLibraries libs; @Mock @SuppressWarnings("NullAway.Init") private LibraryCallback callback; @Captor @SuppressWarnings("NullAway.Init") private ArgumentCaptor libraryCaptor; @BeforeEach void setup() { this.artifacts = Collections.singleton(this.artifact); this.libs = new ArtifactsLibraries(this.artifacts, Collections.emptyList(), null, mock(Log.class)); given(this.artifactHandler.getExtension()).willReturn("jar"); } @Test void callbackForJars() throws Exception { given(this.artifact.getFile()).willReturn(this.file); given(this.artifact.getArtifactHandler()).willReturn(this.artifactHandler); given(this.artifact.getScope()).willReturn("compile"); this.libs.doWithLibraries(this.callback); then(this.callback).should().library(assertArg((library) -> { assertThat(library.getFile()).isEqualTo(this.file); assertThat(library.getScope()).isEqualTo(LibraryScope.COMPILE); assertThat(library.isUnpackRequired()).isFalse(); })); } @Test void callbackWithUnpack() throws Exception { given(this.artifact.getFile()).willReturn(this.file); given(this.artifact.getArtifactHandler()).willReturn(this.artifactHandler); given(this.artifact.getGroupId()).willReturn("gid"); given(this.artifact.getArtifactId()).willReturn("aid"); given(this.artifact.getScope()).willReturn("compile"); Dependency unpack = new Dependency(); unpack.setGroupId("gid"); unpack.setArtifactId("aid"); this.libs = new ArtifactsLibraries(this.artifacts, Collections.emptyList(), Collections.singleton(unpack), mock(Log.class)); this.libs.doWithLibraries(this.callback); then(this.callback).should().library(assertArg((library) -> assertThat(library.isUnpackRequired()).isTrue())); } @Test void renamesDuplicates() throws Exception { Artifact artifact1 = mock(Artifact.class); Artifact artifact2 = mock(Artifact.class); given(artifact1.getScope()).willReturn("compile"); given(artifact1.getGroupId()).willReturn("g1"); given(artifact1.getArtifactId()).willReturn("artifact"); given(artifact1.getBaseVersion()).willReturn("1.0"); given(artifact1.getFile()).willReturn(new File("a")); given(artifact1.getArtifactHandler()).willReturn(this.artifactHandler); given(artifact2.getScope()).willReturn("compile"); given(artifact2.getGroupId()).willReturn("g2"); given(artifact2.getArtifactId()).willReturn("artifact"); given(artifact2.getBaseVersion()).willReturn("1.0"); given(artifact2.getFile()).willReturn(new File("a")); given(artifact2.getArtifactHandler()).willReturn(this.artifactHandler); this.artifacts = new LinkedHashSet<>(Arrays.asList(artifact1, artifact2)); this.libs = new ArtifactsLibraries(this.artifacts, Collections.emptyList(), null, mock(Log.class)); this.libs.doWithLibraries(this.callback); then(this.callback).should(times(2)).library(this.libraryCaptor.capture()); assertThat(this.libraryCaptor.getAllValues().get(0).getName()).isEqualTo("g1-artifact-1.0.jar"); assertThat(this.libraryCaptor.getAllValues().get(1).getName()).isEqualTo("g2-artifact-1.0.jar"); } @Test void libraryCoordinatesVersionUsesBaseVersionOfArtifact() throws IOException { Artifact snapshotArtifact = mock(Artifact.class); given(snapshotArtifact.getScope()).willReturn("compile"); given(snapshotArtifact.getArtifactId()).willReturn("artifact"); given(snapshotArtifact.getBaseVersion()).willReturn("1.0-SNAPSHOT"); given(snapshotArtifact.getFile()).willReturn(new File("a")); given(snapshotArtifact.getArtifactHandler()).willReturn(this.artifactHandler); this.artifacts = Collections.singleton(snapshotArtifact); new ArtifactsLibraries(this.artifacts, Collections.emptyList(), null, mock(Log.class)) .doWithLibraries((library) -> { assertThat(library.isIncluded()).isTrue(); assertThat(library.isLocal()).isFalse(); LibraryCoordinates coordinates = library.getCoordinates(); assertThat(coordinates).isNotNull(); assertThat(coordinates.getVersion()).isEqualTo("1.0-SNAPSHOT"); }); } @Test void artifactForLocalProjectProducesLocalLibrary() throws IOException { Artifact artifact = mock(Artifact.class); given(artifact.getScope()).willReturn("compile"); given(artifact.getArtifactId()).willReturn("artifact"); given(artifact.getBaseVersion()).willReturn("1.0-SNAPSHOT"); given(artifact.getFile()).willReturn(new File("a")); given(artifact.getArtifactHandler()).willReturn(this.artifactHandler); MavenProject mavenProject = mock(MavenProject.class); given(mavenProject.getArtifact()).willReturn(artifact); this.artifacts = Collections.singleton(artifact); new ArtifactsLibraries(this.artifacts, Collections.singleton(mavenProject), null, mock(Log.class)) .doWithLibraries((library) -> assertThat(library.isLocal()).isTrue()); } @Test void attachedArtifactForLocalProjectProducesLocalLibrary() throws IOException { MavenProject mavenProject = mock(MavenProject.class); Artifact artifact = mock(Artifact.class); given(mavenProject.getArtifact()).willReturn(artifact); Artifact attachedArtifact = mock(Artifact.class); given(attachedArtifact.getScope()).willReturn("compile"); given(attachedArtifact.getArtifactId()).willReturn("attached-artifact"); given(attachedArtifact.getBaseVersion()).willReturn("1.0-SNAPSHOT"); given(attachedArtifact.getFile()).willReturn(new File("a")); given(attachedArtifact.getArtifactHandler()).willReturn(this.artifactHandler); given(mavenProject.getAttachedArtifacts()).willReturn(Collections.singletonList(attachedArtifact)); this.artifacts = Collections.singleton(attachedArtifact); new ArtifactsLibraries(this.artifacts, Collections.singleton(mavenProject), null, mock(Log.class)) .doWithLibraries((library) -> assertThat(library.isLocal()).isTrue()); } @Test void nonIncludedArtifact() throws IOException { Artifact artifact = mock(Artifact.class); given(artifact.getScope()).willReturn("compile"); given(artifact.getArtifactId()).willReturn("artifact"); given(artifact.getBaseVersion()).willReturn("1.0-SNAPSHOT"); given(artifact.getFile()).willReturn(new File("a")); given(artifact.getArtifactHandler()).willReturn(this.artifactHandler); MavenProject mavenProject = mock(MavenProject.class); given(mavenProject.getArtifact()).willReturn(artifact); this.artifacts = Collections.singleton(artifact); new ArtifactsLibraries(this.artifacts, Collections.emptySet(), Collections.singleton(mavenProject), null, mock(Log.class)) .doWithLibraries((library) -> assertThat(library.isIncluded()).isFalse()); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ClassPathTests.java ================================================ /* * 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.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.UnaryOperator; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; /** * Test for {@link ClassPath}. * * @author Dmytro Nosan * @author Stephane Nicoll * @author Phillip Webb */ class ClassPathTests { @Test void argsWhenNoClassPathReturnsEmptyList() { assertThat(ClassPath.of(Collections.emptyList()).args(false)).isEmpty(); } @Test void argsWhenSingleUrlOnWindowsUsesPath(@TempDir Path temp) throws Exception { Path path = temp.resolve("test.jar"); ClassPath classPath = ClassPath.of(onWindows(), List.of(path.toUri().toURL())); assertThat(classPath.args(true)).containsExactly("-cp", path.toString()); } @Test void argsWhenSingleUrlNotOnWindowsUsesPath(@TempDir Path temp) throws Exception { Path path = temp.resolve("test.jar"); ClassPath classPath = ClassPath.of(onLinux(), List.of(path.toUri().toURL())); assertThat(classPath.args(true)).containsExactly("-cp", path.toString()); } @Test void argsWhenMultipleUrlsOnWindowsAndAllowedUsesArgFile(@TempDir Path temp) throws Exception { Path path1 = temp.resolve("test1.jar"); Path path2 = temp.resolve("test2.jar"); ClassPath classPath = ClassPath.of(onWindows(), List.of(path1.toUri().toURL(), path2.toUri().toURL())); List args = classPath.args(true); assertThat(args.get(0)).isEqualTo("-cp"); assertThat(args.get(1)).startsWith("@"); assertThat(Paths.get(args.get(1).substring(1))) .hasContent("\"" + (path1 + File.pathSeparator + path2).replace("\\", "\\\\") + "\""); } @Test void argsWhenMultipleUrlsOnWindowsAndNotAllowedUsesPath(@TempDir Path temp) throws Exception { Path path1 = temp.resolve("test1.jar"); Path path2 = temp.resolve("test2.jar"); ClassPath classPath = ClassPath.of(onWindows(), List.of(path1.toUri().toURL(), path2.toUri().toURL())); assertThat(classPath.args(false)).containsExactly("-cp", path1 + File.pathSeparator + path2); } @Test void argsWhenMultipleUrlsNotOnWindowsUsesPath(@TempDir Path temp) throws Exception { Path path1 = temp.resolve("test1.jar"); Path path2 = temp.resolve("test2.jar"); ClassPath classPath = ClassPath.of(onLinux(), List.of(path1.toUri().toURL(), path2.toUri().toURL())); assertThat(classPath.args(true)).containsExactly("-cp", path1 + File.pathSeparator + path2); } @Test void toStringShouldReturnClassPath(@TempDir Path temp) throws Exception { Path path1 = temp.resolve("test1.jar"); Path path2 = temp.resolve("test2.jar"); ClassPath classPath = ClassPath.of(onLinux(), List.of(path1.toUri().toURL(), path2.toUri().toURL())); assertThat(classPath.toString()).isEqualTo(path1 + File.pathSeparator + path2); } private UnaryOperator<@Nullable String> onWindows() { return Map.of("os.name", "windows")::get; } private UnaryOperator<@Nullable String> onLinux() { return Map.of("os.name", "linux")::get; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/CommandLineBuilderTests.java ================================================ /* * 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.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.management.ManagementFactory; import java.net.MalformedURLException; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.loader.tools.JavaExecutable; import org.springframework.boot.maven.sample.ClassWithMainMethod; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link CommandLineBuilder}. * * @author Stephane Nicoll */ class CommandLineBuilderTests { public static final String CLASS_NAME = ClassWithMainMethod.class.getName(); @Test @SuppressWarnings("NullAway") // Maven can't deal with @Nullable arrays / varargs void buildWithNullJvmArgumentsIsIgnored() { assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withJvmArguments((String[]) null).build()) .containsExactly(CLASS_NAME); } @Test void buildWithNullIntermediateJvmArgumentIsIgnored() { assertThat(CommandLineBuilder.forMainClass(CLASS_NAME) .withJvmArguments("-verbose:class", null, "-verbose:gc") .build()).containsExactly("-verbose:class", "-verbose:gc", CLASS_NAME); } @Test void buildWithJvmArgument() { assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withJvmArguments("-verbose:class").build()) .containsExactly("-verbose:class", CLASS_NAME); } @Test void buildWithNullSystemPropertyIsIgnored() { assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withSystemProperties(null).build()) .containsExactly(CLASS_NAME); } @Test void buildWithSystemProperty() { assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withSystemProperties(Map.of("flag", "enabled")).build()) .containsExactly("-Dflag=enabled", CLASS_NAME); } @Test @SuppressWarnings("NullAway") // Maven can't deal with @Nullable arrays / varargs void buildWithNullArgumentsIsIgnored() { assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withArguments((String[]) null).build()) .containsExactly(CLASS_NAME); } @Test void buildWithNullIntermediateArgumentIsIgnored() { assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withArguments("--test", null, "--another").build()) .containsExactly(CLASS_NAME, "--test", "--another"); } @Test @DisabledOnOs(OS.WINDOWS) void buildWithClassPath(@TempDir Path tempDir) throws Exception { Path file = tempDir.resolve("test.jar"); Path file1 = tempDir.resolve("test1.jar"); assertThat(CommandLineBuilder.forMainClass(CLASS_NAME) .withClasspath(file.toUri().toURL(), file1.toUri().toURL()) .build()).containsExactly("-cp", file + File.pathSeparator + file1, CLASS_NAME); } @Test @EnabledOnOs(OS.WINDOWS) void buildWithClassPathOnWindows(@TempDir Path tempDir) throws Exception { Path file = tempDir.resolve("test.jar"); Path file1 = tempDir.resolve("test1.jar"); List args = CommandLineBuilder.forMainClass(CLASS_NAME) .withClasspath(file.toUri().toURL(), file1.toUri().toURL()) .build(); assertThat(args).hasSize(3); assertThat(args.get(0)).isEqualTo("-cp"); assertThat(args.get(1)).startsWith("@"); assertThat(args.get(2)).isEqualTo(CLASS_NAME); assertThat(Paths.get(args.get(1).substring(1))) .hasContent("\"" + (file + File.pathSeparator + file1).replace("\\", "\\\\") + "\""); } @Test void buildAndRunWithLongClassPath() throws IOException, InterruptedException { StringBuilder classPath = new StringBuilder(ManagementFactory.getRuntimeMXBean().getClassPath()); // Simulates [CreateProcess error=206, The filename or extension is too long] while (classPath.length() < 35000) { classPath.append(File.pathSeparator).append(classPath); } URL[] urls = Arrays.stream(classPath.toString().split(File.pathSeparator)).map(this::toURL).toArray(URL[]::new); List command = CommandLineBuilder.forMainClass(ClassWithMainMethod.class.getName()) .withClasspath(urls) .build(); ProcessBuilder pb = new JavaExecutable().processBuilder(command.toArray(new String[0])); Process process = pb.start(); assertThat(process.waitFor()).isEqualTo(0); try (InputStream inputStream = process.getInputStream()) { assertThat(inputStream).hasContent("Hello World"); } } private URL toURL(String path) { try { return Paths.get(path).toUri().toURL(); } catch (MalformedURLException ex) { throw new RuntimeException(ex); } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/CustomLayersProviderTests.java ================================================ /* * 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 javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.springframework.boot.loader.tools.Library; import org.springframework.boot.loader.tools.LibraryCoordinates; import org.springframework.boot.loader.tools.layer.CustomLayers; import org.springframework.core.io.ClassPathResource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link CustomLayersProvider}. * * @author Madhura Bhave * @author Scott Frederick */ class CustomLayersProviderTests { private CustomLayersProvider customLayersProvider; @BeforeEach void setup() { this.customLayersProvider = new CustomLayersProvider(); } @Test void getLayerResolverWhenDocumentValid() throws Exception { CustomLayers layers = this.customLayersProvider.getLayers(getDocument("layers.xml")); assertThat(layers).extracting("name") .containsExactly("my-deps", "my-dependencies-name", "snapshot-dependencies", "my-resources", "configuration", "application"); Library snapshot = mockLibrary("test-SNAPSHOT.jar", "org.foo", "1.0.0-SNAPSHOT"); Library groupId = mockLibrary("my-library", "com.acme", null); Library otherDependency = mockLibrary("other-library", "org.foo", null); Library localSnapshotDependency = mockLibrary("local-library", "org.foo", "1.0-SNAPSHOT"); given(localSnapshotDependency.isLocal()).willReturn(true); assertThat(layers.getLayer(snapshot)).hasToString("snapshot-dependencies"); assertThat(layers.getLayer(groupId)).hasToString("my-deps"); assertThat(layers.getLayer(otherDependency)).hasToString("my-dependencies-name"); assertThat(layers.getLayer(localSnapshotDependency)).hasToString("application"); assertThat(layers.getLayer("META-INF/resources/test.css")).hasToString("my-resources"); assertThat(layers.getLayer("application.yml")).hasToString("configuration"); assertThat(layers.getLayer("test")).hasToString("application"); } private Library mockLibrary(String name, String groupId, @Nullable String version) { Library library = mock(Library.class); given(library.getName()).willReturn(name); given(library.getCoordinates()).willReturn(LibraryCoordinates.of(groupId, null, version)); return library; } @Test void getLayerResolverWhenDocumentContainsLibraryLayerWithNoFilters() throws Exception { CustomLayers layers = this.customLayersProvider.getLayers(getDocument("dependencies-layer-no-filter.xml")); Library library = mockLibrary("my-library", "com.acme", null); assertThat(layers.getLayer(library)).hasToString("my-deps"); assertThatIllegalStateException().isThrownBy(() -> layers.getLayer("application.yml")) .withMessageContaining("match any layer"); } @Test void getLayerResolverWhenDocumentContainsResourceLayerWithNoFilters() throws Exception { CustomLayers layers = this.customLayersProvider.getLayers(getDocument("application-layer-no-filter.xml")); Library library = mockLibrary("my-library", "com.acme", null); assertThat(layers.getLayer("application.yml")).hasToString("my-layer"); assertThatIllegalStateException().isThrownBy(() -> layers.getLayer(library)) .withMessageContaining("match any layer"); } private Document getDocument(String resourceName) throws Exception { ClassPathResource resource = new ClassPathResource(resourceName); InputSource inputSource = new InputSource(resource.getInputStream()); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder documentBuilder = factory.newDocumentBuilder(); return documentBuilder.parse(inputSource); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DependencyFilterMojoTests.java ================================================ /* * 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.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter; import org.apache.maven.shared.artifact.filter.collection.ScopeFilter; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link AbstractDependencyFilterMojo}. * * @author Stephane Nicoll */ class DependencyFilterMojoTests { @TempDir @SuppressWarnings("NullAway.Init") static Path temp; @Test void filterDependencies() throws MojoExecutionException { TestableDependencyFilterMojo mojo = new TestableDependencyFilterMojo(Collections.emptyList(), "com.foo"); Artifact artifact = createArtifact("com.bar", "one"); Set artifacts = mojo.filterDependencies(createArtifact("com.foo", "one"), createArtifact("com.foo", "two"), artifact); assertThat(artifacts).hasSize(1); assertThat(artifacts.iterator().next()).isSameAs(artifact); } @Test void filterGroupIdExactMatch() throws MojoExecutionException { TestableDependencyFilterMojo mojo = new TestableDependencyFilterMojo(Collections.emptyList(), "com.foo"); Artifact artifact = createArtifact("com.foo.bar", "one"); Set artifacts = mojo.filterDependencies(createArtifact("com.foo", "one"), createArtifact("com.foo", "two"), artifact); assertThat(artifacts).hasSize(1); assertThat(artifacts.iterator().next()).isSameAs(artifact); } @Test void filterScopeKeepOrder() throws MojoExecutionException { TestableDependencyFilterMojo mojo = new TestableDependencyFilterMojo(Collections.emptyList(), "", new ScopeFilter(null, Artifact.SCOPE_SYSTEM)); Artifact one = createArtifact("com.foo", "one"); Artifact two = createArtifact("com.foo", "two", Artifact.SCOPE_SYSTEM); Artifact three = createArtifact("com.foo", "three", Artifact.SCOPE_RUNTIME); Set artifacts = mojo.filterDependencies(one, two, three); assertThat(artifacts).containsExactly(one, three); } @Test void filterGroupIdKeepOrder() throws MojoExecutionException { TestableDependencyFilterMojo mojo = new TestableDependencyFilterMojo(Collections.emptyList(), "com.foo"); Artifact one = createArtifact("com.foo", "one"); Artifact two = createArtifact("com.bar", "two"); Artifact three = createArtifact("com.bar", "three"); Artifact four = createArtifact("com.foo", "four"); Set artifacts = mojo.filterDependencies(one, two, three, four); assertThat(artifacts).containsExactly(two, three); } @Test void filterExcludeKeepOrder() throws MojoExecutionException { Exclude exclude = new Exclude(); exclude.setGroupId("com.bar"); exclude.setArtifactId("two"); TestableDependencyFilterMojo mojo = new TestableDependencyFilterMojo(Collections.singletonList(exclude), ""); Artifact one = createArtifact("com.foo", "one"); Artifact two = createArtifact("com.bar", "two"); Artifact three = createArtifact("com.bar", "three"); Artifact four = createArtifact("com.foo", "four"); Set artifacts = mojo.filterDependencies(one, two, three, four); assertThat(artifacts).containsExactly(one, three, four); } @Test void excludeByJarType() throws MojoExecutionException { TestableDependencyFilterMojo mojo = new TestableDependencyFilterMojo(Collections.emptyList(), ""); Artifact one = createArtifact("com.foo", "one", null, "dependencies-starter"); Artifact two = createArtifact("com.bar", "two"); Set artifacts = mojo.filterDependencies(one, two); assertThat(artifacts).containsExactly(two); } private static Artifact createArtifact(String groupId, String artifactId) { return createArtifact(groupId, artifactId, null); } private static Artifact createArtifact(String groupId, String artifactId, @Nullable String scope) { return createArtifact(groupId, artifactId, scope, null); } private static Artifact createArtifact(String groupId, String artifactId, @Nullable String scope, @Nullable String jarType) { Artifact a = mock(Artifact.class); given(a.getGroupId()).willReturn(groupId); given(a.getArtifactId()).willReturn(artifactId); if (scope != null) { given(a.getScope()).willReturn(scope); } given(a.getFile()).willReturn(createArtifactFile(jarType)); return a; } private static File createArtifactFile(@Nullable String jarType) { Path jarPath = temp.resolve(UUID.randomUUID() + ".jar"); Manifest manifest = new Manifest(); manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); if (jarType != null) { manifest.getMainAttributes().putValue("Spring-Boot-Jar-Type", jarType); } try { new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest).close(); } catch (IOException ex) { throw new RuntimeException(ex); } return jarPath.toFile(); } private static final class TestableDependencyFilterMojo extends AbstractDependencyFilterMojo { private final ArtifactsFilter[] additionalFilters; private TestableDependencyFilterMojo(List excludes, String excludeGroupIds, ArtifactsFilter... additionalFilters) { setExcludes(excludes); setExcludeGroupIds(excludeGroupIds); this.additionalFilters = additionalFilters; } Set filterDependencies(Artifact... artifacts) throws MojoExecutionException { Set input = new LinkedHashSet<>(Arrays.asList(artifacts)); return filterDependencies(input, this.additionalFilters); } @Override public void execute() { } } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DependencyFilterTests.java ================================================ /* * 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 filtered = filter.filter(Set.of(a1, a2, a3)); assertThat(filtered).containsExactlyInAnyOrder(a1, a3); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java ================================================ /* * 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.Base64; import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugin.logging.SystemStreamLog; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link Docker}. * * @author Wei Jiang * @author Scott Frederick */ class DockerTests { private final Log log = new SystemStreamLog(); @Test void asDockerConfigurationWithDefaults() { Docker docker = new Docker(); BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker); assertThat(dockerConfiguration.connection()).isNull(); DockerRegistryAuthentication builderRegistryAuthentication = dockerConfiguration .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); assertThat(builderRegistryAuthentication.getAuthHeader()).isNull(); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); String authHeader = publishRegistryAuthentication.getAuthHeader(); assertThat(authHeader).isNotNull(); assertThat(decoded(authHeader)).contains("\"username\" : \"\"") .contains("\"password\" : \"\"") .contains("\"email\" : \"\"") .contains("\"serveraddress\" : \"\""); } @Test void asDockerConfigurationWithHostConfiguration() { Docker docker = new Docker(); docker.setHost("docker.example.com"); docker.setTlsVerify(true); docker.setCertPath("/tmp/ca-cert"); BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker); DockerConnectionConfiguration.Host host = (DockerConnectionConfiguration.Host) dockerConfiguration.connection(); assertThat(host).isNotNull(); assertThat(host.address()).isEqualTo("docker.example.com"); assertThat(host.secure()).isTrue(); assertThat(host.certificatePath()).isEqualTo("/tmp/ca-cert"); assertThat(dockerConfiguration.bindHostToBuilder()).isFalse(); DockerRegistryAuthentication builderRegistryAuthentication = createDockerConfiguration(docker) .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); assertThat(builderRegistryAuthentication.getAuthHeader()).isNull(); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); String authHeader = publishRegistryAuthentication.getAuthHeader(); assertThat(authHeader).isNotNull(); assertThat(decoded(authHeader)).contains("\"username\" : \"\"") .contains("\"password\" : \"\"") .contains("\"email\" : \"\"") .contains("\"serveraddress\" : \"\""); } @Test void asDockerConfigurationWithContextConfiguration() { Docker docker = new Docker(); docker.setContext("test-context"); BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker); DockerConnectionConfiguration.Context context = (DockerConnectionConfiguration.Context) dockerConfiguration .connection(); assertThat(context).isNotNull(); assertThat(context.context()).isEqualTo("test-context"); assertThat(dockerConfiguration.bindHostToBuilder()).isFalse(); DockerRegistryAuthentication builderRegistryAuthentication = createDockerConfiguration(docker) .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); assertThat(builderRegistryAuthentication.getAuthHeader()).isNull(); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); String authHeader = publishRegistryAuthentication.getAuthHeader(); assertThat(authHeader).isNotNull(); assertThat(decoded(authHeader)).contains("\"username\" : \"\"") .contains("\"password\" : \"\"") .contains("\"email\" : \"\"") .contains("\"serveraddress\" : \"\""); } @Test void asDockerConfigurationWithHostAndContextFails() { Docker docker = new Docker(); docker.setContext("test-context"); docker.setHost("docker.example.com"); assertThatIllegalArgumentException().isThrownBy(() -> createDockerConfiguration(docker)) .withMessageContaining("Invalid Docker configuration"); } @Test void asDockerConfigurationWithBindHostToBuilder() { Docker docker = new Docker(); docker.setHost("docker.example.com"); docker.setTlsVerify(true); docker.setCertPath("/tmp/ca-cert"); docker.setBindHostToBuilder(true); BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker); DockerConnectionConfiguration.Host host = (DockerConnectionConfiguration.Host) dockerConfiguration.connection(); assertThat(host).isNotNull(); assertThat(host.address()).isEqualTo("docker.example.com"); assertThat(host.secure()).isTrue(); assertThat(host.certificatePath()).isEqualTo("/tmp/ca-cert"); assertThat(dockerConfiguration.bindHostToBuilder()).isTrue(); DockerRegistryAuthentication builderRegistryAuthentication = createDockerConfiguration(docker) .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); assertThat(builderRegistryAuthentication.getAuthHeader()).isNull(); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); String authHeader = publishRegistryAuthentication.getAuthHeader(); assertThat(authHeader).isNotNull(); assertThat(decoded(authHeader)).contains("\"username\" : \"\"") .contains("\"password\" : \"\"") .contains("\"email\" : \"\"") .contains("\"serveraddress\" : \"\""); } @Test void asDockerConfigurationWithUserAuth() { Docker docker = new Docker(); docker.setBuilderRegistry( new Docker.DockerRegistry("user1", "secret1", "https://docker1.example.com", "docker1@example.com")); docker.setPublishRegistry( new Docker.DockerRegistry("user2", "secret2", "https://docker2.example.com", "docker2@example.com")); BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker); DockerRegistryAuthentication builderRegistryAuthentication = dockerConfiguration .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); String authHeader = builderRegistryAuthentication.getAuthHeader(); assertThat(authHeader).isNotNull(); assertThat(decoded(authHeader)).contains("\"username\" : \"user1\"") .contains("\"password\" : \"secret1\"") .contains("\"email\" : \"docker1@example.com\"") .contains("\"serveraddress\" : \"https://docker1.example.com\""); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); authHeader = publishRegistryAuthentication.getAuthHeader(); assertThat(authHeader).isNotNull(); assertThat(decoded(authHeader)).contains("\"username\" : \"user2\"") .contains("\"password\" : \"secret2\"") .contains("\"email\" : \"docker2@example.com\"") .contains("\"serveraddress\" : \"https://docker2.example.com\""); } @Test void asDockerConfigurationWithIncompleteBuilderUserAuthFails() { Docker docker = new Docker(); docker.setBuilderRegistry( new Docker.DockerRegistry("user", null, "https://docker.example.com", "docker@example.com")); assertThatIllegalArgumentException().isThrownBy(() -> createDockerConfiguration(docker)) .withMessageContaining("Invalid Docker builder registry configuration"); } @Test void asDockerConfigurationWithIncompletePublishUserAuthFails() { Docker docker = new Docker(); docker.setPublishRegistry( new Docker.DockerRegistry("user", null, "https://docker.example.com", "docker@example.com")); assertThatIllegalArgumentException().isThrownBy(() -> createDockerConfiguration(docker)) .withMessageContaining("Invalid Docker publish registry configuration"); } @Test void asDockerConfigurationWithIncompletePublishUserAuthDoesNotFailIfPublishIsDisabled() { Docker docker = new Docker(); docker.setPublishRegistry( new Docker.DockerRegistry("user", null, "https://docker.example.com", "docker@example.com")); BuilderDockerConfiguration dockerConfiguration = docker.asDockerConfiguration(this.log, false); assertThat(dockerConfiguration.publishRegistryAuthentication()).isNull(); } @Test void asDockerConfigurationWithTokenAuth() { Docker docker = new Docker(); docker.setBuilderRegistry(new Docker.DockerRegistry("token1")); docker.setPublishRegistry(new Docker.DockerRegistry("token2")); BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker); DockerRegistryAuthentication builderRegistryAuthentication = dockerConfiguration .builderRegistryAuthentication(); assertThat(builderRegistryAuthentication).isNotNull(); String authHeader = builderRegistryAuthentication.getAuthHeader(); assertThat(authHeader).isNotNull(); assertThat(decoded(authHeader)).contains("\"identitytoken\" : \"token1\""); DockerRegistryAuthentication publishRegistryAuthentication = dockerConfiguration .publishRegistryAuthentication(); assertThat(publishRegistryAuthentication).isNotNull(); authHeader = publishRegistryAuthentication.getAuthHeader(); assertThat(authHeader).isNotNull(); assertThat(decoded(authHeader)).contains("\"identitytoken\" : \"token2\""); } @Test void asDockerConfigurationWithUserAndTokenAuthFails() { Docker.DockerRegistry dockerRegistry = new Docker.DockerRegistry(); dockerRegistry.setUsername("user"); dockerRegistry.setPassword("secret"); dockerRegistry.setToken("token"); Docker docker = new Docker(); docker.setBuilderRegistry(dockerRegistry); assertThatIllegalArgumentException().isThrownBy(() -> createDockerConfiguration(docker)) .withMessageContaining("Invalid Docker builder registry configuration"); } @Test void asDockerConfigurationWithUserAndTokenAuthDoesNotFailIfPublishingIsDisabled() { Docker.DockerRegistry dockerRegistry = new Docker.DockerRegistry(); dockerRegistry.setUsername("user"); dockerRegistry.setPassword("secret"); dockerRegistry.setToken("token"); Docker docker = new Docker(); docker.setPublishRegistry(dockerRegistry); BuilderDockerConfiguration dockerConfiguration = docker.asDockerConfiguration(this.log, false); assertThat(dockerConfiguration.publishRegistryAuthentication()).isNull(); } private BuilderDockerConfiguration createDockerConfiguration(Docker docker) { return docker.asDockerConfiguration(this.log, true); } String decoded(String value) { return new String(Base64.getDecoder().decode(value)); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/EnvVariablesTests.java ================================================ /* * 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.LinkedHashMap; import java.util.Map; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; /** * Tests for {@link EnvVariables}. * * @author Dmytro Nosan */ class EnvVariablesTests { @Test void asNull() { Map args = new EnvVariables(null).asMap(); assertThat(args).isEmpty(); } @Test void asArray() { assertThat(new EnvVariables(getTestArgs()).asArray()).contains("key=My Value", "key1= tt ", "key2= ", "key3="); } @Test void asMap() { assertThat(new EnvVariables(getTestArgs()).asMap()).containsExactly(entry("key", "My Value"), entry("key1", " tt "), entry("key2", " "), entry("key3", "")); } private Map getTestArgs() { Map args = new LinkedHashMap<>(); args.put("key", "My Value"); args.put("key1", " tt "); args.put("key2", " "); args.put("key3", null); return args; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ExcludeFilterTests.java ================================================ /* * 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.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.apache.maven.artifact.Artifact; import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link ExcludeFilter}. * * @author Stephane Nicoll * @author David Turanski */ @SuppressWarnings({ "rawtypes", "unchecked" }) class ExcludeFilterTests { @Test void excludeSimple() throws ArtifactFilterException { ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar"))); Set result = filter.filter(Collections.singleton(createArtifact("com.foo", "bar"))); assertThat(result).isEmpty(); } @Test void excludeGroupIdNoMatch() throws ArtifactFilterException { ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar"))); Artifact artifact = createArtifact("com.baz", "bar"); Set result = filter.filter(Collections.singleton(artifact)); assertThat(result).hasSize(1); assertThat(result.iterator().next()).isSameAs(artifact); } @Test void excludeArtifactIdNoMatch() throws ArtifactFilterException { ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar"))); Artifact artifact = createArtifact("com.foo", "biz"); Set result = filter.filter(Collections.singleton(artifact)); assertThat(result).hasSize(1); assertThat(result.iterator().next()).isSameAs(artifact); } @Test void excludeClassifier() throws ArtifactFilterException { ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar", "jdk5"))); Set result = filter.filter(Collections.singleton(createArtifact("com.foo", "bar", "jdk5"))); assertThat(result).isEmpty(); } @Test void excludeClassifierNoTargetClassifier() throws ArtifactFilterException { ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar", "jdk5"))); Artifact artifact = createArtifact("com.foo", "bar"); Set result = filter.filter(Collections.singleton(artifact)); assertThat(result).hasSize(1); assertThat(result.iterator().next()).isSameAs(artifact); } @Test void excludeClassifierNoMatch() throws ArtifactFilterException { ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar", "jdk5"))); Artifact artifact = createArtifact("com.foo", "bar", "jdk6"); Set result = filter.filter(Collections.singleton(artifact)); assertThat(result).hasSize(1); assertThat(result.iterator().next()).isSameAs(artifact); } @Test void excludeMulti() throws ArtifactFilterException { ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar"), createExclude("com.foo", "bar2"), createExclude("org.acme", "app"))); Set artifacts = new HashSet<>(); artifacts.add(createArtifact("com.foo", "bar")); artifacts.add(createArtifact("com.foo", "bar")); Artifact anotherAcme = createArtifact("org.acme", "another-app"); artifacts.add(anotherAcme); Set result = filter.filter(artifacts); assertThat(result).hasSize(1); assertThat(result.iterator().next()).isSameAs(anotherAcme); } private Exclude createExclude(String groupId, String artifactId) { return createExclude(groupId, artifactId, null); } private Exclude createExclude(String groupId, String artifactId, @Nullable String classifier) { Exclude exclude = new Exclude(); exclude.setGroupId(groupId); exclude.setArtifactId(artifactId); if (classifier != null) { exclude.setClassifier(classifier); } return exclude; } private Artifact createArtifact(String groupId, String artifactId, @Nullable String classifier) { Artifact a = mock(Artifact.class); given(a.getGroupId()).willReturn(groupId); given(a.getArtifactId()).willReturn(artifactId); given(a.getClassifier()).willReturn(classifier); return a; } private Artifact createArtifact(String groupId, String artifactId) { return createArtifact(groupId, artifactId, null); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ImageTests.java ================================================ /* * 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.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Function; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.artifact.versioning.VersionRange; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.build.BuildRequest; import org.springframework.boot.buildpack.platform.build.BuildpackReference; import org.springframework.boot.buildpack.platform.build.Cache; 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.ImageReference; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.maven.CacheInfo.BindCacheInfo; import org.springframework.boot.maven.CacheInfo.VolumeCacheInfo; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; import static org.mockito.Mockito.mock; /** * Tests for {@link Image}. * * @author Phillip Webb * @author Scott Frederick * @author Jeroen Meijer * @author Rafael Ceccone * @author Moritz Halbritter */ class ImageTests { @Test void getBuildRequestWhenNameIsNullDeducesName() { BuildRequest request = new Image().getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getName()).hasToString("docker.io/library/my-app:0.0.1-SNAPSHOT"); } @Test void getBuildRequestWhenNameIsSetUsesName() { Image image = new Image(); image.name = "demo"; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getName()).hasToString("docker.io/library/demo:latest"); } @Test void getBuildRequestWhenNoCustomizationsUsesDefaults() { BuildRequest request = new Image().getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getName()).hasToString("docker.io/library/my-app:0.0.1-SNAPSHOT"); assertThat(request.getBuilder().toString()).contains("paketobuildpacks/builder-noble-java-tiny"); assertThat(request.isTrustBuilder()).isTrue(); assertThat(request.getRunImage()).isNull(); assertThat(request.getEnv()).isEmpty(); assertThat(request.isCleanCache()).isFalse(); assertThat(request.isVerboseLogging()).isFalse(); assertThat(request.getPullPolicy()).isEqualTo(PullPolicy.ALWAYS); assertThat(request.isPublish()).isFalse(); assertThat(request.getBuildpacks()).isEmpty(); assertThat(request.getBindings()).isEmpty(); assertThat(request.getNetwork()).isNull(); assertThat(request.getImagePlatform()).isNull(); } @Test void getBuildRequestWhenHasBuilderUsesBuilder() { Image image = new Image(); image.builder = "springboot/builder:2.2.x"; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getBuilder()).hasToString("docker.io/springboot/builder:2.2.x"); assertThat(request.isTrustBuilder()).isFalse(); } @Test void getBuildRequestWhenHasBuilderAndTrustBuilderUsesBuilderAndTrustBuilder() { Image image = new Image(); image.builder = "springboot/builder:2.2.x"; image.trustBuilder = true; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getBuilder()).hasToString("docker.io/springboot/builder:2.2.x"); assertThat(request.isTrustBuilder()).isTrue(); } @Test void getBuildRequestWhenHasDefaultBuilderAndTrustBuilderUsesTrustBuilder() { Image image = new Image(); image.trustBuilder = false; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getBuilder().toString()).contains("paketobuildpacks/builder-noble-java-tiny"); assertThat(request.isTrustBuilder()).isFalse(); } @Test void getBuildRequestWhenHasRunImageUsesRunImage() { Image image = new Image(); image.runImage = "springboot/run:latest"; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getRunImage()).hasToString("docker.io/springboot/run:latest"); } @Test void getBuildRequestWhenHasEnvUsesEnv() { Image image = new Image(); image.env = Collections.singletonMap("test", "test"); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getEnv()).containsExactly(entry("test", "test")); } @Test void getBuildRequestWhenHasCleanCacheUsesCleanCache() { Image image = new Image(); image.cleanCache = true; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.isCleanCache()).isTrue(); } @Test void getBuildRequestWhenHasVerboseLoggingUsesVerboseLogging() { Image image = new Image(); image.verboseLogging = true; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.isVerboseLogging()).isTrue(); } @Test void getBuildRequestWhenHasPullPolicyUsesPullPolicy() { Image image = new Image(); image.setPullPolicy(PullPolicy.NEVER); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getPullPolicy()).isEqualTo(PullPolicy.NEVER); } @Test void getBuildRequestWhenHasPublishUsesPublish() { Image image = new Image(); image.publish = true; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.isPublish()).isTrue(); } @Test void getBuildRequestWhenHasBuildpacksUsesBuildpacks() { Image image = new Image(); image.buildpacks = Arrays.asList("example/buildpack1@0.0.1", "example/buildpack2@0.0.2"); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getBuildpacks()).containsExactly(BuildpackReference.of("example/buildpack1@0.0.1"), BuildpackReference.of("example/buildpack2@0.0.2")); } @Test void getBuildRequestWhenHasBindingsUsesBindings() { Image image = new Image(); image.bindings = Arrays.asList("host-src:container-dest:ro", "volume-name:container-dest:rw"); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getBindings()).containsExactly(Binding.of("host-src:container-dest:ro"), Binding.of("volume-name:container-dest:rw")); } @Test void getBuildRequestWhenNetworkUsesNetwork() { Image image = new Image(); image.network = "test"; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getNetwork()).isEqualTo("test"); } @Test void getBuildRequestWhenHasTagsUsesTags() { Image image = new Image(); image.tags = Arrays.asList("my-app:latest", "example.com/my-app:0.0.1-SNAPSHOT", "example.com/my-app:latest"); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getTags()).containsExactly(ImageReference.of("my-app:latest"), ImageReference.of("example.com/my-app:0.0.1-SNAPSHOT"), ImageReference.of("example.com/my-app:latest")); } @Test void getBuildRequestWhenHasBuildWorkspaceVolumeUsesWorkspace() { Image image = new Image(); image.buildWorkspace = CacheInfo.fromVolume(new VolumeCacheInfo("build-work-vol")); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getBuildWorkspace()).isEqualTo(Cache.volume("build-work-vol")); } @Test void getBuildRequestWhenHasBuildCacheVolumeUsesCache() { Image image = new Image(); image.buildCache = CacheInfo.fromVolume(new VolumeCacheInfo("build-cache-vol")); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getBuildCache()).isEqualTo(Cache.volume("build-cache-vol")); } @Test void getBuildRequestWhenHasLaunchCacheVolumeUsesCache() { Image image = new Image(); image.launchCache = CacheInfo.fromVolume(new VolumeCacheInfo("launch-cache-vol")); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getLaunchCache()).isEqualTo(Cache.volume("launch-cache-vol")); } @Test void getBuildRequestWhenHasBuildWorkspaceBindUsesWorkspace() { Image image = new Image(); image.buildWorkspace = CacheInfo.fromBind(new BindCacheInfo("build-work-dir")); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getBuildWorkspace()).isEqualTo(Cache.bind("build-work-dir")); } @Test void getBuildRequestWhenHasBuildCacheBindUsesCache() { Image image = new Image(); image.buildCache = CacheInfo.fromBind(new BindCacheInfo("build-cache-dir")); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getBuildCache()).isEqualTo(Cache.bind("build-cache-dir")); } @Test void getBuildRequestWhenHasLaunchCacheBindUsesCache() { Image image = new Image(); image.launchCache = CacheInfo.fromBind(new BindCacheInfo("launch-cache-dir")); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getLaunchCache()).isEqualTo(Cache.bind("launch-cache-dir")); } @Test void getBuildRequestWhenHasCreatedDateUsesCreatedDate() { Image image = new Image(); image.createdDate = "2020-07-01T12:34:56Z"; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getCreatedDate()).isEqualTo("2020-07-01T12:34:56Z"); } @Test void getBuildRequestWhenHasApplicationDirectoryUsesApplicationDirectory() { Image image = new Image(); image.applicationDirectory = "/application"; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getApplicationDirectory()).isEqualTo("/application"); } @Test void getBuildRequestWhenHasNoSecurityOptionsUsesNoSecurityOptions() { Image image = new Image(); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getSecurityOptions()).isNull(); } @Test void getBuildRequestWhenHasSecurityOptionsUsesSecurityOptions() { Image image = new Image(); image.securityOptions = List.of("label=user:USER", "label=role:ROLE"); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getSecurityOptions()).containsExactly("label=user:USER", "label=role:ROLE"); } @Test void getBuildRequestWhenHasEmptySecurityOptionsUsesSecurityOptions() { Image image = new Image(); image.securityOptions = Collections.emptyList(); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getSecurityOptions()).isEmpty(); } @Test void getBuildRequestWhenHasImagePlatformUsesImagePlatform() { Image image = new Image(); image.imagePlatform = "linux/arm64"; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getImagePlatform()).isEqualTo(ImagePlatform.of("linux/arm64")); } @Test void getBuildRequestWhenImagePlatformIsEmptyDoesntSetImagePlatform() { Image image = new Image(); image.imagePlatform = ""; BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getImagePlatform()).isNull(); } private Artifact createArtifact() { return new DefaultArtifact("com.example", "my-app", VersionRange.createFromVersion("0.0.1-SNAPSHOT"), "compile", "jar", null, new DefaultArtifactHandler()); } private Function mockApplicationContent() { return (owner) -> mock(TarArchive.class); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/IncludeFilterTests.java ================================================ /* * 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.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.apache.maven.artifact.Artifact; import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link org.springframework.boot.maven.IncludeFilter}. * * @author David Turanski */ @SuppressWarnings({ "rawtypes", "unchecked" }) class IncludeFilterTests { @Test void includeSimple() throws ArtifactFilterException { IncludeFilter filter = new IncludeFilter(Arrays.asList(createInclude("com.foo", "bar"))); Artifact artifact = createArtifact("com.foo", "bar"); Set result = filter.filter(Collections.singleton(artifact)); assertThat(result).hasSize(1); assertThat(result.iterator().next()).isSameAs(artifact); } @Test void includeGroupIdNoMatch() throws ArtifactFilterException { IncludeFilter filter = new IncludeFilter(Arrays.asList(createInclude("com.foo", "bar"))); Artifact artifact = createArtifact("com.baz", "bar"); Set result = filter.filter(Collections.singleton(artifact)); assertThat(result).isEmpty(); } @Test void includeArtifactIdNoMatch() throws ArtifactFilterException { IncludeFilter filter = new IncludeFilter(Arrays.asList(createInclude("com.foo", "bar"))); Artifact artifact = createArtifact("com.foo", "biz"); Set result = filter.filter(Collections.singleton(artifact)); assertThat(result).isEmpty(); } @Test void includeClassifier() throws ArtifactFilterException { IncludeFilter filter = new IncludeFilter(Arrays.asList(createInclude("com.foo", "bar", "jdk5"))); Artifact artifact = createArtifact("com.foo", "bar", "jdk5"); Set result = filter.filter(Collections.singleton(artifact)); assertThat(result).hasSize(1); assertThat(result.iterator().next()).isSameAs(artifact); } @Test void includeClassifierNoTargetClassifier() throws ArtifactFilterException { IncludeFilter filter = new IncludeFilter(Arrays.asList(createInclude("com.foo", "bar", "jdk5"))); Artifact artifact = createArtifact("com.foo", "bar"); Set result = filter.filter(Collections.singleton(artifact)); assertThat(result).isEmpty(); } @Test void includeClassifierNoMatch() throws ArtifactFilterException { IncludeFilter filter = new IncludeFilter(Arrays.asList(createInclude("com.foo", "bar", "jdk5"))); Artifact artifact = createArtifact("com.foo", "bar", "jdk6"); Set result = filter.filter(Collections.singleton(artifact)); assertThat(result).isEmpty(); } @Test void includeMulti() throws ArtifactFilterException { IncludeFilter filter = new IncludeFilter(Arrays.asList(createInclude("com.foo", "bar"), createInclude("com.foo", "bar2"), createInclude("org.acme", "app"))); Set artifacts = new HashSet<>(); artifacts.add(createArtifact("com.foo", "bar")); artifacts.add(createArtifact("com.foo", "bar")); Artifact anotherAcme = createArtifact("org.acme", "another-app"); artifacts.add(anotherAcme); Set result = filter.filter(artifacts); assertThat(result).hasSize(2); } private Include createInclude(String groupId, String artifactId) { return createInclude(groupId, artifactId, null); } private Include createInclude(String groupId, String artifactId, @Nullable String classifier) { Include include = new Include(); include.setGroupId(groupId); include.setArtifactId(artifactId); if (classifier != null) { include.setClassifier(classifier); } return include; } private Artifact createArtifact(String groupId, String artifactId, @Nullable String classifier) { Artifact a = mock(Artifact.class); given(a.getGroupId()).willReturn(groupId); given(a.getArtifactId()).willReturn(artifactId); given(a.getClassifier()).willReturn(classifier); return a; } private Artifact createArtifact(String groupId, String artifactId) { return createArtifact(groupId, artifactId, null); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/JarTypeFilterTests.java ================================================ /* * 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.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import org.apache.maven.artifact.Artifact; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link JarTypeFilter}. * * @author Andy Wilkinson */ class JarTypeFilterTests { @TempDir @SuppressWarnings("NullAway.Init") Path temp; @Test void whenArtifactHasNoJarTypeThenItIsIncluded() { assertThat(new JarTypeFilter().filter(createArtifact(null))).isFalse(); } @Test void whenArtifactHasJarTypeThatIsNotExcludedThenItIsIncluded() { assertThat(new JarTypeFilter().filter(createArtifact("something-included"))).isFalse(); } @Test void whenArtifactHasDependenciesStarterJarTypeThenItIsExcluded() { assertThat(new JarTypeFilter().filter(createArtifact("dependencies-starter"))).isTrue(); } @Test void whenArtifactHasAnnotationProcessorJarTypeThenItIsExcluded() { assertThat(new JarTypeFilter().filter(createArtifact("annotation-processor"))).isTrue(); } @Test void whenArtifactHasDevelopmentToolJarTypeThenItIsExcluded() { assertThat(new JarTypeFilter().filter(createArtifact("development-tool"))).isTrue(); } @Test void whenArtifactHasNoManifestFileThenItIsIncluded() { assertThat(new JarTypeFilter().filter(createArtifactWithNoManifest())).isFalse(); } private Artifact createArtifact(@Nullable String springBootJarType) { Path jarPath = this.temp.resolve("test.jar"); Manifest manifest = new Manifest(); manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); if (springBootJarType != null) { manifest.getMainAttributes().putValue("Spring-Boot-Jar-Type", springBootJarType); } try { new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest).close(); } catch (IOException ex) { throw new RuntimeException(ex); } return mockArtifact(jarPath); } private Artifact createArtifactWithNoManifest() { Path jarPath = this.temp.resolve("test.jar"); try { new JarOutputStream(new FileOutputStream(jarPath.toFile())).close(); } catch (IOException ex) { throw new RuntimeException(ex); } return mockArtifact(jarPath); } private Artifact mockArtifact(Path jarPath) { Artifact artifact = mock(Artifact.class); given(artifact.getFile()).willReturn(jarPath.toFile()); return artifact; } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/JavaCompilerPluginConfigurationTests.java ================================================ /* * 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.io.IOException; import java.io.StringReader; import java.util.Arrays; import java.util.Properties; import org.apache.maven.model.Plugin; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.codehaus.plexus.util.xml.Xpp3DomBuilder; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link JavaCompilerPluginConfiguration}. * * @author Scott Frederick */ class JavaCompilerPluginConfigurationTests { private MavenProject project; private Plugin plugin; @BeforeEach void setUp() { this.project = mock(MavenProject.class); this.plugin = mock(Plugin.class); given(this.project.getPlugin(anyString())).willReturn(this.plugin); } @Test void versionsAreNullWithNoConfiguration() { given(this.plugin.getConfiguration()).willReturn(null); given(this.project.getProperties()).willReturn(new Properties()); JavaCompilerPluginConfiguration configuration = new JavaCompilerPluginConfiguration(this.project); assertThat(configuration.getSourceMajorVersion()).isNull(); assertThat(configuration.getTargetMajorVersion()).isNull(); assertThat(configuration.getReleaseVersion()).isNull(); } @Test void versionsAreReturnedFromConfiguration() throws IOException, XmlPullParserException { Xpp3Dom dom = buildConfigurationDom("1.9", "11", "12"); given(this.plugin.getConfiguration()).willReturn(dom); Properties properties = new Properties(); properties.setProperty("maven.compiler.source", "1.8"); properties.setProperty("maven.compiler.target", "10"); properties.setProperty("maven.compiler.release", "11"); given(this.project.getProperties()).willReturn(properties); JavaCompilerPluginConfiguration configuration = new JavaCompilerPluginConfiguration(this.project); assertThat(configuration.getSourceMajorVersion()).isEqualTo("9"); assertThat(configuration.getTargetMajorVersion()).isEqualTo("11"); assertThat(configuration.getReleaseVersion()).isEqualTo("12"); } @Test void versionsAreReturnedFromProperties() { given(this.plugin.getConfiguration()).willReturn(null); Properties properties = new Properties(); properties.setProperty("maven.compiler.source", "1.8"); properties.setProperty("maven.compiler.target", "11"); properties.setProperty("maven.compiler.release", "12"); given(this.project.getProperties()).willReturn(properties); JavaCompilerPluginConfiguration configuration = new JavaCompilerPluginConfiguration(this.project); assertThat(configuration.getSourceMajorVersion()).isEqualTo("8"); assertThat(configuration.getTargetMajorVersion()).isEqualTo("11"); assertThat(configuration.getReleaseVersion()).isEqualTo("12"); } private Xpp3Dom buildConfigurationDom(String... properties) throws IOException, XmlPullParserException { return Xpp3DomBuilder .build(new StringReader("" + Arrays.toString(properties) + "")); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/MavenBuildOutputTimestampTests.java ================================================ /* * 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.nio.file.attribute.FileTime; import java.time.Instant; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link MavenBuildOutputTimestamp}. * * @author Moritz Halbritter */ class MavenBuildOutputTimestampTests { @Test void shouldParseNull() { assertThat(parse(null)).isNull(); } @Test void shouldParseSingleDigit() { assertThat(parse("0")).isEqualTo(Instant.parse("1970-01-01T00:00:00Z")); } @Test void shouldNotParseSingleCharacter() { assertThat(parse("a")).isNull(); } @Test void shouldParseIso8601() { assertThat(parse("2011-12-03T10:15:30Z")).isEqualTo(Instant.parse("2011-12-03T10:15:30.000Z")); } @Test void shouldParseIso8601WithMilliseconds() { assertThat(parse("2011-12-03T10:15:30.123Z")).isEqualTo(Instant.parse("2011-12-03T10:15:30.123Z")); } @Test void shouldFailIfIso8601BeforeMin() { assertThatIllegalArgumentException().isThrownBy(() -> parse("1970-01-01T00:00:00Z")) .withMessage( "'1970-01-01T00:00:00Z' is not within the valid range 1980-01-01T00:00:02Z to 2099-12-31T23:59:59Z"); } @Test void shouldFailIfIso8601AfterMax() { assertThatIllegalArgumentException().isThrownBy(() -> parse("2100-01-01T00:00:00Z")) .withMessage( "'2100-01-01T00:00:00Z' is not within the valid range 1980-01-01T00:00:02Z to 2099-12-31T23:59:59Z"); } @Test void shouldFailIfNotIso8601() { assertThatIllegalArgumentException().isThrownBy(() -> parse("dummy")) .withMessage("Can't parse 'dummy' to instant"); } @Test void shouldParseIso8601WithOffset() { assertThat(parse("2019-10-05T20:37:42+06:00")).isEqualTo(Instant.parse("2019-10-05T14:37:42Z")); } @Test void shouldParseToFileTime() { assertThat(parseFileTime(null)).isEqualTo(null); assertThat(parseFileTime("0")).isEqualTo(FileTime.fromMillis(0)); assertThat(parseFileTime("2019-10-05T14:37:42Z")).isEqualTo(FileTime.fromMillis(1570286262000L)); } private static @Nullable Instant parse(@Nullable String timestamp) { return new MavenBuildOutputTimestamp(timestamp).toInstant(); } private static @Nullable FileTime parseFileTime(@Nullable String timestamp) { return new MavenBuildOutputTimestamp(timestamp).toFileTime(); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/PropertiesMergingResourceTransformerTests.java ================================================ /* * 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.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link PropertiesMergingResourceTransformer}. * * @author Dave Syer */ class PropertiesMergingResourceTransformerTests { private final PropertiesMergingResourceTransformer transformer = new PropertiesMergingResourceTransformer(); @Test void testProcess() throws Exception { assertThat(this.transformer.hasTransformedResource()).isFalse(); this.transformer.processResource("foo", new ByteArrayInputStream("foo=bar".getBytes()), Collections.emptyList(), 0); assertThat(this.transformer.hasTransformedResource()).isTrue(); } @Test void testMerge() throws Exception { this.transformer.processResource("foo", new ByteArrayInputStream("foo=bar".getBytes()), Collections.emptyList(), 0); this.transformer.processResource("bar", new ByteArrayInputStream("foo=spam".getBytes()), Collections.emptyList(), 0); assertThat(this.transformer.getData().getProperty("foo")).isEqualTo("bar,spam"); } @Test void testOutput() throws Exception { this.transformer.setResource("foo"); long time = 1592911068000L; this.transformer.processResource("foo", new ByteArrayInputStream("foo=bar".getBytes()), Collections.emptyList(), time); ByteArrayOutputStream out = new ByteArrayOutputStream(); JarOutputStream os = new JarOutputStream(out); this.transformer.modifyOutputStream(os); os.flush(); os.close(); byte[] bytes = out.toByteArray(); assertThat(bytes).isNotEmpty(); List entries = new ArrayList<>(); try (JarInputStream is = new JarInputStream(new ByteArrayInputStream(bytes))) { JarEntry entry; while ((entry = is.getNextJarEntry()) != null) { entries.add(entry); } } assertThat(entries).hasSize(1); assertThat(entries.get(0).getTime()).isEqualTo(time); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/RunArgumentsTests.java ================================================ /* * 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 org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link RunArguments}. * * @author Stephane Nicoll */ class RunArgumentsTests { @Test void parseNull() { String[] args = parseArgs(null); assertThat(args).isNotNull(); assertThat(args).isEmpty(); } @Test @SuppressWarnings("NullAway") // Maven can't handle nullable arrays void parseNullArray() { String[] args = new RunArguments((String[]) null).asArray(); assertThat(args).isNotNull(); assertThat(args).isEmpty(); } @Test void parseArrayContainingNullValue() { String[] args = new RunArguments(new String[] { "foo", null, "bar" }).asArray(); assertThat(args).isNotNull(); assertThat(args).containsOnly("foo", "bar"); } @Test void parseArrayContainingEmptyValue() { String[] args = new RunArguments(new String[] { "foo", "", "bar" }).asArray(); assertThat(args).isNotNull(); assertThat(args).containsOnly("foo", "", "bar"); } @Test void parseEmpty() { String[] args = parseArgs(" "); assertThat(args).isNotNull(); assertThat(args).isEmpty(); } @Test void parseDebugFlag() { String[] args = parseArgs("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"); assertThat(args).hasSize(1); assertThat(args[0]).isEqualTo("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"); } @Test void parseWithExtraSpaces() { String[] args = parseArgs(" -Dfoo=bar -Dfoo2=bar2 "); assertThat(args).hasSize(2); assertThat(args[0]).isEqualTo("-Dfoo=bar"); assertThat(args[1]).isEqualTo("-Dfoo2=bar2"); } @Test void parseWithNewLinesAndTabs() { String[] args = parseArgs(" -Dfoo=bar \n\t\t -Dfoo2=bar2 "); assertThat(args).hasSize(2); assertThat(args[0]).isEqualTo("-Dfoo=bar"); assertThat(args[1]).isEqualTo("-Dfoo2=bar2"); } @Test void quoteHandledProperly() { String[] args = parseArgs("-Dvalue=\"My Value\" "); assertThat(args).hasSize(1); assertThat(args[0]).isEqualTo("-Dvalue=My Value"); } private String[] parseArgs(@Nullable String args) { return new RunArguments(args).asArray(); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/SystemPropertyFormatterTests.java ================================================ /* * 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 org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link SystemPropertyFormatter}. */ class SystemPropertyFormatterTests { @Test void parseEmpty() { assertThat(SystemPropertyFormatter.format(null, null)).isEmpty(); } @Test void parseOnlyKey() { assertThat(SystemPropertyFormatter.format("key1", null)).isEqualTo("-Dkey1"); } @Test void parseKeyWithValue() { assertThat(SystemPropertyFormatter.format("key1", "value1")).isEqualTo("-Dkey1=value1"); } @Test void parseKeyWithEmptyValue() { assertThat(SystemPropertyFormatter.format("key1", "")).isEqualTo("-Dkey1"); } @Test void parseKeyWithOnlySpaces() { assertThat(SystemPropertyFormatter.format("key1", " ")).isEqualTo("-Dkey1= "); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/sample/ClassWithMainMethod.java ================================================ /* * 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.sample; /** * Sample class with a main method. * * @author Phillip Webb */ public class ClassWithMainMethod { public void run() { System.out.println("Hello World"); } public static void main(String[] args) { new ClassWithMainMethod().run(); } } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/sample/ClassWithoutMainMethod.java ================================================ /* * 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.sample; /** * Sample class without a main method. * * @author Phillip Webb */ public class ClassWithoutMainMethod { } ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/sample/package-info.java ================================================ /* * 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. */ /** * Sample code for testing the Maven plugin for Spring Boot. */ @NullMarked package org.springframework.boot.maven.sample; import org.jspecify.annotations.NullMarked; ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/resources/application-layer-no-filter.xml ================================================ my-layer ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/resources/dependencies-layer-no-filter.xml ================================================ my-deps ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/resources/layers.xml ================================================ META-INF/resources/** *.properties **/application*.* *:*:*-SNAPSHOT com.acme:* my-deps my-dependencies-name snapshot-dependencies my-resources configuration application ================================================ FILE: build-plugin/spring-boot-maven-plugin/src/test/resources/resource-layer-no-filter.xml ================================================ my-layer ================================================ FILE: build.gradle ================================================ /* * 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. */ plugins { id "base" } description = "Spring Boot Build" defaultTasks 'build' allprojects { group = "org.springframework.boot" } subprojects { apply plugin: "org.springframework.boot.conventions" repositories { mavenCentral() maven { name = "Shibboleth Releases" url = "https://build.shibboleth.net/nexus/content/repositories/releases" content { includeGroup "org.opensaml" includeGroup "net.shibboleth" } } spring.mavenRepositories() } configurations.all { resolutionStrategy.cacheChangingModulesFor 0, "minutes" } } ================================================ FILE: buildSrc/SpringRepositorySupport.groovy ================================================ /* * Copyright 2012-2024 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. */ // // This script can be used in the `pluginManagement` block of a `settings.gradle` file to provide // support for spring maven repositories. // // To use the script add the following as the first line in the `pluginManagement` block: // // evaluate(new File("${rootDir}/buildSrc/SpringRepositorySupport.groovy")).apply(this) // // You can then use `spring.mavenRepositories()` to add the Spring repositories required for the // version being built. // import java.util.function.* def apply(settings) { def version = property(settings, 'version') def buildType = property(settings, 'spring.build-type') SpringRepositoriesExtension.addTo(settings.pluginManagement.repositories, version, buildType) settings.gradle.allprojects { SpringRepositoriesExtension.addTo(repositories, version, buildType) } } private def property(settings, name) { def value = null try { value = settings.gradle.parent?.rootProject?.findProperty(name) } catch (Exception ex) { } try { value = (value != null) ? value : settings.ext.find(name) } catch (Exception ex) { } value = (value != null) ? value : loadProperty(settings, name) return value } private def loadProperty(settings, name) { def scriptDir = new File(getClass().protectionDomain.codeSource.location.path).parent new File(scriptDir, "../gradle.properties").withInputStream { def properties = new Properties() properties.load(it) return properties.get(name) } } return this class SpringRepositoriesExtension { private final def repositories private final def version private final def buildType private final UnaryOperator environment @javax.inject.Inject SpringRepositoriesExtension(repositories, version, buildType) { this(repositories, version, buildType, System::getenv) } SpringRepositoriesExtension(repositories, version, buildType, environment) { this.repositories = repositories this.version = version this.buildType = buildType this.environment = environment } def mavenRepositories() { addRepositories { } } def mavenRepositoriesFor(version) { addRepositories(version) { } } def mavenRepositoriesExcludingBootGroup() { addRepositories { maven -> maven.content { content -> content.excludeGroup("org.springframework.boot") } } } private void addRepositories(action) { addRepositories(this.version, action) } private void addRepositories(version, action) { addCommercialRepositoryIfNecessary("release", false, "/spring-enterprise-maven-prod-local", action) if (version.endsWith("-SNAPSHOT")) { addCommercialRepositoryIfNecessary("snapshot", true, "/spring-enterprise-maven-dev-local", action) addOssRepository("snapshot", true, "/snapshot", action) } } private void addOssRepository(id, snapshot, path, action) { def name = "spring-oss-" + id def url = "https://repo.spring.io" + path addRepository(name, snapshot, url, action) } private void addCommercialRepositoryIfNecessary(id, snapshot, path, action) { if (!"commercial".equalsIgnoreCase(this.buildType)) return def name = "spring-commercial-" + id def url = fromEnv("COMMERCIAL_%SREPO_URL", id, "https://usw1.packages.broadcom.com" + path) def username = fromEnv("COMMERCIAL_%SREPO_USERNAME", id) def password = fromEnv("COMMERCIAL_%SREPO_PASSWORD", id) addRepository(name, snapshot, url, { maven -> maven.credentials { credentials -> credentials.setUsername(username) credentials.setPassword(password) } action(maven) }) } private void addRepository(name, snapshot, url, action) { this.repositories.maven { maven -> maven.setName(name) maven.setUrl(url) maven.mavenContent { mavenContent -> if (snapshot) { mavenContent.snapshotsOnly() } else { mavenContent.releasesOnly() } } action(maven) } } private String fromEnv(template, id) { return fromEnv(template, id, null) } private String fromEnv(template, id, defaultValue) { String value = this.environment.apply(template.formatted(id.toUpperCase() + "_")) value = (value != null) ? value : this.environment.apply(template.formatted("")) return (value != null) ? value : defaultValue } static def addTo(repositories, version, buildType) { repositories.extensions.create("spring", SpringRepositoriesExtension.class, repositories, version, buildType) } } ================================================ FILE: buildSrc/build.gradle ================================================ /* * 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. */ plugins { id "java-gradle-plugin" id "io.spring.javaformat" version "${javaFormatVersion}" id "checkstyle" id "eclipse" id "org.jetbrains.dokka" version "2.1.0" } repositories { mavenCentral() spring.mavenRepositoriesFor("${springFrameworkVersion}") gradlePluginPortal() maven { url = "https://repo.spring.io/snapshot" } } java { sourceCompatibility = 17 targetCompatibility = 17 } checkstyle { toolVersion = "${checkstyleToolVersion}" } dependencies { checkstyle("com.puppycrawl.tools:checkstyle:${checkstyle.toolVersion}") checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}") implementation(platform("com.fasterxml.jackson:jackson-bom:${jackson2Version}")) implementation(platform("tools.jackson:jackson-bom:${jacksonVersion}")) implementation(platform("org.springframework:spring-framework-bom:${springFrameworkVersion}")) implementation("com.github.node-gradle:gradle-node-plugin:7.1.0") implementation("com.gradle:develocity-gradle-plugin:4.2.2") implementation("com.tngtech.archunit:archunit:1.4.1") implementation("commons-codec:commons-codec:${commonsCodecVersion}") implementation("de.undercouch.download:de.undercouch.download.gradle.plugin:5.5.0") implementation("org.jetbrains.dokka:dokka-gradle-plugin:2.1.0") implementation("dev.detekt:detekt-gradle-plugin:2.0.0-alpha.0") implementation("io.spring.gradle.antora:spring-antora-plugin:0.0.1") implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}") implementation("io.spring.nohttp:nohttp-gradle:0.0.11") implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1") implementation("org.apache.maven:maven-artifact:${mavenVersion}") implementation("org.antora:gradle-antora-plugin:1.0.0") implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") implementation("org.springframework:spring-context") implementation("org.springframework:spring-core") implementation("org.springframework:spring-web") implementation("org.yaml:snakeyaml:${snakeYamlVersion}") implementation("io.spring.gradle.nullability:nullability-plugin:${nullabilityPluginVersion}") implementation("tools.jackson.core:jackson-databind") testImplementation(platform("org.junit:junit-bom:${junitJupiterVersion}")) testImplementation("org.assertj:assertj-core:${assertjVersion}") testImplementation("org.hamcrest:hamcrest:${hamcrestVersion}") testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.mockito:mockito-core:${mockitoVersion}") testImplementation("org.springframework:spring-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } configurations.all { exclude group:"org.slf4j", module:"slf4j-api" exclude group:"ch.qos.logback", module:"logback-classic" exclude group:"ch.qos.logback", module:"logback-core" resolutionStrategy.cacheChangingModulesFor 0, "minutes" } gradlePlugin { plugins { aggregatorPlugin { id = "org.springframework.boot.aggregator" implementationClass = "org.springframework.boot.build.aggregation.AggregatorPlugin" } annotationProcessorPlugin { id = "org.springframework.boot.annotation-processor" implementationClass = "org.springframework.boot.build.processors.AnnotationProcessorPlugin" } antoraAggregatedPlugin { id = "org.springframework.boot.antora-contributor" implementationClass = "org.springframework.boot.build.antora.AntoraContributorPlugin" } antoraAggregatorPlugin { id = "org.springframework.boot.antora-dependencies" implementationClass = "org.springframework.boot.build.antora.AntoraDependenciesPlugin" } architecturePlugin { id = "org.springframework.boot.architecture" implementationClass = "org.springframework.boot.build.architecture.ArchitecturePlugin" } autoConfigurationPlugin { id = "org.springframework.boot.auto-configuration" implementationClass = "org.springframework.boot.build.autoconfigure.AutoConfigurationPlugin" } bomPlugin { id = "org.springframework.boot.bom" implementationClass = "org.springframework.boot.build.bom.BomPlugin" } configurationMetadataPlugin { id = "org.springframework.boot.configuration-metadata" implementationClass = "org.springframework.boot.build.context.properties.ConfigurationMetadataPlugin" } configurationPropertiesPlugin { id = "org.springframework.boot.configuration-properties" implementationClass = "org.springframework.boot.build.context.properties.ConfigurationPropertiesPlugin" } conventionsPlugin { id = "org.springframework.boot.conventions" implementationClass = "org.springframework.boot.build.ConventionsPlugin" } deployedPlugin { id = "org.springframework.boot.deployed" implementationClass = "org.springframework.boot.build.DeployedPlugin" } dockerTestPlugin { id = "org.springframework.boot.docker-test" implementationClass = "org.springframework.boot.build.test.DockerTestPlugin" } integrationTestPlugin { id = "org.springframework.boot.integration-test" implementationClass = "org.springframework.boot.build.test.IntegrationTestPlugin" } mavenPluginPlugin { id = "org.springframework.boot.maven-plugin" implementationClass = "org.springframework.boot.build.mavenplugin.MavenPluginPlugin" } mavenRepositoryPlugin { id = "org.springframework.boot.maven-repository" implementationClass = "org.springframework.boot.build.MavenRepositoryPlugin" } optionalDependenciesPlugin { id = "org.springframework.boot.optional-dependencies" implementationClass = "org.springframework.boot.build.optional.OptionalDependenciesPlugin" } starterPlugin { id = "org.springframework.boot.starter" 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 { id = "org.springframework.boot.test-failures" implementationClass = "org.springframework.boot.build.testing.TestFailuresPlugin" } testSlicePlugin { id = "org.springframework.boot.test-slice" implementationClass = "org.springframework.boot.build.test.autoconfigure.TestSlicePlugin" } } } test { useJUnitPlatform() } eclipse { jdt { file { withProperties { it["org.eclipse.jdt.core.compiler.ignoreUnnamedModuleForSplitPackage"] = "enabled" } } } } jar.dependsOn check ================================================ FILE: buildSrc/config/checkstyle/checkstyle.xml ================================================ ================================================ FILE: buildSrc/settings.gradle ================================================ /* * 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. */ pluginManagement { new File(rootDir.parentFile, "gradle.properties").withInputStream { def properties = new Properties() properties.load(it) properties.forEach(settings.ext::set) gradle.rootProject { properties.forEach(project.ext::set) } } evaluate(new File("${rootDir}/SpringRepositorySupport.groovy")).apply(this) repositories { mavenCentral() gradlePluginPortal() } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java ================================================ /* * 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; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.github.gradle.node.NodeExtension; import com.github.gradle.node.npm.task.NpmInstallTask; import io.spring.gradle.antora.GenerateAntoraYmlPlugin; import io.spring.gradle.antora.GenerateAntoraYmlTask; import org.antora.gradle.AntoraPlugin; import org.antora.gradle.AntoraTask; import org.gradle.StartParameter; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.Directory; import org.gradle.api.file.FileCollection; import org.gradle.api.logging.LogLevel; import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Copy; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import tools.jackson.databind.json.JsonMapper; import org.springframework.boot.build.antora.AntoraAsciidocAttributes; import org.springframework.boot.build.antora.CheckJavadocMacros; import org.springframework.boot.build.antora.GenerateAntoraPlaybook; import org.springframework.boot.build.bom.BomExtension; import org.springframework.boot.build.bom.ResolvedBom; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Conventions that are applied in the presence of the {@link AntoraPlugin}. * * @author Phillip Webb */ public class AntoraConventions { private static final String DEPENDENCIES_PATH = ":platform:spring-boot-dependencies"; private static final List NAV_FILES = List.of("nav.adoc", "local-nav.adoc"); /** * Default Antora source directory. */ public static final String ANTORA_SOURCE_DIR = "src/docs/antora"; /** * Name of the {@link GenerateAntoraPlaybook} task. */ public static final String GENERATE_ANTORA_PLAYBOOK_TASK_NAME = "generateAntoraPlaybook"; void apply(Project project) { project.getPlugins().withType(AntoraPlugin.class, (antoraPlugin) -> apply(project, antoraPlugin)); } private void apply(Project project, AntoraPlugin antoraPlugin) { Configuration resolvedBom = project.getConfigurations().create("resolveBom"); project.getDependencies() .add(resolvedBom.getName(), project.getDependencies() .project(Map.of("path", DEPENDENCIES_PATH, "configuration", "resolvedBom"))); project.getPlugins().apply(GenerateAntoraYmlPlugin.class); TaskContainer tasks = project.getTasks(); TaskProvider generateAntoraPlaybookTask = tasks.register( GENERATE_ANTORA_PLAYBOOK_TASK_NAME, GenerateAntoraPlaybook.class, (task) -> configureGenerateAntoraPlaybookTask(project, task)); TaskProvider copyAntoraPackageJsonTask = tasks.register("copyAntoraPackageJson", Copy.class, (task) -> configureCopyAntoraPackageJsonTask(project, task)); TaskProvider npmInstallTask = tasks.register("antoraNpmInstall", NpmInstallTask.class, (task) -> configureNpmInstallTask(project, task, copyAntoraPackageJsonTask)); tasks.withType(GenerateAntoraYmlTask.class, (generateAntoraYmlTask) -> configureGenerateAntoraYmlTask(project, generateAntoraYmlTask, resolvedBom)); tasks.withType(AntoraTask.class, (antoraTask) -> configureAntoraTask(project, antoraTask, npmInstallTask, generateAntoraPlaybookTask)); project.getExtensions() .configure(NodeExtension.class, (nodeExtension) -> configureNodeExtension(project, nodeExtension)); TaskProvider checkAntoraJavadocMacros = tasks.register("checkAntoraJavadocMacros", CheckJavadocMacros.class, (task) -> { task.setSource(project.files(ANTORA_SOURCE_DIR)); task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir(task.getName())); }); project.getPlugins().withType(JavaPlugin.class, (java) -> { String runtimeClasspathConfigurationName = project.getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets() .getByName(SourceSet.MAIN_SOURCE_SET_NAME) .getRuntimeClasspathConfigurationName(); Configuration javadocMacros = project.getConfigurations().create("javadocMacros", (configuration) -> { configuration.extendsFrom(project.getConfigurations().getByName(runtimeClasspathConfigurationName)); configuration.setDescription( "Dependencies referenced in javadoc macros. Extends from " + runtimeClasspathConfigurationName); configuration.setCanBeResolved(true); configuration.setCanBeDeclared(true); configuration.setCanBeConsumed(false); }); checkAntoraJavadocMacros.configure((macrosTask) -> macrosTask.setClasspath(javadocMacros)); }); } private void configureGenerateAntoraPlaybookTask(Project project, GenerateAntoraPlaybook generateAntoraPlaybookTask) { Provider nodeProjectDir = getNodeProjectDir(project); generateAntoraPlaybookTask.getOutputFile() .set(nodeProjectDir.map((directory) -> directory.file("antora-playbook.yml"))); } private void configureCopyAntoraPackageJsonTask(Project project, Copy copyAntoraPackageJsonTask) { copyAntoraPackageJsonTask .from(project.getRootProject().file("antora"), (spec) -> spec.include("package.json", "package-lock.json", "patches/**")) .into(getNodeProjectDir(project)); } private void configureNpmInstallTask(Project project, NpmInstallTask npmInstallTask, TaskProvider copyAntoraPackageJson) { npmInstallTask.dependsOn(copyAntoraPackageJson); Map environment = new HashMap<>(); environment.put("npm_config_omit", "optional"); environment.put("npm_config_update_notifier", "false"); npmInstallTask.getEnvironment().set(environment); npmInstallTask.getNpmCommand().set(List.of("ci", "--silent", "--no-progress")); } private void configureGenerateAntoraYmlTask(Project project, GenerateAntoraYmlTask generateAntoraYmlTask, Configuration resolvedBom) { generateAntoraYmlTask.getOutputs().doNotCacheIf("getAsciidocAttributes() changes output", (task) -> true); generateAntoraYmlTask.dependsOn(resolvedBom); generateAntoraYmlTask.setProperty("componentName", "boot"); generateAntoraYmlTask.setProperty("outputFile", project.getLayout().getBuildDirectory().file("generated/docs/antora-yml/antora.yml")); generateAntoraYmlTask.setProperty("yml", getDefaultYml(project)); generateAntoraYmlTask.getAsciidocAttributes().putAll(getAsciidocAttributes(project, resolvedBom)); } private Map getDefaultYml(Project project) { String navFile = null; for (String candidate : NAV_FILES) { if (project.file(ANTORA_SOURCE_DIR + "/" + candidate).exists()) { Assert.state(navFile == null, "Multiple nav files found"); navFile = candidate; } } Map defaultYml = new LinkedHashMap<>(); defaultYml.put("title", "Spring Boot"); if (navFile != null) { defaultYml.put("nav", List.of(navFile)); } return defaultYml; } private Provider> getAsciidocAttributes(Project project, FileCollection resolvedBoms) { return project.provider(() -> { BomExtension bom = (BomExtension) project.project(DEPENDENCIES_PATH).getExtensions().getByName("bom"); ResolvedBom resolvedBom = ResolvedBom.readFrom(resolvedBoms.getSingleFile()); return new AntoraAsciidocAttributes(project, bom, resolvedBom).get(); }); } private void configureAntoraTask(Project project, AntoraTask antoraTask, TaskProvider npmInstallTask, TaskProvider generateAntoraPlaybookTask) { antoraTask.setGroup("Documentation"); antoraTask.dependsOn(npmInstallTask, generateAntoraPlaybookTask); antoraTask.setPlaybook("antora-playbook.yml"); antoraTask.setUiBundleUrl(getUiBundleUrl(project)); antoraTask.getArgs().set(project.provider(() -> getAntoraNpxArs(project, antoraTask))); project.getPlugins() .withType(JavaBasePlugin.class, (javaBasePlugin) -> project.getTasks() .getByName(JavaBasePlugin.CHECK_TASK_NAME) .dependsOn(antoraTask)); } private List getAntoraNpxArs(Project project, AntoraTask antoraTask) { logWarningIfNodeModulesInUserHome(project); StartParameter startParameter = project.getGradle().getStartParameter(); boolean showStacktrace = startParameter.getShowStacktrace().name().startsWith("ALWAYS"); boolean debugLogging = project.getGradle().getStartParameter().getLogLevel() == LogLevel.DEBUG; String playbookPath = antoraTask.getPlaybook(); List arguments = new ArrayList<>(); arguments.addAll(List.of("--package", "@antora/cli")); arguments.add("antora"); arguments.addAll((!showStacktrace) ? Collections.emptyList() : List.of("--stacktrace")); arguments.addAll((!debugLogging) ? List.of("--quiet") : List.of("--log-level", "all")); arguments.addAll(List.of("--ui-bundle-url", antoraTask.getUiBundleUrl())); arguments.add(playbookPath); return arguments; } private void logWarningIfNodeModulesInUserHome(Project project) { if (new File(System.getProperty("user.home"), "node_modules").exists()) { project.getLogger() .warn("Detected the existence of $HOME/node_modules. This directory is " + "not compatible with this plugin. Please remove it."); } } private String getUiBundleUrl(Project project) { File packageJson = project.getRootProject().file("antora/package.json"); JsonMapper jsonMapper = new JsonMapper(); Map json = jsonMapper.readerFor(Map.class).readValue(packageJson); Map config = (json != null) ? (Map) json.get("config") : null; String url = (config != null) ? (String) config.get("ui-bundle-url") : null; Assert.state(StringUtils.hasText(url.toString()), "package.json has not ui-bundle-url config"); return url; } private void configureNodeExtension(Project project, NodeExtension nodeExtension) { nodeExtension.getWorkDir().set(project.getLayout().getBuildDirectory().dir(".gradle/nodejs")); nodeExtension.getNpmWorkDir().set(project.getLayout().getBuildDirectory().dir(".gradle/npm")); nodeExtension.getNodeProjectDir().set(getNodeProjectDir(project)); } private Provider getNodeProjectDir(Project project) { return project.getLayout().getBuildDirectory().dir(".gradle/nodeproject"); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java ================================================ /* * 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; import org.antora.gradle.AntoraPlugin; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; /** * Plugin to apply conventions to projects that are part of Spring Boot's build. * Conventions are applied in response to various plugins being applied. * * When the {@link JavaBasePlugin} is applied, the conventions in {@link JavaConventions} * are applied. * * When the {@link MavenPublishPlugin} is applied, the conventions in * {@link MavenPublishingConventions} are applied. * * When the {@link AntoraPlugin} is applied, the conventions in {@link AntoraConventions} * are applied. * * @author Andy Wilkinson * @author Christoph Dreis * @author Mike Smithson */ public class ConventionsPlugin implements Plugin { @Override public void apply(Project project) { SystemRequirementsExtension systemRequirements = project.getExtensions() .create("systemRequirements", SystemRequirementsExtension.class); new NoHttpConventions().apply(project); new JavaConventions(systemRequirements).apply(project); new MavenPublishingConventions().apply(project); new AntoraConventions().apply(project); new KotlinConventions().apply(project); new WarConventions().apply(project); new EclipseConventions(systemRequirements).apply(project); new TestFixturesConventions().apply(project); RepositoryTransformersExtension.apply(project); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/DeployedPlugin.java ================================================ /* * 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; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.plugins.JavaPlatformPlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.publish.PublishingExtension; import org.gradle.api.publish.maven.MavenPublication; import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; import org.gradle.api.tasks.bundling.Jar; /** * A plugin applied to a project that should be deployed. * * @author Andy Wilkinson */ public class DeployedPlugin implements Plugin { /** * Name of the task that generates the deployed pom file. */ public static final String GENERATE_POM_TASK_NAME = "generatePomFileForMavenPublication"; @Override @SuppressWarnings("deprecation") public void apply(Project project) { project.getPlugins().apply(MavenPublishPlugin.class); project.getPlugins().apply(MavenRepositoryPlugin.class); PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); MavenPublication mavenPublication = publishing.getPublications().create("maven", MavenPublication.class); project.afterEvaluate((evaluated) -> project.getPlugins().withType(JavaPlugin.class).all((javaPlugin) -> { if (((Jar) project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME)).isEnabled()) { project.getComponents() .matching((component) -> component.getName().equals("java")) .all(mavenPublication::from); } })); project.getPlugins() .withType(JavaPlatformPlugin.class) .all((javaPlugin) -> project.getComponents() .matching((component) -> component.getName().equals("javaPlatform")) .all(mavenPublication::from)); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/EclipseConventions.java ================================================ /* * 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; import org.gradle.api.DomainObjectCollection; import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.tasks.TaskProvider; import org.gradle.plugins.ide.api.XmlFileContentMerger; import org.gradle.plugins.ide.eclipse.EclipsePlugin; import org.gradle.plugins.ide.eclipse.model.Classpath; import org.gradle.plugins.ide.eclipse.model.ClasspathEntry; import org.gradle.plugins.ide.eclipse.model.EclipseClasspath; import org.gradle.plugins.ide.eclipse.model.EclipseJdt; import org.gradle.plugins.ide.eclipse.model.EclipseModel; import org.gradle.plugins.ide.eclipse.model.Library; /** * Conventions that are applied in the presence of the {@link EclipsePlugin} to work * around buildship issue {@code #1238}. * * @author Phillip Webb */ class EclipseConventions { private final SystemRequirementsExtension systemRequirements; EclipseConventions(SystemRequirementsExtension systemRequirements) { this.systemRequirements = systemRequirements; } void apply(Project project) { project.getPlugins().withType(EclipsePlugin.class, (eclipse) -> configure(project, eclipse)); project.afterEvaluate(this::setJavaRuntimeName); } private DomainObjectCollection configure(Project project, EclipsePlugin eclipsePlugin) { TaskProvider synchronizeResourceSettings = registerEclipseSynchronizeResourceSettings(project); TaskProvider synchronizeJdtSettings = registerEclipseSynchronizeJdtSettings(project); return project.getPlugins().withType(JavaBasePlugin.class, (javaBase) -> { EclipseModel model = project.getExtensions().getByType(EclipseModel.class); model.synchronizationTasks(synchronizeResourceSettings, synchronizeJdtSettings); model.jdt(this::configureJdt); model.classpath(this::configureClasspath); }); } private TaskProvider registerEclipseSynchronizeResourceSettings(Project project) { TaskProvider eclipseSynchronizateResource = project.getTasks() .register("eclipseSynchronizateResourceSettings", EclipseSynchronizeResourceSettings.class); eclipseSynchronizateResource.configure((task) -> { task.setDescription("Synchronizate the Eclipse resource settings file from Buildship."); task.setOutputFile(project.file(".settings/org.eclipse.core.resources.prefs")); task.setInputFile(project.file(".settings/org.eclipse.core.resources.prefs")); }); return eclipseSynchronizateResource; } private TaskProvider registerEclipseSynchronizeJdtSettings(Project project) { TaskProvider taskProvider = project.getTasks() .register("eclipseSynchronizeJdtSettings", EclipseSynchronizeJdtSettings.class); taskProvider.configure((task) -> { task.setDescription("Synchronizate the Eclipse JDT settings file from Buildship."); task.setOutputFile(project.file(".settings/org.eclipse.jdt.core.prefs")); task.setInputFile(project.file(".settings/org.eclipse.jdt.core.prefs")); }); return taskProvider; } private void configureJdt(EclipseJdt jdt) { jdt.setSourceCompatibility(JavaVersion.toVersion(JavaConventions.RUNTIME_JAVA_VERSION)); jdt.setTargetCompatibility(JavaVersion.toVersion(JavaConventions.RUNTIME_JAVA_VERSION)); } private void configureClasspath(EclipseClasspath classpath) { classpath.file(this::configureClasspathFile); } private void configureClasspathFile(XmlFileContentMerger merger) { merger.whenMerged((content) -> { if (content instanceof Classpath classpath) { classpath.getEntries().removeIf(this::isKotlinPluginContributedBuildDirectory); } }); } private void setJavaRuntimeName(Project project) { EclipseModel model = project.getExtensions().findByType(EclipseModel.class); EclipseJdt jdt = (model != null) ? model.getJdt() : null; if (jdt != null) { model.getJdt().setJavaRuntimeName("JavaSE-" + this.systemRequirements.getJava().getVersion()); } } private boolean isKotlinPluginContributedBuildDirectory(ClasspathEntry entry) { return (entry instanceof Library library) && isKotlinPluginContributedBuildDirectory(library.getPath()) && isTest(library); } private boolean isKotlinPluginContributedBuildDirectory(String path) { return path.contains("/main") && (path.contains("/build/classes/") || path.contains("/build/resources/")); } private boolean isTest(Library library) { Object value = library.getEntryAttributes().get("test"); return (value instanceof String string && Boolean.parseBoolean(string)); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/EclipseSynchronizeJdtSettings.java ================================================ /* * Copyright 2025 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; import java.util.Properties; import org.gradle.api.Task; import org.gradle.api.internal.PropertiesTransformer; import org.gradle.plugins.ide.api.PropertiesGeneratorTask; import org.springframework.boot.build.EclipseSynchronizeJdtSettings.Configuration; /** * {@link Task} to synchronize Eclipse JDT settings. * * @author Phillip Webb */ public abstract class EclipseSynchronizeJdtSettings extends PropertiesGeneratorTask { @Override protected Configuration create() { return new Configuration(getTransformer()); } @Override protected void configure(Configuration configuration) { } static class Configuration extends EmptyPropertiesPersistableConfigurationObject { Configuration(PropertiesTransformer transformer) { super(transformer); } @Override protected void store(Properties properties) { properties.put("org.eclipse.jdt.core.compiler.release", "true"); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/EclipseSynchronizeResourceSettings.java ================================================ /* * Copyright 2025 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; import java.util.Properties; import org.gradle.api.Task; import org.gradle.api.internal.PropertiesTransformer; import org.gradle.plugins.ide.api.PropertiesGeneratorTask; import org.springframework.boot.build.EclipseSynchronizeResourceSettings.Configuration; /** * {@link Task} to synchronize Eclipse resource settings. * * @author Phillip Webb */ public abstract class EclipseSynchronizeResourceSettings extends PropertiesGeneratorTask { @Override protected Configuration create() { return new Configuration(getTransformer()); } @Override protected void configure(Configuration configuration) { } public static class Configuration extends EmptyPropertiesPersistableConfigurationObject { Configuration(PropertiesTransformer transformer) { super(transformer); } @Override protected void store(Properties properties) { properties.put("encoding/", "UTF-8"); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/EmptyPropertiesPersistableConfigurationObject.java ================================================ /* * Copyright 2025 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; import java.io.ByteArrayInputStream; import java.util.Properties; import org.gradle.api.internal.PropertiesTransformer; import org.gradle.internal.UncheckedException; import org.gradle.plugins.ide.internal.generator.PropertiesPersistableConfigurationObject; /** * Base class for {@link PropertiesPersistableConfigurationObject} instances start empty * and have no default resource. * * @author Phillip Webb */ abstract class EmptyPropertiesPersistableConfigurationObject extends PropertiesPersistableConfigurationObject { EmptyPropertiesPersistableConfigurationObject(PropertiesTransformer transformer) { super(transformer); } @Override protected String getDefaultResourceName() { return null; } @Override public void loadDefaults() { try { load(new ByteArrayInputStream(new byte[0])); } catch (Exception ex) { throw UncheckedException.throwAsUncheckedException(ex); } } @Override protected void load(Properties properties) { } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/ExtractResources.java ================================================ /* * 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; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.Task; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import org.springframework.util.FileCopyUtils; import org.springframework.util.PropertyPlaceholderHelper; /** * {@link Task} to extract resources from the classpath and write them to disk. * * @author Andy Wilkinson */ public abstract class ExtractResources extends DefaultTask { private final PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}"); @Input public abstract Property getPackageName(); @Input public abstract ListProperty getResourceNames(); @OutputDirectory public abstract DirectoryProperty getDestinationDirectory(); @Input public abstract MapProperty getProperties(); @TaskAction void extractResources() throws IOException { for (String resourceName : getResourceNames().get()) { InputStream resourceStream = getClass().getClassLoader() .getResourceAsStream(getPackageName().getOrElse("").replace(".", "/") + "/" + resourceName); if (resourceStream == null) { throw new GradleException("Resource '" + resourceName + "' does not exist"); } String resource = FileCopyUtils.copyToString(new InputStreamReader(resourceStream, StandardCharsets.UTF_8)); resource = this.propertyPlaceholderHelper.replacePlaceholders(resource, getProperties().get()::get); FileCopyUtils.copy(resource, new FileWriter(getDestinationDirectory().file(resourceName).get().getAsFile())); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java ================================================ /* * 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; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; import com.gradle.develocity.agent.gradle.test.DevelocityTestConfiguration; import com.gradle.develocity.agent.gradle.test.PredictiveTestSelectionConfiguration; import com.gradle.develocity.agent.gradle.test.TestRetryConfiguration; import io.spring.gradle.nullability.NullabilityPlugin; import io.spring.gradle.nullability.NullabilityPluginExtension; import io.spring.javaformat.gradle.SpringJavaFormatPlugin; import io.spring.javaformat.gradle.tasks.CheckFormat; import io.spring.javaformat.gradle.tasks.Format; import org.gradle.api.GradleException; import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.DependencySet; import org.gradle.api.file.FileTreeElement; import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.quality.Checkstyle; import org.gradle.api.plugins.quality.CheckstyleExtension; import org.gradle.api.plugins.quality.CheckstylePlugin; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.SourceTask; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.javadoc.Javadoc; import org.gradle.api.tasks.testing.Test; import org.gradle.external.javadoc.CoreJavadocOptions; import org.gradle.jvm.toolchain.JavaCompiler; import org.gradle.jvm.toolchain.JavaInstallationMetadata; import org.gradle.jvm.toolchain.JavaLanguageVersion; import org.springframework.boot.build.architecture.ArchitecturePlugin; import org.springframework.boot.build.classpath.CheckClasspathForProhibitedDependencies; import org.springframework.boot.build.optional.OptionalDependenciesPlugin; import org.springframework.boot.build.springframework.CheckAotFactories; import org.springframework.boot.build.springframework.CheckSpringFactories; import org.springframework.boot.build.testing.TestFailuresPlugin; import org.springframework.boot.build.toolchain.ToolchainPlugin; import org.springframework.util.StringUtils; /** * Conventions that are applied in the presence of the {@link JavaBasePlugin}. When the * plugin is applied: * *

    *
  • The project is configured with source and target compatibility of 17 *
  • {@link SpringJavaFormatPlugin Spring Java Format}, {@link CheckstylePlugin * Checkstyle}, {@link TestFailuresPlugin Test Failures}, {@link ArchitecturePlugin * Architecture} and {@link NullabilityPlugin} plugins are applied *
  • {@link Test} tasks are configured: *
      *
    • to use JUnit Platform *
    • with a max heap of 1536M *
    • to run after any Checkstyle and format checking tasks *
    • to enable retries with a maximum of three attempts when running on CI *
    • to use predictive test selection when the value of the * {@code ENABLE_PREDICTIVE_TEST_SELECTION} environment variable is {@code true} *
    *
  • A {@code testRuntimeOnly} dependency upon * {@code org.junit.platform:junit-platform-launcher} is added to projects with the * {@link JavaPlugin} applied *
  • {@link JavaCompile}, {@link Javadoc}, and {@link Format} tasks are configured to * use UTF-8 encoding *
  • {@link JavaCompile} tasks are configured to: *
      *
    • Use {@code -parameters}. *
    • Treat warnings as errors *
    • Enable {@code unchecked}, {@code deprecation}, {@code rawtypes}, and * {@code varargs} warnings *
    *
  • {@link Jar} tasks are configured to produce jars with LICENSE.txt and NOTICE.txt * files and the following manifest entries: *
      *
    • {@code Automatic-Module-Name} *
    • {@code Build-Jdk-Spec} *
    • {@code Built-By} *
    • {@code Implementation-Title} *
    • {@code Implementation-Version} *
    *
  • {@code spring-boot-parent} is used for dependency management
  • *
  • Additional checks are configured: *
      *
    • For all source sets: *
        *
      • Prohibited dependencies on the compile classpath *
      • Prohibited dependencies on the runtime classpath *
      *
    • For the {@code main} source set: *
        *
      • {@code META-INF/spring/aot.factories} *
      • {@code META-INF/spring.factories} *
      *
    *
* *

* * @author Andy Wilkinson * @author Christoph Dreis * @author Mike Smithson * @author Scott Frederick */ class JavaConventions { public static final int BUILD_JAVA_VERSION = 25; public static final int RUNTIME_JAVA_VERSION = 17; private final SystemRequirementsExtension systemRequirements; JavaConventions(SystemRequirementsExtension systemRequirements) { this.systemRequirements = systemRequirements; } void apply(Project project) { project.getPlugins().withType(JavaBasePlugin.class, (java) -> { project.getPlugins().apply(TestFailuresPlugin.class); project.getPlugins().apply(ArchitecturePlugin.class); configureSpringJavaFormat(project); configureJavaConventions(project); configureJavadocConventions(project); configureTestConventions(project); configureJarManifestConventions(project); configureDependencyManagement(project); configureToolchain(project); configureProhibitedDependencyChecks(project); configureFactoriesFilesChecks(project); configureNullability(project); }); } private void configureJarManifestConventions(Project project) { TaskProvider extractLegalResources = project.getTasks() .register("extractLegalResources", ExtractResources.class, (task) -> { task.getPackageName().set("org.springframework.boot.build.legal"); task.getDestinationDirectory().set(project.getLayout().getBuildDirectory().dir("legal")); task.getResourceNames().set(Arrays.asList("LICENSE.txt", "NOTICE.txt")); task.getProperties().put("version", project.getVersion().toString()); }); SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); Set sourceJarTaskNames = sourceSets.stream() .map(SourceSet::getSourcesJarTaskName) .collect(Collectors.toSet()); Set javadocJarTaskNames = sourceSets.stream() .map(SourceSet::getJavadocJarTaskName) .collect(Collectors.toSet()); project.getTasks().withType(Jar.class, (jar) -> project.afterEvaluate((evaluated) -> { jar.metaInf((metaInf) -> metaInf.from(extractLegalResources)); jar.manifest((manifest) -> { Map attributes = new TreeMap<>(); attributes.put("Automatic-Module-Name", project.getName().replace("-", ".")); // Build-Jdk-Spec is used by buildpacks to pick the JRE to install attributes.put("Build-Jdk-Spec", this.systemRequirements.getJava().getVersion()); attributes.put("Built-By", "Spring"); attributes.put("Implementation-Title", determineImplementationTitle(project, sourceJarTaskNames, javadocJarTaskNames, jar)); attributes.put("Implementation-Version", project.getVersion()); manifest.attributes(attributes); }); })); } private String determineImplementationTitle(Project project, Set sourceJarTaskNames, Set javadocJarTaskNames, Jar jar) { if (sourceJarTaskNames.contains(jar.getName())) { return "Source for " + project.getName(); } if (javadocJarTaskNames.contains(jar.getName())) { return "Javadoc for " + project.getName(); } return project.getDescription(); } private void configureTestConventions(Project project) { project.getTasks().withType(Test.class, (test) -> { test.useJUnitPlatform(); test.setMaxHeapSize("1536M"); project.getTasks().withType(Checkstyle.class, test::mustRunAfter); project.getTasks().withType(CheckFormat.class, test::mustRunAfter); configureTestRetries(test); configurePredictiveTestSelection(test); }); project.getPlugins() .withType(JavaPlugin.class, (javaPlugin) -> project.getDependencies() .add(JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME, "org.junit.platform:junit-platform-launcher")); } private void configureTestRetries(Test test) { TestRetryConfiguration testRetry = test.getExtensions() .getByType(DevelocityTestConfiguration.class) .getTestRetry(); testRetry.getFailOnPassedAfterRetry().set(false); testRetry.getMaxRetries().set(isCi() ? 3 : 0); } private boolean isCi() { return Boolean.parseBoolean(System.getenv("CI")); } private void configurePredictiveTestSelection(Test test) { if (isPredictiveTestSelectionEnabled()) { PredictiveTestSelectionConfiguration predictiveTestSelection = test.getExtensions() .getByType(DevelocityTestConfiguration.class) .getPredictiveTestSelection(); predictiveTestSelection.getEnabled().convention(true); } } private boolean isPredictiveTestSelectionEnabled() { return Boolean.parseBoolean(System.getenv("ENABLE_PREDICTIVE_TEST_SELECTION")); } private void configureJavadocConventions(Project project) { project.getTasks().withType(Javadoc.class, (javadoc) -> { CoreJavadocOptions options = (CoreJavadocOptions) javadoc.getOptions(); options.source("17"); options.encoding("UTF-8"); addValuelessOption(options, "Xdoclint:none"); addValuelessOption(options, "quiet"); if (!javadoc.getName().contains("aggregated")) { addValuelessOption(options, "-no-fonts"); } }); } private void addValuelessOption(CoreJavadocOptions options, String option) { options.addMultilineMultiValueOption(option).setValue(List.of(Collections.emptyList())); } private void configureJavaConventions(Project project) { project.getTasks().withType(JavaCompile.class, (compile) -> { compile.doFirst((task) -> assertCompatible(compile)); compile.getOptions().setEncoding("UTF-8"); compile.getOptions().getRelease().set(RUNTIME_JAVA_VERSION); Set args = new LinkedHashSet<>(compile.getOptions().getCompilerArgs()); args.addAll(List.of("-parameters", "-Werror", "-Xlint:unchecked", "-Xlint:deprecation", "-Xlint:rawtypes", "-Xlint:varargs")); compile.getOptions().setCompilerArgs(new ArrayList<>(args)); }); } private void assertCompatible(JavaCompile compile) { JavaVersion requiredVersion = JavaVersion.toVersion(BUILD_JAVA_VERSION); JavaVersion actualVersion = compile.getJavaCompiler() .map(JavaCompiler::getMetadata) .map(JavaInstallationMetadata::getLanguageVersion) .map(JavaLanguageVersion::asInt) .map(JavaVersion::toVersion) .orElse(JavaVersion.current()) .get(); if (!actualVersion.isCompatibleWith(requiredVersion)) { throw new GradleException("This project should be built with Java %s or above".formatted(requiredVersion)); } } private void configureSpringJavaFormat(Project project) { project.getPlugins().apply(SpringJavaFormatPlugin.class); project.getTasks().withType(Format.class, (Format) -> Format.setEncoding("UTF-8")); project.getPlugins().apply(CheckstylePlugin.class); CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class); String checkstyleToolVersion = (String) project.findProperty("checkstyleToolVersion"); checkstyle.setToolVersion(checkstyleToolVersion); checkstyle.getConfigDirectory().set(project.getRootProject().file("config/checkstyle")); String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion(); DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies(); checkstyleDependencies .add(project.getDependencies().create("com.puppycrawl.tools:checkstyle:" + checkstyle.getToolVersion())); checkstyleDependencies .add(project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version)); project.getTasks().withType(CheckFormat.class, this::excludeGeneratedSources); project.getTasks().withType(Checkstyle.class, this::excludeGeneratedSources); } private SourceTask excludeGeneratedSources(SourceTask task) { return task.exclude(this::isGeneratedSource); } private boolean isGeneratedSource(FileTreeElement candidate) { String path = StringUtils.cleanPath(candidate.getFile().getPath()); return path.contains("/generated/sources/") || path.contains("/generated-source/"); } private void configureDependencyManagement(Project project) { ConfigurationContainer configurations = project.getConfigurations(); Configuration dependencyManagement = configurations.create("dependencyManagement", (configuration) -> { configuration.setVisible(false); configuration.setCanBeConsumed(false); configuration.setCanBeResolved(false); }); configurations .matching((configuration) -> (configuration.getName().endsWith("Classpath") || JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME.equals(configuration.getName())) && (!configuration.getName().contains("dokka"))) .all((configuration) -> configuration.extendsFrom(dependencyManagement)); Dependency springBootParent = project.getDependencies() .enforcedPlatform(project.getDependencies() .project(Collections.singletonMap("path", ":platform:spring-boot-internal-dependencies"))); dependencyManagement.getDependencies().add(springBootParent); project.getPlugins() .withType(OptionalDependenciesPlugin.class, (optionalDependencies) -> configurations .getByName(OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME) .extendsFrom(dependencyManagement)); } private void configureToolchain(Project project) { project.getPlugins().apply(ToolchainPlugin.class); } private void configureProhibitedDependencyChecks(Project project) { SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); sourceSets.all((sourceSet) -> createProhibitedDependenciesChecks(project, sourceSet.getCompileClasspathConfigurationName(), sourceSet.getRuntimeClasspathConfigurationName())); } private void createProhibitedDependenciesChecks(Project project, String... configurationNames) { ConfigurationContainer configurations = project.getConfigurations(); for (String configurationName : configurationNames) { Configuration configuration = configurations.getByName(configurationName); createProhibitedDependenciesCheck(configuration, project); } } private void createProhibitedDependenciesCheck(Configuration classpath, Project project) { TaskProvider checkClasspathForProhibitedDependencies = project .getTasks() .register("check" + StringUtils.capitalize(classpath.getName() + "ForProhibitedDependencies"), CheckClasspathForProhibitedDependencies.class, (task) -> task.setClasspath(classpath)); project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForProhibitedDependencies); } private void configureFactoriesFilesChecks(Project project) { SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets(); sourceSets.matching((sourceSet) -> SourceSet.MAIN_SOURCE_SET_NAME.equals(sourceSet.getName())) .configureEach((main) -> { TaskProvider check = project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME); TaskProvider checkAotFactories = project.getTasks() .register("checkAotFactories", CheckAotFactories.class, (task) -> { task.setSource(main.getResources()); task.setClasspath(main.getOutput().getClassesDirs()); task.setDescription("Checks the META-INF/spring/aot.factories file of the main source set."); }); check.configure((task) -> task.dependsOn(checkAotFactories)); TaskProvider checkSpringFactories = project.getTasks() .register("checkSpringFactories", CheckSpringFactories.class, (task) -> { task.setSource(main.getResources()); task.setClasspath(main.getOutput().getClassesDirs()); task.setDescription("Checks the META-INF/spring.factories file of the main source set."); }); check.configure((task) -> task.dependsOn(checkSpringFactories)); }); } private void configureNullability(Project project) { project.getPlugins().apply(NullabilityPlugin.class); NullabilityPluginExtension extension = project.getExtensions().getByType(NullabilityPluginExtension.class); String nullAwayVersion = (String) project.findProperty("nullAwayVersion"); if (nullAwayVersion != null) { extension.getNullAwayVersion().set(nullAwayVersion); } String errorProneVersion = (String) project.findProperty("errorProneVersion"); if (errorProneVersion != null) { extension.getErrorProneVersion().set(errorProneVersion); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java ================================================ /* * 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; import java.net.URI; import dev.detekt.gradle.Detekt; import dev.detekt.gradle.extensions.DetektExtension; import dev.detekt.gradle.plugin.DetektPlugin; import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.jetbrains.dokka.gradle.DokkaExtension; import org.jetbrains.dokka.gradle.formats.DokkaHtmlPlugin; import org.jetbrains.kotlin.gradle.dsl.JvmTarget; import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions; import org.jetbrains.kotlin.gradle.dsl.KotlinVersion; import org.jetbrains.kotlin.gradle.tasks.KotlinCompile; /** * Conventions that are applied in the presence of the {@code org.jetbrains.kotlin.jvm} * plugin. When the plugin is applied: * *

    *
  • {@link KotlinCompile} tasks are configured to: *
      *
    • Use {@code apiVersion} and {@code languageVersion} 1.7. *
    • Use {@code jvmTarget} 17. *
    • Treat all warnings as errors *
    • Suppress version warnings *
    *
  • Detekt plugin is applied to perform static analysis of Kotlin code *
* *

* * @author Andy Wilkinson */ class KotlinConventions { private static final JvmTarget JVM_TARGET = JvmTarget.JVM_17; private static final KotlinVersion KOTLIN_VERSION = KotlinVersion.KOTLIN_2_2; void apply(Project project) { project.getPlugins().withId("org.jetbrains.kotlin.jvm", (plugin) -> { project.getTasks().withType(KotlinCompile.class, this::configure); project.getPlugins().withType(DokkaHtmlPlugin.class, (dokkaPlugin) -> configureDokka(project)); configureDetekt(project); }); } private void configure(KotlinCompile compile) { KotlinJvmCompilerOptions compilerOptions = compile.getCompilerOptions(); compilerOptions.getApiVersion().set(KOTLIN_VERSION); compilerOptions.getLanguageVersion().set(KOTLIN_VERSION); compilerOptions.getJvmTarget().set(JVM_TARGET); compilerOptions.getAllWarningsAsErrors().set(true); compilerOptions.getFreeCompilerArgs() .addAll("-Xsuppress-version-warnings", "-Xannotation-default-target=param-property"); } private void configureDokka(Project project) { DokkaExtension dokka = project.getExtensions().getByType(DokkaExtension.class); dokka.getDokkaSourceSets().configureEach((sourceSet) -> { if (SourceSet.MAIN_SOURCE_SET_NAME.equals(sourceSet.getName())) { sourceSet.getSourceRoots().setFrom(project.file("src/main/kotlin")); sourceSet.getClasspath() .from(project.getExtensions() .getByType(SourceSetContainer.class) .getByName(SourceSet.MAIN_SOURCE_SET_NAME) .getOutput()); sourceSet.getExternalDocumentationLinks().create("spring-boot-javadoc", (link) -> { link.getUrl().set(URI.create("https://docs.spring.io/spring-boot/api/java/")); link.getPackageListUrl() .set(URI.create("https://docs.spring.io/spring-boot/api/java/element-list")); }); sourceSet.getExternalDocumentationLinks().create("spring-framework-javadoc", (link) -> { String url = "https://docs.spring.io/spring-framework/docs/%s/javadoc-api/" .formatted(project.property("springFrameworkVersion")); link.getUrl().set(URI.create(url)); link.getPackageListUrl().set(URI.create(url + "/element-list")); }); } else { sourceSet.getSuppress().set(true); } }); } private void configureDetekt(Project project) { project.getPlugins().apply(DetektPlugin.class); DetektExtension detekt = project.getExtensions().getByType(DetektExtension.class); detekt.getConfig().setFrom(project.getRootProject().file("config/detekt/config.yml")); project.getTasks() .withType(Detekt.class) .configureEach((task) -> task.getJvmTarget().set(JVM_TARGET.getTarget())); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/MavenPublishingConventions.java ================================================ /* * 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; import org.gradle.api.Project; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.attributes.Usage; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.publish.PublishingExtension; import org.gradle.api.publish.VariantVersionMappingStrategy; import org.gradle.api.publish.maven.MavenPom; import org.gradle.api.publish.maven.MavenPomDeveloperSpec; import org.gradle.api.publish.maven.MavenPomIssueManagement; import org.gradle.api.publish.maven.MavenPomLicenseSpec; import org.gradle.api.publish.maven.MavenPomOrganization; import org.gradle.api.publish.maven.MavenPomScm; import org.gradle.api.publish.maven.MavenPublication; import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.build.properties.BuildProperties; import org.springframework.boot.build.properties.BuildType; /** * Conventions that are applied in the presence of the {@link MavenPublishPlugin}. When * the plugin is applied: * *

    *
  • If the {@code deploymentRepository} property has been set, a * {@link MavenArtifactRepository Maven artifact repository} is configured to publish to * it. *
  • The poms of all {@link MavenPublication Maven publications} are customized to meet * Maven Central's requirements. *
  • If the {@link JavaPlugin Java plugin} has also been applied: *
      *
    • Creation of Javadoc and source jars is enabled. *
    • Publication metadata (poms and Gradle module metadata) is configured to use * resolved versions. *
    *
* * @author Andy Wilkinson * @author Christoph Dreis * @author Mike Smithson */ class MavenPublishingConventions { private static final Logger logger = LoggerFactory.getLogger(MavenPublishingConventions.class); void apply(Project project) { project.getPlugins().withType(MavenPublishPlugin.class).all((mavenPublish) -> { PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); if (project.hasProperty("deploymentRepository")) { publishing.getRepositories().maven((mavenRepository) -> { mavenRepository.setUrl(project.property("deploymentRepository")); mavenRepository.setName("deployment"); }); } publishing.getPublications() .withType(MavenPublication.class) .all((mavenPublication) -> customizeMavenPublication(mavenPublication, project)); project.getPlugins().withType(JavaPlugin.class).all((javaPlugin) -> { JavaPluginExtension extension = project.getExtensions().getByType(JavaPluginExtension.class); extension.withJavadocJar(); extension.withSourcesJar(); }); }); } private void customizeMavenPublication(MavenPublication publication, Project project) { customizePom(publication.getPom(), project); project.getPlugins() .withType(JavaPlugin.class) .all((javaPlugin) -> customizeJavaMavenPublication(publication, project)); } private void customizePom(MavenPom pom, Project project) { pom.getUrl().set("https://spring.io/projects/spring-boot"); pom.getName().set(project.provider(project::getName)); pom.getDescription().set(project.provider(project::getDescription)); if (!isUserInherited(project)) { pom.organization(this::customizeOrganization); } pom.licenses(this::customizeLicences); pom.developers(this::customizeDevelopers); pom.scm((scm) -> customizeScm(scm, project)); pom.issueManagement((issueManagement) -> customizeIssueManagement(issueManagement, project)); } private void customizeJavaMavenPublication(MavenPublication publication, Project project) { publication.versionMapping((strategy) -> strategy.usage(Usage.JAVA_API, (mappingStrategy) -> mappingStrategy .fromResolutionOf(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME))); publication.versionMapping( (strategy) -> strategy.usage(Usage.JAVA_RUNTIME, VariantVersionMappingStrategy::fromResolutionResult)); } private void customizeOrganization(MavenPomOrganization organization) { organization.getName().set("VMware, Inc."); organization.getUrl().set("https://spring.io"); } private void customizeLicences(MavenPomLicenseSpec licences) { licences.license((licence) -> { licence.getName().set("Apache License, Version 2.0"); licence.getUrl().set("https://www.apache.org/licenses/LICENSE-2.0"); }); } private void customizeDevelopers(MavenPomDeveloperSpec developers) { developers.developer((developer) -> { developer.getName().set("Spring"); developer.getEmail().set("ask@spring.io"); developer.getOrganization().set("VMware, Inc."); developer.getOrganizationUrl().set("https://www.spring.io"); }); } private void customizeScm(MavenPomScm scm, Project project) { if (BuildProperties.get(project).buildType() != BuildType.OPEN_SOURCE) { logger.debug("Skipping Maven POM SCM for non open source build type"); return; } scm.getUrl().set("https://github.com/spring-projects/spring-boot"); if (!isUserInherited(project)) { scm.getConnection().set("scm:git:git://github.com/spring-projects/spring-boot.git"); scm.getDeveloperConnection().set("scm:git:ssh://git@github.com/spring-projects/spring-boot.git"); } } private void customizeIssueManagement(MavenPomIssueManagement issueManagement, Project project) { if (BuildProperties.get(project).buildType() != BuildType.OPEN_SOURCE) { logger.debug("Skipping Maven POM SCM for non open source build type"); return; } if (!isUserInherited(project)) { issueManagement.getSystem().set("GitHub"); issueManagement.getUrl().set("https://github.com/spring-projects/spring-boot/issues"); } } private boolean isUserInherited(Project project) { return "spring-boot-starter-parent".equals(project.getName()) || "spring-boot-dependencies".equals(project.getName()); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/MavenRepositoryPlugin.java ================================================ /* * 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; import java.io.File; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.DependencySet; import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.artifacts.ProjectDependency; import org.gradle.api.attributes.Category; import org.gradle.api.plugins.JavaLibraryPlugin; import org.gradle.api.plugins.JavaPlatformPlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.publish.PublishingExtension; import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; import org.springframework.util.FileSystemUtils; /** * A plugin to make a project's {@code deployment} publication available as a Maven * repository. The repository can be consumed by depending upon the project using the * {@code mavenRepository} configuration. * * @author Andy Wilkinson */ public class MavenRepositoryPlugin implements Plugin { /** * Name of the {@code mavenRepository} configuration. */ public static final String MAVEN_REPOSITORY_CONFIGURATION_NAME = "mavenRepository"; /** * Name of the task that publishes to the project repository. */ public static final String PUBLISH_TO_PROJECT_REPOSITORY_TASK_NAME = "publishMavenPublicationToProjectRepository"; @Override public void apply(Project project) { project.getPlugins().apply(MavenPublishPlugin.class); PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); File repositoryLocation = project.getLayout().getBuildDirectory().dir("maven-repository").get().getAsFile(); publishing.getRepositories().maven((mavenRepository) -> { mavenRepository.setName("project"); mavenRepository.setUrl(repositoryLocation.toURI()); }); project.getTasks() .matching((task) -> task.getName().equals(PUBLISH_TO_PROJECT_REPOSITORY_TASK_NAME)) .all((task) -> setUpProjectRepository(project, task, repositoryLocation)); project.getTasks() .matching((task) -> task.getName().equals("publishPluginMavenPublicationToProjectRepository")) .all((task) -> setUpProjectRepository(project, task, repositoryLocation)); } private void setUpProjectRepository(Project project, Task publishTask, File repositoryLocation) { publishTask.doFirst(new CleanAction(repositoryLocation)); Configuration projectRepository = project.getConfigurations().create(MAVEN_REPOSITORY_CONFIGURATION_NAME); project.getArtifacts() .add(projectRepository.getName(), repositoryLocation, (artifact) -> artifact.builtBy(publishTask)); DependencySet target = projectRepository.getDependencies(); project.getPlugins() .withType(JavaPlugin.class) .all((javaPlugin) -> addMavenRepositoryProjectDependencies(project, JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME, target)); project.getPlugins() .withType(JavaLibraryPlugin.class) .all((javaLibraryPlugin) -> addMavenRepositoryProjectDependencies(project, JavaPlugin.API_CONFIGURATION_NAME, target)); project.getPlugins().withType(JavaPlatformPlugin.class).all((javaPlugin) -> { addMavenRepositoryProjectDependencies(project, JavaPlatformPlugin.API_CONFIGURATION_NAME, target); addMavenRepositoryPlatformDependencies(project, JavaPlatformPlugin.API_CONFIGURATION_NAME, target); }); } private void addMavenRepositoryProjectDependencies(Project project, String sourceConfigurationName, DependencySet target) { project.getConfigurations() .getByName(sourceConfigurationName) .getDependencies() .withType(ProjectDependency.class) .all((dependency) -> { ProjectDependency copy = dependency.copy(); if (copy.getAttributes().isEmpty()) { copy.setTargetConfiguration(MAVEN_REPOSITORY_CONFIGURATION_NAME); } target.add(copy); }); } private void addMavenRepositoryPlatformDependencies(Project project, String sourceConfigurationName, DependencySet target) { project.getConfigurations() .getByName(sourceConfigurationName) .getDependencies() .withType(ModuleDependency.class) .matching((dependency) -> { Category category = dependency.getAttributes().getAttribute(Category.CATEGORY_ATTRIBUTE); return Category.REGULAR_PLATFORM.equals(category.getName()); }) .all((dependency) -> { Dependency pom = project.getDependencies() .create(dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion()); target.add(pom); }); } private static final class CleanAction implements Action { private final File location; private CleanAction(File location) { this.location = location; } @Override public void execute(Task task) { FileSystemUtils.deleteRecursively(this.location); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/NoHttpConventions.java ================================================ /* * 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; import io.spring.nohttp.gradle.NoHttpCheckstylePlugin; import io.spring.nohttp.gradle.NoHttpExtension; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileTree; import org.gradle.api.plugins.quality.Checkstyle; /** * Conventions that are applied to enforce that no HTTP urls are used. * * @author Phillip Webb */ public class NoHttpConventions { void apply(Project project) { project.getPluginManager().apply(NoHttpCheckstylePlugin.class); configureNoHttpExtension(project, project.getExtensions().getByType(NoHttpExtension.class)); project.getTasks() .named(NoHttpCheckstylePlugin.CHECKSTYLE_NOHTTP_TASK_NAME, Checkstyle.class) .configure((task) -> task.getConfigDirectory().set(project.getRootProject().file("config/nohttp"))); } private void configureNoHttpExtension(Project project, NoHttpExtension extension) { extension.setAllowlistFile(project.getRootProject().file("config/nohttp/allowlist.lines")); ConfigurableFileTree source = extension.getSource(); source.exclude("bin/**"); source.exclude("build/**"); source.exclude("out/**"); source.exclude("target/**"); source.exclude(".settings/**"); source.exclude(".classpath"); source.exclude(".project"); source.exclude(".gradle"); source.exclude("**/docker/export.tar"); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/RepositoryTransformersExtension.java ================================================ /* * 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; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.BiFunction; import java.util.function.Function; import javax.inject.Inject; import org.gradle.api.Project; import org.gradle.api.Transformer; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; /** * Extension to add {@code springRepositoryTransformers} utility methods. * * @author Phillip Webb */ public class RepositoryTransformersExtension { private static final String CREDENTIALS_MARKER = "{spring.mavenCredentials}"; private static final String REPOSITORIES_MARKER = "{spring.mavenRepositories}"; private final Project project; @Inject public RepositoryTransformersExtension(Project project) { this.project = project; } public Transformer ant() { return this::transformAnt; } private String transformAnt(String line) { if (line.contains(REPOSITORIES_MARKER)) { return transform(line, (repository, indent) -> { String name = repository.getName(); URI url = repository.getUrl(); return "%s".formatted(indent, name, url); }); } if (line.contains(CREDENTIALS_MARKER)) { Map hostCredentials = new LinkedHashMap<>(); getSpringRepositories().forEach((repository) -> { if (repository.getName().startsWith("spring-commercial-")) { String host = repository.getUrl().getHost(); hostCredentials.put(host, new MavenCredential("${env.COMMERCIAL_REPO_USERNAME}", "${env.COMMERCIAL_REPO_PASSWORD")); } }); return transform(line, hostCredentials.entrySet(), (entry, indent) -> "%s%n" .formatted(indent, entry.getKey(), entry.getValue().username(), entry.getValue().password())); } return line; } private String transformMavenRepositories(String line, boolean pluginRepository) { return transform(line, (repository, indent) -> mavenRepositoryXml(indent, repository, pluginRepository)); } private String mavenRepositoryXml(String indent, MavenArtifactRepository repository, boolean pluginRepository) { String rootTag = pluginRepository ? "pluginRepository" : "repository"; boolean snapshots = repository.getName().endsWith("-snapshot"); StringBuilder xml = new StringBuilder(); xml.append("%s<%s>%n".formatted(indent, rootTag)); xml.append("%s\t%s%n".formatted(indent, repository.getName())); xml.append("%s\t%s%n".formatted(indent, repository.getUrl())); xml.append("%s\t%n".formatted(indent)); xml.append("%s\t\t%s%n".formatted(indent, !snapshots)); xml.append("%s\t%n".formatted(indent)); xml.append("%s\t%n".formatted(indent)); xml.append("%s\t\t%s%n".formatted(indent, snapshots)); xml.append("%s\t%n".formatted(indent)); xml.append("%s".formatted(indent, rootTag)); return xml.toString(); } private String transform(String line, BiFunction generator) { return transform(line, getSpringRepositories(), generator); } private String transform(String line, Iterable iterable, BiFunction generator) { StringBuilder result = new StringBuilder(); String indent = getIndent(line); iterable.forEach((item) -> { String fragment = generator.apply(item, indent); if (fragment != null) { result.append(!result.isEmpty() ? "\n" : ""); result.append(fragment); } }); return result.toString(); } private List getSpringRepositories() { List springRepositories = new ArrayList<>(this.project.getRepositories() .withType(MavenArtifactRepository.class) .stream() .filter(this::isSpringRepository) .toList()); Function bySnapshots = (repository) -> repository.getName() .contains("snapshot"); Function byName = MavenArtifactRepository::getName; Collections.sort(springRepositories, Comparator.comparing(bySnapshots).thenComparing(byName)); return springRepositories; } private boolean isSpringRepository(MavenArtifactRepository repository) { return (repository.getName().startsWith("spring-")); } private String getIndent(String line) { return line.substring(0, line.length() - line.stripLeading().length()); } static void apply(Project project) { project.getExtensions().create("springRepositoryTransformers", RepositoryTransformersExtension.class, project); } record MavenCredential(String username, String password) { } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/SyncAppSource.java ================================================ /* * 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; import javax.inject.Inject; import org.gradle.api.DefaultTask; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileSystemOperations; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputDirectory; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; /** * Tasks for syncing the source code of a Spring Boot application, filtering its * {@code build.gradle} to set the version of its {@code org.springframework.boot} plugin. * * @author Andy Wilkinson */ public abstract class SyncAppSource extends DefaultTask { private final FileSystemOperations fileSystemOperations; @Inject public SyncAppSource(FileSystemOperations fileSystemOperations) { getPluginVersion().convention(getProject().provider(() -> getProject().getVersion().toString())); this.fileSystemOperations = fileSystemOperations; } @InputDirectory public abstract DirectoryProperty getSourceDirectory(); @OutputDirectory public abstract DirectoryProperty getDestinationDirectory(); @Input public abstract Property getPluginVersion(); @TaskAction void syncAppSources() { this.fileSystemOperations.sync((copySpec) -> { copySpec.from(getSourceDirectory()); copySpec.into(getDestinationDirectory()); copySpec.filter((line) -> line.replace("id \"org.springframework.boot\"", "id \"org.springframework.boot\" version \"" + getPluginVersion().get() + "\"")); }); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/SystemRequirementsExtension.java ================================================ /* * Copyright 2026 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; import javax.inject.Inject; import org.gradle.api.Action; import org.gradle.api.model.ObjectFactory; /** * DSL extension for configuring a project's system requirements. * * @author Andy Wilkinson */ public class SystemRequirementsExtension { private final JavaSpec javaSpec; @Inject public SystemRequirementsExtension(ObjectFactory objects) { this.javaSpec = objects.newInstance(JavaSpec.class); } public void java(Action action) { action.execute(this.javaSpec); } public JavaSpec getJava() { return this.javaSpec; } public abstract static class JavaSpec { private int version = 17; public int getVersion() { return this.version; } public void setVersion(int version) { this.version = version; } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/TestFixturesConventions.java ================================================ /* * 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; import org.gradle.api.Project; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.plugins.JavaTestFixturesPlugin; /** * Conventions that are applied in the presence of the {@link JavaTestFixturesPlugin}. * When the plugin is applied: * *
    *
  • Publishing of the test fixtures is disabled. *
* * @author Andy Wilkinson */ class TestFixturesConventions { void apply(Project project) { project.getPlugins().withType(JavaTestFixturesPlugin.class, (testFixtures) -> disablePublishing(project)); } private void disablePublishing(Project project) { ConfigurationContainer configurations = project.getConfigurations(); AdhocComponentWithVariants javaComponent = (AdhocComponentWithVariants) project.getComponents() .getByName("java"); javaComponent.withVariantsFromConfiguration(configurations.getByName("testFixturesApiElements"), (variant) -> variant.skip()); javaComponent.withVariantsFromConfiguration(configurations.getByName("testFixturesRuntimeElements"), (variant) -> variant.skip()); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/WarConventions.java ================================================ /* * 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; import java.util.ArrayList; import java.util.List; import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.internal.IConventionAware; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.plugins.ide.eclipse.EclipseWtpPlugin; import org.gradle.plugins.ide.eclipse.model.EclipseModel; import org.gradle.plugins.ide.eclipse.model.Facet; /** * Conventions that are applied in the presence of the {WarPlugin}. When the plugin is * applied: *
    *
  • Update Eclipse WTP Plugin facets to use Servlet 5.0
  • *
* * @author Phillip Webb */ public class WarConventions { void apply(Project project) { project.getPlugins().withType(EclipseWtpPlugin.class, (wtp) -> { project.getTasks().getByName(EclipseWtpPlugin.ECLIPSE_WTP_FACET_TASK_NAME).doFirst((task) -> { EclipseModel eclipseModel = project.getExtensions().getByType(EclipseModel.class); ((IConventionAware) eclipseModel.getWtp().getFacet()).getConventionMapping() .map("facets", () -> getFacets(project)); }); }); } private List getFacets(Project project) { JavaVersion javaVersion = project.getExtensions().getByType(JavaPluginExtension.class).getSourceCompatibility(); List facets = new ArrayList<>(); facets.add(new Facet(Facet.FacetType.fixed, "jst.web", null)); facets.add(new Facet(Facet.FacetType.installed, "jst.web", "5.0")); facets.add(new Facet(Facet.FacetType.installed, "jst.java", javaVersion.toString())); return facets; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/aggregation/Aggregate.java ================================================ /* * 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.aggregation; import org.gradle.api.Named; import org.gradle.api.attributes.Category; import org.gradle.api.attributes.Usage; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.provider.Property; /** * An aggregate. * * @author Andy Wilkinson */ public interface Aggregate extends Named { /** * The {@link Category} used to select the variant that's included in the aggregate. * @return the category */ Property getCategory(); /** * The {@link Usage} used to select the variant that's included in the aggregate. * @return the usage */ Property getUsage(); /** * The aggregated files. * @return the aggregated files */ ConfigurableFileCollection getFiles(); } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/aggregation/AggregatorPlugin.java ================================================ /* * 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.aggregation; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.NamedDomainObjectProvider; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.DependencyScopeConfiguration; import org.gradle.api.artifacts.ResolvableConfiguration; import org.gradle.api.attributes.Category; import org.gradle.api.attributes.Usage; import org.gradle.api.model.ObjectFactory; /** * {@link Plugin} for aggregating the output of other projects. * * @author Andy Wilkinson */ public class AggregatorPlugin implements Plugin { @Override public void apply(Project target) { NamedDomainObjectContainer aggregates = target.getObjects().domainObjectContainer(Aggregate.class); target.getExtensions().add("aggregates", aggregates); aggregates.all((aggregate) -> { NamedDomainObjectProvider dependencies = target.getConfigurations() .dependencyScope(aggregate.getName() + "Dependencies"); NamedDomainObjectProvider aggregated = target.getConfigurations() .resolvable(aggregate.getName(), (configuration) -> { configuration.extendsFrom(dependencies.get()); configureAttributes(configuration, aggregate, target.getObjects()); }); target.getRootProject() .allprojects((project) -> target.getDependencies().add(dependencies.getName(), project)); aggregate.getFiles() .convention(aggregated.map((configuration) -> configuration.getIncoming() .artifactView((view) -> view.setLenient(true)) .getFiles())); }); } private void configureAttributes(Configuration configuration, Aggregate aggregate, ObjectFactory objects) { configuration.attributes((attributes) -> { attributes.attributeProvider(Category.CATEGORY_ATTRIBUTE, aggregate.getCategory().map((category) -> objects.named(Category.class, category))); attributes.attributeProvider(Usage.USAGE_ATTRIBUTE, aggregate.getUsage().map((usage) -> objects.named(Usage.class, usage))); }); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/AggregateContentContribution.java ================================================ /* * 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.antora; import org.gradle.api.Project; /** * A contribution of aggregate content. * * @author Andy Wilkinson */ class AggregateContentContribution extends ConsumableContentContribution { protected AggregateContentContribution(Project project, String name) { super(project, "aggregate", name); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java ================================================ /* * 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.antora; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.stream.Collectors; import org.gradle.api.Project; import org.springframework.boot.build.artifacts.ArtifactRelease; import org.springframework.boot.build.bom.BomExtension; import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.ResolvedBom; import org.springframework.boot.build.bom.ResolvedBom.Bom; import org.springframework.boot.build.bom.ResolvedBom.Id; import org.springframework.boot.build.bom.ResolvedBom.ResolvedLibrary; import org.springframework.boot.build.properties.BuildProperties; import org.springframework.boot.build.properties.BuildType; import org.springframework.util.Assert; /** * Generates Asciidoctor attributes for use with Antora. * * @author Phillip Webb */ public class AntoraAsciidocAttributes { private static final String DASH_SNAPSHOT = "-SNAPSHOT"; private final String version; private final boolean latestVersion; private final BuildType buildType; private final ArtifactRelease artifactRelease; private final List libraries; private final Map dependencyVersions; private final Map projectProperties; public AntoraAsciidocAttributes(Project project, BomExtension dependencyBom, ResolvedBom resolvedBom) { this.version = String.valueOf(project.getVersion()); this.latestVersion = Boolean.parseBoolean(String.valueOf(project.findProperty("latestVersion"))); this.buildType = BuildProperties.get(project).buildType(); this.artifactRelease = ArtifactRelease.forProject(project); this.libraries = dependencyBom.getLibraries(); this.dependencyVersions = dependencyVersionsOf(resolvedBom); this.projectProperties = project.getProperties(); } private static Map dependencyVersionsOf(ResolvedBom resolvedBom) { Map dependencyVersions = new HashMap<>(); for (ResolvedLibrary library : resolvedBom.libraries()) { dependencyVersions.putAll(dependencyVersionsOf(library.managedDependencies())); for (Bom importedBom : library.importedBoms()) { dependencyVersions.putAll(dependencyVersionsOf(importedBom)); } } return dependencyVersions; } private static Map dependencyVersionsOf(Bom bom) { Map dependencyVersions = new HashMap<>(); if (bom != null) { dependencyVersions.putAll(dependencyVersionsOf(bom.managedDependencies())); dependencyVersions.putAll(dependencyVersionsOf(bom.parent())); for (Bom importedBom : bom.importedBoms()) { dependencyVersions.putAll(dependencyVersionsOf(importedBom)); } } return dependencyVersions; } private static Map dependencyVersionsOf(Collection managedDependencies) { Map dependencyVersions = new HashMap<>(); for (Id managedDependency : managedDependencies) { dependencyVersions.put(managedDependency.groupId() + ":" + managedDependency.artifactId(), managedDependency.version()); } return dependencyVersions; } AntoraAsciidocAttributes(String version, boolean latestVersion, BuildType buildType, List libraries, Map dependencyVersions, Map projectProperties) { this.version = version; this.latestVersion = latestVersion; this.buildType = buildType; this.artifactRelease = ArtifactRelease.forVersion(version); this.libraries = (libraries != null) ? libraries : Collections.emptyList(); this.dependencyVersions = (dependencyVersions != null) ? dependencyVersions : Collections.emptyMap(); this.projectProperties = (projectProperties != null) ? projectProperties : Collections.emptyMap(); } public Map get() { Map attributes = new LinkedHashMap<>(); Map internal = new LinkedHashMap<>(); addBuildTypeAttribute(attributes); addGitHubAttributes(attributes); addVersionAttributes(attributes, internal); addArtifactAttributes(attributes); addUrlJava(attributes); addUrlLibraryLinkAttributes(attributes); addPropertyAttributes(attributes, internal); return attributes; } private void addBuildTypeAttribute(Map attributes) { attributes.put("build-type", this.buildType.toIdentifier()); } private void addGitHubAttributes(Map attributes) { attributes.put("github-repo", "spring-projects/spring-boot"); attributes.put("github-ref", determineGitHubRef()); } private String determineGitHubRef() { int snapshotIndex = this.version.lastIndexOf(DASH_SNAPSHOT); if (snapshotIndex == -1) { return "v" + this.version; } if (this.latestVersion) { return "main"; } String versionRoot = this.version.substring(0, snapshotIndex); int lastDot = versionRoot.lastIndexOf('.'); return versionRoot.substring(0, lastDot) + ".x"; } private void addVersionAttributes(Map attributes, Map internal) { this.libraries.forEach((library) -> { String name = "version-" + library.getLinkRootName(); String value = library.getVersion().toString(); attributes.put(name, value); }); attributes.put("version-native-build-tools", (String) this.projectProperties.get("nativeBuildToolsVersion")); attributes.put("version-graal", (String) this.projectProperties.get("graalVersion")); addDependencyVersion(attributes, "jackson-annotations", "com.fasterxml.jackson.core:jackson-annotations"); addDependencyVersion(attributes, "jackson-core", "tools.jackson.core:jackson-core"); addDependencyVersion(attributes, "jackson-databind", "tools.jackson.core:jackson-databind"); addDependencyVersion(attributes, "jackson-dataformat-xml", "tools.jackson.dataformat:jackson-dataformat-xml"); addDependencyVersion(attributes, "jackson2-databind", "com.fasterxml.jackson.core:jackson-databind"); addSpringDataDependencyVersion(attributes, internal, "spring-data-commons"); addSpringDataDependencyVersion(attributes, internal, "spring-data-couchbase"); addSpringDataDependencyVersion(attributes, internal, "spring-data-cassandra"); addSpringDataDependencyVersion(attributes, internal, "spring-data-elasticsearch"); addSpringDataDependencyVersion(attributes, internal, "spring-data-jdbc"); addSpringDataDependencyVersion(attributes, internal, "spring-data-jpa"); addSpringDataDependencyVersion(attributes, internal, "spring-data-mongodb"); addSpringDataDependencyVersion(attributes, internal, "spring-data-neo4j"); addSpringDataDependencyVersion(attributes, internal, "spring-data-r2dbc"); addSpringDataDependencyVersion(attributes, internal, "spring-data-redis"); addSpringDataDependencyVersion(attributes, internal, "spring-data-rest", "spring-data-rest-core"); addSpringDataDependencyVersion(attributes, internal, "spring-data-ldap"); addDependencyVersion(attributes, "pulsar-client-api", "org.apache.pulsar:pulsar-client-api"); } private void addSpringDataDependencyVersion(Map attributes, Map internal, String artifactId) { addSpringDataDependencyVersion(attributes, internal, artifactId, artifactId); } private void addSpringDataDependencyVersion(Map attributes, Map internal, String name, String artifactId) { String groupAndArtifactId = "org.springframework.data:" + artifactId; addDependencyVersion(attributes, name, groupAndArtifactId); String version = getVersion(groupAndArtifactId); String majorMinor = Arrays.stream(version.split("\\.")).limit(2).collect(Collectors.joining(".")); String antoraVersion = version.endsWith(DASH_SNAPSHOT) ? majorMinor + DASH_SNAPSHOT : majorMinor; internal.put("antoraversion-" + name, antoraVersion); internal.put("dotxversion-" + name, majorMinor + ".x"); } private void addDependencyVersion(Map attributes, String name, String groupAndArtifactId) { attributes.put("version-" + name, getVersion(groupAndArtifactId)); } private String getVersion(String groupAndArtifactId) { String version = this.dependencyVersions.get(groupAndArtifactId); Assert.notNull(version, () -> "No version found for " + groupAndArtifactId); return version; } private void addArtifactAttributes(Map attributes) { attributes.put("url-artifact-repository", this.artifactRelease.getDownloadRepo()); attributes.put("artifact-release-type", this.artifactRelease.getType()); attributes.put("build-and-artifact-release-type", this.buildType.toIdentifier() + "-" + this.artifactRelease.getType()); } private void addUrlJava(Map attributes) { attributes.put("url-javase-javadoc", "https://docs.oracle.com/en/java/javase/17/docs/api"); attributes.put("javadoc-location-java", "{url-javase-javadoc}/java.base"); attributes.put("javadoc-location-java-beans", "{url-javase-javadoc}/java.desktop"); attributes.put("javadoc-location-java-sql", "{url-javase-javadoc}/java.sql"); attributes.put("javadoc-location-javax", "{url-javase-javadoc}/java.base"); attributes.put("javadoc-location-javax-management", "{url-javase-javadoc}/java.management"); attributes.put("javadoc-location-javax-net", "{url-javase-javadoc}/java.base"); attributes.put("javadoc-location-javax-sql", "{url-javase-javadoc}/java.sql"); attributes.put("javadoc-location-javax-xml", "{url-javase-javadoc}/java.xml"); } private void addUrlLibraryLinkAttributes(Map attributes) { Map packageAttributes = new LinkedHashMap<>(); this.libraries.forEach((library) -> { library.getLinks().forEach((name, links) -> links.forEach((link) -> { String linkRootName = (link.rootName() != null) ? link.rootName() : library.getLinkRootName(); String linkName = "url-" + linkRootName + "-" + name; attributes.put(linkName, link.url(library)); link.packages() .stream() .map(this::packageAttributeName) .forEach((packageAttributeName) -> packageAttributes.put(packageAttributeName, "{" + linkName + "}")); })); }); attributes.putAll(packageAttributes); } private String packageAttributeName(String packageName) { return "javadoc-location-" + packageName.replace('.', '-'); } private void addPropertyAttributes(Map attributes, Map internal) { Properties properties = new Properties() { @Override public synchronized Object put(Object key, Object value) { // Put directly because order is important for us return attributes.put(key.toString(), resolve(value.toString(), internal)); } }; try (InputStream in = getClass().getResourceAsStream("antora-asciidoc-attributes.properties")) { properties.load(in); } catch (IOException ex) { throw new UncheckedIOException(ex); } } private String resolve(String value, Map internal) { for (Map.Entry entry : internal.entrySet()) { value = value.replace("{" + entry.getKey() + "}", entry.getValue()); } return value; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraContributorPlugin.java ================================================ /* * 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.antora; import javax.inject.Inject; import org.antora.gradle.AntoraPlugin; import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.file.CopySpec; /** * {@link Plugin} for a project that contributes to Antora-based documentation that is * {@link AntoraDependenciesPlugin depended upon} by another project. * * @author Andy Wilkinson */ public class AntoraContributorPlugin implements Plugin { @Override public void apply(Project project) { project.getPlugins().apply(AntoraPlugin.class); NamedDomainObjectContainer antoraContributions = project.getObjects() .domainObjectContainer(Contribution.class, (name) -> project.getObjects().newInstance(Contribution.class, name, project)); project.getExtensions().add("antoraContributions", antoraContributions); } public static class Contribution { private final String name; private final Project project; private boolean publish; @Inject public Contribution(String name, Project project) { this.name = name; this.project = project; } public String getName() { return this.name; } public void publish() { this.publish = true; } public void source() { new SourceContribution(this.project, this.name).produce(); } public void catalogContent(Action action) { CopySpec copySpec = this.project.copySpec(); action.execute(copySpec); new CatalogContentContribution(this.project, this.name).produceFrom(copySpec, this.publish); } public void aggregateContent(Action action) { CopySpec copySpec = this.project.copySpec(); action.execute(copySpec); new AggregateContentContribution(this.project, this.name).produceFrom(copySpec, this.publish); } public void localAggregateContent(Action action) { CopySpec copySpec = this.project.copySpec(); action.execute(copySpec); new LocalAggregateContentContribution(this.project, this.name).produceFrom(copySpec); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraDependenciesPlugin.java ================================================ /* * 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.antora; import javax.inject.Inject; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Plugin; import org.gradle.api.Project; /** * {@link Plugin} for a project that depends on {@link AntoraContributorPlugin * contributed} Antora-based documentation. * * @author Andy Wilkinson */ public class AntoraDependenciesPlugin implements Plugin { @Override public void apply(Project project) { NamedDomainObjectContainer antoraDependencies = project.getObjects() .domainObjectContainer(AntoraDependency.class); project.getExtensions().add("antoraDependencies", antoraDependencies); } public static class AntoraDependency { private final String name; private final Project project; private String path; @Inject public AntoraDependency(String name, Project project) { this.name = name; this.project = project; } public String getName() { return this.name; } public String getPath() { return this.path; } public void setPath(String path) { this.path = path; } public void catalogContent() { new CatalogContentContribution(this.project, this.name).consumeFrom(this.path); } public void aggregateContent() { new AggregateContentContribution(this.project, this.name).consumeFrom(this.path); } public void source() { new SourceContribution(this.project, this.name).consumeFrom(this.path); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/CatalogContentContribution.java ================================================ /* * 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.antora; import org.gradle.api.Project; /** * A contribution of catalog content. * * @author Andy Wilkinson */ class CatalogContentContribution extends ConsumableContentContribution { CatalogContentContribution(Project project, String name) { super(project, "catalog", name); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/CheckJavadocMacros.java ================================================ /* * Copyright 2025 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.antora; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; 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.tasks.Classpath; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.VerificationException; import org.springframework.asm.ClassReader; import org.springframework.asm.ClassVisitor; import org.springframework.asm.FieldVisitor; import org.springframework.asm.MethodVisitor; import org.springframework.asm.SpringAsmInfo; import org.springframework.asm.Type; import org.springframework.util.function.ThrowingConsumer; /** * A task to check {@code javadoc:[]} macros in Antora source files. * * @author Andy Wilkinson */ public abstract class CheckJavadocMacros extends DefaultTask { private static final Pattern JAVADOC_MACRO_PATTERN = Pattern.compile("javadoc:(.*?)\\[(.*?)\\]"); private final Path projectRoot; private FileCollection source; private FileCollection classpath; public CheckJavadocMacros() { this.projectRoot = getProject().getRootDir().toPath(); } @InputFiles public FileCollection getSource() { return this.source; } public void setSource(FileCollection source) { this.source = source; } @Optional @Classpath public FileCollection getClasspath() { return this.classpath; } public void setClasspath(FileCollection classpath) { this.classpath = classpath; } @OutputDirectory public abstract DirectoryProperty getOutputDirectory(); @TaskAction void checkJavadocMacros() { Set availableClasses = indexClasspath(); List problems = new ArrayList<>(); this.source.getAsFileTree() .filter((file) -> file.getName().endsWith(".adoc")) .forEach((file) -> problems.addAll(checkJavadocMacros(file, availableClasses))); File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); writeReport(problems, outputFile); if (!problems.isEmpty()) { throw new VerificationException("Javadoc macro check failed. See '%s' for details".formatted(outputFile)); } } private Set indexClasspath() { Set availableClasses = StreamSupport.stream(this.classpath.spliterator(), false).flatMap((root) -> { if (root.isFile()) { try (JarFile jar = new JarFile(root)) { return jar.stream() .map(JarEntry::getName) .filter((entryName) -> entryName.endsWith(".class")) .map((className) -> { if (className.startsWith("META-INF/versions/")) { className = className.substring("META-INF/versions/".length()); } className = className.substring(0, className.length() - ".class".length()); className = className.replace('/', '.'); return className; }) .toList() .stream(); } catch (IOException ex) { throw new UncheckedIOException(ex); } } return Stream.empty(); }).collect(Collectors.toSet()); return availableClasses; } private List checkJavadocMacros(File adocFile, Set availableClasses) { List problems = new ArrayList<>(); List macros = JavadocMacro.parse(adocFile); for (JavadocMacro macro : macros) { if (!classIsAvailable(macro.className.name, availableClasses)) { problems.add(this.projectRoot.relativize(macro.className.origin.file.toPath()) + ":" + macro.className.origin.line + ":" + macro.className.origin.column + ": class " + macro.className.name + " does not exist."); } else { JavadocAnchor anchor = macro.anchor; if (anchor != null) { if (anchor instanceof MethodAnchor methodAnchor) { MethodMatcher methodMatcher = new MethodMatcher(methodAnchor); inputStreamOf(macro.className.name, (stream) -> { ClassReader reader = new ClassReader(stream); reader.accept(methodMatcher, 0); }); if (!methodMatcher.matched) { problems.add(this.projectRoot.relativize(macro.anchor.origin.file.toPath()) + ":" + macro.anchor.origin.line + ":" + methodAnchor.origin().column + ": method " + methodAnchor + " does not exist"); } } else if (anchor instanceof FieldAnchor fieldAnchor) { FieldMatcher fieldMatcher = new FieldMatcher(fieldAnchor); inputStreamOf(macro.className.name, (stream) -> { ClassReader reader = new ClassReader(stream); reader.accept(fieldMatcher, 0); }); if (!fieldMatcher.matched) { problems.add(this.projectRoot.relativize(macro.anchor.origin.file.toPath()) + ":" + macro.anchor.origin.line + ":" + fieldAnchor.origin().column + ": field " + fieldAnchor.name + " does not exist"); } } } } } return problems; } private boolean classIsAvailable(String className, Set availableClasses) { if (availableClasses.contains(className)) { return true; } if (className.startsWith("java.") || className.startsWith("javax.")) { return jdkResourceForClass(className) != null; } return false; } private URL jdkResourceForClass(String className) { return getClass().getClassLoader().getResource(className.replace(".", "/") + ".class"); } private void inputStreamOf(String className, ThrowingConsumer streamHandler) { for (File root : this.classpath) { if (root.isFile()) { try (JarFile jar = new JarFile(root)) { ZipEntry entry = jar.getEntry(className.replace(".", "/") + ".class"); if (entry != null) { try (InputStream stream = jar.getInputStream(entry)) { streamHandler.accept(stream); } return; } } catch (IOException ex) { throw new UncheckedIOException(ex); } } } URL resource = jdkResourceForClass(className); if (resource != null) { try (InputStream stream = resource.openStream()) { streamHandler.accept(stream); } catch (IOException ex) { throw new UncheckedIOException(ex); } } } private void writeReport(List problems, File outputFile) { outputFile.getParentFile().mkdirs(); StringBuilder report = new StringBuilder(); if (!problems.isEmpty()) { if (problems.size() == 1) { report.append("Found 1 javadoc macro problem:%n".formatted()); } else { report.append("Found %d javadoc macro problems:%n".formatted(problems.size())); } problems.forEach((problem) -> report.append("%s%n".formatted(problem))); } try { Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } catch (IOException ex) { throw new UncheckedIOException(ex); } } private static final class JavadocMacro { private final ClassName className; private final JavadocAnchor anchor; private JavadocMacro(ClassName className, JavadocAnchor anchor) { this.className = className; this.anchor = anchor; } private static List parse(File adocFile) { List macros = new ArrayList<>(); try { Path adocFilePath = adocFile.toPath(); List lines = Files.readAllLines(adocFilePath); for (int i = 0; i < lines.size(); i++) { Matcher matcher = JAVADOC_MACRO_PATTERN.matcher(lines.get(i)); while (matcher.find()) { Origin classNameOrigin = new Origin(adocFile, i + 1, matcher.start(1) + 1); String target = matcher.group(1); String className = target; int endOfUrlAttribute = className.indexOf("}/"); if (endOfUrlAttribute != -1) { className = className.substring(endOfUrlAttribute + 2); } JavadocAnchor anchor = null; int anchorIndex = className.indexOf("#"); if (anchorIndex != -1) { anchor = JavadocAnchor.of(className.substring(anchorIndex + 1), new Origin(adocFile, classNameOrigin.line(), classNameOrigin.column + anchorIndex + 1)); className = className.substring(0, anchorIndex); } macros.add(new JavadocMacro(new ClassName(classNameOrigin, className), anchor)); } } } catch (IOException ex) { throw new UncheckedIOException(ex); } return macros; } } private static final class ClassName { private final Origin origin; private final String name; private ClassName(Origin origin, String name) { this.origin = origin; this.name = name; } } private record Origin(File file, int line, int column) { } private abstract static class JavadocAnchor { private final Origin origin; protected JavadocAnchor(Origin origin) { this.origin = origin; } Origin origin() { return this.origin; } private static JavadocAnchor of(String anchor, Origin origin) { JavadocAnchor javadocAnchor = WellKnownAnchor.of(anchor, origin); if (javadocAnchor == null) { javadocAnchor = MethodAnchor.of(anchor, origin); } if (javadocAnchor == null) { javadocAnchor = FieldAnchor.of(anchor, origin); } return javadocAnchor; } } private static final class WellKnownAnchor extends JavadocAnchor { private WellKnownAnchor(Origin origin) { super(origin); } private static WellKnownAnchor of(String anchor, Origin origin) { if (anchor.equals("enum-constant-summary")) { return new WellKnownAnchor(origin); } return null; } } private static final class MethodAnchor extends JavadocAnchor { private final String name; private final List arguments; private MethodAnchor(String name, List arguments, Origin origin) { super(origin); this.name = name; this.arguments = arguments; } @Override public String toString() { return this.name + "(" + String.join(", ", this.arguments + ")"); } static MethodAnchor of(String anchor, Origin origin) { if (!anchor.contains("(")) { return null; } int openingIndex = anchor.indexOf('('); String name = anchor.substring(0, openingIndex); List arguments = Stream.of(anchor.substring(openingIndex + 1, anchor.length() - 1).split(",")) .map(String::trim) .map((argument) -> argument.endsWith("...") ? argument.replace("...", "[]") : argument) .toList(); return new MethodAnchor(name, arguments, origin); } } private static final class FieldAnchor extends JavadocAnchor { private final String name; private FieldAnchor(String name, Origin origin) { super(origin); this.name = name; } static FieldAnchor of(String anchor, Origin origin) { return new FieldAnchor(anchor, origin); } } private static final class MethodMatcher extends ClassVisitor { private final MethodAnchor methodAnchor; private boolean matched = false; private MethodMatcher(MethodAnchor methodAnchor) { super(SpringAsmInfo.ASM_VERSION); this.methodAnchor = methodAnchor; } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { if (!this.matched && name.equals(this.methodAnchor.name)) { Type type = Type.getType(descriptor); if (type.getArgumentCount() == this.methodAnchor.arguments.size()) { List argumentTypeNames = Arrays.asList(type.getArgumentTypes()) .stream() .map(Type::getClassName) .toList(); if (argumentTypeNames.equals(this.methodAnchor.arguments)) { this.matched = true; } } } return null; } } private static final class FieldMatcher extends ClassVisitor { private final FieldAnchor fieldAnchor; private boolean matched = false; private FieldMatcher(FieldAnchor fieldAnchor) { super(SpringAsmInfo.ASM_VERSION); this.fieldAnchor = fieldAnchor; } @Override public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { if (!this.matched && name.equals(this.fieldAnchor.name)) { this.matched = true; } return null; } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/ConsumableContentContribution.java ================================================ /* * 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.antora; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.file.CopySpec; import org.gradle.api.file.Directory; import org.gradle.api.file.RegularFile; import org.gradle.api.provider.Provider; import org.gradle.api.publish.PublishingExtension; import org.gradle.api.publish.maven.MavenPublication; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; /** * A contribution of content to Antora that can be consumed by other projects. * * @author Andy Wilkinson */ class ConsumableContentContribution extends ContentContribution { protected ConsumableContentContribution(Project project, String type, String name) { super(project, name, type); } @Override void produceFrom(CopySpec copySpec) { this.produceFrom(copySpec, false); } void produceFrom(CopySpec copySpec, boolean publish) { TaskProvider producer = super.configureProduction(copySpec); if (publish) { publish(producer); } Configuration configuration = createConfiguration(getName(), "Configuration for %s Antora %s content artifacts."); configuration.setCanBeConsumed(true); configuration.setCanBeResolved(false); getProject().getArtifacts().add(configuration.getName(), producer); } void consumeFrom(String path) { Configuration configuration = createConfiguration(getName(), "Configuration for %s Antora %s content."); configuration.setCanBeConsumed(false); configuration.setCanBeResolved(true); DependencyHandler dependencies = getProject().getDependencies(); dependencies.add(configuration.getName(), getProject().provider(() -> projectDependency(path, configuration.getName()))); Provider outputDirectory = outputDirectory("content", getName()); TaskContainer tasks = getProject().getTasks(); TaskProvider copyAntoraContent = tasks.register(taskName("copy", "%s", configuration.getName()), CopyAntoraContent.class, (task) -> configureCopyContent(task, path, configuration, outputDirectory)); configureAntora(addInputFrom(copyAntoraContent, configuration.getName())); configurePlaybookGeneration(this::addToZipContentsCollectorDependencies); publish(copyAntoraContent); } void publish(TaskProvider producer) { getProject().getExtensions() .getByType(PublishingExtension.class) .getPublications() .withType(MavenPublication.class) .configureEach((mavenPublication) -> addPublishedMavenArtifact(mavenPublication, producer)); } private void configureCopyContent(CopyAntoraContent task, String path, Configuration configuration, Provider outputDirectory) { task.setDescription( "Syncs the %s Antora %s content from %s.".formatted(getName(), toDescription(getType()), path)); task.setSource(configuration); task.getOutputFile().set(outputDirectory.map(this::getContentZipFile)); } private void addToZipContentsCollectorDependencies(GenerateAntoraPlaybook task) { task.getAntoraExtensions().getZipContentsCollector().getDependencies().add(getName()); } private void addPublishedMavenArtifact(MavenPublication mavenPublication, TaskProvider producer) { if ("maven".equals(mavenPublication.getName())) { String classifier = "%s-%s-content".formatted(getName(), getType()); mavenPublication.artifact(producer, (mavenArtifact) -> mavenArtifact.setClassifier(classifier)); } } private RegularFile getContentZipFile(Directory dir) { Object version = getProject().getVersion(); return dir.file("spring-boot-docs-%s-%s-%s-content.zip".formatted(version, getName(), getType())); } private static String toDescription(String input) { return input.replace("-", " "); } private Configuration createConfiguration(String name, String description) { return getProject().getConfigurations() .create(configurationName(name, "Antora%sContent", getType()), (configuration) -> configuration.setDescription(description.formatted(getName(), getType()))); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/ContentContribution.java ================================================ /* * 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.antora; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.file.CopySpec; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Zip; /** * A contribution of content to Antora. * * @author Andy Wilkinson */ abstract class ContentContribution extends Contribution { private final String type; protected ContentContribution(Project project, String name, String type) { super(project, name); this.type = type; } protected String getType() { return this.type; } abstract void produceFrom(CopySpec copySpec); protected TaskProvider configureProduction(CopySpec copySpec) { TaskContainer tasks = getProject().getTasks(); TaskProvider zipContent = tasks.register(taskName("zip", "%sAntora%sContent", getName(), this.type), Zip.class, (zip) -> { zip.getDestinationDirectory() .set(getProject().getLayout().getBuildDirectory().dir("generated/docs/antora-content")); zip.getArchiveClassifier().set("%s-%s-content".formatted(getName(), this.type)); zip.with(copySpec); zip.setDescription("Creates a zip archive of the %s Antora %s content.".formatted(getName(), toDescription(this.type))); }); configureAntora(addInputFrom(zipContent, zipContent.getName())); return zipContent; } private static String toDescription(String input) { return input.replace("-", " "); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/Contribution.java ================================================ /* * 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.antora; import java.util.Arrays; import java.util.Map; import org.antora.gradle.AntoraTask; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.artifacts.Dependency; import org.gradle.api.file.Directory; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.TaskProvider; import org.springframework.boot.build.AntoraConventions; import org.springframework.util.StringUtils; /** * A contribution to Antora. * * @author Andy Wilkinson */ abstract class Contribution { private final Project project; private final String name; protected Contribution(Project project, String name) { this.project = project; this.name = name; } protected Project getProject() { return this.project; } protected String getName() { return this.name; } protected Dependency projectDependency(String path, String configurationName) { return getProject().getDependencies().project(Map.of("path", path, "configuration", configurationName)); } protected Provider outputDirectory(String dependencyType, String theName) { return getProject().getLayout() .getBuildDirectory() .dir("generated/docs/antora-dependencies-" + dependencyType + "/" + theName); } protected String taskName(String verb, String object, String... args) { return name(verb, object, args); } protected String configurationName(String name, String type, String... args) { return name(toCamelCase(name), type, args); } protected void configurePlaybookGeneration(Action action) { this.project.getTasks() .named(AntoraConventions.GENERATE_ANTORA_PLAYBOOK_TASK_NAME, GenerateAntoraPlaybook.class, action); } protected void configureAntora(Action action) { this.project.getTasks().named("antora", AntoraTask.class, action); } protected Action addInputFrom(TaskProvider task, String propertyName) { return (antora) -> antora.getInputs() .files(task) .withPathSensitivity(PathSensitivity.RELATIVE) .withPropertyName(propertyName); } private String name(String prefix, String format, String... args) { return prefix + format.formatted(Arrays.stream(args).map(this::toPascalCase).toArray()); } private String toPascalCase(String input) { return StringUtils.capitalize(toCamelCase(input)); } private String toCamelCase(String input) { StringBuilder output = new StringBuilder(input.length()); boolean capitalize = false; for (char c : input.toCharArray()) { if (c == '-') { capitalize = true; } else { output.append(capitalize ? Character.toUpperCase(c) : c); capitalize = false; } } return output.toString(); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/CopyAntoraContent.java ================================================ /* * 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.antora; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import javax.inject.Inject; import org.gradle.api.DefaultTask; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; /** * Tasks to copy Antora content. * * @author Andy Wilkinson */ public abstract class CopyAntoraContent extends DefaultTask { private FileCollection source; @Inject public CopyAntoraContent() { } @InputFiles public FileCollection getSource() { return this.source; } public void setSource(FileCollection source) { this.source = source; } @OutputFile public abstract RegularFileProperty getOutputFile(); @TaskAction void copyAntoraContent() throws IllegalStateException, IOException { Path source = this.source.getSingleFile().toPath(); Path target = getOutputFile().getAsFile().get().toPath(); Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/Extensions.java ================================================ /* * 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.antora; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.function.Consumer; import java.util.stream.Stream; /** * Antora and Asciidoc extensions used by Spring Boot. * * @author Phillip Webb */ public final class Extensions { private static final String ROOT_COMPONENT_EXTENSION = "@springio/antora-extensions/root-component-extension"; private static final List antora; static { List extensions = new ArrayList<>(); extensions.add(new Extension("@springio/antora-extensions", ROOT_COMPONENT_EXTENSION, "@springio/antora-extensions/static-page-extension", "@springio/antora-extensions/override-navigation-builder-extension")); extensions.add(new Extension("@springio/antora-xref-extension")); extensions.add(new Extension("@springio/antora-zip-contents-collector-extension")); antora = List.copyOf(extensions); } private static final List asciidoc; static { List extensions = new ArrayList<>(); extensions.add(new Extension("@asciidoctor/tabs")); extensions.add(new Extension("@springio/asciidoctor-extensions", "@springio/asciidoctor-extensions", "@springio/asciidoctor-extensions/javadoc-extension", "@springio/asciidoctor-extensions/configuration-properties-extension", "@springio/asciidoctor-extensions/section-ids-extension")); asciidoc = List.copyOf(extensions); } private static final Map localOverrides = Collections.emptyMap(); private Extensions() { } static List> antora(Consumer extensions) { AntoraExtensionsConfiguration result = new AntoraExtensionsConfiguration( antora.stream().flatMap(Extension::names).sorted().toList()); extensions.accept(result); return result.config(); } static List asciidoc() { return asciidoc.stream().flatMap(Extension::names).sorted().toList(); } private record Extension(String name, String... includeNames) { Stream names() { return (this.includeNames.length != 0) ? Arrays.stream(this.includeNames) : Stream.of(this.name); } } static final class AntoraExtensionsConfiguration { private final Map> extensions = new TreeMap<>(); private AntoraExtensionsConfiguration(List names) { names.forEach((name) -> this.extensions.put(name, null)); } void xref(Consumer xref) { xref.accept(new Xref()); } void zipContentsCollector(Consumer zipContentsCollector) { zipContentsCollector.accept(new ZipContentsCollector()); } void rootComponent(Consumer rootComponent) { rootComponent.accept(new RootComponent()); } List> config() { List> config = new ArrayList<>(); Map> orderedExtensions = new LinkedHashMap<>(this.extensions); // The root component extension must be last Map rootComponentConfig = orderedExtensions.remove(ROOT_COMPONENT_EXTENSION); orderedExtensions.put(ROOT_COMPONENT_EXTENSION, rootComponentConfig); orderedExtensions.forEach((name, customizations) -> { Map extensionConfig = new LinkedHashMap<>(); extensionConfig.put("require", localOverrides.getOrDefault(name, name)); if (customizations != null) { extensionConfig.putAll(customizations); } config.add(extensionConfig); }); return List.copyOf(config); } abstract class Customizer { private final String name; Customizer(String name) { this.name = name; } protected void customize(String key, Object value) { AntoraExtensionsConfiguration.this.extensions.computeIfAbsent(this.name, (name) -> new TreeMap<>()) .put(key, value); } } class Xref extends Customizer { Xref() { super("@springio/antora-xref-extension"); } void stub(List stub) { if (stub != null && !stub.isEmpty()) { customize("stub", stub); } } } class ZipContentsCollector extends Customizer { ZipContentsCollector() { super("@springio/antora-zip-contents-collector-extension"); } void versionFile(String versionFile) { customize("version_file", versionFile); } void locations(List locations) { customize("locations", locations); } void alwaysInclude(List alwaysInclude) { if (alwaysInclude != null && !alwaysInclude.isEmpty()) { customize("always_include", alwaysInclude.stream().map(AlwaysInclude::asMap).toList()); } } record AlwaysInclude(String name, String classifier) implements Serializable { private Map asMap() { return new TreeMap<>(Map.of("name", name(), "classifier", classifier())); } } } class RootComponent extends Customizer { RootComponent() { super(ROOT_COMPONENT_EXTENSION); } void name(String name) { customize("root_component_name", name); } } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/GenerateAntoraPlaybook.java ================================================ /* * 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.antora; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.gradle.api.DefaultTask; import org.gradle.api.Project; import org.gradle.api.file.Directory; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import org.springframework.boot.build.AntoraConventions; import org.springframework.boot.build.antora.Extensions.AntoraExtensionsConfiguration.ZipContentsCollector.AlwaysInclude; /** * Task to generate a local Antora playbook. * * @author Phillip Webb */ public abstract class GenerateAntoraPlaybook extends DefaultTask { private static final String GENERATED_DOCS = "build/generated/docs/"; private final Path root; private final Provider playbookOutputDir; private final String version; private final AntoraExtensions antoraExtensions; private final AsciidocExtensions asciidocExtensions; private final ContentSource contentSource; @OutputFile public abstract RegularFileProperty getOutputFile(); public GenerateAntoraPlaybook() { this.root = toRealPath(getProject().getRootDir().toPath()); this.antoraExtensions = getProject().getObjects().newInstance(AntoraExtensions.class, this.root); this.asciidocExtensions = getProject().getObjects().newInstance(AsciidocExtensions.class); this.version = getProject().getVersion().toString(); this.playbookOutputDir = configurePlaybookOutputDir(getProject()); this.contentSource = getProject().getObjects().newInstance(ContentSource.class, this.root); setGroup("Documentation"); setDescription("Generates an Antora playbook.yml file for local use"); getOutputFile().convention(getProject().getLayout() .getBuildDirectory() .file("generated/docs/antora-playbook/antora-playbook.yml")); this.contentSource.addStartPath(getProject() .provider(() -> getProject().getLayout().getProjectDirectory().dir(AntoraConventions.ANTORA_SOURCE_DIR))); } @Nested public AntoraExtensions getAntoraExtensions() { return this.antoraExtensions; } @Nested public AsciidocExtensions getAsciidocExtensions() { return this.asciidocExtensions; } @Nested public ContentSource getContentSource() { return this.contentSource; } private Provider configurePlaybookOutputDir(Project project) { Path siteDirectory = getProject().getLayout().getBuildDirectory().dir("site").get().getAsFile().toPath(); return project.provider(() -> { Path playbookDir = toRealPath(getOutputFile().get().getAsFile().toPath()).getParent(); Path outputDir = toRealPath(siteDirectory); return "." + File.separator + playbookDir.relativize(outputDir); }); } @TaskAction public void writePlaybookYml() throws IOException { File file = getOutputFile().get().getAsFile(); file.getParentFile().mkdirs(); try (FileWriter out = new FileWriter(file)) { createYaml().dump(getData(), out); } } private Map getData() throws IOException { Map data = loadPlaybookTemplate(); addExtensions(data); addSources(data); addDir(data); return data; } @SuppressWarnings("unchecked") private Map loadPlaybookTemplate() throws IOException { try (InputStream resource = getClass().getResourceAsStream("antora-playbook-template.yml")) { return createYaml().loadAs(resource, LinkedHashMap.class); } } @SuppressWarnings("unchecked") private void addExtensions(Map data) { Map antora = (Map) data.get("antora"); antora.put("extensions", Extensions.antora((extensions) -> { extensions.xref( (xref) -> xref.stub(this.antoraExtensions.getXref().getStubs().getOrElse(Collections.emptyList()))); extensions.zipContentsCollector((zipContentsCollector) -> { zipContentsCollector.versionFile("gradle.properties"); zipContentsCollector.locations(this.antoraExtensions.getZipContentsCollector() .getLocations() .getOrElse(Collections.emptyList())); zipContentsCollector .alwaysInclude(this.antoraExtensions.getZipContentsCollector().getAlwaysInclude().getOrNull()); }); extensions.rootComponent((rootComponent) -> rootComponent.name("boot")); })); Map asciidoc = (Map) data.get("asciidoc"); List asciidocExtensions = Extensions.asciidoc(); if (this.asciidocExtensions.getExcludeJavadocExtension().getOrElse(Boolean.FALSE)) { asciidocExtensions = new ArrayList<>(asciidocExtensions); asciidocExtensions.remove("@springio/asciidoctor-extensions/javadoc-extension"); } asciidoc.put("extensions", asciidocExtensions); } private void addSources(Map data) { List> contentSources = getList(data, "content.sources"); contentSources.add(createContentSource()); } private Map createContentSource() { Map source = new LinkedHashMap<>(); Path playbookPath = getOutputFile().get().getAsFile().toPath().getParent(); StringBuilder url = new StringBuilder("."); this.root.relativize(playbookPath).normalize().forEach((path) -> url.append(File.separator).append("..")); source.put("url", url.toString()); source.put("branches", "HEAD"); source.put("version", this.version); source.put("start_paths", this.contentSource.getStartPaths().get()); return source; } private void addDir(Map data) { data.put("output", Map.of("dir", this.playbookOutputDir.get())); } @SuppressWarnings("unchecked") private List getList(Map data, String location) { return (List) get(data, location); } @SuppressWarnings("unchecked") private Object get(Map data, String location) { Object result = data; String[] keys = location.split("\\."); for (String key : keys) { result = ((Map) result).get(key); } return result; } private Yaml createYaml() { DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); options.setPrettyFlow(true); return new Yaml(options); } private static Path toRealPath(Path path) { try { return Files.exists(path) ? path.toRealPath() : path; } catch (IOException ex) { throw new UncheckedIOException(ex); } } public abstract static class AntoraExtensions { private final Xref xref; private final ZipContentsCollector zipContentsCollector; @Inject public AntoraExtensions(ObjectFactory objects, Path root) { this.xref = objects.newInstance(Xref.class); this.zipContentsCollector = objects.newInstance(ZipContentsCollector.class, root); } @Nested public Xref getXref() { return this.xref; } @Nested public ZipContentsCollector getZipContentsCollector() { return this.zipContentsCollector; } public abstract static class Xref { @Input @Optional public abstract ListProperty getStubs(); } public abstract static class ZipContentsCollector { private final Provider> locations; @Inject public ZipContentsCollector(Project project, Path root) { this.locations = configureZipContentCollectorLocations(project, root); } private Provider> configureZipContentCollectorLocations(Project project, Path root) { ListProperty locations = project.getObjects().listProperty(String.class); Path relativeProjectPath = relativize(root, project.getProjectDir().toPath()); String locationName = project.getName() + "-${version}-${name}-${classifier}.zip"; locations.add(project .provider(() -> relativeProjectPath.resolve(GENERATED_DOCS + "antora-content/" + locationName) .toString())); locations.addAll(getDependencies().map((dependencies) -> dependencies.stream() .map((dependency) -> relativeProjectPath .resolve(GENERATED_DOCS + "antora-dependencies-content/" + dependency + "/" + locationName)) .map(Path::toString) .toList())); return locations; } private static Path relativize(Path root, Path subPath) { return toRealPath(root).relativize(toRealPath(subPath)).normalize(); } @Input @Optional public abstract ListProperty getAlwaysInclude(); @Input @Optional public Provider> getLocations() { return this.locations; } @Input @Optional public abstract SetProperty getDependencies(); } } public abstract static class AsciidocExtensions { @Inject public AsciidocExtensions() { } @Input @Optional public abstract Property getExcludeJavadocExtension(); } public abstract static class ContentSource { private final Path root; @Inject public ContentSource(Path root) { this.root = root; } @Input public abstract ListProperty getStartPaths(); void addStartPath(Provider startPath) { getStartPaths() .add(startPath.map((dir) -> this.root.relativize(toRealPath(dir.getAsFile().toPath())).toString())); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/LocalAggregateContentContribution.java ================================================ /* * 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.antora; import org.gradle.api.Project; import org.gradle.api.file.CopySpec; import org.springframework.boot.build.antora.Extensions.AntoraExtensionsConfiguration.ZipContentsCollector.AlwaysInclude; /** * A contribution of aggregate content that cannot be consumed by other projects. * * @author Andy Wilkinson */ class LocalAggregateContentContribution extends ContentContribution { protected LocalAggregateContentContribution(Project project, String name) { super(project, name, "local-aggregate"); } @Override void produceFrom(CopySpec copySpec) { super.configureProduction(copySpec); configurePlaybookGeneration(this::addToAlwaysInclude); } private void addToAlwaysInclude(GenerateAntoraPlaybook task) { task.getAntoraExtensions() .getZipContentsCollector() .getAlwaysInclude() .add(new AlwaysInclude(getName(), "local-aggregate-content")); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/SourceContribution.java ================================================ /* * 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.antora; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.file.Directory; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Zip; import org.springframework.boot.build.AntoraConventions; /** * A contribution of source to Antora. * * @author Andy Wilkinson */ class SourceContribution extends Contribution { private static final String CONFIGURATION_NAME = "antoraSource"; SourceContribution(Project project, String name) { super(project, name); } void produce() { Configuration antoraSource = getProject().getConfigurations().create(CONFIGURATION_NAME); TaskProvider antoraSourceZip = getProject().getTasks().register("antoraSourceZip", Zip.class, (zip) -> { zip.getDestinationDirectory().set(getProject().getLayout().getBuildDirectory().dir("antora-source")); zip.from(AntoraConventions.ANTORA_SOURCE_DIR); zip.setDescription( "Creates a zip archive of the Antora source in %s.".formatted(AntoraConventions.ANTORA_SOURCE_DIR)); }); getProject().getArtifacts().add(antoraSource.getName(), antoraSourceZip); } void consumeFrom(String path) { Configuration configuration = createConfiguration(getName()); DependencyHandler dependencies = getProject().getDependencies(); dependencies.add(configuration.getName(), getProject().provider(() -> projectDependency(path, CONFIGURATION_NAME))); Provider outputDirectory = outputDirectory("source", getName()); TaskContainer tasks = getProject().getTasks(); TaskProvider syncSource = tasks.register(taskName("sync", "%s", configuration.getName()), SyncAntoraSource.class, (task) -> configureSyncSource(task, path, configuration, outputDirectory)); configureAntora(addInputFrom(syncSource, configuration.getName())); configurePlaybookGeneration( (generatePlaybook) -> generatePlaybook.getContentSource().addStartPath(outputDirectory)); } private void configureSyncSource(SyncAntoraSource task, String path, Configuration configuration, Provider outputDirectory) { task.setDescription("Syncs the %s Antora source from %s.".formatted(getName(), path)); task.setSource(configuration); task.getOutputDirectory().set(outputDirectory); } private Configuration createConfiguration(String name) { return getProject().getConfigurations().create(configurationName(name, "AntoraSource")); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/antora/SyncAntoraSource.java ================================================ /* * 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.antora; import javax.inject.Inject; import org.gradle.api.DefaultTask; import org.gradle.api.file.ArchiveOperations; import org.gradle.api.file.CopySpec; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileSystemOperations; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; /** * Task sync Antora source. * * @author Andy Wilkinson */ public abstract class SyncAntoraSource extends DefaultTask { private final FileSystemOperations fileSystemOperations; private final ArchiveOperations archiveOperations; private FileCollection source; @Inject public SyncAntoraSource(FileSystemOperations fileSystemOperations, ArchiveOperations archiveOperations) { this.fileSystemOperations = fileSystemOperations; this.archiveOperations = archiveOperations; } @OutputDirectory public abstract DirectoryProperty getOutputDirectory(); @InputFiles public FileCollection getSource() { return this.source; } public void setSource(FileCollection source) { this.source = source; } @TaskAction void syncAntoraSource() { this.fileSystemOperations.sync(this::syncAntoraSource); } private void syncAntoraSource(CopySpec sync) { sync.into(getOutputDirectory()); this.source.getFiles().forEach((file) -> sync.from(this.archiveOperations.zipTree(file))); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java ================================================ /* * 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; import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.function.Supplier; import java.util.stream.Stream; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.EvaluationResult; import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.Transformer; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTree; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.IgnoreEmptyDirectories; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Optional; 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.SourceSet; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.VerificationException; /** * {@link Task} that checks for architecture problems. * * @author Andy Wilkinson * @author Yanming Zhou * @author Scott Frederick * @author Ivan Malutin * @author Phillip Webb * @author Dmytro Nosan * @author Moritz Halbritter * @author Stefano Cordio */ public abstract class ArchitectureCheck extends DefaultTask { private FileCollection classes; public ArchitectureCheck() { getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); getAnnotationClasses().convention(ArchitectureCheckAnnotation.asMap()); getRules().addAll(getProhibitObjectsRequireNonNull().convention(true) .map(whenTrue(ArchitectureRules::noClassesShouldCallObjectsRequireNonNull))); getRules().addAll(ArchitectureRules.standard()); getRules().addAll(whenMainSources(() -> ArchitectureRules.beanMethods(ArchitectureCheckAnnotation .classFor(getAnnotationClasses().get(), ArchitectureCheckAnnotation.CONDITIONAL_ON_CLASS)))); getRules().addAll(whenMainSources(() -> ArchitectureRules.conditionalOnMissingBean(ArchitectureCheckAnnotation .classFor(getAnnotationClasses().get(), ArchitectureCheckAnnotation.CONDITIONAL_ON_MISSING_BEAN)))); getRules().addAll(whenMainSources(() -> ArchitectureRules.configurationProperties(ArchitectureCheckAnnotation .classFor(getAnnotationClasses().get(), ArchitectureCheckAnnotation.CONFIGURATION_PROPERTIES)))); getRules().addAll(whenMainSources( () -> ArchitectureRules.configurationPropertiesBinding(ArchitectureCheckAnnotation.classFor( getAnnotationClasses().get(), ArchitectureCheckAnnotation.CONFIGURATION_PROPERTIES_BINDING)))); getRules().addAll(whenMainSources( () -> ArchitectureRules.configurationPropertiesDeprecation(ArchitectureCheckAnnotation.classFor( getAnnotationClasses().get(), ArchitectureCheckAnnotation.DEPRECATED_CONFIGURATION_PROPERTY)))); getRules().addAll(whenMainSources(() -> Collections.singletonList( ArchitectureRules.allCustomAssertionMethodsNotReturningSelfShouldBeAnnotatedWithCheckReturnValue()))); getRuleDescriptions().set(getRules().map(this::asDescriptions)); } private Provider> whenMainSources(Supplier> rules) { return isMainSourceSet().map(whenTrue(rules)); } private Provider isMainSourceSet() { return getSourceSet().convention(SourceSet.MAIN_SOURCE_SET_NAME).map(SourceSet.MAIN_SOURCE_SET_NAME::equals); } private Transformer, Boolean> whenTrue(Supplier> rules) { return (in) -> (!in) ? Collections.emptyList() : rules.get(); } private List asDescriptions(List rules) { return rules.stream().map(ArchRule::getDescription).toList(); } @TaskAction void checkArchitecture() throws Exception { withCompileClasspath(() -> { JavaClasses javaClasses = new ClassFileImporter().importPaths(classFilesPaths()); List results = new ArrayList<>(); evaluate(javaClasses).forEach(results::add); results.add(new AutoConfigurationChecker().check(javaClasses)); File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); List violations = results.stream().filter(EvaluationResult::hasViolation).toList(); writeViolationReport(violations, outputFile); if (!violations.isEmpty()) { throw new VerificationException("Architecture check failed. See '" + outputFile + "' for details."); } return null; }); } private List classFilesPaths() { return this.classes.getFiles().stream().map(File::toPath).toList(); } private Stream evaluate(JavaClasses javaClasses) { return getRules().get().stream().map((rule) -> rule.evaluate(javaClasses)); } private void withCompileClasspath(Callable callable) throws Exception { ClassLoader previous = Thread.currentThread().getContextClassLoader(); try { List urls = new ArrayList<>(); for (File file : getCompileClasspath().getFiles()) { urls.add(file.toURI().toURL()); } ClassLoader classLoader = new URLClassLoader(urls.toArray(new URL[0]), getClass().getClassLoader()); Thread.currentThread().setContextClassLoader(classLoader); callable.call(); } finally { Thread.currentThread().setContextClassLoader(previous); } } private void writeViolationReport(List violations, File outputFile) throws IOException { outputFile.getParentFile().mkdirs(); StringBuilder report = new StringBuilder(); for (EvaluationResult violation : violations) { report.append(violation.getFailureReport()); report.append(String.format("%n")); } Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } public void setClasses(FileCollection classes) { this.classes = classes; } @Internal public FileCollection getClasses() { return this.classes; } @InputFiles @SkipWhenEmpty @IgnoreEmptyDirectories @PathSensitive(PathSensitivity.RELATIVE) final FileTree getInputClasses() { return this.classes.getAsFileTree(); } @InputFiles @Classpath public abstract ConfigurableFileCollection getCompileClasspath(); @Optional @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public abstract DirectoryProperty getResourcesDirectory(); @OutputDirectory public abstract DirectoryProperty getOutputDirectory(); @Internal public abstract ListProperty getRules(); @Internal public abstract Property getProhibitObjectsRequireNonNull(); @Internal abstract Property getSourceSet(); @Input // Use descriptions as input since rules aren't serializable abstract ListProperty getRuleDescriptions(); @Internal abstract MapProperty getAnnotationClasses(); } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheckAnnotation.java ================================================ /* * 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; import java.util.Map; /** * Annotations used in architecture checks, which can be overridden in architecture rule * tests. * * @author Scott Frederick * @author Stephane Nicoll */ public enum ArchitectureCheckAnnotation { /** * Condition on class check. */ CONDITIONAL_ON_CLASS, /** * Condition on missing bean check. */ CONDITIONAL_ON_MISSING_BEAN, /** * Configuration properties bean. */ CONFIGURATION_PROPERTIES, /** * Deprecated configuration property. */ DEPRECATED_CONFIGURATION_PROPERTY, /** * Custom implementation to bind configuration properties. */ CONFIGURATION_PROPERTIES_BINDING; private static final Map annotationNameToClassName = Map.of(CONDITIONAL_ON_CLASS.name(), "org.springframework.boot.autoconfigure.condition.ConditionalOnClass", CONDITIONAL_ON_MISSING_BEAN.name(), "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean", CONFIGURATION_PROPERTIES.name(), "org.springframework.boot.context.properties.ConfigurationProperties", DEPRECATED_CONFIGURATION_PROPERTY.name(), "org.springframework.boot.context.properties.DeprecatedConfigurationProperty", CONFIGURATION_PROPERTIES_BINDING.name(), "org.springframework.boot.context.properties.ConfigurationPropertiesBinding"); static Map asMap() { return annotationNameToClassName; } static String classFor(Map annotationProperty, ArchitectureCheckAnnotation annotation) { String name = annotation.name(); return annotationProperty.getOrDefault(name, asMap().get(name)); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java ================================================ /* * 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; import org.gradle.api.Plugin; import org.gradle.api.Project; 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.api.tasks.compile.JavaCompile; import org.gradle.language.base.plugins.LifecycleBasePlugin; import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool; import org.springframework.util.StringUtils; /** * {@link Plugin} for verifying a project's architecture. * * @author Andy Wilkinson */ public class ArchitecturePlugin implements Plugin { @Override public void apply(Project project) { project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> registerTasks(project)); } private void registerTasks(Project project) { JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); for (SourceSet sourceSet : javaPluginExtension.getSourceSets()) { registerArchitectureCheck(sourceSet, "java", project) .configure((task) -> task.setClasses(project.files(project.getTasks() .named(sourceSet.getCompileTaskName("java"), JavaCompile.class) .flatMap((compile) -> compile.getDestinationDirectory())))); project.getPlugins() .withId("org.jetbrains.kotlin.jvm", (kotlinPlugin) -> registerArchitectureCheck(sourceSet, "kotlin", project) .configure((task) -> task.setClasses(project.files(project.getTasks() .named(sourceSet.getCompileTaskName("kotlin"), KotlinCompileTool.class) .flatMap((compile) -> compile.getDestinationDirectory()))))); } } private TaskProvider registerArchitectureCheck(SourceSet sourceSet, String language, Project project) { TaskProvider checkArchitecture = project.getTasks() .register( "checkArchitecture" + StringUtils.capitalize(sourceSet.getName() + StringUtils.capitalize(language)), ArchitectureCheck.class, (task) -> { task.getSourceSet().set(sourceSet.getName()); task.getCompileClasspath().from(sourceSet.getCompileClasspath()); task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir()); task.dependsOn(sourceSet.getProcessResourcesTaskName()); task.setDescription("Checks the architecture of the " + language + " classes of the " + sourceSet.getName() + " source set."); task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); }); project.getTasks() .named(LifecycleBasePlugin.CHECK_TASK_NAME) .configure((check) -> check.dependsOn(checkArchitecture)); return checkArchitecture; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java ================================================ /* * 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; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.AccessTarget.CodeUnitCallTarget; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaCall; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClass.Predicates; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaConstructor; import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaMember; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.JavaPackage; import com.tngtech.archunit.core.domain.JavaParameter; import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.properties.CanBeAnnotated; import com.tngtech.archunit.core.domain.properties.HasAnnotations; import com.tngtech.archunit.core.domain.properties.HasName; import com.tngtech.archunit.core.domain.properties.HasOwner; import com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With; import com.tngtech.archunit.core.domain.properties.HasParameterTypes; import com.tngtech.archunit.lang.AbstractClassesTransformer; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.ClassesTransformer; import com.tngtech.archunit.lang.ConditionEvents; import com.tngtech.archunit.lang.SimpleConditionEvent; import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; import com.tngtech.archunit.lang.syntax.elements.ClassesShould; import com.tngtech.archunit.lang.syntax.elements.GivenMethodsConjunction; import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Role; import org.springframework.lang.CheckReturnValue; import org.springframework.util.ResourceUtils; /** * Factory used to create {@link ArchRule architecture rules}. * * @author Andy Wilkinson * @author Yanming Zhou * @author Scott Frederick * @author Ivan Malutin * @author Phillip Webb * @author Ngoc Nhan * @author Moritz Halbritter * @author Stefano Cordio */ final class ArchitectureRules { private static final String AUTOCONFIGURATION_ANNOTATION = "org.springframework.boot.autoconfigure.AutoConfiguration"; private ArchitectureRules() { } static List noClassesShouldCallObjectsRequireNonNull() { return List.of( noClassesShould().callMethod(Objects.class, "requireNonNull", Object.class, String.class) .because(shouldUse("org.springframework.utils.Assert.notNull(Object, String)")), noClassesShould().callMethod(Objects.class, "requireNonNull", Object.class, Supplier.class) .because(shouldUse("org.springframework.utils.Assert.notNull(Object, Supplier)"))); } static List standard() { List rules = new ArrayList<>(); rules.add(allPackagesShouldBeFreeOfTangles()); rules.add(allBeanPostProcessorBeanMethodsShouldBeStaticAndNotCausePrematureInitialization()); rules.add(allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveOnlyInjectEnvironment()); rules.add(noClassesShouldCallStepVerifierStepVerifyComplete()); rules.add(noClassesShouldConfigureDefaultStepVerifierTimeout()); rules.add(noClassesShouldCallCollectorsToList()); rules.add(noClassesShouldCallURLEncoderWithStringEncoding()); rules.add(noClassesShouldCallURLDecoderWithStringEncoding()); rules.add(noClassesShouldLoadResourcesUsingResourceUtils()); rules.add(noClassesShouldCallStringToUpperCaseWithoutLocale()); rules.add(noClassesShouldCallStringToLowerCaseWithoutLocale()); rules.add(enumSourceShouldNotHaveValueThatIsTheSameAsTypeOfMethodsFirstParameter()); rules.add(conditionsShouldNotBePublic()); rules.add(autoConfigurationClassesShouldBePublicAndFinal()); rules.add(autoConfigurationClassesShouldHaveNoPublicMembers()); rules.add(testAutoConfigurationClassesShouldBePackagePrivateAndFinal()); return List.copyOf(rules); } static List beanMethods(String annotationClass) { return List.of(allBeanMethodsShouldReturnNonPrivateType(), allBeanMethodsShouldNotHaveConditionalOnClassAnnotation(annotationClass)); } static List conditionalOnMissingBean(String annotationClass) { return List .of(conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType(annotationClass)); } static List configurationProperties(String annotationClass) { return List.of(classLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute(annotationClass), methodLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute(annotationClass)); } static List configurationPropertiesBinding(String annotationClass) { return List.of(allConfigurationPropertiesBindingBeanMethodsShouldBeStatic(annotationClass)); } static List configurationPropertiesDeprecation(String annotationClass) { return List.of(allDeprecatedConfigurationPropertiesShouldIncludeSince(annotationClass)); } private static ArchRule allBeanMethodsShouldReturnNonPrivateType() { return methodsThatAreAnnotatedWith("org.springframework.context.annotation.Bean").should(check( "not return types declared with the %s modifier, as such types are incompatible with Spring AOT processing" .formatted(JavaModifier.PRIVATE), (method, events) -> { JavaClass returnType = method.getRawReturnType(); if (returnType.getModifiers().contains(JavaModifier.PRIVATE)) { addViolation(events, method, "%s returns %s which is declared as %s".formatted( method.getDescription(), returnType.getDescription(), returnType.getModifiers())); } })) .allowEmptyShould(true); } private static ArchRule allBeanMethodsShouldNotHaveConditionalOnClassAnnotation(String annotationName) { return methodsThatAreAnnotatedWith("org.springframework.context.annotation.Bean").should() .notBeAnnotatedWith(annotationName) .because("@ConditionalOnClass on @Bean methods is ineffective - it doesn't prevent " + "the method signature from being loaded. Such condition need to be placed" + " on a @Configuration class, allowing the condition to back off before the type is loaded.") .allowEmptyShould(true); } static ArchRule allCustomAssertionMethodsNotReturningSelfShouldBeAnnotatedWithCheckReturnValue() { return ArchRuleDefinition.methods() .that() .areDeclaredInClassesThat() .implement("org.assertj.core.api.Assert") .and() .arePublic() .and() .doNotHaveModifier(JavaModifier.BRIDGE) .and(doNotReturnSelfType()) .should() .beAnnotatedWith(CheckReturnValue.class) .allowEmptyShould(true); } private static DescribedPredicate doNotReturnSelfType() { return DescribedPredicate.describe("do not return self type", (method) -> !method.getRawReturnType().equals(method.getOwner())); } private static ArchRule allPackagesShouldBeFreeOfTangles() { return SlicesRuleDefinition.slices() .matching("(**)") .should() .beFreeOfCycles() .ignoreDependency("org.springframework.boot.env.EnvironmentPostProcessor", "org.springframework.boot.SpringApplication"); } private static ArchRule allBeanPostProcessorBeanMethodsShouldBeStaticAndNotCausePrematureInitialization() { return methodsThatAreAnnotatedWith("org.springframework.context.annotation.Bean").and() .haveRawReturnType(assignableTo("org.springframework.beans.factory.config.BeanPostProcessor")) .should(onlyHaveParametersThatWillNotCauseEagerInitialization()) .andShould() .beStatic() .allowEmptyShould(true); } private static ArchCondition onlyHaveParametersThatWillNotCauseEagerInitialization() { return check("not have parameters that will cause eager initialization", ArchitectureRules::allBeanPostProcessorBeanMethodsShouldBeStaticAndNotCausePrematureInitialization); } private static void allBeanPostProcessorBeanMethodsShouldBeStaticAndNotCausePrematureInitialization(JavaMethod item, ConditionEvents events) { DescribedPredicate notAnnotatedWithLazy = DescribedPredicate .not(CanBeAnnotated.Predicates.annotatedWith("org.springframework.context.annotation.Lazy")); DescribedPredicate notOfASafeType = notAssignableTo( "org.springframework.beans.factory.ObjectProvider", "org.springframework.context.ApplicationContext", "org.springframework.core.env.Environment") .and(notAnnotatedWithRoleInfrastructure()); item.getParameters() .stream() .filter(notAnnotatedWithLazy) .filter((parameter) -> notOfASafeType.test(parameter.getRawType())) .forEach((parameter) -> addViolation(events, parameter, parameter.getDescription() + " will cause eager initialization as it is " + notAnnotatedWithLazy.getDescription() + " and is " + notOfASafeType.getDescription())); } private static DescribedPredicate notAnnotatedWithRoleInfrastructure() { return is("not annotated with @Role(BeanDefinition.ROLE_INFRASTRUCTURE", (candidate) -> { if (!candidate.isAnnotatedWith(Role.class)) { return true; } Role role = candidate.getAnnotationOfType(Role.class); return role.value() != BeanDefinition.ROLE_INFRASTRUCTURE; }); } private static ArchRule allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveOnlyInjectEnvironment() { return methodsThatAreAnnotatedWith("org.springframework.context.annotation.Bean").and() .haveRawReturnType(assignableTo("org.springframework.beans.factory.config.BeanFactoryPostProcessor")) .should(onlyInjectEnvironment()) .andShould() .beStatic() .allowEmptyShould(true); } private static ArchCondition onlyInjectEnvironment() { return check("only inject Environment", ArchitectureRules::onlyInjectEnvironment); } private static void onlyInjectEnvironment(JavaMethod item, ConditionEvents events) { if (item.getParameters().stream().anyMatch(ArchitectureRules::isNotEnvironment)) { addViolation(events, item, item.getDescription() + " should only inject Environment"); } } private static boolean isNotEnvironment(JavaParameter parameter) { return !"org.springframework.core.env.Environment".equals(parameter.getType().getName()); } private static ArchRule noClassesShouldCallStepVerifierStepVerifyComplete() { return noClassesShould().callMethod("reactor.test.StepVerifier$Step", "verifyComplete") .because("it can block indefinitely and " + shouldUse("expectComplete().verify(Duration)")); } private static ArchRule noClassesShouldConfigureDefaultStepVerifierTimeout() { return noClassesShould().callMethod("reactor.test.StepVerifier", "setDefaultTimeout", "java.time.Duration") .because(shouldUse("expectComplete().verify(Duration)")); } private static ArchRule noClassesShouldCallCollectorsToList() { return noClassesShould().callMethod(Collectors.class, "toList") .because(shouldUse("java.util.stream.Stream.toList()")); } private static ArchRule noClassesShouldCallURLEncoderWithStringEncoding() { return noClassesShould().callMethod(URLEncoder.class, "encode", String.class, String.class) .because(shouldUse("java.net.URLEncoder.encode(String s, Charset charset)")); } private static ArchRule noClassesShouldCallURLDecoderWithStringEncoding() { return noClassesShould().callMethod(URLDecoder.class, "decode", String.class, String.class) .because(shouldUse("java.net.URLDecoder.decode(String s, Charset charset)")); } private static ArchRule noClassesShouldLoadResourcesUsingResourceUtils() { DescribedPredicate> resourceUtilsGetURL = hasJavaCallTarget(ownedByResourceUtils()) .and(hasJavaCallTarget(hasNameOf("getURL"))) .and(hasJavaCallTarget(hasRawStringParameterType())); DescribedPredicate> resourceUtilsGetFile = hasJavaCallTarget(ownedByResourceUtils()) .and(hasJavaCallTarget(hasNameOf("getFile"))) .and(hasJavaCallTarget(hasRawStringParameterType())); return noClassesShould().callMethodWhere(resourceUtilsGetURL.or(resourceUtilsGetFile)) .because(shouldUse("org.springframework.boot.io.ApplicationResourceLoader")); } private static ArchRule noClassesShouldCallStringToUpperCaseWithoutLocale() { return noClassesShould().callMethod(String.class, "toUpperCase") .because(shouldUse("String.toUpperCase(Locale.ROOT)")); } private static ArchRule noClassesShouldCallStringToLowerCaseWithoutLocale() { return noClassesShould().callMethod(String.class, "toLowerCase") .because(shouldUse("String.toLowerCase(Locale.ROOT)")); } private static ArchRule conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType( String annotation) { return methodsThatAreAnnotatedWith(annotation) .should(notSpecifyOnlyATypeThatIsTheSameAsTheMethodReturnType(annotation)) .allowEmptyShould(true); } private static ArchCondition notSpecifyOnlyATypeThatIsTheSameAsTheMethodReturnType( String annotation) { return check("not specify only a type that is the same as the method's return type", (item, events) -> { JavaAnnotation conditionalAnnotation = item.getAnnotationOfType(annotation); Map properties = conditionalAnnotation.getProperties(); if (!hasProperty("type", properties) && !hasProperty("name", properties)) { conditionalAnnotation.get("value").ifPresent((value) -> { if (containsOnlySingleType((JavaType[]) value, item.getReturnType())) { addViolation(events, item, conditionalAnnotation.getDescription() + " should not specify only a value that is the same as the method's return type"); } }); } }); } private static boolean hasProperty(String name, Map properties) { Object property = properties.get(name); if (property == null) { return false; } return (property.getClass().isArray()) ? ((Object[]) property).length > 0 : !property.toString().isEmpty(); } private static ArchRule enumSourceShouldNotHaveValueThatIsTheSameAsTypeOfMethodsFirstParameter() { return ArchRuleDefinition.methods() .that() .areAnnotatedWith("org.junit.jupiter.params.provider.EnumSource") .should(notHaveValueThatIsTheSameAsTheTypeOfTheMethodsFirstParameter()) .allowEmptyShould(true); } private static ArchCondition notHaveValueThatIsTheSameAsTheTypeOfTheMethodsFirstParameter() { return check("not have a value that is the same as the type of the method's first parameter", ArchitectureRules::notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType); } private static void notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType(JavaMethod item, ConditionEvents events) { JavaAnnotation enumSourceAnnotation = item .getAnnotationOfType("org.junit.jupiter.params.provider.EnumSource"); enumSourceAnnotation.get("value").ifPresent((value) -> { JavaType parameterType = item.getParameterTypes().get(0); if (value.equals(parameterType)) { addViolation(events, item, enumSourceAnnotation.getDescription() + " should not specify a value that is the same as the type of the method's first parameter"); } }); } private static ArchRule classLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute( String annotationClass) { return ArchRuleDefinition.classes() .that() .areAnnotatedWith(annotationClass) .should(notSpecifyOnlyPrefixAttributeOfConfigurationProperties(annotationClass)) .allowEmptyShould(true); } private static ArchRule methodLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute( String annotationClass) { return ArchRuleDefinition.methods() .that() .areAnnotatedWith(annotationClass) .should(notSpecifyOnlyPrefixAttributeOfConfigurationProperties(annotationClass)) .allowEmptyShould(true); } private static ArchCondition> notSpecifyOnlyPrefixAttributeOfConfigurationProperties( String annotationClass) { return check("not specify only prefix attribute of @ConfigurationProperties", (item, events) -> notSpecifyOnlyPrefixAttributeOfConfigurationProperties(annotationClass, item, events)); } private static void notSpecifyOnlyPrefixAttributeOfConfigurationProperties(String annotationClass, HasAnnotations item, ConditionEvents events) { JavaAnnotation configurationPropertiesAnnotation = item.getAnnotationOfType(annotationClass); Map properties = configurationPropertiesAnnotation.getProperties(); if (hasProperty("prefix", properties) && !hasProperty("value", properties) && properties.get("ignoreInvalidFields").equals(false) && properties.get("ignoreUnknownFields").equals(true)) { addViolation(events, item, configurationPropertiesAnnotation.getDescription() + " should specify implicit 'value' attribute other than explicit 'prefix' attribute"); } } private static ArchRule conditionsShouldNotBePublic() { String springBootCondition = "org.springframework.boot.autoconfigure.condition.SpringBootCondition"; return ArchRuleDefinition.noClasses() .that() .areAssignableTo(springBootCondition) .and() .doNotHaveModifier(JavaModifier.ABSTRACT) .and() .areNotAnnotatedWith(Deprecated.class) .should() .bePublic() .allowEmptyShould(true); } private static ArchRule allConfigurationPropertiesBindingBeanMethodsShouldBeStatic(String annotationClass) { return methodsThatAreAnnotatedWith("org.springframework.context.annotation.Bean").and() .areAnnotatedWith(annotationClass) .should() .beStatic() .allowEmptyShould(true); } private static ArchRule allDeprecatedConfigurationPropertiesShouldIncludeSince(String annotationName) { return methodsThatAreAnnotatedWith(annotationName) .should(check("include a non-empty 'since' attribute", (method, events) -> { JavaAnnotation annotation = method.getAnnotationOfType(annotationName); Map properties = annotation.getProperties(); Object since = properties.get("since"); if (!(since instanceof String) || ((String) since).isEmpty()) { addViolation(events, method, annotation.getDescription() + " should include a non-empty 'since' attribute of @DeprecatedConfigurationProperty"); } })) .allowEmptyShould(true); } private static ArchRule autoConfigurationClassesShouldBePublicAndFinal() { return ArchRuleDefinition.classes() .that(areRegularAutoConfiguration()) .should() .bePublic() .andShould() .haveModifier(JavaModifier.FINAL) .allowEmptyShould(true); } private static ArchRule testAutoConfigurationClassesShouldBePackagePrivateAndFinal() { return ArchRuleDefinition.classes() .that(areTestAutoConfiguration()) .should() .bePackagePrivate() .andShould() .haveModifier(JavaModifier.FINAL) .allowEmptyShould(true); } private static ArchRule autoConfigurationClassesShouldHaveNoPublicMembers() { return ArchRuleDefinition.members() .that() .areDeclaredInClassesThat(areRegularAutoConfiguration()) .and() .areDeclaredInClassesThat(areNotKotlinClasses()) .and(areNotDefaultConstructors()) .and(areNotConstants()) .and(dontOverridePublicMethods()) .should() .notBePublic() .allowEmptyShould(true); } static ArchRule shouldHaveNoPublicMembers() { return ArchRuleDefinition.members() .that(areNotDefaultConstructors()) .and(areNotConstants()) .and(dontOverridePublicMethods()) .should() .notBePublic() .allowEmptyShould(true); } static DescribedPredicate areRegularAutoConfiguration() { return DescribedPredicate.describe("are regular @AutoConfiguration", (javaClass) -> javaClass.isAnnotatedWith(AUTOCONFIGURATION_ANNOTATION) && !javaClass.getName().contains("TestAutoConfiguration") && !javaClass.isAnnotation()); } static DescribedPredicate areNotKotlinClasses() { return DescribedPredicate.describe("are not Kotlin classes", (javaClass) -> !javaClass.isAnnotatedWith("kotlin.Metadata")); } static DescribedPredicate areTestAutoConfiguration() { return DescribedPredicate.describe("are test @AutoConfiguration", (javaClass) -> javaClass.isAnnotatedWith(AUTOCONFIGURATION_ANNOTATION) && javaClass.getName().contains("TestAutoConfiguration") && !javaClass.isAnnotation()); } private static DescribedPredicate dontOverridePublicMethods() { OverridesPublicMethod predicate = new OverridesPublicMethod<>(); return DescribedPredicate.describe("don't override public methods", (member) -> !predicate.test(member)); } private static DescribedPredicate areNotDefaultConstructors() { return DescribedPredicate.describe("aren't default constructors", (member) -> !areDefaultConstructors().test(member)); } private static DescribedPredicate areDefaultConstructors() { return DescribedPredicate.describe("are default constructors", (member) -> { if (!(member instanceof JavaConstructor constructor)) { return false; } return constructor.getParameters().isEmpty(); }); } private static DescribedPredicate areNotConstants() { return DescribedPredicate.describe("aren't constants", (member) -> !areConstants().test(member)); } private static DescribedPredicate areConstants() { return DescribedPredicate.describe("are constants", (member) -> { if (member instanceof JavaField field) { Set modifiers = field.getModifiers(); return modifiers.contains(JavaModifier.STATIC) && modifiers.contains(JavaModifier.FINAL); } return false; }); } private static boolean containsOnlySingleType(JavaType[] types, JavaType type) { return types.length == 1 && type.equals(types[0]); } private static ClassesShould noClassesShould() { return ArchRuleDefinition.noClasses().should(); } private static GivenMethodsConjunction methodsThatAreAnnotatedWith(String annotation) { return ArchRuleDefinition.methods().that().areAnnotatedWith(annotation); } private static DescribedPredicate> ownedByResourceUtils() { return With.owner(Predicates.type(ResourceUtils.class)); } private static DescribedPredicate hasNameOf(String name) { return HasName.Predicates.name(name); } private static DescribedPredicate hasRawStringParameterType() { return HasParameterTypes.Predicates.rawParameterTypes(String.class); } private static DescribedPredicate> hasJavaCallTarget( DescribedPredicate predicate) { return JavaCall.Predicates.target(predicate); } private static DescribedPredicate notAssignableTo(String... typeNames) { return DescribedPredicate.not(assignableTo(typeNames)); } private static DescribedPredicate assignableTo(String... typeNames) { DescribedPredicate result = null; for (String typeName : typeNames) { DescribedPredicate assignableTo = Predicates.assignableTo(typeName); result = (result != null) ? result.or(assignableTo) : assignableTo; } return result; } private static DescribedPredicate is(String description, Predicate predicate) { return new DescribedPredicate<>(description) { @Override public boolean test(JavaClass t) { return predicate.test(t); } }; } private static ArchCondition check(String description, BiConsumer check) { return new ArchCondition<>(description) { @Override public void check(T item, ConditionEvents events) { check.accept(item, events); } }; } private static void addViolation(ConditionEvents events, Object correspondingObject, String message) { events.add(SimpleConditionEvent.violated(correspondingObject, message)); } private static String shouldUse(String string) { return string + " should be used instead"; } static ClassesTransformer packages(Predicate filter) { return new AbstractClassesTransformer<>("packages") { @Override public Iterable doTransform(JavaClasses collection) { return collection.stream().map(JavaClass::getPackage).filter(filter).collect(Collectors.toSet()); } }; } private static class OverridesPublicMethod extends DescribedPredicate { OverridesPublicMethod() { super("overrides public method"); } @Override public boolean test(T member) { if (!(member instanceof JavaMethod javaMethod)) { return false; } Stream superClassMethods = member.getOwner() .getAllRawSuperclasses() .stream() .flatMap((superClass) -> superClass.getMethods().stream()); Stream interfaceMethods = member.getOwner() .getAllRawInterfaces() .stream() .flatMap((iface) -> iface.getMethods().stream()); return Stream.concat(superClassMethods, interfaceMethods) .anyMatch((superMethod) -> isPublic(superMethod) && isOverridden(superMethod, javaMethod)); } private boolean isPublic(JavaMethod method) { return method.getModifiers().contains(JavaModifier.PUBLIC); } private boolean isOverridden(JavaMethod superMethod, JavaMethod method) { return superMethod.getName().equals(method.getName()) && superMethod.getRawParameterTypes().size() == method.getRawParameterTypes().size() && superMethod.getDescriptor().equals(method.getDescriptor()); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/architecture/AutoConfigurationChecker.java ================================================ /* * 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; import java.util.HashMap; import java.util.Map; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.lang.EvaluationResult; /** * Finds all configurations from auto-configurations (either nested configurations or * imported ones) and checks that these classes don't contain public members. *

* Kotlin classes are ignored as Kotlin does not have package-private visibility and * {@code internal} isn't a good substitute. * * @author Moritz Halbritter */ class AutoConfigurationChecker { private final DescribedPredicate isAutoConfiguration = ArchitectureRules.areRegularAutoConfiguration() .and(ArchitectureRules.areNotKotlinClasses()); EvaluationResult check(JavaClasses javaClasses) { AutoConfigurations autoConfigurations = new AutoConfigurations(javaClasses); for (JavaClass javaClass : javaClasses) { if (isAutoConfigurationOrEnclosedInAutoConfiguration(javaClass)) { autoConfigurations.add(javaClass); } } return ArchitectureRules.shouldHaveNoPublicMembers().evaluate(autoConfigurations.getConfigurations()); } private boolean isAutoConfigurationOrEnclosedInAutoConfiguration(JavaClass javaClass) { if (this.isAutoConfiguration.test(javaClass)) { return true; } JavaClass enclosingClass = javaClass.getEnclosingClass().orElse(null); while (enclosingClass != null) { if (this.isAutoConfiguration.test(enclosingClass)) { return true; } enclosingClass = enclosingClass.getEnclosingClass().orElse(null); } return false; } private static final class AutoConfigurations { private static final String SPRING_BOOT_ROOT_PACKAGE = "org.springframework.boot"; private static final String IMPORT = "org.springframework.context.annotation.Import"; private static final String CONFIGURATION = "org.springframework.context.annotation.Configuration"; private final JavaClasses classes; private final Map autoConfigurationClasses = new HashMap<>(); AutoConfigurations(JavaClasses classes) { this.classes = classes; } void add(JavaClass autoConfiguration) { if (!autoConfiguration.isMetaAnnotatedWith(CONFIGURATION)) { return; } if (this.autoConfigurationClasses.putIfAbsent(autoConfiguration.getName(), autoConfiguration) != null) { return; } processImports(autoConfiguration, this.autoConfigurationClasses); } JavaClasses getConfigurations() { DescribedPredicate isAutoConfiguration = DescribedPredicate.describe("is an auto-configuration", (c) -> this.autoConfigurationClasses.containsKey(c.getName())); return this.classes.that(isAutoConfiguration); } private void processImports(JavaClass javaClass, Map result) { JavaClass[] importedClasses = getImportedClasses(javaClass); for (JavaClass importedClass : importedClasses) { if (!isBootClass(importedClass)) { continue; } if (result.putIfAbsent(importedClass.getName(), importedClass) != null) { continue; } // Recursively find other imported classes processImports(importedClass, result); } } private JavaClass[] getImportedClasses(JavaClass javaClass) { if (!javaClass.isAnnotatedWith(IMPORT)) { return new JavaClass[0]; } JavaAnnotation imports = javaClass.getAnnotationOfType(IMPORT); return (JavaClass[]) imports.get("value").orElse(new JavaClass[0]); } private boolean isBootClass(JavaClass javaClass) { String pkg = javaClass.getPackage().getName(); return pkg.equals(SPRING_BOOT_ROOT_PACKAGE) || pkg.startsWith(SPRING_BOOT_ROOT_PACKAGE + "."); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/artifacts/ArtifactRelease.java ================================================ /* * 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.artifacts; import java.util.Locale; import org.gradle.api.Project; /** * Information about artifacts produced by a build. * * @author Andy Wilkinson * @author Scott Frederick */ public final class ArtifactRelease { private static final String SPRING_SNAPSHOT_REPO = "https://repo.spring.io/snapshot"; private static final String MAVEN_REPO = "https://repo.maven.apache.org/maven2"; private final Type type; private ArtifactRelease(Type type) { this.type = type; } public String getType() { return this.type.toString().toLowerCase(Locale.ROOT); } public String getDownloadRepo() { return (this.type == Type.SNAPSHOT) ? SPRING_SNAPSHOT_REPO : MAVEN_REPO; } public boolean isRelease() { return this.type == Type.RELEASE; } public static ArtifactRelease forProject(Project project) { return forVersion(project.getVersion().toString()); } public static ArtifactRelease forVersion(String version) { return new ArtifactRelease(Type.forVersion(version)); } enum Type { SNAPSHOT, MILESTONE, RELEASE; static Type forVersion(String version) { int modifierIndex = version.lastIndexOf('-'); if (modifierIndex == -1) { return RELEASE; } String type = version.substring(modifierIndex + 1); if (type.startsWith("M") || type.startsWith("RC")) { return MILESTONE; } return SNAPSHOT; } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationClass.java ================================================ /* * 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.autoconfigure; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import org.springframework.asm.AnnotationVisitor; import org.springframework.asm.ClassReader; import org.springframework.asm.ClassVisitor; import org.springframework.asm.SpringAsmInfo; import org.springframework.asm.Type; /** * An {@code @AutoConfiguration} class. * * @param name name of the auto-configuration class * @param before values of the {@code before} attribute * @param beforeName values of the {@code beforeName} attribute * @param after values of the {@code after} attribute * @param afterName values of the {@code afterName} attribute * @author Andy Wilkinson */ public record AutoConfigurationClass(String name, List before, List beforeName, List after, List afterName) { private AutoConfigurationClass(String name, Map> attributes) { this(name, attributes.getOrDefault("before", Collections.emptyList()), attributes.getOrDefault("beforeName", Collections.emptyList()), attributes.getOrDefault("after", Collections.emptyList()), attributes.getOrDefault("afterName", Collections.emptyList())); } public static AutoConfigurationClass of(InputStream input) { try { ClassReader classReader = new ClassReader(input); AutoConfigurationClassVisitor visitor = new AutoConfigurationClassVisitor(); classReader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); return visitor.autoConfigurationClass; } catch (IOException ex) { throw new UncheckedIOException(ex); } } 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 AutoConfigurationClass autoConfigurationClass; private String name; private AutoConfigurationClassVisitor() { super(SpringAsmInfo.ASM_VERSION); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.name = Type.getObjectType(name).getClassName(); } @Override public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { String annotationClassName = Type.getType(descriptor).getClassName(); if ("org.springframework.boot.autoconfigure.AutoConfiguration".equals(annotationClassName)) { return new AutoConfigurationAnnotationVisitor(); } return null; } private final class AutoConfigurationAnnotationVisitor extends AnnotationVisitor { private Map> attributes = new HashMap<>(); private static final Set INTERESTING_ATTRIBUTES = Set.of("before", "beforeName", "after", "afterName"); private AutoConfigurationAnnotationVisitor() { super(SpringAsmInfo.ASM_VERSION); } @Override public void visitEnd() { AutoConfigurationClassVisitor.this.autoConfigurationClass = new AutoConfigurationClass( AutoConfigurationClassVisitor.this.name, this.attributes); } @Override public AnnotationVisitor visitArray(String attributeName) { if (INTERESTING_ATTRIBUTES.contains(attributeName)) { return new AnnotationVisitor(SpringAsmInfo.ASM_VERSION) { @Override public void visit(String name, Object value) { if (value instanceof Type type) { value = type.getClassName(); } AutoConfigurationAnnotationVisitor.this.attributes .computeIfAbsent(attributeName, (n) -> new ArrayList<>()) .add(Objects.toString(value)); } }; } return null; } } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationImportsTask.java ================================================ /* * 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.autoconfigure; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.util.List; import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTree; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SkipWhenEmpty; /** * A {@link Task} that uses a project's auto-configuration imports. * * @author Andy Wilkinson */ public abstract class AutoConfigurationImportsTask extends DefaultTask { /** * 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(); @InputFiles @SkipWhenEmpty @PathSensitive(PathSensitivity.RELATIVE) public FileTree getSource() { return this.sourceFiles.getAsFileTree().matching((filter) -> filter.include(IMPORTS_FILE)); } public void setSource(Object source) { this.sourceFiles = getProject().getObjects().fileCollection().from(source); } protected List loadImports() { File importsFile = getSource().getSingleFile(); try { return Files.readAllLines(importsFile.toPath()); } catch (IOException ex) { throw new UncheckedIOException(ex); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationMetadata.java ================================================ /* * 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.autoconfigure; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Properties; import java.util.Set; 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.tasks.Classpath; import org.gradle.api.tasks.InputFile; 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.asm.ClassReader; import org.springframework.asm.Opcodes; import org.springframework.core.CollectionFactory; /** * A {@link Task} for generating metadata describing a project's auto-configuration * classes. * * @author Andy Wilkinson * @author Scott Frederick */ public abstract class AutoConfigurationMetadata extends DefaultTask { private static final String COMMENT_START = "#"; private final String moduleName; private FileCollection classesDirectories; public AutoConfigurationMetadata() { this.moduleName = getProject().getName(); } public void setSourceSet(SourceSet sourceSet) { getAutoConfigurationImports().set(new File(sourceSet.getOutput().getResourcesDir(), "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports")); this.classesDirectories = sourceSet.getOutput().getClassesDirs(); dependsOn(sourceSet.getOutput()); } @InputFile @PathSensitive(PathSensitivity.RELATIVE) abstract RegularFileProperty getAutoConfigurationImports(); @OutputFile public abstract RegularFileProperty getOutputFile(); @Classpath FileCollection getClassesDirectories() { return this.classesDirectories; } @TaskAction void documentAutoConfiguration() throws IOException { Properties autoConfiguration = readAutoConfiguration(); File outputFile = getOutputFile().get().getAsFile(); outputFile.getParentFile().mkdirs(); try (FileWriter writer = new FileWriter(outputFile)) { autoConfiguration.store(writer, null); } } private Properties readAutoConfiguration() throws IOException { Properties autoConfiguration = CollectionFactory.createSortedProperties(true); List classNames = readAutoConfigurationsFile(); Set publicClassNames = new LinkedHashSet<>(); for (String className : classNames) { File classFile = findClassFile(className); if (classFile == null) { throw new IllegalStateException("Auto-configuration class '" + className + "' not found."); } try (InputStream in = new FileInputStream(classFile)) { int access = new ClassReader(in).getAccess(); if ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC) { publicClassNames.add(className); } } } autoConfiguration.setProperty("autoConfigurationClassNames", String.join(",", publicClassNames)); autoConfiguration.setProperty("module", this.moduleName); return autoConfiguration; } /** * Reads auto-configurations from * META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. * @return auto-configurations */ private List readAutoConfigurationsFile() throws IOException { File file = getAutoConfigurationImports().getAsFile().get(); if (!file.exists()) { return Collections.emptyList(); } try (BufferedReader reader = new BufferedReader(new FileReader(file))) { return reader.lines().map(this::stripComment).filter((line) -> !line.isEmpty()).toList(); } } private String stripComment(String line) { int commentStart = line.indexOf(COMMENT_START); if (commentStart == -1) { return line.trim(); } return line.substring(0, commentStart).trim(); } private File findClassFile(String className) { String classFileName = className.replace(".", "/") + ".class"; for (File classesDir : this.classesDirectories) { File classFile = new File(classesDir, classFileName); if (classFile.isFile()) { return classFile; } } return null; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java ================================================ /* * 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.autoconfigure; import java.util.Arrays; import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.Dependency; import org.gradle.api.attributes.Category; import org.gradle.api.attributes.Usage; import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.springframework.boot.build.DeployedPlugin; import org.springframework.boot.build.optional.OptionalDependenciesPlugin; /** * {@link Plugin} for projects that define auto-configuration. When applied, the plugin * applies the {@link DeployedPlugin}. Additionally, when the {@link JavaPlugin} is * applied it: * *

    *
  • Adds a dependency on the auto-configuration annotation processor. *
  • Defines a task that produces metadata describing the auto-configuration. The * metadata is made available as an artifact in the {@code autoConfigurationMetadata} * configuration. *
  • Add checks to ensure import files and annotations are correct
  • *
* * @author Andy Wilkinson */ public class AutoConfigurationPlugin implements Plugin { private static final String AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME = "autoConfigurationMetadata"; @Override public void apply(Project project) { project.getPlugins().apply(DeployedPlugin.class); project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> new Configurer(project).configure()); } private static class Configurer { private final Project project; private SourceSet main; Configurer(Project project) { this.project = project; this.main = project.getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets() .getByName(SourceSet.MAIN_SOURCE_SET_NAME); } void configure() { addAnnotationProcessorsDependencies(); TaskContainer tasks = this.project.getTasks(); ConfigurationContainer configurations = this.project.getConfigurations(); configurations.consumable(AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME, (configuration) -> { configuration.attributes((attributes) -> { attributes.attribute(Category.CATEGORY_ATTRIBUTE, this.project.getObjects().named(Category.class, Category.DOCUMENTATION)); attributes.attribute(Usage.USAGE_ATTRIBUTE, this.project.getObjects().named(Usage.class, "auto-configuration-metadata")); }); }); tasks.register("autoConfigurationMetadata", AutoConfigurationMetadata.class, this::configureAutoConfigurationMetadata); TaskProvider checkAutoConfigurationImports = tasks.register( "checkAutoConfigurationImports", CheckAutoConfigurationImports.class, this::configureCheckAutoConfigurationImports); Configuration requiredClasspath = configurations.create("autoConfigurationRequiredClasspath") .extendsFrom(configurations.getByName(this.main.getImplementationConfigurationName()), configurations.getByName(this.main.getRuntimeOnlyConfigurationName())); requiredClasspath.getDependencies().add(projectDependency(":core:spring-boot-autoconfigure")); TaskProvider checkAutoConfigurationClasses = tasks.register( "checkAutoConfigurationClasses", CheckAutoConfigurationClasses.class, (task) -> configureCheckAutoConfigurationClasses(requiredClasspath, task)); this.project.getPlugins() .withType(OptionalDependenciesPlugin.class, (plugin) -> configureCheckAutoConfigurationClassesForOptionalDependencies(configurations, checkAutoConfigurationClasses)); this.project.getTasks() .getByName(JavaBasePlugin.CHECK_TASK_NAME) .dependsOn(checkAutoConfigurationImports, checkAutoConfigurationClasses); } private void addAnnotationProcessorsDependencies() { this.project.getConfigurations() .getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME) .getDependencies() .addAll(projectDependencies(":core:spring-boot-autoconfigure-processor", ":configuration-metadata:spring-boot-configuration-processor")); } private void configureAutoConfigurationMetadata(AutoConfigurationMetadata task) { task.setSourceSet(this.main); task.dependsOn(this.main.getClassesTaskName()); task.getOutputFile() .set(this.project.getLayout().getBuildDirectory().file("auto-configuration-metadata.properties")); this.project.getArtifacts() .add(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME, task.getOutputFile(), (artifact) -> artifact.builtBy(task)); } private void configureCheckAutoConfigurationImports(CheckAutoConfigurationImports task) { task.setSource(this.main.getResources()); task.setClasspath(this.main.getOutput().getClassesDirs()); task.setDescription( "Checks the %s file of the main source set.".formatted(AutoConfigurationImportsTask.IMPORTS_FILE)); } private void configureCheckAutoConfigurationClasses(Configuration requiredClasspath, CheckAutoConfigurationClasses task) { task.setSource(this.main.getResources()); task.setClasspath(this.main.getOutput().getClassesDirs()); task.setRequiredDependencies(requiredClasspath); task.setDescription("Checks the auto-configuration classes of the main source set."); } private void configureCheckAutoConfigurationClassesForOptionalDependencies( ConfigurationContainer configurations, TaskProvider checkAutoConfigurationClasses) { checkAutoConfigurationClasses.configure((check) -> { Configuration optionalClasspath = configurations.create("autoConfigurationOptionalClassPath") .extendsFrom(configurations.getByName(OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME)); check.setOptionalDependencies(optionalClasspath); }); } private Set projectDependencies(String... paths) { return Arrays.stream(paths).map((path) -> projectDependency(path)).collect(Collectors.toSet()); } private Dependency projectDependency(String path) { return this.project.getDependencies().project(Collections.singletonMap("path", path)); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationClasses.java ================================================ /* * 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.autoconfigure; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.stream.Stream; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.VerificationException; import org.gradle.language.base.plugins.LifecycleBasePlugin; /** * Task to check a project's {@code @AutoConfiguration} classes. * * @author Andy Wilkinson */ public abstract class CheckAutoConfigurationClasses extends AutoConfigurationImportsTask { private FileCollection classpath = getProject().getObjects().fileCollection(); private FileCollection optionalDependencies = getProject().getObjects().fileCollection(); private FileCollection requiredDependencies = getProject().getObjects().fileCollection(); private SetProperty optionalDependencyClassNames = getProject().getObjects().setProperty(String.class); private SetProperty requiredDependencyClassNames = getProject().getObjects().setProperty(String.class); public CheckAutoConfigurationClasses() { getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); this.optionalDependencyClassNames.set(getProject().provider(() -> classNamesOf(this.optionalDependencies))); this.requiredDependencyClassNames.set(getProject().provider(() -> classNamesOf(this.requiredDependencies))); } private static List classNamesOf(FileCollection classpath) { return classpath.getFiles().stream().flatMap((file) -> { try (JarFile jarFile = new JarFile(file)) { return Collections.list(jarFile.entries()) .stream() .filter((entry) -> !entry.isDirectory()) .map(JarEntry::getName) .filter((entryName) -> entryName.endsWith(".class")) .map((entryName) -> entryName.substring(0, entryName.length() - ".class".length())) .map((entryName) -> entryName.replace("/", ".")); } catch (IOException ex) { throw new UncheckedIOException(ex); } }).toList(); } @Classpath public FileCollection getClasspath() { return this.classpath; } public void setClasspath(Object classpath) { this.classpath = getProject().getObjects().fileCollection().from(classpath); } @Classpath public FileCollection getOptionalDependencies() { return this.optionalDependencies; } public void setOptionalDependencies(Object classpath) { this.optionalDependencies = getProject().getObjects().fileCollection().from(classpath); } @Classpath public FileCollection getRequiredDependencies() { return this.requiredDependencies; } public void setRequiredDependencies(Object classpath) { this.requiredDependencies = getProject().getObjects().fileCollection().from(classpath); } @OutputDirectory public abstract DirectoryProperty getOutputDirectory(); @Input public abstract SetProperty getOmittedFromImports(); @TaskAction void execute() { Map> problems = new TreeMap<>(); Set optionalOnlyClassNames = new HashSet<>(this.optionalDependencyClassNames.get()); Set requiredClassNames = this.requiredDependencyClassNames.get(); optionalOnlyClassNames.removeAll(requiredClassNames); List imports = loadImports(); classFiles().forEach((classFile) -> { AutoConfigurationClass autoConfigurationClass = AutoConfigurationClass.of(classFile); if (autoConfigurationClass != null) { check(autoConfigurationClass, optionalOnlyClassNames, requiredClassNames, imports, problems); } }); File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); writeReport(problems, outputFile); if (!problems.isEmpty()) { throw new VerificationException( "Auto-configuration class check failed. See '%s' for details".formatted(outputFile)); } } private List classFiles() { List classFiles = new ArrayList<>(); for (File root : this.classpath.getFiles()) { if (root.exists()) { try (Stream files = Files.walk(root.toPath())) { files.forEach((file) -> { if (Files.isRegularFile(file) && file.getFileName().toString().endsWith(".class")) { classFiles.add(file.toFile()); } }); } catch (IOException ex) { throw new UncheckedIOException(ex); } } } return classFiles; } private void check(AutoConfigurationClass autoConfigurationClass, Set optionalOnlyClassNames, Set requiredClassNames, List imports, Map> problems) { if (!autoConfigurationClass.name().endsWith("AutoConfiguration")) { problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()) .add("Name of a class annotated with @AutoConfiguration should end with AutoConfiguration"); } boolean testAutoConfiguration = autoConfigurationClass.name().endsWith("TestAutoConfiguration"); if (!getOmittedFromImports().getOrElse(Collections.emptySet()).contains(autoConfigurationClass.name()) && !imports.contains(autoConfigurationClass.name()) && !testAutoConfiguration) { problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()) .add("Class is not registered in AutoConfiguration.imports"); } if ((getOmittedFromImports().getOrElse(Collections.emptySet()).contains(autoConfigurationClass.name()) || testAutoConfiguration) && imports.contains(autoConfigurationClass.name())) { problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()) .add("Class should not be registered in AutoConfiguration.imports"); } autoConfigurationClass.before().forEach((before) -> { if (optionalOnlyClassNames.contains(before)) { problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()) .add("before '%s' is from an optional dependency and should be declared in beforeName" .formatted(before)); } }); autoConfigurationClass.beforeName().forEach((beforeName) -> { if (!optionalOnlyClassNames.contains(beforeName)) { String problem = requiredClassNames.contains(beforeName) ? "beforeName '%s' is from a required dependency and should be declared in before" .formatted(beforeName) : "beforeName '%s' not found".formatted(beforeName); problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()).add(problem); } }); autoConfigurationClass.after().forEach((after) -> { if (optionalOnlyClassNames.contains(after)) { problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()) .add("after '%s' is from an optional dependency and should be declared in afterName" .formatted(after)); } }); autoConfigurationClass.afterName().forEach((afterName) -> { if (!optionalOnlyClassNames.contains(afterName)) { String problem = requiredClassNames.contains(afterName) ? "afterName '%s' is from a required dependency and should be declared in after" .formatted(afterName) : "afterName '%s' not found".formatted(afterName); problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()).add(problem); } }); } private void writeReport(Map> problems, File outputFile) { outputFile.getParentFile().mkdirs(); StringBuilder report = new StringBuilder(); if (!problems.isEmpty()) { report.append("Found auto-configuration class problems:%n".formatted()); problems.forEach((className, classProblems) -> { report.append(" - %s:%n".formatted(className)); classProblems.forEach((problem) -> report.append(" - %s%n".formatted(problem))); }); } try { Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } catch (IOException ex) { throw new UncheckedIOException(ex); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationImports.java ================================================ /* * 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.autoconfigure; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.VerificationException; import org.gradle.language.base.plugins.LifecycleBasePlugin; /** * Task to check the contents of a project's * {@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports} * file. * * @author Andy Wilkinson */ public abstract class CheckAutoConfigurationImports extends AutoConfigurationImportsTask { private FileCollection classpath = getProject().getObjects().fileCollection(); public CheckAutoConfigurationImports() { getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); } @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() { File importsFile = getSource().getSingleFile(); check(importsFile); } private void check(File importsFile) { List imports = loadImports(); List problems = new ArrayList<>(); for (String imported : imports) { File classFile = find(imported); if (classFile == null) { problems.add("'%s' was not found".formatted(imported)); } else if (!correctlyAnnotated(classFile)) { problems.add("'%s' is not annotated with @AutoConfiguration".formatted(imported)); } } List sortedValues = new ArrayList<>(imports); Collections.sort(sortedValues); if (!sortedValues.equals(imports)) { File sortedOutputFile = getOutputDirectory().file("sorted-" + importsFile.getName()).get().getAsFile(); writeString(sortedOutputFile, sortedValues.stream().collect(Collectors.joining(System.lineSeparator())) + System.lineSeparator()); problems.add("Entries should be sorted alphabetically (expect content written to " + sortedOutputFile.getAbsolutePath() + ")"); } File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); writeReport(importsFile, problems, outputFile); if (!problems.isEmpty()) { throw new VerificationException("%s check failed. See '%s' for details" .formatted(AutoConfigurationImportsTask.IMPORTS_FILE, outputFile)); } } private File find(String className) { for (File root : this.classpath.getFiles()) { String classFilePath = className.replace(".", "/") + ".class"; File classFile = new File(root, classFilePath); if (classFile.isFile()) { return classFile; } } return null; } private boolean correctlyAnnotated(File classFile) { return AutoConfigurationClass.of(classFile) != null; } private void writeReport(File importsFile, List problems, File outputFile) { outputFile.getParentFile().mkdirs(); StringBuilder report = new StringBuilder(); if (!problems.isEmpty()) { report.append("Found problems in '%s':%n".formatted(importsFile)); 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); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java ================================================ /* * 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.autoconfigure; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.UncheckedIOException; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.stream.Collectors; import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; 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.TaskAction; import org.springframework.util.FileSystemUtils; import org.springframework.util.StringUtils; /** * {@link Task} used to document auto-configuration classes. * * @author Andy Wilkinson */ public abstract class DocumentAutoConfigurationClasses extends DefaultTask { private FileCollection autoConfiguration; @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getAutoConfiguration() { return this.autoConfiguration; } public void setAutoConfiguration(FileCollection autoConfiguration) { this.autoConfiguration = autoConfiguration; } @OutputDirectory public abstract DirectoryProperty getOutputDir(); @TaskAction void documentAutoConfigurationClasses() throws IOException { FileSystemUtils.deleteRecursively(getOutputDir().getAsFile().get()); List autoConfigurations = load(); autoConfigurations.forEach(this::writeModuleAdoc); for (File metadataFile : this.autoConfiguration) { Properties metadata = new Properties(); try (Reader reader = new FileReader(metadataFile)) { metadata.load(reader); } AutoConfiguration autoConfiguration = new AutoConfiguration(metadata.getProperty("module"), new TreeSet<>( StringUtils.commaDelimitedListToSet(metadata.getProperty("autoConfigurationClassNames")))); writeModuleAdoc(autoConfiguration); } writeNavAdoc(autoConfigurations); } private List load() { return this.autoConfiguration.getFiles() .stream() .map(AutoConfiguration::of) .sorted((a1, a2) -> a1.module.compareTo(a2.module)) .toList(); } private void writeModuleAdoc(AutoConfiguration autoConfigurationClasses) { File outputDir = getOutputDir().getAsFile().get(); outputDir.mkdirs(); try (PrintWriter writer = new PrintWriter( new FileWriter(new File(outputDir, autoConfigurationClasses.module + ".adoc")))) { writer.println("[[appendix.auto-configuration-classes.%s]]".formatted(autoConfigurationClasses.module)); writer.println("= %s".formatted(autoConfigurationClasses.module)); writer.println(); writer.println("The following auto-configuration classes are from the `%s` module:" .formatted(autoConfigurationClasses.module)); writer.println(); writer.println("[cols=\"4,1\"]"); writer.println("|==="); writer.println("| Configuration Class | Links"); for (AutoConfigurationClass autoConfigurationClass : autoConfigurationClasses.classes) { writer.println(); writer.printf("| {code-spring-boot}/module/%s/src/main/java/%s.java[`%s`]%n", autoConfigurationClasses.module, autoConfigurationClass.path, autoConfigurationClass.name); writer.printf("| xref:api:java/%s.html[javadoc]%n", autoConfigurationClass.path); } writer.println("|==="); } catch (IOException ex) { throw new UncheckedIOException(ex); } } private void writeNavAdoc(List autoConfigurations) { File outputDir = getOutputDir().getAsFile().get(); outputDir.mkdirs(); try (PrintWriter writer = new PrintWriter(new FileWriter(new File(outputDir, "nav.adoc")))) { autoConfigurations.forEach((autoConfigurationClasses) -> writer .println("*** xref:appendix:auto-configuration-classes/%s.adoc[]" .formatted(autoConfigurationClasses.module))); } catch (IOException ex) { throw new UncheckedIOException(ex); } } private static final class AutoConfiguration { private final String module; private final SortedSet classes; private AutoConfiguration(String module, Set classNames) { this.module = module; this.classes = classNames.stream().map((className) -> { String path = className.replace('.', '/'); String name = className.substring(className.lastIndexOf('.') + 1); return new AutoConfigurationClass(name, path); }).collect(Collectors.toCollection(TreeSet::new)); } private static AutoConfiguration of(File metadataFile) { Properties metadata = new Properties(); try (Reader reader = new FileReader(metadataFile)) { metadata.load(reader); } catch (IOException ex) { throw new UncheckedIOException(ex); } return new AutoConfiguration(metadata.getProperty("module"), new TreeSet<>( StringUtils.commaDelimitedListToSet(metadata.getProperty("autoConfigurationClassNames")))); } } private static final class AutoConfigurationClass implements Comparable { private final String name; private final String path; private AutoConfigurationClass(String name, String path) { this.name = name; this.path = path; } @Override public int compareTo(AutoConfigurationClass other) { return this.name.compareTo(other.name); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java ================================================ /* * 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.bom; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.function.Predicate; import javax.inject.Inject; import groovy.lang.Closure; import groovy.lang.GroovyObjectSupport; import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; import org.apache.maven.artifact.versioning.VersionRange; import org.gradle.api.Action; import org.gradle.api.InvalidUserCodeException; import org.gradle.api.InvalidUserDataException; import org.gradle.api.Project; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.JavaPlatformPlugin; import org.springframework.boot.build.bom.BomExtension.LibraryHandler.AlignWithHandler.PropertyHandler; import org.springframework.boot.build.bom.BomExtension.LibraryHandler.AlignWithHandler.VersionHandler; import org.springframework.boot.build.bom.Library.BomAlignment; import org.springframework.boot.build.bom.Library.DependencyVersionAlignment; import org.springframework.boot.build.bom.Library.Exclusion; import org.springframework.boot.build.bom.Library.Group; import org.springframework.boot.build.bom.Library.ImportedBom; import org.springframework.boot.build.bom.Library.LibraryVersion; import org.springframework.boot.build.bom.Library.Link; import org.springframework.boot.build.bom.Library.Module; import org.springframework.boot.build.bom.Library.PermittedDependency; import org.springframework.boot.build.bom.Library.PomPropertyVersionAlignment; import org.springframework.boot.build.bom.Library.ProhibitedVersion; import org.springframework.boot.build.bom.Library.VersionAlignment; import org.springframework.boot.build.bom.ResolvedBom.Id; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.boot.build.properties.BuildProperties; import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; /** * DSL extensions for {@link BomPlugin}. * * @author Andy Wilkinson * @author Phillip Webb */ public class BomExtension { private final String id; private final Project project; private final UpgradeHandler upgradeHandler; private final Map properties = new LinkedHashMap<>(); private final Map artifactVersionProperties = new HashMap<>(); private final List libraries = new ArrayList<>(); public BomExtension(Project project) { this.project = project; this.upgradeHandler = project.getObjects().newInstance(UpgradeHandler.class, project); this.id = "%s:%s:%s".formatted(project.getGroup(), project.getName(), project.getVersion()); } public String getId() { return this.id; } public List getLibraries() { return this.libraries; } public Library getLibrary(String name) { return getLibraries().stream() .filter((library) -> library.getName().equals(name)) .findFirst() .orElseThrow(() -> new IllegalStateException("No library found named '%s'".formatted(name))); } public void upgrade(Action action) { action.execute(this.upgradeHandler); } public Upgrade getUpgrade() { GitHubHandler gitHub = this.upgradeHandler.gitHub; return new Upgrade(this.upgradeHandler.upgradePolicy, new GitHub(gitHub.organization, gitHub.repository, gitHub.issueLabels)); } public void library(String name, Action action) { library(name, null, action); } public void library(String name, String version, Action action) { ObjectFactory objects = this.project.getObjects(); LibraryHandler libraryHandler = objects.newInstance(LibraryHandler.class, this.project, (version != null) ? version : ""); action.execute(libraryHandler); LibraryVersion libraryVersion = new LibraryVersion(DependencyVersion.parse(libraryHandler.version)); addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups, libraryHandler.upgradePolicy, libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots, versionAlignment(libraryHandler), libraryHandler.alignWith.bomAlignment, libraryHandler.linkRootName, libraryHandler.links)); } private VersionAlignment versionAlignment(LibraryHandler libraryHandler) { VersionHandler version = libraryHandler.alignWith.version; if (version != null) { return new DependencyVersionAlignment(version.of, version.from, version.managedBy, this.project, this.libraries, libraryHandler.groups); } PropertyHandler property = libraryHandler.alignWith.property; if (property != null) { return new PomPropertyVersionAlignment(property.name, property.of, property.managedBy, this.project, this.libraries); } return null; } private String createDependencyNotation(String groupId, String artifactId, DependencyVersion version) { return groupId + ":" + artifactId + ":" + version; } Map getProperties() { return this.properties; } String getArtifactVersionProperty(String groupId, String artifactId, String classifier) { String coordinates = groupId + ":" + artifactId + ":" + classifier; return this.artifactVersionProperties.get(coordinates); } private void putArtifactVersionProperty(String groupId, String artifactId, String versionProperty) { putArtifactVersionProperty(groupId, artifactId, null, versionProperty); } private void putArtifactVersionProperty(String groupId, String artifactId, String classifier, String versionProperty) { String coordinates = groupId + ":" + artifactId + ":" + ((classifier != null) ? classifier : ""); String existing = this.artifactVersionProperties.putIfAbsent(coordinates, versionProperty); if (existing != null) { throw new InvalidUserDataException("Cannot put version property for '" + coordinates + "'. Version property '" + existing + "' has already been stored."); } } private void addLibrary(Library library) { DependencyHandler dependencies = this.project.getDependencies(); this.libraries.add(library); String versionProperty = library.getVersionProperty(); if (versionProperty != null) { this.properties.put(versionProperty, library.getVersion().getVersion()); } for (Group group : library.getGroups()) { for (Module module : group.getModules()) { addModule(library, dependencies, versionProperty, group, module); } for (ImportedBom bomImport : group.getBoms()) { addBomImport(library, dependencies, versionProperty, group, bomImport.name()); } } } private void addModule(Library library, DependencyHandler dependencies, String versionProperty, Group group, Module module) { putArtifactVersionProperty(group.getId(), module.getName(), module.getClassifier(), versionProperty); String constraint = createDependencyNotation(group.getId(), module.getName(), library.getVersion().getVersion()); dependencies.getConstraints().add(JavaPlatformPlugin.API_CONFIGURATION_NAME, constraint); } private void addBomImport(Library library, DependencyHandler dependencies, String versionProperty, Group group, String bomImport) { putArtifactVersionProperty(group.getId(), bomImport, versionProperty); String bomDependency = createDependencyNotation(group.getId(), bomImport, library.getVersion().getVersion()); dependencies.add(JavaPlatformPlugin.API_CONFIGURATION_NAME, dependencies.platform(bomDependency)); dependencies.add(BomPlugin.API_ENFORCED_CONFIGURATION_NAME, dependencies.enforcedPlatform(bomDependency)); } public static class LibraryHandler { private final Project project; private final List groups = new ArrayList<>(); private UpgradePolicy upgradePolicy; private final List prohibitedVersions = new ArrayList<>(); private final AlignWithHandler alignWith; private boolean considerSnapshots; private String version; private String calendarName; private String linkRootName; private final Map> links = new HashMap<>(); @Inject public LibraryHandler(Project project, String version) { this.project = project; this.version = version; this.alignWith = project.getObjects().newInstance(AlignWithHandler.class); } public void version(String version) { this.version = version; } public void considerSnapshots() { this.considerSnapshots = true; } public void setCalendarName(String calendarName) { this.calendarName = calendarName; } public void group(String id, Action action) { GroupHandler groupHandler = this.project.getObjects().newInstance(GroupHandler.class, id); action.execute(groupHandler); this.groups .add(new Group(groupHandler.id, groupHandler.modules, groupHandler.plugins, groupHandler.imports)); } public void setUpgradePolicy(UpgradePolicy upgradePolicy) { this.upgradePolicy = upgradePolicy; } public void prohibit(Action action) { ProhibitedHandler handler = new ProhibitedHandler(); action.execute(handler); this.prohibitedVersions.add(new ProhibitedVersion(handler.versionRange, handler.startsWith, handler.endsWith, handler.contains, handler.reason)); } public void alignWith(Action action) { action.execute(this.alignWith); } public void links(Action action) { links(null, action); } public void links(String linkRootName, Action action) { LinksHandler handler = new LinksHandler(); action.execute(handler); this.linkRootName = linkRootName; this.links.putAll(handler.links); } public static class ProhibitedHandler { private String reason; private final List startsWith = new ArrayList<>(); private final List endsWith = new ArrayList<>(); private final List contains = new ArrayList<>(); private VersionRange versionRange; public void versionRange(String versionRange) { try { this.versionRange = VersionRange.createFromVersionSpec(versionRange); } catch (InvalidVersionSpecificationException ex) { throw new InvalidUserCodeException("Invalid version range", ex); } } public void startsWith(String startsWith) { this.startsWith.add(startsWith); } public void startsWith(Collection startsWith) { this.startsWith.addAll(startsWith); } public void endsWith(String endsWith) { this.endsWith.add(endsWith); } public void endsWith(Collection endsWith) { this.endsWith.addAll(endsWith); } public void contains(String contains) { this.contains.add(contains); } public void contains(List contains) { this.contains.addAll(contains); } public void because(String because) { this.reason = because; } } public static class GroupHandler extends GroovyObjectSupport { private final String id; private List modules = new ArrayList<>(); private List imports = new ArrayList<>(); private List plugins = new ArrayList<>(); @Inject public GroupHandler(String id) { this.id = id; } public void setModules(List modules) { this.modules = modules.stream() .map((input) -> (input instanceof Module module) ? module : new Module((String) input)) .toList(); } public void bom(String bom) { this.imports.add(new ImportedBom(bom)); } public void bom(String bom, Action action) { ImportBomHandler handler = new ImportBomHandler(); action.execute(handler); this.imports.add(new ImportedBom(bom, handler.permittedDependencies)); } public void setPlugins(List plugins) { this.plugins = plugins; } public Object methodMissing(String name, Object args) { if (args instanceof Object[] argsArray && argsArray.length == 1) { if (argsArray[0] instanceof Closure closure) { ModuleHandler moduleHandler = new ModuleHandler(); closure.setResolveStrategy(Closure.DELEGATE_FIRST); closure.setDelegate(moduleHandler); closure.call(moduleHandler); return new Module(name, moduleHandler.type, moduleHandler.classifier, moduleHandler.exclusions); } } throw new InvalidUserDataException("Invalid configuration for module '" + name + "'"); } public class ModuleHandler { private final List exclusions = new ArrayList<>(); private String type; private String classifier; public void exclude(Map exclusion) { this.exclusions.add(new Exclusion(exclusion.get("group"), exclusion.get("module"))); } public void setType(String type) { this.type = type; } public void setClassifier(String classifier) { this.classifier = classifier; } } public class ImportBomHandler { private final List permittedDependencies = new ArrayList<>(); public void permit(String allowed) { String[] components = allowed.split(":"); this.permittedDependencies.add(new PermittedDependency(components[0], components[1])); } } } public static class AlignWithHandler { private VersionHandler version; private PropertyHandler property; private BomAlignment bomAlignment; public void version(Action action) { this.version = new VersionHandler(); action.execute(this.version); } public void property(Action action) { this.property = new PropertyHandler(); action.execute(this.property); } public void dependencyManagementDeclaredIn(String bomCoordinates) { this.bomAlignment = new BomAlignment(bomCoordinates, (id) -> false); } public void dependencyManagementDeclaredIn(String bomCoordinates, Action action) { DependencyManagementDeclaredInHandler handler = new DependencyManagementDeclaredInHandler(); action.execute(handler); this.bomAlignment = new BomAlignment(bomCoordinates, handler.exclusions); } public static class VersionHandler { private String of; private String from; private String managedBy; public void of(String of) { this.of = of; } public void from(String from) { this.from = from; } public void managedBy(String managedBy) { this.managedBy = managedBy; } } public static class PropertyHandler { private String name; private String of; private String managedBy; public void name(String name) { this.name = name; } public void of(String dependency) { this.of = dependency; } public void managedBy(String managedBy) { this.managedBy = managedBy; } } public static class DependencyManagementDeclaredInHandler { private Predicate exclusions = (id) -> false; public void excluding(Predicate exclusion) { this.exclusions = this.exclusions.or(exclusion); } } } } public static class LinksHandler { private final Map> links = new HashMap<>(); public void site(String linkTemplate) { site(asFactory(linkTemplate)); } public void site(Function linkFactory) { add("site", linkFactory); } public void github(String linkTemplate) { github(asFactory(linkTemplate)); } public void github(Function linkFactory) { add("github", linkFactory); } public void docs(String linkTemplate) { docs(asFactory(linkTemplate)); } public void docs(Function linkFactory) { add("docs", linkFactory); } public void javadoc(String linkTemplate) { javadoc(asFactory(linkTemplate)); } public void javadoc(String linkTemplate, String... packages) { javadoc(asFactory(linkTemplate), packages); } public void javadoc(Function linkFactory) { add("javadoc", linkFactory); } public void javadoc(Function linkFactory, String... packages) { add("javadoc", linkFactory, packages); } public void javadoc(String rootName, Function linkFactory, String... packages) { add(rootName, "javadoc", linkFactory, packages); } public void releaseNotes(String linkTemplate) { releaseNotes(asFactory(linkTemplate)); } public void releaseNotes(Function linkFactory) { add("releaseNotes", linkFactory); } public void add(String name, String linkTemplate) { add(name, asFactory(linkTemplate)); } public void add(String name, Function linkFactory) { add(name, linkFactory, null); } public void add(String name, Function linkFactory, String[] packages) { add(null, name, linkFactory, packages); } private void add(String rootName, String name, Function linkFactory, String[] packages) { Link link = new Link(rootName, linkFactory, (packages != null) ? List.of(packages) : null); this.links.computeIfAbsent(name, (key) -> new ArrayList<>()).add(link); } private Function asFactory(String linkTemplate) { return (version) -> { PlaceholderResolver resolver = (name) -> "version".equals(name) ? version.toString() : null; return new PropertyPlaceholderHelper("{", "}").replacePlaceholders(linkTemplate, resolver); }; } } public static class UpgradeHandler { private UpgradePolicy upgradePolicy; private final GitHubHandler gitHub; @Inject public UpgradeHandler(Project project) { this.gitHub = new GitHubHandler(project); } public void setPolicy(UpgradePolicy upgradePolicy) { this.upgradePolicy = upgradePolicy; } public void gitHub(Action action) { action.execute(this.gitHub); } } public static final class Upgrade { private final UpgradePolicy upgradePolicy; private final GitHub gitHub; private Upgrade(UpgradePolicy upgradePolicy, GitHub gitHub) { this.upgradePolicy = upgradePolicy; this.gitHub = gitHub; } public UpgradePolicy getPolicy() { return this.upgradePolicy; } public GitHub getGitHub() { return this.gitHub; } } public static class GitHubHandler { private String organization; private String repository; private List issueLabels; public GitHubHandler(Project project) { BuildProperties buildProperties = BuildProperties.get(project); this.organization = buildProperties.gitHub().organization(); this.repository = buildProperties.gitHub().repository(); } public void setOrganization(String organization) { this.organization = organization; } public void setRepository(String repository) { this.repository = repository; } public void setIssueLabels(List issueLabels) { this.issueLabels = issueLabels; } } public static final class GitHub { private final String organization; private final String repository; private final List issueLabels; private GitHub(String organization, String repository, List issueLabels) { this.organization = organization; this.repository = repository; this.issueLabels = issueLabels; } public String getOrganization() { return this.organization; } public String getRepository() { return this.repository; } public List getIssueLabels() { return this.issueLabels; } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java ================================================ /* * 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.bom; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import groovy.namespace.QName; import groovy.util.Node; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.plugins.JavaPlatformExtension; import org.gradle.api.plugins.JavaPlatformPlugin; import org.gradle.api.plugins.PluginContainer; import org.gradle.api.publish.PublishingExtension; import org.gradle.api.publish.maven.MavenPom; import org.gradle.api.publish.maven.MavenPublication; import org.gradle.api.tasks.TaskProvider; import org.springframework.boot.build.MavenRepositoryPlugin; import org.springframework.boot.build.bom.Library.Group; import org.springframework.boot.build.bom.Library.Module; import org.springframework.boot.build.bom.bomr.MoveToSnapshots; import org.springframework.boot.build.bom.bomr.UpgradeBom; /** * {@link Plugin} for defining a bom. Dependencies are added as constraints in the * {@code api} configuration. Imported boms are added as enforced platforms in the * {@code api} configuration. * * @author Andy Wilkinson */ public class BomPlugin implements Plugin { static final String API_ENFORCED_CONFIGURATION_NAME = "apiEnforced"; @Override public void apply(Project project) { PluginContainer plugins = project.getPlugins(); plugins.apply(MavenRepositoryPlugin.class); plugins.apply(JavaPlatformPlugin.class); JavaPlatformExtension javaPlatform = project.getExtensions().getByType(JavaPlatformExtension.class); javaPlatform.allowDependencies(); createApiEnforcedConfiguration(project); BomExtension bom = project.getExtensions().create("bom", BomExtension.class, project); TaskProvider createResolvedBom = project.getTasks() .register("createResolvedBom", CreateResolvedBom.class, bom); TaskProvider checkBom = project.getTasks().register("bomrCheck", CheckBom.class, bom); checkBom.configure( (task) -> task.getResolvedBomFile().set(createResolvedBom.flatMap(CreateResolvedBom::getOutputFile))); project.getTasks().named("check").configure((check) -> check.dependsOn(checkBom)); project.getTasks().register("bomrUpgrade", UpgradeBom.class, bom); project.getTasks().register("moveToSnapshots", MoveToSnapshots.class, bom); project.getTasks().register("checkLinks", CheckLinks.class, bom); Configuration resolvedBomConfiguration = project.getConfigurations().create("resolvedBom"); project.getArtifacts() .add(resolvedBomConfiguration.getName(), createResolvedBom.map(CreateResolvedBom::getOutputFile), (artifact) -> artifact.builtBy(createResolvedBom)); new PublishingCustomizer(project, bom).customize(); } private void createApiEnforcedConfiguration(Project project) { Configuration apiEnforced = project.getConfigurations() .create(API_ENFORCED_CONFIGURATION_NAME, (configuration) -> { configuration.setCanBeConsumed(false); configuration.setCanBeResolved(false); configuration.setVisible(false); }); project.getConfigurations() .getByName(JavaPlatformPlugin.ENFORCED_API_ELEMENTS_CONFIGURATION_NAME) .extendsFrom(apiEnforced); project.getConfigurations() .getByName(JavaPlatformPlugin.ENFORCED_RUNTIME_ELEMENTS_CONFIGURATION_NAME) .extendsFrom(apiEnforced); } private static final class PublishingCustomizer { private final Project project; private final BomExtension bom; private PublishingCustomizer(Project project, BomExtension bom) { this.project = project; this.bom = bom; } private void customize() { PublishingExtension publishing = this.project.getExtensions().getByType(PublishingExtension.class); publishing.getPublications().withType(MavenPublication.class).all(this::configurePublication); } private void configurePublication(MavenPublication publication) { publication.pom(this::customizePom); } @SuppressWarnings("unchecked") private void customizePom(MavenPom pom) { pom.withXml((xml) -> { Node projectNode = xml.asNode(); Node properties = new Node(null, "properties"); this.bom.getProperties().forEach(properties::appendNode); Node dependencyManagement = findChild(projectNode, "dependencyManagement"); if (dependencyManagement != null) { addPropertiesBeforeDependencyManagement(projectNode, properties); addClassifiedManagedDependencies(dependencyManagement); replaceVersionsWithVersionPropertyReferences(dependencyManagement); addExclusionsToManagedDependencies(dependencyManagement); addTypesToManagedDependencies(dependencyManagement); } else { projectNode.children().add(properties); } addPluginManagement(projectNode); }); } @SuppressWarnings("unchecked") private void addPropertiesBeforeDependencyManagement(Node projectNode, Node properties) { for (int i = 0; i < projectNode.children().size(); i++) { if (isNodeWithName(projectNode.children().get(i), "dependencyManagement")) { projectNode.children().add(i, properties); break; } } } private void replaceVersionsWithVersionPropertyReferences(Node dependencyManagement) { Node dependencies = findChild(dependencyManagement, "dependencies"); if (dependencies != null) { for (Node dependency : findChildren(dependencies, "dependency")) { String groupId = findChild(dependency, "groupId").text(); String artifactId = findChild(dependency, "artifactId").text(); Node classifierNode = findChild(dependency, "classifier"); String classifier = (classifierNode != null) ? classifierNode.text() : ""; String versionProperty = this.bom.getArtifactVersionProperty(groupId, artifactId, classifier); if (versionProperty != null) { findChild(dependency, "version").setValue("${" + versionProperty + "}"); } } } } private void addExclusionsToManagedDependencies(Node dependencyManagement) { Node dependencies = findChild(dependencyManagement, "dependencies"); if (dependencies != null) { for (Node dependency : findChildren(dependencies, "dependency")) { String groupId = findChild(dependency, "groupId").text(); String artifactId = findChild(dependency, "artifactId").text(); this.bom.getLibraries() .stream() .flatMap((library) -> library.getGroups().stream()) .filter((group) -> group.getId().equals(groupId)) .flatMap((group) -> group.getModules().stream()) .filter((module) -> module.getName().equals(artifactId)) .flatMap((module) -> module.getExclusions().stream()) .forEach((exclusion) -> { Node exclusions = findOrCreateNode(dependency, "exclusions"); Node node = new Node(exclusions, "exclusion"); node.appendNode("groupId", exclusion.getGroupId()); node.appendNode("artifactId", exclusion.getArtifactId()); }); } } } private void addTypesToManagedDependencies(Node dependencyManagement) { Node dependencies = findChild(dependencyManagement, "dependencies"); if (dependencies != null) { for (Node dependency : findChildren(dependencies, "dependency")) { String groupId = findChild(dependency, "groupId").text(); String artifactId = findChild(dependency, "artifactId").text(); Set types = this.bom.getLibraries() .stream() .flatMap((library) -> library.getGroups().stream()) .filter((group) -> group.getId().equals(groupId)) .flatMap((group) -> group.getModules().stream()) .filter((module) -> module.getName().equals(artifactId)) .map(Module::getType) .filter(Objects::nonNull) .collect(Collectors.toSet()); if (types.size() > 1) { throw new IllegalStateException( "Multiple types for " + groupId + ":" + artifactId + ": " + types); } if (types.size() == 1) { String type = types.iterator().next(); dependency.appendNode("type", type); } } } } @SuppressWarnings("unchecked") private void addClassifiedManagedDependencies(Node dependencyManagement) { Node dependencies = findChild(dependencyManagement, "dependencies"); if (dependencies != null) { for (Node dependency : findChildren(dependencies, "dependency")) { String groupId = findChild(dependency, "groupId").text(); String artifactId = findChild(dependency, "artifactId").text(); String version = findChild(dependency, "version").text(); Set classifiers = this.bom.getLibraries() .stream() .flatMap((library) -> library.getGroups().stream()) .filter((group) -> group.getId().equals(groupId)) .flatMap((group) -> group.getModules().stream()) .filter((module) -> module.getName().equals(artifactId)) .map(Module::getClassifier) .filter(Objects::nonNull) .collect(Collectors.toSet()); Node target = dependency; for (String classifier : classifiers) { if (!classifier.isEmpty()) { if (target == null) { target = new Node(null, "dependency"); target.appendNode("groupId", groupId); target.appendNode("artifactId", artifactId); target.appendNode("version", version); int index = dependency.parent().children().indexOf(dependency); dependency.parent().children().add(index + 1, target); } target.appendNode("classifier", classifier); } target = null; } } } } private void addPluginManagement(Node projectNode) { for (Library library : this.bom.getLibraries()) { for (Group group : library.getGroups()) { Node plugins = findOrCreateNode(projectNode, "build", "pluginManagement", "plugins"); for (String pluginName : group.getPlugins()) { Node plugin = new Node(plugins, "plugin"); plugin.appendNode("groupId", group.getId()); plugin.appendNode("artifactId", pluginName); String versionProperty = library.getVersionProperty(); String value = (versionProperty != null) ? "${" + versionProperty + "}" : library.getVersion().getVersion().toString(); plugin.appendNode("version", value); } } } } private Node findOrCreateNode(Node parent, String... path) { Node current = parent; for (String nodeName : path) { Node child = findChild(current, nodeName); if (child == null) { child = new Node(current, nodeName); } current = child; } return current; } private Node findChild(Node parent, String name) { for (Object child : parent.children()) { if (isNodeWithName(child, name)) { return (Node) child; } } return null; } @SuppressWarnings("unchecked") private List findChildren(Node parent, String name) { return parent.children().stream().filter((child) -> isNodeWithName(child, name)).toList(); } private boolean isNodeWithName(Object candidate, String name) { if (candidate instanceof Node node) { if ((node.name() instanceof QName qname) && name.equals(qname.getLocalPart())) { return true; } return name.equals(node.name()); } return false; } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/BomResolver.java ================================================ /* * 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.bom; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.springframework.boot.build.bom.Library.Group; import org.springframework.boot.build.bom.Library.ImportedBom; import org.springframework.boot.build.bom.Library.Link; import org.springframework.boot.build.bom.Library.Module; import org.springframework.boot.build.bom.ResolvedBom.Bom; import org.springframework.boot.build.bom.ResolvedBom.Id; import org.springframework.boot.build.bom.ResolvedBom.JavadocLink; import org.springframework.boot.build.bom.ResolvedBom.Links; import org.springframework.boot.build.bom.ResolvedBom.ResolvedLibrary; /** * Creates a {@link ResolvedBom resolved bom}. * * @author Andy Wilkinson */ class BomResolver { private final ConfigurationContainer configurations; private final DependencyHandler dependencies; private final DocumentBuilder documentBuilder; BomResolver(ConfigurationContainer configurations, DependencyHandler dependencies) { this.configurations = configurations; this.dependencies = dependencies; try { this.documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (ParserConfigurationException ex) { throw new RuntimeException(ex); } } ResolvedBom resolve(BomExtension bomExtension) { List libraries = new ArrayList<>(); for (Library library : bomExtension.getLibraries()) { List managedDependencies = new ArrayList<>(); List imports = new ArrayList<>(); for (Group group : library.getGroups()) { for (Module module : group.getModules()) { Id id = new Id(group.getId(), module.getName(), library.getVersion().getVersion().toString()); managedDependencies.add(id); } for (ImportedBom imported : group.getBoms()) { Bom bom = bomFrom(resolveBom( "%s:%s:%s".formatted(group.getId(), imported.name(), library.getVersion().getVersion()))); imports.add(bom); } } List javadocLinks = javadocLinksOf(library).stream() .map((link) -> new JavadocLink(URI.create(link.url(library)), link.packages())) .toList(); ResolvedLibrary resolvedLibrary = new ResolvedLibrary(library.getName(), library.getVersion().getVersion().toString(), library.getVersionProperty(), managedDependencies, imports, new Links(javadocLinks)); libraries.add(resolvedLibrary); } String[] idComponents = bomExtension.getId().split(":"); return new ResolvedBom(new Id(idComponents[0], idComponents[1], idComponents[2]), libraries); } private List javadocLinksOf(Library library) { List javadocLinks = library.getLinks("javadoc"); return (javadocLinks != null) ? javadocLinks : Collections.emptyList(); } Bom resolveMavenBom(String coordinates) { return bomFrom(resolveBom(coordinates)); } private File resolveBom(String coordinates) { Set artifacts = this.configurations .detachedConfiguration(this.dependencies.create(coordinates + "@pom")) .getResolvedConfiguration() .getResolvedArtifacts(); if (artifacts.size() != 1) { throw new IllegalStateException("Expected a single artifact but '%s' resolved to %d artifacts" .formatted(coordinates, artifacts.size())); } return artifacts.iterator().next().getFile(); } private Bom bomFrom(File bomFile) { try { Node bom = nodeFrom(bomFile); File parentBomFile = parentBomFile(bom); Bom parent = null; if (parentBomFile != null) { parent = bomFrom(parentBomFile); } Properties properties = Properties.from(bom, this::nodeFrom); List dependencyNodes = bom.nodesAt("/project/dependencyManagement/dependencies/dependency"); List managedDependencies = new ArrayList<>(); List imports = new ArrayList<>(); for (Node dependency : dependencyNodes) { String groupId = properties.replace(dependency.textAt("groupId")); String artifactId = properties.replace(dependency.textAt("artifactId")); String version = properties.replace(dependency.textAt("version")); String classifier = properties.replace(dependency.textAt("classifier")); String scope = properties.replace(dependency.textAt("scope")); Bom importedBom = null; if ("import".equals(scope)) { String type = properties.replace(dependency.textAt("type")); if ("pom".equals(type)) { importedBom = bomFrom(resolveBom(groupId + ":" + artifactId + ":" + version)); } } if (importedBom != null) { imports.add(importedBom); } else { managedDependencies.add(new Id(groupId, artifactId, version, classifier)); } } String groupId = bom.textAt("/project/groupId"); if ((groupId == null || groupId.isEmpty()) && parent != null) { groupId = parent.id().groupId(); } String artifactId = bom.textAt("/project/artifactId"); String version = bom.textAt("/project/version"); if ((version == null || version.isEmpty()) && parent != null) { version = parent.id().version(); } return new Bom(new Id(groupId, artifactId, version), parent, managedDependencies, imports); } catch (Exception ex) { throw new RuntimeException(ex); } } private Node nodeFrom(String coordinates) { return nodeFrom(resolveBom(coordinates)); } private Node nodeFrom(File bomFile) { try { Document document = this.documentBuilder.parse(bomFile); return new Node(document); } catch (Exception ex) { throw new RuntimeException(ex); } } private File parentBomFile(Node bom) { Node parent = bom.nodeAt("/project/parent"); if (parent != null) { String parentGroupId = parent.textAt("groupId"); String parentArtifactId = parent.textAt("artifactId"); String parentVersion = parent.textAt("version"); return resolveBom(parentGroupId + ":" + parentArtifactId + ":" + parentVersion); } return null; } private static final class Node { private final XPath xpath; private final org.w3c.dom.Node delegate; private Node(org.w3c.dom.Node delegate) { this(delegate, XPathFactory.newInstance().newXPath()); } private Node(org.w3c.dom.Node delegate, XPath xpath) { this.delegate = delegate; this.xpath = xpath; } private String textAt(String expression) { String text = (String) evaluate(expression + "/text()", XPathConstants.STRING); return (text != null && !text.isBlank()) ? text : null; } private Node nodeAt(String expression) { org.w3c.dom.Node result = (org.w3c.dom.Node) evaluate(expression, XPathConstants.NODE); return (result != null) ? new Node(result, this.xpath) : null; } private List nodesAt(String expression) { NodeList nodes = (NodeList) evaluate(expression, XPathConstants.NODESET); List things = new ArrayList<>(nodes.getLength()); for (int i = 0; i < nodes.getLength(); i++) { things.add(new Node(nodes.item(i), this.xpath)); } return things; } private Object evaluate(String expression, QName type) { try { return this.xpath.evaluate(expression, this.delegate, type); } catch (XPathExpressionException ex) { throw new RuntimeException(ex); } } private String name() { return this.delegate.getNodeName(); } private String textContent() { return this.delegate.getTextContent(); } } private static final class Properties { private final Map properties; private Properties(Map properties) { this.properties = properties; } private static Properties from(Node bom, Function resolver) { try { Map properties = new HashMap<>(); Node current = bom; while (current != null) { String groupId = current.textAt("/project/groupId"); if (groupId != null && !groupId.isEmpty()) { properties.putIfAbsent("${project.groupId}", groupId); } String version = current.textAt("/project/version"); if (version != null && !version.isEmpty()) { properties.putIfAbsent("${project.version}", version); } List propertyNodes = current.nodesAt("/project/properties/*"); for (Node property : propertyNodes) { properties.putIfAbsent("${%s}".formatted(property.name()), property.textContent()); } current = parent(current, resolver); } return new Properties(properties); } catch (Exception ex) { throw new RuntimeException(ex); } } private static Node parent(Node current, Function resolver) { Node parent = current.nodeAt("/project/parent"); if (parent != null) { String parentGroupId = parent.textAt("groupId"); String parentArtifactId = parent.textAt("artifactId"); String parentVersion = parent.textAt("version"); return resolver.apply(parentGroupId + ":" + parentArtifactId + ":" + parentVersion); } return null; } private String replace(String input) { if (input != null && input.startsWith("${") && input.endsWith("}")) { String value = this.properties.get(input); if (value != null) { return replace(value); } throw new IllegalStateException("No replacement for " + input); } return input; } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java ================================================ /* * 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.bom; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.inject.Inject; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.apache.maven.artifact.versioning.Restriction; import org.apache.maven.artifact.versioning.VersionRange; import org.gradle.api.DefaultTask; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.file.RegularFile; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.VerificationException; import org.springframework.boot.build.bom.Library.BomAlignment; import org.springframework.boot.build.bom.Library.Group; import org.springframework.boot.build.bom.Library.ImportedBom; import org.springframework.boot.build.bom.Library.Module; import org.springframework.boot.build.bom.Library.PermittedDependency; import org.springframework.boot.build.bom.Library.ProhibitedVersion; import org.springframework.boot.build.bom.Library.VersionAlignment; import org.springframework.boot.build.bom.ResolvedBom.Bom; import org.springframework.boot.build.bom.ResolvedBom.Id; import org.springframework.boot.build.bom.ResolvedBom.ResolvedLibrary; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; /** * Checks the validity of a bom. * * @author Andy Wilkinson * @author Wick Dynex */ public abstract class CheckBom extends DefaultTask { private final BomExtension bom; private final List checks; @Inject public CheckBom(BomExtension bom) { ConfigurationContainer configurations = getProject().getConfigurations(); DependencyHandler dependencies = getProject().getDependencies(); Provider resolvedBom = getResolvedBomFile().map(RegularFile::getAsFile).map(ResolvedBom::readFrom); this.checks = List.of(new CheckExclusions(configurations, dependencies), new CheckProhibitedVersions(), new CheckVersionAlignment(), new CheckDependencyManagementAlignment(resolvedBom, configurations, dependencies), new CheckForUnwantedDependencyManagement(resolvedBom)); this.bom = bom; } @InputFile @PathSensitive(PathSensitivity.RELATIVE) public abstract RegularFileProperty getResolvedBomFile(); @TaskAction void checkBom() { List errors = new ArrayList<>(); for (Library library : this.bom.getLibraries()) { errors.addAll(checkLibrary(library)); } if (!errors.isEmpty()) { System.out.println(); errors.forEach(System.out::println); System.out.println(); throw new VerificationException("Bom check failed. See previous output for details."); } } private List checkLibrary(Library library) { List libraryErrors = new ArrayList<>(); this.checks.stream().flatMap((check) -> check.check(library).stream()).forEach(libraryErrors::add); List errors = new ArrayList<>(); if (!libraryErrors.isEmpty()) { errors.add(library.getName()); for (String libraryError : libraryErrors) { errors.add(" - " + libraryError); } } return errors; } private interface LibraryCheck { List check(Library library); } private static final class CheckExclusions implements LibraryCheck { private final ConfigurationContainer configurations; private final DependencyHandler dependencies; private CheckExclusions(ConfigurationContainer configurations, DependencyHandler dependencies) { this.configurations = configurations; this.dependencies = dependencies; } @Override public List check(Library library) { List errors = new ArrayList<>(); for (Group group : library.getGroups()) { for (Module module : group.getModules()) { if (!module.getExclusions().isEmpty()) { checkExclusions(group.getId(), module, library.getVersion().getVersion(), errors); } } } return errors; } private void checkExclusions(String groupId, Module module, DependencyVersion version, List errors) { Set resolved = this.configurations .detachedConfiguration(this.dependencies.create(groupId + ":" + module.getName() + ":" + version)) .getResolvedConfiguration() .getResolvedArtifacts() .stream() .map((artifact) -> artifact.getModuleVersion().getId()) .map((id) -> id.getGroup() + ":" + id.getModule().getName()) .collect(Collectors.toSet()); Set exclusions = module.getExclusions() .stream() .map((exclusion) -> exclusion.getGroupId() + ":" + exclusion.getArtifactId()) .collect(Collectors.toSet()); Set unused = new TreeSet<>(); for (String exclusion : exclusions) { if (!resolved.contains(exclusion)) { if (exclusion.endsWith(":*")) { String group = exclusion.substring(0, exclusion.indexOf(':') + 1); if (resolved.stream().noneMatch((candidate) -> candidate.startsWith(group))) { unused.add(exclusion); } } else { unused.add(exclusion); } } } exclusions.removeAll(resolved); if (!unused.isEmpty()) { errors.add("Unnecessary exclusions on " + groupId + ":" + module.getName() + ": " + exclusions); } } } private static final class CheckProhibitedVersions implements LibraryCheck { @Override public List check(Library library) { List errors = new ArrayList<>(); ArtifactVersion currentVersion = new DefaultArtifactVersion(library.getVersion().getVersion().toString()); for (ProhibitedVersion prohibited : library.getProhibitedVersions()) { if (prohibited.isProhibited(library.getVersion().getVersion().toString())) { errors.add("Current version " + currentVersion + " is prohibited"); } else { VersionRange versionRange = prohibited.getRange(); if (versionRange != null) { check(currentVersion, versionRange, errors); } } } return errors; } private void check(ArtifactVersion currentVersion, VersionRange versionRange, List errors) { for (Restriction restriction : versionRange.getRestrictions()) { ArtifactVersion upperBound = restriction.getUpperBound(); if (upperBound == null) { return; } int comparison = currentVersion.compareTo(upperBound); if ((restriction.isUpperBoundInclusive() && comparison <= 0) || ((!restriction.isUpperBoundInclusive()) && comparison < 0)) { return; } } errors.add("Version range " + versionRange + " is ineffective as the current version, " + currentVersion + ", is greater than its upper bound"); } } private static final class CheckVersionAlignment implements LibraryCheck { @Override public List check(Library library) { List errors = new ArrayList<>(); VersionAlignment versionAlignment = library.getVersionAlignment(); if (versionAlignment != null) { check(versionAlignment, library, errors); } return errors; } private void check(VersionAlignment versionAlignment, Library library, List errors) { Set alignedVersions = versionAlignment.resolve(); if (alignedVersions.size() == 1) { String alignedVersion = alignedVersions.iterator().next(); if (!alignedVersion.equals(library.getVersion().getVersion().toString())) { errors.add("Version " + library.getVersion().getVersion() + " is misaligned. It should be " + alignedVersion + "."); } } else { if (alignedVersions.isEmpty()) { errors.add("Version alignment requires a single version but none were found."); } else { errors.add("Version alignment requires a single version but " + alignedVersions.size() + " were found: " + alignedVersions + "."); } } } } private abstract static class ResolvedLibraryCheck implements LibraryCheck { private final Provider resolvedBom; private ResolvedLibraryCheck(Provider resolvedBom) { this.resolvedBom = resolvedBom; } @Override public List check(Library library) { ResolvedLibrary resolvedLibrary = getResolvedLibrary(library); return check(library, resolvedLibrary); } protected abstract List check(Library library, ResolvedLibrary resolvedLibrary); private ResolvedLibrary getResolvedLibrary(Library library) { ResolvedBom resolvedBom = this.resolvedBom.get(); Optional resolvedLibrary = resolvedBom.libraries() .stream() .filter((candidate) -> candidate.name().equals(library.getName())) .findFirst(); if (!resolvedLibrary.isPresent()) { throw new RuntimeException("Library '%s' not found in resolved bom".formatted(library.getName())); } return resolvedLibrary.get(); } } private static final class CheckDependencyManagementAlignment extends ResolvedLibraryCheck { private final BomResolver bomResolver; private CheckDependencyManagementAlignment(Provider resolvedBom, ConfigurationContainer configurations, DependencyHandler dependencies) { super(resolvedBom); this.bomResolver = new BomResolver(configurations, dependencies); } @Override public List check(Library library, ResolvedLibrary resolvedLibrary) { List errors = new ArrayList<>(); BomAlignment alignsWithBom = library.getAlignsWithBom(); if (alignsWithBom != null) { Bom mavenBom = this.bomResolver .resolveMavenBom(alignsWithBom.getCoordinates() + ":" + library.getVersion().getVersion()); checkDependencyManagementAlignment(resolvedLibrary, mavenBom, errors, alignsWithBom::exclude); } return errors; } private void checkDependencyManagementAlignment(ResolvedLibrary library, Bom mavenBom, List errors, Predicate excluded) { List managedByLibrary = library.managedDependencies(); List managedByBom = managedDependenciesOf(mavenBom); List missing = new ArrayList<>(managedByBom); missing.removeIf(excluded); missing.removeAll(managedByLibrary); List unexpected = new ArrayList<>(managedByLibrary); unexpected.removeAll(managedByBom); if (missing.isEmpty() && unexpected.isEmpty()) { return; } String error = "Dependency management does not align with " + mavenBom.id() + ":"; if (!missing.isEmpty()) { error = error + "%n - Missing:%n %s".formatted(String.join("\n ", missing.stream().map((dependency) -> dependency.toString()).toList())); } if (!unexpected.isEmpty()) { error = error + "%n - Unexpected:%n %s".formatted(String.join("\n ", unexpected.stream().map((dependency) -> dependency.toString()).toList())); } errors.add(error); } private List managedDependenciesOf(Bom mavenBom) { List managedDependencies = new ArrayList<>(); managedDependencies.addAll(mavenBom.managedDependencies()); if (mavenBom.parent() != null) { managedDependencies.addAll(managedDependenciesOf(mavenBom.parent())); } for (Bom importedBom : mavenBom.importedBoms()) { managedDependencies.addAll(managedDependenciesOf(importedBom)); } return managedDependencies; } } private static final class CheckForUnwantedDependencyManagement extends ResolvedLibraryCheck { private CheckForUnwantedDependencyManagement(Provider resolvedBom) { super(resolvedBom); } @Override public List check(Library library, ResolvedLibrary resolvedLibrary) { Map> unwanted = findUnwantedDependencyManagement(library, resolvedLibrary); List errors = new ArrayList<>(); if (!unwanted.isEmpty()) { StringBuilder error = new StringBuilder("Unwanted dependency management:"); unwanted.forEach((bom, dependencies) -> { error.append("%n - %s:".formatted(bom)); error.append("%n - %s".formatted(String.join("\n - ", dependencies))); }); errors.add(error.toString()); } Map> unnecessary = findUnnecessaryPermittedDependencies(library, resolvedLibrary); if (!unnecessary.isEmpty()) { StringBuilder error = new StringBuilder("Dependencies permitted unnecessarily:"); unnecessary.forEach((bom, dependencies) -> { error.append("%n - %s:".formatted(bom)); error.append("%n - %s".formatted(String.join("\n - ", dependencies))); }); errors.add(error.toString()); } return errors; } private Map> findUnwantedDependencyManagement(Library library, ResolvedLibrary resolvedLibrary) { Map> unwanted = new LinkedHashMap<>(); for (Bom bom : resolvedLibrary.importedBoms()) { Set notPermitted = new TreeSet<>(); Set managedDependencies = managedDependenciesOf(bom); managedDependencies.stream() .filter((dependency) -> unwanted(bom, dependency, findPermittedDependencies(library, bom))) .map(Id::toString) .forEach(notPermitted::add); if (!notPermitted.isEmpty()) { unwanted.put(bom.id().artifactId(), notPermitted); } } return unwanted; } private List findPermittedDependencies(Library library, Bom bom) { for (Group group : library.getGroups()) { for (ImportedBom importedBom : group.getBoms()) { if (importedBom.name().equals(bom.id().artifactId()) && group.getId().equals(bom.id().groupId())) { return importedBom.permittedDependencies(); } } } return Collections.emptyList(); } private Set managedDependenciesOf(Bom bom) { Set managedDependencies = new TreeSet<>(); if (bom != null) { managedDependencies.addAll(bom.managedDependencies()); managedDependencies.addAll(managedDependenciesOf(bom.parent())); for (Bom importedBom : bom.importedBoms()) { managedDependencies.addAll(managedDependenciesOf(importedBom)); } } return managedDependencies; } private boolean unwanted(Bom bom, Id managedDependency, List permittedDependencies) { if (bom.id().groupId().equals(managedDependency.groupId()) || managedDependency.groupId().startsWith(bom.id().groupId() + ".")) { return false; } for (PermittedDependency permittedDependency : permittedDependencies) { if (permittedDependency.artifactId().equals(managedDependency.artifactId()) && permittedDependency.groupId().equals(managedDependency.groupId())) { return false; } } return true; } private Map> findUnnecessaryPermittedDependencies(Library library, ResolvedLibrary resolvedLibrary) { Map> unnecessary = new HashMap<>(); for (Bom bom : resolvedLibrary.importedBoms()) { Set permittedDependencies = findPermittedDependencies(library, bom).stream() .map((dependency) -> dependency.groupId() + ":" + dependency.artifactId()) .collect(Collectors.toCollection(TreeSet::new)); Set dependencies = managedDependenciesOf(bom).stream() .map((dependency) -> dependency.groupId() + ":" + dependency.artifactId()) .collect(Collectors.toCollection(TreeSet::new)); permittedDependencies.removeAll(dependencies); if (!permittedDependencies.isEmpty()) { unnecessary.put(bom.id().artifactId(), permittedDependencies); } } return unnecessary; } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/CheckLinks.java ================================================ /* * 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.bom; import java.net.URI; import java.net.URISyntaxException; import javax.inject.Inject; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.gradle.api.DefaultTask; import org.gradle.api.tasks.TaskAction; import org.gradle.internal.impldep.org.apache.http.client.config.CookieSpecs; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.NoOpResponseErrorHandler; import org.springframework.web.client.RestTemplate; /** * Task to check that links are working. * * @author Andy Wilkinson * @author Phillip Webb */ public abstract class CheckLinks extends DefaultTask { private final BomExtension bom; @Inject public CheckLinks(BomExtension bom) { this.bom = bom; } @TaskAction void releaseNotes() { RequestConfig config = RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build(); CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(config).build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); RestTemplate restTemplate = new RestTemplate(requestFactory); restTemplate.setErrorHandler(new NoOpResponseErrorHandler()); for (Library library : this.bom.getLibraries()) { library.getLinks().forEach((name, links) -> links.forEach((link) -> { URI uri; try { uri = new URI(link.url(library)); ResponseEntity response = restTemplate.exchange(uri, HttpMethod.HEAD, null, String.class); System.out.printf("[%3d] %s - %s (%s)%n", response.getStatusCode().value(), library.getName(), name, uri); } catch (URISyntaxException ex) { throw new RuntimeException(ex); } })); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/CreateResolvedBom.java ================================================ /* * 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.bom; import java.io.FileWriter; import java.io.IOException; import javax.inject.Inject; import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; /** * {@link Task} to create a {@link ResolvedBom resolved bom}. * * @author Andy Wilkinson */ public abstract class CreateResolvedBom extends DefaultTask { private final BomExtension bomExtension; private final BomResolver bomResolver; @Inject public CreateResolvedBom(BomExtension bomExtension) { getOutputs().upToDateWhen((spec) -> false); this.bomExtension = bomExtension; this.bomResolver = new BomResolver(getProject().getConfigurations(), getProject().getDependencies()); getOutputFile().convention(getProject().getLayout().getBuildDirectory().file(getName() + "/resolved-bom.json")); } @OutputFile public abstract RegularFileProperty getOutputFile(); @TaskAction void createResolvedBom() throws IOException { ResolvedBom resolvedBom = this.bomResolver.resolve(this.bomExtension); try (FileWriter writer = new FileWriter(getOutputFile().get().getAsFile())) { resolvedBom.writeTo(writer); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java ================================================ /* * 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.bom; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathFactory; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.apache.maven.artifact.versioning.VersionRange; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.result.DependencyResult; import org.gradle.api.artifacts.result.ResolutionResult; import org.w3c.dom.Document; import org.springframework.boot.build.bom.ResolvedBom.Id; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; /** * A collection of modules, Maven plugins, and Maven boms that are versioned and released * together. * * @author Andy Wilkinson */ public class Library { private final String name; private final String calendarName; private final LibraryVersion version; private final List groups; private final String versionProperty; private final UpgradePolicy upgradePolicy; private final List prohibitedVersions; private final boolean considerSnapshots; private final VersionAlignment versionAlignment; private final BomAlignment bomAlignment; private final String linkRootName; private final Map> links; /** * Create a new {@code Library} with the given {@code name}, {@code version}, and * {@code groups}. * @param name name of the library * @param calendarName name of the library as it appears in the Spring Calendar. May * be {@code null} in which case the {@code name} is used. * @param version version of the library * @param groups groups in the library * @param upgradePolicy the upgrade policy of the library, or {@code null} to use the * containing bom's policy * @param prohibitedVersions version of the library that are prohibited * @param considerSnapshots whether to consider snapshots * @param versionAlignment version alignment, if any, for the library * @param bomAlignment the bom, if any, that this library should align with * @param linkRootName the root name to use when generating link variable or * {@code null} to generate one based on the library {@code name} * @param links a list of HTTP links relevant to the library */ public Library(String name, String calendarName, LibraryVersion version, List groups, UpgradePolicy upgradePolicy, List prohibitedVersions, boolean considerSnapshots, VersionAlignment versionAlignment, BomAlignment bomAlignment, String linkRootName, Map> links) { this.name = name; this.calendarName = (calendarName != null) ? calendarName : name; this.version = version; this.groups = groups; this.versionProperty = "Spring Boot".equals(name) ? null : name.toLowerCase(Locale.ENGLISH).replace(' ', '-') + ".version"; this.upgradePolicy = upgradePolicy; this.prohibitedVersions = prohibitedVersions; this.considerSnapshots = considerSnapshots; this.versionAlignment = versionAlignment; this.bomAlignment = bomAlignment; this.linkRootName = (linkRootName != null) ? linkRootName : generateLinkRootName(name); this.links = (links != null) ? Collections.unmodifiableMap(new TreeMap<>(links)) : Collections.emptyMap(); } private static String generateLinkRootName(String name) { return name.replace("-", "").replace(" ", "-").toLowerCase(Locale.ROOT); } public String getName() { return this.name; } public String getCalendarName() { return this.calendarName; } public LibraryVersion getVersion() { return this.version; } public List getGroups() { return this.groups; } public String getVersionProperty() { return this.versionProperty; } public UpgradePolicy getUpgradePolicy() { return this.upgradePolicy; } public List getProhibitedVersions() { return this.prohibitedVersions; } public boolean isConsiderSnapshots() { return this.considerSnapshots; } public VersionAlignment getVersionAlignment() { return this.versionAlignment; } public String getLinkRootName() { return this.linkRootName; } public BomAlignment getAlignsWithBom() { return this.bomAlignment; } public Map> getLinks() { return this.links; } public String getLinkUrl(String name) { List links = getLinks(name); if (links == null || links.isEmpty()) { return null; } if (links.size() > 1) { throw new IllegalStateException("Expected a single '%s' link for %s".formatted(name, getName())); } return links.get(0).url(this); } public List getLinks(String name) { return this.links.get(name); } public String getNameAndVersion() { return getName() + " " + getVersion(); } public Library withVersion(LibraryVersion version) { return new Library(this.name, this.calendarName, version, this.groups, this.upgradePolicy, this.prohibitedVersions, this.considerSnapshots, this.versionAlignment, this.bomAlignment, this.linkRootName, this.links); } /** * A version or range of versions that are prohibited from being used in a bom. */ public static class ProhibitedVersion { private final VersionRange range; private final List startsWith; private final List endsWith; private final List contains; private final String reason; public ProhibitedVersion(VersionRange range, List startsWith, List endsWith, List contains, String reason) { this.range = range; this.startsWith = startsWith; this.endsWith = endsWith; this.contains = contains; this.reason = reason; } public VersionRange getRange() { return this.range; } public List getStartsWith() { return this.startsWith; } public List getEndsWith() { return this.endsWith; } public List getContains() { return this.contains; } public String getReason() { return this.reason; } public boolean isProhibited(String candidate) { boolean result = false; result = result || (this.range != null && this.range.containsVersion(new DefaultArtifactVersion(candidate))); result = result || this.startsWith.stream().anyMatch(candidate::startsWith); result = result || this.endsWith.stream().anyMatch(candidate::endsWith); result = result || this.contains.stream().anyMatch(candidate::contains); return result; } } public static class LibraryVersion { private final DependencyVersion version; public LibraryVersion(DependencyVersion version) { this.version = version; } public DependencyVersion getVersion() { return this.version; } public int[] componentInts() { return Arrays.stream(parts()).mapToInt(Integer::parseInt).toArray(); } public String major() { return parts()[0]; } public String minor() { return parts()[1]; } public String patch() { return parts()[2]; } @Override public String toString() { return this.version.toString(); } public String toString(String separator) { return this.version.toString().replace(".", separator); } public String forAntora() { String[] parts = parts(); String result = parts[0] + "." + parts[1]; if (toString().endsWith("SNAPSHOT")) { result += "-SNAPSHOT"; } return result; } public String forMajorMinorGeneration() { String[] parts = parts(); String result = parts[0] + "." + parts[1] + ".x"; if (toString().endsWith("SNAPSHOT")) { result += "-SNAPSHOT"; } return result; } private String[] parts() { return toString().split("[.-]"); } } /** * A collection of modules, Maven plugins, and Maven boms with the same group ID. */ public static class Group { private final String id; private final List modules; private final List plugins; private final List boms; public Group(String id, List modules, List plugins, List boms) { this.id = id; this.modules = modules; this.plugins = plugins; this.boms = boms; } public String getId() { return this.id; } public List getModules() { return this.modules; } public List getPlugins() { return this.plugins; } public List getBoms() { return this.boms; } } /** * A module in a group. */ public static class Module { private final String name; private final String type; private final String classifier; private final List exclusions; public Module(String name) { this(name, Collections.emptyList()); } public Module(String name, String type) { this(name, type, null, Collections.emptyList()); } public Module(String name, List exclusions) { this(name, null, null, exclusions); } public Module(String name, String type, String classifier, List exclusions) { this.name = name; this.type = type; this.classifier = (classifier != null) ? classifier : ""; this.exclusions = exclusions; } public String getName() { return this.name; } public String getClassifier() { return this.classifier; } public String getType() { return this.type; } public List getExclusions() { return this.exclusions; } } /** * An exclusion of a dependency identified by its group ID and artifact ID. */ public static class Exclusion { private final String groupId; private final String artifactId; public Exclusion(String groupId, String artifactId) { this.groupId = groupId; this.artifactId = artifactId; } public String getGroupId() { return this.groupId; } public String getArtifactId() { return this.artifactId; } } public interface VersionAlignment { Set resolve(); default Configuration alignmentConfiguration(Project project, Collection dependencies) { Configuration alignmentConfiguration = project.getConfigurations() .detachedConfiguration(dependencies.toArray(new Dependency[0])); alignmentConfiguration.getResolutionStrategy().cacheChangingModulesFor(0, TimeUnit.SECONDS); return alignmentConfiguration; } } public static class BomAlignment { private final String coordinates; private final Predicate excluding; public BomAlignment(String bomCoordinates, Predicate excluding) { this.coordinates = bomCoordinates; this.excluding = excluding; } public String getCoordinates() { return this.coordinates; } public boolean exclude(Id id) { return this.excluding.test(id); } } /** * Version alignment for a library based on a dependency of another module. */ public static class DependencyVersionAlignment implements VersionAlignment { private final String dependency; private final String from; private final String managedBy; private final Project project; private final List libraries; private final List groups; private Set alignedVersions; DependencyVersionAlignment(String dependency, String from, String managedBy, Project project, List libraries, List groups) { this.dependency = dependency; this.from = from; this.managedBy = managedBy; this.project = project; this.libraries = libraries; this.groups = groups; } @Override public Set resolve() { if (this.alignedVersions != null) { return this.alignedVersions; } Map versions = resolveAligningDependencies(); if (this.dependency != null) { String version = versions.get(this.dependency); this.alignedVersions = (version != null) ? Set.of(version) : Collections.emptySet(); } else { Set versionsInLibrary = new HashSet<>(); for (Group group : this.groups) { for (Module module : group.getModules()) { String version = versions.get(group.getId() + ":" + module.getName()); if (version != null) { versionsInLibrary.add(version); } } for (String plugin : group.getPlugins()) { String version = versions.get(group.getId() + ":" + plugin); if (version != null) { versionsInLibrary.add(version); } } } this.alignedVersions = versionsInLibrary; } return this.alignedVersions; } private Map resolveAligningDependencies() { List dependencies = getAligningDependencies(); Configuration alignmentConfiguration = alignmentConfiguration(this.project, dependencies); Map versions = new HashMap<>(); ResolutionResult resolutionResult = alignmentConfiguration.getIncoming().getResolutionResult(); for (DependencyResult dependency : resolutionResult.getAllDependencies()) { versions.put(dependency.getFrom().getModuleVersion().getModule().toString(), dependency.getFrom().getModuleVersion().getVersion()); } return versions; } private List getAligningDependencies() { if (this.managedBy == null) { Library fromLibrary = findFromLibrary(); return List .of(this.project.getDependencies().create(this.from + ":" + fromLibrary.getVersion().getVersion())); } else { Library managingLibrary = findManagingLibrary(); List boms = getBomDependencies(managingLibrary); List dependencies = new ArrayList<>(); dependencies.addAll(boms); dependencies.add(this.project.getDependencies().create(this.from)); return dependencies; } } private Library findFromLibrary() { for (Library library : this.libraries) { for (Group group : library.getGroups()) { for (Module module : group.getModules()) { if (this.from.equals(group.getId() + ":" + module.getName())) { return library; } } } } return null; } private Library findManagingLibrary() { if (this.managedBy == null) { return null; } return this.libraries.stream() .filter((candidate) -> this.managedBy.equals(candidate.getName())) .findFirst() .orElseThrow(() -> new IllegalStateException("Managing library '" + this.managedBy + "' not found.")); } private List getBomDependencies(Library manager) { if (manager == null) { return Collections.emptyList(); } return manager.getGroups() .stream() .flatMap((group) -> group.getBoms() .stream() .map((bom) -> this.project.getDependencies() .platform(group.getId() + ":" + bom.name() + ":" + manager.getVersion().getVersion()))) .toList(); } String getFrom() { return this.from; } String getManagedBy() { return this.managedBy; } @Override public String toString() { String result = "version from dependencies of " + this.from; if (this.managedBy != null) { result += " that is managed by " + this.managedBy; } return result; } } /** * Version alignment for a library based on a property in the pom of another module. */ public static class PomPropertyVersionAlignment implements VersionAlignment { private final String name; private final String from; private final String managedBy; private final Project project; private final List libraries; private Set alignedVersions; PomPropertyVersionAlignment(String name, String from, String managedBy, Project project, List libraries) { this.name = name; this.from = from; this.managedBy = managedBy; this.project = project; this.libraries = libraries; } @Override public Set resolve() { if (this.alignedVersions != null) { return this.alignedVersions; } Configuration alignmentConfiguration = alignmentConfiguration(this.project, getAligningDependencies()); Set files = alignmentConfiguration.resolve(); if (files.size() != 1) { throw new IllegalStateException( "Expected a single file when resolving the pom of " + this.from + " but found " + files.size()); } File pomFile = files.iterator().next(); return Set.of(propertyFrom(pomFile)); } private List getAligningDependencies() { Library managingLibrary = findManagingLibrary(); List boms = getBomDependencies(managingLibrary); List dependencies = new ArrayList<>(); dependencies.addAll(boms); dependencies.add(this.project.getDependencies().create(this.from + "@pom")); return dependencies; } private Library findManagingLibrary() { if (this.managedBy == null) { return null; } return this.libraries.stream() .filter((candidate) -> this.managedBy.equals(candidate.getName())) .findFirst() .orElseThrow(() -> new IllegalStateException("Managing library '" + this.managedBy + "' not found.")); } private List getBomDependencies(Library manager) { return manager.getGroups() .stream() .flatMap((group) -> group.getBoms() .stream() .map((bom) -> this.project.getDependencies() .platform(group.getId() + ":" + bom.name() + ":" + manager.getVersion().getVersion()))) .toList(); } private String propertyFrom(File pomFile) { try { DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document document = documentBuilder.parse(pomFile); XPath xpath = XPathFactory.newInstance().newXPath(); return xpath.evaluate("/project/properties/" + this.name + "/text()", document); } catch (Exception ex) { throw new RuntimeException(ex); } } @Override public String toString() { String result = "version from properties of " + this.from; if (this.managedBy != null) { result += " that is managed by " + this.managedBy; } return result; } } public record Link(String rootName, Function factory, List packages) { private static final Pattern PACKAGE_EXPAND = Pattern.compile("^(.*)\\[(.*)\\]$"); public Link { packages = (packages != null) ? List.copyOf(expandPackages(packages)) : Collections.emptyList(); } private static List expandPackages(List packages) { return packages.stream().flatMap(Link::expandPackage).toList(); } private static Stream expandPackage(String packageName) { Matcher matcher = PACKAGE_EXPAND.matcher(packageName); if (!matcher.matches()) { return Stream.of(packageName); } String root = matcher.group(1); String[] suffixes = matcher.group(2).split("\\|"); return Stream.of(suffixes).map((suffix) -> root + suffix); } public String url(Library library) { return url(library.getVersion()); } public String url(LibraryVersion libraryVersion) { return factory().apply(libraryVersion); } } public record ImportedBom(String name, List permittedDependencies) { public ImportedBom(String name) { this(name, Collections.emptyList()); } } public record PermittedDependency(String groupId, String artifactId) { } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/ResolvedBom.java ================================================ /* * 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.bom; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.UncheckedIOException; import java.io.Writer; import java.net.URI; import java.util.List; import com.fasterxml.jackson.annotation.JsonInclude.Include; import tools.jackson.databind.json.JsonMapper; /** * A resolved bom. * * @author Andy Wilkinson * @param id the ID of the resolved bom * @param libraries the libraries declared in the bom */ public record ResolvedBom(Id id, List libraries) { private static final JsonMapper jsonMapper; static { jsonMapper = JsonMapper.builder() .changeDefaultPropertyInclusion((value) -> value.withContentInclusion(Include.NON_EMPTY)) .build(); } public static ResolvedBom readFrom(File file) { try (FileReader reader = new FileReader(file)) { return jsonMapper.readValue(reader, ResolvedBom.class); } catch (IOException ex) { throw new UncheckedIOException(ex); } } public void writeTo(Writer writer) { jsonMapper.writeValue(writer, this); } public record ResolvedLibrary(String name, String version, String versionProperty, List managedDependencies, List importedBoms, Links links) { } public record Id(String groupId, String artifactId, String version, String classifier) implements Comparable { Id(String groupId, String artifactId, String version) { this(groupId, artifactId, version, null); } @Override public int compareTo(Id o) { int result = this.groupId.compareTo(o.groupId); if (result != 0) { return result; } result = this.artifactId.compareTo(o.artifactId); if (result != 0) { return result; } return this.version.compareTo(o.version); } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(this.groupId); builder.append(":"); builder.append(this.artifactId); builder.append(":"); builder.append(this.version); if (this.classifier != null) { builder.append(":"); builder.append(this.classifier); } return builder.toString(); } } public record Bom(Id id, Bom parent, List managedDependencies, List importedBoms) { } public record Links(List javadoc) { } public record JavadocLink(URI uri, List packages) { } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/UpgradePolicy.java ================================================ /* * 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.bom; import java.util.function.BiPredicate; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; /** * Policies used to decide which versions are considered as possible upgrades. * * @author Andy Wilkinson */ public enum UpgradePolicy implements BiPredicate { /** * Any version. */ ANY((candidate, current) -> true), /** * Minor versions of the current major version. */ SAME_MAJOR_VERSION(DependencyVersion::isSameMajor), /** * Patch versions of the current minor version. */ SAME_MINOR_VERSION(DependencyVersion::isSameMinor); private final BiPredicate delegate; UpgradePolicy(BiPredicate delegate) { this.delegate = delegate; } @Override public boolean test(DependencyVersion candidate, DependencyVersion current) { return this.delegate.test(candidate, current); } public static UpgradePolicy max(UpgradePolicy one, UpgradePolicy two) { if (one == null && two != null) { return two; } else if (one != null && two == null) { return one; } return (one.ordinal() < two.ordinal()) ? one : two; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java ================================================ /* * 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.bom.bomr; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import org.gradle.api.internal.tasks.userinput.UserInputHandler; import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.Library.VersionAlignment; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; /** * Interactive {@link UpgradeResolver} that uses command line input to choose the upgrades * to apply. * * @author Andy Wilkinson */ public final class InteractiveUpgradeResolver implements UpgradeResolver { private final UserInputHandler userInputHandler; private final LibraryUpdateResolver libraryUpdateResolver; InteractiveUpgradeResolver(UserInputHandler userInputHandler, LibraryUpdateResolver libraryUpdateResolver) { this.userInputHandler = userInputHandler; this.libraryUpdateResolver = libraryUpdateResolver; } @Override public List resolveUpgrades(Collection librariesToUpgrade, Collection libraries) { Map librariesByName = new HashMap<>(); for (Library library : libraries) { librariesByName.put(library.getName(), library); } try { return this.libraryUpdateResolver.findLibraryUpdates(librariesToUpgrade, librariesByName) .stream() .map(this::resolveUpgrade) .filter(Objects::nonNull) .toList(); } catch (UpgradesInterruptedException ex) { return Collections.emptyList(); } } private Upgrade resolveUpgrade(LibraryWithVersionOptions libraryWithVersionOptions) { Library library = libraryWithVersionOptions.getLibrary(); List versionOptions = libraryWithVersionOptions.getVersionOptions(); if (versionOptions.isEmpty()) { return null; } VersionOption defaultOption = defaultOption(library); VersionOption selected = selectOption(defaultOption, library, versionOptions); return (selected.equals(defaultOption)) ? null : selected.upgrade(library); } private VersionOption defaultOption(Library library) { VersionAlignment alignment = library.getVersionAlignment(); Set alignedVersions = (alignment != null) ? alignment.resolve() : null; if (alignedVersions != null && alignedVersions.size() == 1) { DependencyVersion alignedVersion = DependencyVersion.parse(alignedVersions.iterator().next()); if (alignedVersion.equals(library.getVersion().getVersion())) { return new VersionOption.AlignedVersionOption(alignedVersion, alignment); } } return new VersionOption(library.getVersion().getVersion()); } private VersionOption selectOption(VersionOption defaultOption, Library library, List versionOptions) { VersionOption selected = this.userInputHandler.askUser((questions) -> { String question = library.getNameAndVersion(); List options = new ArrayList<>(); options.add(defaultOption); options.addAll(versionOptions); return questions.selectOption(question, options, defaultOption); }).get(); if (this.userInputHandler.interrupted()) { throw new UpgradesInterruptedException(); } return selected; } static class UpgradesInterruptedException extends RuntimeException { } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/LibraryUpdateResolver.java ================================================ /* * 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.bom.bomr; import java.util.Collection; import java.util.List; import java.util.Map; import org.springframework.boot.build.bom.Library; /** * Resolves library updates. * * @author Moritz Halbritter */ public interface LibraryUpdateResolver { /** * Finds library updates. * @param librariesToUpgrade libraries to update * @param librariesByName libraries indexed by name * @return library which have updates */ List findLibraryUpdates(Collection librariesToUpgrade, Map librariesByName); } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/LibraryWithVersionOptions.java ================================================ /* * 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.bom.bomr; import java.util.List; import org.springframework.boot.build.bom.Library; class LibraryWithVersionOptions { private final Library library; private final List versionOptions; LibraryWithVersionOptions(Library library, List versionOptions) { this.library = library; this.versionOptions = versionOptions; } Library getLibrary() { return this.library; } List getVersionOptions() { return this.versionOptions; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MavenMetadataVersionResolver.java ================================================ /* * 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.bom.bomr; import java.io.StringReader; import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.stream.Collectors; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.artifacts.repositories.PasswordCredentials; import org.gradle.api.credentials.Credentials; import org.gradle.internal.artifacts.repositories.AuthenticationSupportedInternal; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; /** * A {@link VersionResolver} that examines {@code maven-metadata.xml} to determine the * available versions. * * @author Andy Wilkinson */ final class MavenMetadataVersionResolver implements VersionResolver { private final RestTemplate rest; private final Collection repositories; MavenMetadataVersionResolver(Collection repositories) { this(new RestTemplate(Collections.singletonList(new StringHttpMessageConverter())), repositories); } MavenMetadataVersionResolver(RestTemplate restTemplate, Collection repositories) { this.rest = restTemplate; this.repositories = repositories; } @Override public SortedSet resolveVersions(String groupId, String artifactId) { Set versions = new HashSet<>(); for (MavenArtifactRepository repository : this.repositories) { versions.addAll(resolveVersions(groupId, artifactId, repository)); } return versions.stream().map(DependencyVersion::parse).collect(Collectors.toCollection(TreeSet::new)); } private Set resolveVersions(String groupId, String artifactId, MavenArtifactRepository repository) { Set versions = new HashSet<>(); URI url = UriComponentsBuilder.fromUri(repository.getUrl()) .pathSegment(groupId.replace('.', '/'), artifactId, "maven-metadata.xml") .build() .toUri(); try { HttpHeaders headers = new HttpHeaders(); PasswordCredentials credentials = credentialsOf(repository); String username = (credentials != null) ? credentials.getUsername() : null; if (username != null) { headers.setBasicAuth(username, credentials.getPassword()); } HttpEntity request = new HttpEntity<>(headers); String metadata = this.rest.exchange(url, HttpMethod.GET, request, String.class).getBody(); Document metadataDocument = DocumentBuilderFactory.newInstance() .newDocumentBuilder() .parse(new InputSource(new StringReader(metadata))); NodeList versionNodes = (NodeList) XPathFactory.newInstance() .newXPath() .evaluate("/metadata/versioning/versions/version", metadataDocument, XPathConstants.NODESET); for (int i = 0; i < versionNodes.getLength(); i++) { versions.add(versionNodes.item(i).getTextContent()); } } catch (HttpClientErrorException ex) { if (ex.getStatusCode() != HttpStatus.NOT_FOUND) { System.err.println("Failed to download maven-metadata.xml for " + groupId + ":" + artifactId + " from " + url + ": " + ex.getMessage()); } } catch (Exception ex) { System.err.println("Failed to resolve versions for module " + groupId + ":" + artifactId + " in repository " + repository + ": " + ex.getMessage()); } return versions; } /** * Retrieves the configured credentials of the given {@code repository}. We cannot use * {@link MavenArtifactRepository#getCredentials()} as, if the repository has no * credentials, it has the unwanted side-effect of assigning an empty set of username * and password credentials to the repository which may cause subsequent "Username * must not be null!" failures. * @param repository the repository that is the source of the credentials * @return the configured password credentials or {@code null} */ private PasswordCredentials credentialsOf(MavenArtifactRepository repository) { Credentials credentials = ((AuthenticationSupportedInternal) repository).getConfiguredCredentials().getOrNull(); if (credentials != null) { if (credentials instanceof PasswordCredentials passwordCredentials) { return passwordCredentials; } throw new IllegalStateException("Repository '%s (%s)' has credentials '%s' that are not PasswordCredentials" .formatted(repository.getName(), repository.getUrl(), credentials)); } return null; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MoveToSnapshots.java ================================================ /* * 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.bom.bomr; import java.time.OffsetDateTime; import java.util.List; import java.util.Map; import java.util.function.BiFunction; import javax.inject.Inject; import org.gradle.api.Task; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.tasks.TaskAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.build.bom.BomExtension; import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.bomr.ReleaseSchedule.Release; import org.springframework.boot.build.bom.bomr.github.Milestone; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.boot.build.properties.BuildProperties; import org.springframework.boot.build.properties.BuildType; /** * A {@link Task} to move to snapshot dependencies. * * @author Andy Wilkinson */ public abstract class MoveToSnapshots extends UpgradeDependencies { private static final Logger logger = LoggerFactory.getLogger(MoveToSnapshots.class); private final BuildType buildType = BuildProperties.get(getProject()).buildType(); @Inject public MoveToSnapshots(BomExtension bom) { super(bom, true); getProject().getRepositories().withType(MavenArtifactRepository.class, (repository) -> { String name = repository.getName(); if (name.startsWith("spring-") && name.endsWith("-snapshot")) { getRepositoryNames().add(name); } }); } @Override @TaskAction void upgradeDependencies() { super.upgradeDependencies(); } @Override protected String commitMessage(Upgrade upgrade, int issueNumber) { return "Start building against " + upgrade.toRelease().getNameAndVersion() + " snapshots" + "\n\nSee gh-" + issueNumber; } @Override protected boolean eligible(Library library) { return library.isConsiderSnapshots() && super.eligible(library); } @Override protected BiFunction createVersionOptionResolver(Milestone milestone) { return switch (this.buildType) { case OPEN_SOURCE -> createOpenSourceVersionOptionResolver(milestone); case COMMERCIAL -> super.createVersionOptionResolver(milestone); }; } private BiFunction createOpenSourceVersionOptionResolver( Milestone milestone) { Map> scheduledReleases = getScheduledOpenSourceReleases(milestone); BiFunction resolver = super.createVersionOptionResolver(milestone); return (library, dependencyVersion) -> { VersionOption versionOption = resolver.apply(library, dependencyVersion); if (versionOption != null) { List releases = scheduledReleases.get(library.getCalendarName()); if (releases != null) { List matches = releases.stream() .filter((release) -> dependencyVersion.isSnapshotFor(release.getVersion())) .toList(); if (!matches.isEmpty()) { return new VersionOption.SnapshotVersionOption(versionOption.getVersion(), matches.get(0).getVersion()); } } if (logger.isInfoEnabled()) { logger.info("Ignoring {}. No release of {} scheduled before {}", dependencyVersion, library.getName(), milestone.getDueOn()); } } return null; }; } private Map> getScheduledOpenSourceReleases(Milestone milestone) { ReleaseSchedule releaseSchedule = new ReleaseSchedule(); return releaseSchedule.releasesBetween(OffsetDateTime.now(), milestone.getDueOn()); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MultithreadedLibraryUpdateResolver.java ================================================ /* * 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.bom.bomr; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.build.bom.Library; /** * {@link LibraryUpdateResolver} decorator that uses multiple threads to find library * updates. * * @author Moritz Halbritter * @author Andy Wilkinson */ class MultithreadedLibraryUpdateResolver implements LibraryUpdateResolver { private static final Logger logger = LoggerFactory.getLogger(MultithreadedLibraryUpdateResolver.class); private final int threads; private final LibraryUpdateResolver delegate; MultithreadedLibraryUpdateResolver(int threads, LibraryUpdateResolver delegate) { this.threads = threads; this.delegate = delegate; } @Override public List findLibraryUpdates(Collection librariesToUpgrade, Map librariesByName) { logger.info("Looking for updates using {} threads", this.threads); ExecutorService executorService = Executors.newFixedThreadPool(this.threads); try { return librariesToUpgrade.stream().map((library) -> { if (library.getVersionAlignment() == null) { return executorService.submit(() -> this.delegate .findLibraryUpdates(Collections.singletonList(library), librariesByName)); } else { return CompletableFuture.completedFuture( this.delegate.findLibraryUpdates(Collections.singletonList(library), librariesByName)); } }).flatMap(this::getResult).toList(); } finally { executorService.shutdownNow(); } } private Stream getResult(Future> job) { try { return job.get().stream(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new RuntimeException(ex); } catch (ExecutionException ex) { throw new RuntimeException(ex); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/ReleaseSchedule.java ================================================ /* * 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.bom.bomr; import java.time.LocalDate; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.http.ResponseEntity; import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; /** * Release schedule for Spring projects, retrieved from * https://calendar.spring.io. * * @author Andy Wilkinson */ class ReleaseSchedule { private static final Pattern LIBRARY_AND_VERSION = Pattern.compile("([A-Za-z0-9 ]+) ([0-9A-Za-z.-]+)"); private final RestOperations rest; ReleaseSchedule() { this(new RestTemplate()); } ReleaseSchedule(RestOperations rest) { this.rest = rest; } @SuppressWarnings({ "unchecked", "rawtypes" }) Map> releasesBetween(OffsetDateTime start, OffsetDateTime end) { ResponseEntity response = this.rest .getForEntity("https://calendar.spring.io/releases?start=" + start + "&end=" + end, List.class); List> body = response.getBody(); Map> releasesByLibrary = new LinkedCaseInsensitiveMap<>(); body.stream() .map(this::asRelease) .filter(Objects::nonNull) .forEach((release) -> releasesByLibrary.computeIfAbsent(release.getLibraryName(), (l) -> new ArrayList<>()) .add(release)); return releasesByLibrary; } private Release asRelease(Map entry) { LocalDate due = LocalDate.parse(entry.get("start")); String title = entry.get("title"); Matcher matcher = LIBRARY_AND_VERSION.matcher(title); if (!matcher.matches()) { return null; } String library = matcher.group(1); String version = matcher.group(2); return new Release(library, DependencyVersion.parse(version), due); } static class Release { private final String libraryName; private final DependencyVersion version; private final LocalDate dueOn; Release(String libraryName, DependencyVersion version, LocalDate dueOn) { this.libraryName = libraryName; this.version = version; this.dueOn = dueOn; } String getLibraryName() { return this.libraryName; } DependencyVersion getVersion() { return this.version; } LocalDate getDueOn() { return this.dueOn; } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/StandardLibraryUpdateResolver.java ================================================ /* * 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.bom.bomr; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.function.BiFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.Library.Group; import org.springframework.boot.build.bom.Library.ImportedBom; import org.springframework.boot.build.bom.Library.Module; import org.springframework.boot.build.bom.Library.VersionAlignment; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; /** * Standard implementation for {@link LibraryUpdateResolver}. * * @author Andy Wilkinson */ class StandardLibraryUpdateResolver implements LibraryUpdateResolver { private static final Logger logger = LoggerFactory.getLogger(StandardLibraryUpdateResolver.class); private final VersionResolver versionResolver; private final BiFunction versionOptionResolver; StandardLibraryUpdateResolver(VersionResolver versionResolver, BiFunction versionOptionResolver) { this.versionResolver = versionResolver; this.versionOptionResolver = versionOptionResolver; } @Override public List findLibraryUpdates(Collection librariesToUpgrade, Map librariesByName) { List result = new ArrayList<>(); for (Library library : librariesToUpgrade) { if (isLibraryExcluded(library)) { continue; } logger.info("Looking for updates for {}", library.getName()); long start = System.nanoTime(); List versionOptions = getVersionOptions(library); result.add(new LibraryWithVersionOptions(library, versionOptions)); logger.info("Found {} updates for {}, took {}", versionOptions.size(), library.getName(), Duration.ofNanos(System.nanoTime() - start)); } return result; } protected boolean isLibraryExcluded(Library library) { return library.getName().equals("Spring Boot"); } protected List getVersionOptions(Library library) { List options = new ArrayList<>(); VersionOption alignedOption = determineAlignedVersionOption(library); if (alignedOption != null) { options.add(alignedOption); } for (VersionOption resolvedOption : determineResolvedVersionOptions(library)) { if (alignedOption == null || !alignedOption.getVersion().equals(resolvedOption.getVersion())) { options.add(resolvedOption); } } return options; } private VersionOption determineAlignedVersionOption(Library library) { VersionAlignment versionAlignment = library.getVersionAlignment(); if (versionAlignment != null) { Set alignedVersions = versionAlignment.resolve(); if (alignedVersions != null && alignedVersions.size() == 1) { DependencyVersion alignedVersion = DependencyVersion.parse(alignedVersions.iterator().next()); if (!alignedVersion.equals(library.getVersion().getVersion())) { return new VersionOption.AlignedVersionOption(alignedVersion, versionAlignment); } } } return null; } private List determineResolvedVersionOptions(Library library) { Map> moduleVersions = new LinkedHashMap<>(); for (Group group : library.getGroups()) { for (Module module : group.getModules()) { moduleVersions.put(group.getId() + ":" + module.getName(), getLaterVersionsForModule(group.getId(), module.getName(), library)); } for (ImportedBom bom : group.getBoms()) { moduleVersions.put(group.getId() + ":" + bom, getLaterVersionsForModule(group.getId(), bom.name(), library)); } for (String plugin : group.getPlugins()) { moduleVersions.put(group.getId() + ":" + plugin, getLaterVersionsForModule(group.getId(), plugin, library)); } } List versionOptions = new ArrayList<>(); moduleVersions.values().stream().flatMap(SortedSet::stream).distinct().forEach((dependencyVersion) -> { VersionOption versionOption = this.versionOptionResolver.apply(library, dependencyVersion); if (versionOption != null) { List missingModules = getMissingModules(moduleVersions, dependencyVersion); if (!missingModules.isEmpty()) { versionOption = new VersionOption.ResolvedVersionOption(versionOption.getVersion(), missingModules); } versionOptions.add(versionOption); } }); return versionOptions; } private List getMissingModules(Map> moduleVersions, DependencyVersion version) { List missingModules = new ArrayList<>(); moduleVersions.forEach((name, versions) -> { if (!versions.contains(version)) { missingModules.add(name); } }); return missingModules; } private SortedSet getLaterVersionsForModule(String groupId, String artifactId, Library library) { return this.versionResolver.resolveVersions(groupId, artifactId); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/Upgrade.java ================================================ /* * 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.bom.bomr; import org.springframework.boot.build.bom.Library; /** * An upgrade to change a {@link Library} to use a new version. * * @author Andy Wilkinson * @author Phillip Webb * @param from the library we're upgrading from * @param to the library we're upgrading to (may be a SNAPSHOT) * @param toRelease the release version of the library we're ultimately upgrading to */ record Upgrade(Library from, Library to, Library toRelease) { Upgrade(Library from, Library to) { this(from, to, to); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeApplicator.java ================================================ /* * 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.bom.bomr; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * {@code UpgradeApplicator} is used to apply an {@link Upgrade}. Modifies the bom * configuration in the build file or a version property in {@code gradle.properties}. * * @author Andy Wilkinson */ class UpgradeApplicator { private final Path buildFile; private final Path gradleProperties; UpgradeApplicator(Path buildFile, Path gradleProperties) { this.buildFile = buildFile; this.gradleProperties = gradleProperties; } Path apply(Upgrade upgrade) throws IOException { String buildFileContents = Files.readString(this.buildFile); String toName = upgrade.to().getName(); Matcher matcher = Pattern.compile("library\\(\"" + toName + "\", \"(.+)\"\\)").matcher(buildFileContents); if (!matcher.find()) { matcher = Pattern.compile("library\\(\"" + toName + "\"\\) \\{\\s+version\\(\"(.+)\"\\)", Pattern.MULTILINE) .matcher(buildFileContents); if (!matcher.find()) { throw new IllegalStateException("Failed to find definition for library '" + upgrade.to().getName() + "' in bom '" + this.buildFile + "'"); } } String version = matcher.group(1); if (version.startsWith("${") && version.endsWith("}")) { updateGradleProperties(upgrade, version); return this.gradleProperties; } else { updateBuildFile(upgrade, buildFileContents, matcher.start(1), matcher.end(1)); return this.buildFile; } } private void updateGradleProperties(Upgrade upgrade, String version) throws IOException { String property = version.substring(2, version.length() - 1); String gradlePropertiesContents = Files.readString(this.gradleProperties); String modified = gradlePropertiesContents.replace(property + "=" + upgrade.from().getVersion(), property + "=" + upgrade.to().getVersion()); overwrite(this.gradleProperties, modified); } private void updateBuildFile(Upgrade upgrade, String buildFileContents, int versionStart, int versionEnd) throws IOException { String modified = buildFileContents.substring(0, versionStart) + upgrade.to().getVersion() + buildFileContents.substring(versionEnd); overwrite(this.buildFile, modified); } private void overwrite(Path target, String content) throws IOException { Files.writeString(target, content, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java ================================================ /* * 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.bom.bomr; import java.util.List; import javax.inject.Inject; import org.gradle.api.Task; import org.gradle.api.artifacts.ArtifactRepositoryContainer; import org.gradle.api.artifacts.dsl.RepositoryHandler; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.springframework.boot.build.bom.BomExtension; import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.properties.BuildProperties; /** * {@link Task} to upgrade the libraries managed by a bom. * * @author Andy Wilkinson * @author Moritz Halbritter */ public abstract class UpgradeBom extends UpgradeDependencies { @Inject public UpgradeBom(BomExtension bom) { super(bom); switch (BuildProperties.get(getProject()).buildType()) { case OPEN_SOURCE -> addOpenSourceRepositories(getProject().getRepositories()); case COMMERCIAL -> addCommercialRepositories(); } } private void addOpenSourceRepositories(RepositoryHandler repositories) { getRepositoryNames().add(ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME); repositories.withType(MavenArtifactRepository.class, (repository) -> { String name = repository.getName(); if (name.startsWith("spring-") && !name.endsWith("-snapshot")) { getRepositoryNames().add(name); } }); } private void addCommercialRepositories() { getRepositoryNames().addAll(ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME, "spring-commercial-release"); } @Override protected String commitMessage(Upgrade upgrade, int issueNumber) { return issueTitle(upgrade) + "\n\nCloses gh-" + issueNumber; } @Override protected void upgradesApplied(List upgrades) { if (upgrades.isEmpty()) { return; } System.out.println(); System.out.println("Upgrade release notes:"); System.out.println(); for (Upgrade upgrade : upgrades) { Library library = upgrade.toRelease(); String releaseNotes = library.getLinkUrl("releaseNotes"); if (releaseNotes != null) { System.out.println("* " + releaseNotes + "[" + library.getNameAndVersion() + "]"); } else { System.out.println("* " + library.getNameAndVersion()); } } System.out.println(); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeDependencies.java ================================================ /* * 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.bom.bomr; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.function.BiFunction; import java.util.function.BiPredicate; import java.util.function.Predicate; import java.util.regex.Pattern; import javax.inject.Inject; import org.gradle.api.DefaultTask; import org.gradle.api.InvalidUserDataException; import org.gradle.api.artifacts.dsl.RepositoryHandler; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.internal.tasks.userinput.UserInputHandler; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskExecutionException; import org.gradle.api.tasks.options.Option; import org.springframework.boot.build.bom.BomExtension; import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.UpgradePolicy; import org.springframework.boot.build.bom.bomr.github.GitHub; import org.springframework.boot.build.bom.bomr.github.GitHubRepository; import org.springframework.boot.build.bom.bomr.github.Issue; import org.springframework.boot.build.bom.bomr.github.Milestone; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.util.StringUtils; /** * Base class for tasks that upgrade dependencies in a BOM. * * @author Andy Wilkinson * @author Moritz Halbritter */ public abstract class UpgradeDependencies extends DefaultTask { private final BomExtension bom; private final boolean movingToSnapshots; private final UpgradeApplicator upgradeApplicator; private final RepositoryHandler repositories; @Inject public UpgradeDependencies(BomExtension bom) { this(bom, false); } protected UpgradeDependencies(BomExtension bom, boolean movingToSnapshots) { this.bom = bom; getThreads().convention(2); this.movingToSnapshots = movingToSnapshots; this.upgradeApplicator = new UpgradeApplicator(getProject().getBuildFile().toPath(), new File(getProject().getRootProject().getProjectDir(), "gradle.properties").toPath()); this.repositories = getProject().getRepositories(); } @Input @Option(option = "milestone", description = "Milestone to which dependency upgrade issues should be assigned") public abstract Property getMilestone(); @Input @Optional @Option(option = "threads", description = "Number of Threads to use for update resolution") public abstract Property getThreads(); @Input @Optional @Option(option = "libraries", description = "Regular expression that identifies the libraries to upgrade") public abstract Property getLibraries(); @Input @Optional @Option(option = "dry-run-upgrades", description = "Whether to perform a dry run that doesn't open issues or change the bom") public abstract Property getDryRun(); @Input abstract ListProperty getRepositoryNames(); @TaskAction void upgradeDependencies() { GitHubRepository repository = createGitHub().getRepository(this.bom.getUpgrade().getGitHub().getOrganization(), this.bom.getUpgrade().getGitHub().getRepository()); List issueLabels = verifyLabels(repository); Milestone milestone = determineMilestone(repository); List upgrades = resolveUpgrades(milestone); if (!getDryRun().getOrElse(false)) { applyUpgrades(repository, issueLabels, milestone, upgrades); } upgradesApplied(upgrades); } protected void upgradesApplied(List upgrades) { } private void applyUpgrades(GitHubRepository repository, List issueLabels, Milestone milestone, List upgrades) { List existingUpgradeIssues = repository.findIssues(issueLabels, milestone); System.out.println("Applying upgrades..."); System.out.println(""); for (Upgrade upgrade : upgrades) { System.out.println(upgrade.to().getNameAndVersion()); Issue existingUpgradeIssue = findExistingUpgradeIssue(existingUpgradeIssues, upgrade); try { Path modified = this.upgradeApplicator.apply(upgrade); String title = issueTitle(upgrade); String body = issueBody(upgrade, existingUpgradeIssue); int issueNumber = getOrOpenUpgradeIssue(repository, issueLabels, milestone, title, body, existingUpgradeIssue); if (existingUpgradeIssue != null && existingUpgradeIssue.getState() == Issue.State.CLOSED) { existingUpgradeIssue.label(Arrays.asList("type: task", "status: superseded")); } System.out.println(" Issue: " + issueNumber + " - " + title + getExistingUpgradeIssueMessageDetails(existingUpgradeIssue)); if (new ProcessBuilder().command("git", "add", modified.toFile().getAbsolutePath()) .start() .waitFor() != 0) { throw new IllegalStateException("git add failed"); } String commitMessage = commitMessage(upgrade, issueNumber); if (new ProcessBuilder().command("git", "commit", "-m", commitMessage).start().waitFor() != 0) { throw new IllegalStateException("git commit failed"); } System.out.println(" Commit: " + commitMessage.substring(0, commitMessage.indexOf('\n'))); } catch (IOException ex) { throw new TaskExecutionException(this, ex); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } private int getOrOpenUpgradeIssue(GitHubRepository repository, List issueLabels, Milestone milestone, String title, String body, Issue existingUpgradeIssue) { if (existingUpgradeIssue != null && existingUpgradeIssue.getState() == Issue.State.OPEN) { return existingUpgradeIssue.getNumber(); } return repository.openIssue(title, body, issueLabels, milestone); } private String getExistingUpgradeIssueMessageDetails(Issue existingUpgradeIssue) { if (existingUpgradeIssue == null) { return ""; } if (existingUpgradeIssue.getState() != Issue.State.CLOSED) { return " (completes existing upgrade)"; } return " (supersedes #" + existingUpgradeIssue.getNumber() + " " + existingUpgradeIssue.getTitle() + ")"; } private List verifyLabels(GitHubRepository repository) { Set availableLabels = repository.getLabels(); List issueLabels = this.bom.getUpgrade().getGitHub().getIssueLabels(); if (!availableLabels.containsAll(issueLabels)) { List unknownLabels = new ArrayList<>(issueLabels); unknownLabels.removeAll(availableLabels); String suffix = (unknownLabels.size() == 1) ? "" : "s"; throw new InvalidUserDataException( "Unknown label" + suffix + ": " + StringUtils.collectionToCommaDelimitedString(unknownLabels)); } return issueLabels; } private GitHub createGitHub() { Properties bomrProperties = new Properties(); try (Reader reader = new FileReader(new File(System.getProperty("user.home"), ".bomr.properties"))) { bomrProperties.load(reader); String username = bomrProperties.getProperty("bomr.github.username"); String password = bomrProperties.getProperty("bomr.github.password"); return GitHub.withCredentials(username, password); } catch (IOException ex) { throw new InvalidUserDataException("Failed to load .bomr.properties from user home", ex); } } private Milestone determineMilestone(GitHubRepository repository) { List milestones = repository.getMilestones(); java.util.Optional matchingMilestone = milestones.stream() .filter((milestone) -> milestone.getName().equals(getMilestone().get())) .findFirst(); if (matchingMilestone.isEmpty()) { throw new InvalidUserDataException("Unknown milestone: " + getMilestone().get()); } return matchingMilestone.get(); } private Issue findExistingUpgradeIssue(List existingUpgradeIssues, Upgrade upgrade) { String toMatch = "Upgrade to " + upgrade.toRelease().getName(); for (Issue existingUpgradeIssue : existingUpgradeIssues) { String title = existingUpgradeIssue.getTitle(); int lastSpaceIndex = title.lastIndexOf(' '); if (lastSpaceIndex > -1) { title = title.substring(0, lastSpaceIndex); } if (title.equals(toMatch)) { return existingUpgradeIssue; } } return null; } @SuppressWarnings("deprecation") private List resolveUpgrades(Milestone milestone) { InteractiveUpgradeResolver upgradeResolver = new InteractiveUpgradeResolver( getServices().get(UserInputHandler.class), getLibraryUpdateResolver(milestone)); return upgradeResolver.resolveUpgrades(matchingLibraries(), this.bom.getLibraries()); } private LibraryUpdateResolver getLibraryUpdateResolver(Milestone milestone) { VersionResolver versionResolver = new MavenMetadataVersionResolver(getRepositories()); LibraryUpdateResolver libraryResolver = new StandardLibraryUpdateResolver(versionResolver, createVersionOptionResolver(milestone)); return new MultithreadedLibraryUpdateResolver(getThreads().get(), libraryResolver); } private Collection getRepositories() { return getRepositoryNames().map(this::asRepositories).get(); } private List asRepositories(List repositoryNames) { return repositoryNames.stream() .map(this.repositories::getByName) .map(MavenArtifactRepository.class::cast) .toList(); } protected BiFunction createVersionOptionResolver(Milestone milestone) { List> updatePredicates = new ArrayList<>(); updatePredicates.add(this::compliesWithUpgradePolicy); updatePredicates.add(this::isAnUpgrade); updatePredicates.add(this::isNotProhibited); return (library, dependencyVersion) -> { if (this.compliesWithUpgradePolicy(library, dependencyVersion) && this.isAnUpgrade(library, dependencyVersion) && this.isNotProhibited(library, dependencyVersion)) { return new VersionOption.ResolvedVersionOption(dependencyVersion, Collections.emptyList()); } return null; }; } private boolean compliesWithUpgradePolicy(Library library, DependencyVersion candidate) { UpgradePolicy libraryPolicy = library.getUpgradePolicy(); UpgradePolicy bomPolicy = this.bom.getUpgrade().getPolicy(); UpgradePolicy upgradePolicy = UpgradePolicy.max(libraryPolicy, bomPolicy); return upgradePolicy.test(candidate, library.getVersion().getVersion()); } private boolean isAnUpgrade(Library library, DependencyVersion candidate) { return library.getVersion().getVersion().isUpgrade(candidate, this.movingToSnapshots); } private boolean isNotProhibited(Library library, DependencyVersion candidate) { return library.getProhibitedVersions() .stream() .noneMatch((prohibited) -> prohibited.isProhibited(candidate.toString())); } private List matchingLibraries() { List matchingLibraries = this.bom.getLibraries().stream().filter(this::eligible).toList(); if (matchingLibraries.isEmpty()) { throw new InvalidUserDataException("No libraries to upgrade"); } return matchingLibraries; } protected boolean eligible(Library library) { String pattern = getLibraries().getOrNull(); if (pattern == null) { return true; } Predicate libraryPredicate = Pattern.compile(pattern).asPredicate(); return libraryPredicate.test(library.getName()); } protected abstract String commitMessage(Upgrade upgrade, int issueNumber); protected String issueTitle(Upgrade upgrade) { return "Upgrade to " + upgrade.toRelease().getNameAndVersion(); } protected String issueBody(Upgrade upgrade, Issue existingUpgrade) { String description = upgrade.toRelease().getNameAndVersion(); String releaseNotesLink = upgrade.toRelease().getLinkUrl("releaseNotes"); String body = (releaseNotesLink != null) ? "Upgrade to [%s](%s).".formatted(description, releaseNotesLink) : "Upgrade to %s.".formatted(description); if (existingUpgrade != null) { body += "\n\nSupersedes #" + existingUpgrade.getNumber(); } return body; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeResolver.java ================================================ /* * 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.bom.bomr; import java.util.Collection; import java.util.List; import org.springframework.boot.build.bom.Library; /** * Resolves upgrades for the libraries in a bom. * * @author Andy Wilkinson */ interface UpgradeResolver { /** * Resolves the upgrades to be applied to the given {@code libraries}. * @param librariesToUpgrade the libraries to upgrade * @param libraries all libraries * @return the upgrades */ List resolveUpgrades(Collection librariesToUpgrade, Collection libraries); } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/VersionOption.java ================================================ /* * 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.bom.bomr; import java.util.List; import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.Library.LibraryVersion; import org.springframework.boot.build.bom.Library.VersionAlignment; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.util.StringUtils; /** * An option for a library update. * * @author Andy Wilkinson */ class VersionOption { private final DependencyVersion version; VersionOption(DependencyVersion version) { this.version = version; } DependencyVersion getVersion() { return this.version; } @Override public String toString() { return this.version.toString(); } Upgrade upgrade(Library library) { return new Upgrade(library, library.withVersion(new LibraryVersion(this.version))); } static final class AlignedVersionOption extends VersionOption { private final VersionAlignment alignedWith; AlignedVersionOption(DependencyVersion version, VersionAlignment alignedWith) { super(version); this.alignedWith = alignedWith; } @Override public String toString() { return super.toString() + " (aligned with " + this.alignedWith + ")"; } } static final class ResolvedVersionOption extends VersionOption { private final List missingModules; ResolvedVersionOption(DependencyVersion version, List missingModules) { super(version); this.missingModules = missingModules; } @Override public String toString() { if (this.missingModules.isEmpty()) { return super.toString(); } return super.toString() + " (some modules are missing: " + StringUtils.collectionToDelimitedString(this.missingModules, ", ") + ")"; } } static final class SnapshotVersionOption extends VersionOption { private final DependencyVersion releaseVersion; SnapshotVersionOption(DependencyVersion version, DependencyVersion releaseVersion) { super(version); this.releaseVersion = releaseVersion; } @Override public String toString() { return super.toString() + " (for " + this.releaseVersion + ")"; } @Override Upgrade upgrade(Library library) { return new Upgrade(library, library.withVersion(new LibraryVersion(super.version)), library.withVersion(new LibraryVersion(this.releaseVersion))); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/VersionResolver.java ================================================ /* * 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.bom.bomr; import java.util.SortedSet; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; /** * Resolves the available versions for a module. * * @author Andy Wilkinson */ interface VersionResolver { /** * Resolves the available versions for the module identified by the given * {@code groupId} and {@code artifactId}. * @param groupId module's group ID * @param artifactId module's artifact ID * @return the available versions */ SortedSet resolveVersions(String groupId, String artifactId); } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHub.java ================================================ /* * 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.bom.bomr.github; /** * Minimal API for interacting with GitHub. * * @author Andy Wilkinson */ public interface GitHub { /** * Returns a {@link GitHubRepository} with the given {@code name} in the given * {@code organization}. * @param organization the organization * @param name the name of the repository * @return the repository */ GitHubRepository getRepository(String organization, String name); /** * Creates a new {@code GitHub} that will authenticate with given {@code username} and * {@code password}. * @param username username for authentication * @param password password for authentication * @return the new {@code GitHub} instance */ static GitHub withCredentials(String username, String password) { return new StandardGitHub(username, password); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHubRepository.java ================================================ /* * 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.bom.bomr.github; import java.util.List; import java.util.Set; /** * Minimal API for interacting with a GitHub repository. * * @author Andy Wilkinson */ public interface GitHubRepository { /** * Opens a new issue with the given title. The given {@code labels} will be applied to * the issue and it will be assigned to the given {@code milestone}. * @param title the title of the issue * @param body the body of the issue * @param labels the labels to apply to the issue * @param milestone the milestone to assign the issue to * @return the number of the new issue */ int openIssue(String title, String body, List labels, Milestone milestone); /** * Returns the labels in the repository. * @return the labels */ Set getLabels(); /** * Returns the milestones in the repository. * @return the milestones */ List getMilestones(); /** * Finds issues that have the given {@code labels} and are assigned to the given * {@code milestone}. * @param labels issue labels * @param milestone assigned milestone * @return the matching issues */ List findIssues(List labels, Milestone milestone); } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/Issue.java ================================================ /* * 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.bom.bomr.github; import java.util.Collections; import java.util.List; import java.util.Map; import org.springframework.web.client.RestTemplate; /** * Minimal representation of a GitHub issue. * * @author Andy Wilkinson */ public class Issue { private final RestTemplate rest; private final int number; private final String title; private final State state; Issue(RestTemplate rest, int number, String title, State state) { this.rest = rest; this.number = number; this.title = title; this.state = state; } public int getNumber() { return this.number; } public String getTitle() { return this.title; } public State getState() { return this.state; } /** * Labels the issue with the given {@code labels}. Any existing labels are removed. * @param labels the labels to apply to the issue */ public void label(List labels) { Map> body = Collections.singletonMap("labels", labels); this.rest.put("issues/" + this.number + "/labels", body); } public enum State { /** * The issue is open. */ OPEN, /** * The issue is closed. */ CLOSED; static State of(String state) { if ("open".equals(state)) { return OPEN; } if ("closed".equals(state)) { return CLOSED; } else { throw new IllegalArgumentException("Unknown state '" + state + "'"); } } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/Milestone.java ================================================ /* * 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.bom.bomr.github; import java.time.OffsetDateTime; /** * A milestone in a {@link GitHubRepository GitHub repository}. * * @author Andy Wilkinson */ public class Milestone { private final String name; private final int number; private final OffsetDateTime dueOn; Milestone(String name, int number, OffsetDateTime dueOn) { this.name = name; this.number = number; this.dueOn = dueOn; } /** * Returns the name of the milestone. * @return the name */ public String getName() { return this.name; } /** * Returns the number of the milestone. * @return the number */ public int getNumber() { return this.number; } public OffsetDateTime getDueOn() { return this.dueOn; } @Override public String toString() { return this.name + " (" + this.number + ")"; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHub.java ================================================ /* * 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.bom.bomr.github; import java.util.Base64; import java.util.Collections; import org.springframework.http.MediaType; import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.DefaultUriBuilderFactory; import org.springframework.web.util.UriTemplateHandler; /** * Standard implementation of {@link GitHub}. * * @author Andy Wilkinson */ final class StandardGitHub implements GitHub { private final String username; private final String password; StandardGitHub(String username, String password) { this.username = username; this.password = password; } @Override public GitHubRepository getRepository(String organization, String name) { RestTemplate restTemplate = createRestTemplate(); restTemplate.getInterceptors().add((request, body, execution) -> { request.getHeaders().add("User-Agent", StandardGitHub.this.username); request.getHeaders() .add("Authorization", "Basic " + Base64.getEncoder() .encodeToString((StandardGitHub.this.username + ":" + StandardGitHub.this.password).getBytes())); request.getHeaders().add("Accept", MediaType.APPLICATION_JSON_VALUE); return execution.execute(request, body); }); UriTemplateHandler uriTemplateHandler = new DefaultUriBuilderFactory( "https://api.github.com/repos/" + organization + "/" + name + "/"); restTemplate.setUriTemplateHandler(uriTemplateHandler); return new StandardGitHubRepository(restTemplate); } @SuppressWarnings({ "deprecation", "removal" }) private RestTemplate createRestTemplate() { return new RestTemplate(Collections.singletonList(new JacksonJsonHttpMessageConverter())); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHubRepository.java ================================================ /* * 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.bom.bomr.github; import java.time.Duration; import java.time.OffsetDateTime; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import org.springframework.http.ResponseEntity; import org.springframework.web.client.HttpClientErrorException.Forbidden; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; /** * Standard implementation of {@link GitHubRepository}. * * @author Andy Wilkinson */ final class StandardGitHubRepository implements GitHubRepository { private final RestTemplate rest; StandardGitHubRepository(RestTemplate restTemplate) { this.rest = restTemplate; } @Override @SuppressWarnings("rawtypes") public int openIssue(String title, String body, List labels, Milestone milestone) { Map requestBody = new HashMap<>(); requestBody.put("title", title); if (milestone != null) { requestBody.put("milestone", milestone.getNumber()); } if (!labels.isEmpty()) { requestBody.put("labels", labels); } requestBody.put("body", body); try { ResponseEntity response = this.rest.postForEntity("issues", requestBody, Map.class); // See gh-30304 sleep(Duration.ofSeconds(3)); return (Integer) response.getBody().get("number"); } catch (RestClientException ex) { if (ex instanceof Forbidden forbidden) { System.out.println("Received 403 response with headers " + forbidden.getResponseHeaders()); } throw ex; } } @Override public Set getLabels() { return new HashSet<>(get("labels?per_page=100", (label) -> (String) label.get("name"))); } @Override public List getMilestones() { return get("milestones?per_page=100", (milestone) -> new Milestone((String) milestone.get("title"), (Integer) milestone.get("number"), (milestone.get("due_on") != null) ? OffsetDateTime.parse((String) milestone.get("due_on")) : null)); } @Override public List findIssues(List labels, Milestone milestone) { return get( "issues?per_page=100&state=all&labels=" + String.join(",", labels) + "&milestone=" + milestone.getNumber(), (issue) -> new Issue(this.rest, (Integer) issue.get("number"), (String) issue.get("title"), Issue.State.of((String) issue.get("state")))); } @SuppressWarnings({ "rawtypes", "unchecked" }) private List get(String name, Function, T> mapper) { ResponseEntity response = this.rest.getForEntity(name, List.class); return ((List>) response.getBody()).stream().map(mapper).toList(); } private static void sleep(Duration duration) { try { Thread.sleep(duration.toMillis()); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/AbstractDependencyVersion.java ================================================ /* * 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.bom.bomr.version; import org.apache.maven.artifact.versioning.ComparableVersion; /** * Base class for {@link DependencyVersion} implementations. * * @author Andy Wilkinson */ abstract class AbstractDependencyVersion implements DependencyVersion { private final ComparableVersion comparableVersion; protected AbstractDependencyVersion(ComparableVersion comparableVersion) { this.comparableVersion = comparableVersion; } @Override public int compareTo(DependencyVersion other) { ComparableVersion otherComparable = (other instanceof AbstractDependencyVersion otherVersion) ? otherVersion.comparableVersion : new ComparableVersion(other.toString()); return this.comparableVersion.compareTo(otherComparable); } @Override public boolean isUpgrade(DependencyVersion candidate, boolean movingToSnapshots) { ComparableVersion comparableCandidate = (candidate instanceof AbstractDependencyVersion abstractDependencyVersion) ? abstractDependencyVersion.comparableVersion : new ComparableVersion(candidate.toString()); return comparableCandidate.compareTo(this.comparableVersion) > 0; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } AbstractDependencyVersion other = (AbstractDependencyVersion) obj; return this.comparableVersion.equals(other.comparableVersion); } @Override public int hashCode() { return this.comparableVersion.hashCode(); } @Override public String toString() { return this.comparableVersion.toString(); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ArtifactVersionDependencyVersion.java ================================================ /* * 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.bom.bomr.version; import java.util.Objects; import java.util.Optional; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.ComparableVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.springframework.util.StringUtils; /** * A {@link DependencyVersion} backed by an {@link ArtifactVersion}. * * @author Andy Wilkinson */ class ArtifactVersionDependencyVersion extends AbstractDependencyVersion { private final ArtifactVersion artifactVersion; protected ArtifactVersionDependencyVersion(ArtifactVersion artifactVersion) { super(new ComparableVersion(toNormalizedString(artifactVersion))); this.artifactVersion = artifactVersion; } private static String toNormalizedString(ArtifactVersion artifactVersion) { String versionString = artifactVersion.toString(); if (versionString.endsWith(".RELEASE")) { return versionString.substring(0, versionString.length() - 8); } if (versionString.endsWith(".BUILD-SNAPSHOT")) { return versionString.substring(0, versionString.length() - 15) + "-SNAPSHOT"; } return versionString; } protected ArtifactVersionDependencyVersion(ArtifactVersion artifactVersion, ComparableVersion comparableVersion) { super(comparableVersion); this.artifactVersion = artifactVersion; } @Override public boolean isSameMajor(DependencyVersion other) { if (other instanceof ReleaseTrainDependencyVersion) { return false; } return extractArtifactVersionDependencyVersion(other).map(this::isSameMajor).orElse(true); } private boolean isSameMajor(ArtifactVersionDependencyVersion other) { return this.artifactVersion.getMajorVersion() == other.artifactVersion.getMajorVersion(); } @Override public boolean isSameMinor(DependencyVersion other) { if (other instanceof ReleaseTrainDependencyVersion) { return false; } return extractArtifactVersionDependencyVersion(other).map(this::isSameMinor).orElse(true); } private boolean isSameMinor(ArtifactVersionDependencyVersion other) { return isSameMajor(other) && this.artifactVersion.getMinorVersion() == other.artifactVersion.getMinorVersion(); } @Override public boolean isUpgrade(DependencyVersion candidate, boolean movingToSnapshots) { if (candidate instanceof MultipleComponentsDependencyVersion) { return super.isUpgrade(candidate, movingToSnapshots); } if (!(candidate instanceof ArtifactVersionDependencyVersion)) { return false; } ArtifactVersion other = ((ArtifactVersionDependencyVersion) candidate).artifactVersion; if (this.artifactVersion.equals(other)) { return false; } if (sameMajorMinorIncremental(other)) { if (!StringUtils.hasLength(this.artifactVersion.getQualifier()) || "RELEASE".equals(this.artifactVersion.getQualifier())) { return false; } if (isSnapshot()) { return true; } else if (((ArtifactVersionDependencyVersion) candidate).isSnapshot()) { return movingToSnapshots; } } return super.isUpgrade(candidate, movingToSnapshots); } private boolean sameMajorMinorIncremental(ArtifactVersion other) { return this.artifactVersion.getMajorVersion() == other.getMajorVersion() && this.artifactVersion.getMinorVersion() == other.getMinorVersion() && this.artifactVersion.getIncrementalVersion() == other.getIncrementalVersion(); } private boolean isSnapshot() { return "SNAPSHOT".equals(this.artifactVersion.getQualifier()) || "BUILD".equals(this.artifactVersion.getQualifier()); } @Override public boolean isSnapshotFor(DependencyVersion candidate) { if (!isSnapshot() || !(candidate instanceof ArtifactVersionDependencyVersion)) { return false; } return sameMajorMinorIncremental(((ArtifactVersionDependencyVersion) candidate).artifactVersion); } @Override public int compareTo(DependencyVersion other) { if (other instanceof ArtifactVersionDependencyVersion otherArtifactDependencyVersion) { ArtifactVersion otherArtifactVersion = otherArtifactDependencyVersion.artifactVersion; if ((!Objects.equals(this.artifactVersion.getQualifier(), otherArtifactVersion.getQualifier())) && "snapshot".equalsIgnoreCase(otherArtifactVersion.getQualifier()) && otherArtifactVersion.getMajorVersion() == this.artifactVersion.getMajorVersion() && otherArtifactVersion.getMinorVersion() == this.artifactVersion.getMinorVersion() && otherArtifactVersion.getIncrementalVersion() == this.artifactVersion.getIncrementalVersion()) { return 1; } } return super.compareTo(other); } @Override public String toString() { return this.artifactVersion.toString(); } protected Optional extractArtifactVersionDependencyVersion( DependencyVersion other) { ArtifactVersionDependencyVersion artifactVersion = null; if (other instanceof ArtifactVersionDependencyVersion otherVersion) { artifactVersion = otherVersion; } return Optional.ofNullable(artifactVersion); } static ArtifactVersionDependencyVersion parse(String version) { ArtifactVersion artifactVersion = new DefaultArtifactVersion(version); if (artifactVersion.getQualifier() != null && artifactVersion.getQualifier().equals(version)) { return null; } return new ArtifactVersionDependencyVersion(artifactVersion); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/CalendarVersionDependencyVersion.java ================================================ /* * 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.bom.bomr.version; import java.util.regex.Pattern; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.ComparableVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; /** * A specialization of {@link ArtifactVersionDependencyVersion} for calendar versions. * Calendar versions are always considered to be newer than * {@link ReleaseTrainDependencyVersion release train versions}. * * @author Andy Wilkinson */ class CalendarVersionDependencyVersion extends ArtifactVersionDependencyVersion { private static final Pattern CALENDAR_VERSION_PATTERN = Pattern.compile("\\d{4}\\.\\d+\\.\\d+(-.+)?"); protected CalendarVersionDependencyVersion(ArtifactVersion artifactVersion) { super(artifactVersion); } protected CalendarVersionDependencyVersion(ArtifactVersion artifactVersion, ComparableVersion comparableVersion) { super(artifactVersion, comparableVersion); } static CalendarVersionDependencyVersion parse(String version) { if (!CALENDAR_VERSION_PATTERN.matcher(version).matches()) { return null; } ArtifactVersion artifactVersion = new DefaultArtifactVersion(version); if (artifactVersion.getQualifier() != null && artifactVersion.getQualifier().equals(version)) { return null; } return new CalendarVersionDependencyVersion(artifactVersion); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/CombinedPatchAndQualifierDependencyVersion.java ================================================ /* * 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.bom.bomr.version; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; /** * A {@link DependencyVersion} where the patch and qualifier are not separated. * * @author Andy Wilkinson */ final class CombinedPatchAndQualifierDependencyVersion extends ArtifactVersionDependencyVersion { private static final Pattern PATTERN = Pattern.compile("([0-9]+\\.[0-9]+\\.[0-9]+)([A-Za-z][A-Za-z0-9]+)"); private final String original; private CombinedPatchAndQualifierDependencyVersion(ArtifactVersion artifactVersion, String original) { super(artifactVersion); this.original = original; } @Override public String toString() { return this.original; } static CombinedPatchAndQualifierDependencyVersion parse(String version) { Matcher matcher = PATTERN.matcher(version); if (!matcher.matches()) { return null; } ArtifactVersion artifactVersion = new DefaultArtifactVersion(matcher.group(1) + "." + matcher.group(2)); if (artifactVersion.getQualifier() != null && artifactVersion.getQualifier().equals(version)) { return null; } return new CombinedPatchAndQualifierDependencyVersion(artifactVersion, version); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/DependencyVersion.java ================================================ /* * 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.bom.bomr.version; import java.util.Arrays; import java.util.List; import java.util.function.Function; /** * Version of a dependency. * * @author Andy Wilkinson */ public interface DependencyVersion extends Comparable { /** * Returns whether this version has the same major and minor versions as the * {@code other} version. * @param other the version to test * @return {@code true} if this version has the same major and minor, otherwise * {@code false} */ boolean isSameMinor(DependencyVersion other); /** * Returns whether this version has the same major version as the {@code other} * version. * @param other the version to test * @return {@code true} if this version has the same major, otherwise {@code false} */ boolean isSameMajor(DependencyVersion other); /** * Returns whether the given {@code candidate} is an upgrade of this version. * @param candidate the version to consider * @param movingToSnapshots whether the upgrade is to be considered as part of moving * to snapshots * @return {@code true} if the candidate is an upgrade, otherwise false */ boolean isUpgrade(DependencyVersion candidate, boolean movingToSnapshots); /** * Returns whether this version is a snapshot for the given {@code candidate}. * @param candidate the version to consider * @return {@code true} if this version is a snapshot for the candidate, otherwise * false */ boolean isSnapshotFor(DependencyVersion candidate); static DependencyVersion parse(String version) { List> parsers = Arrays.asList(CalendarVersionDependencyVersion::parse, ArtifactVersionDependencyVersion::parse, ReleaseTrainDependencyVersion::parse, MultipleComponentsDependencyVersion::parse, CombinedPatchAndQualifierDependencyVersion::parse, LeadingZeroesDependencyVersion::parse, UnstructuredDependencyVersion::parse); for (Function parser : parsers) { DependencyVersion result = parser.apply(version); if (result != null) { return result; } } throw new IllegalArgumentException("Version '" + version + "' could not be parsed"); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/LeadingZeroesDependencyVersion.java ================================================ /* * 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.bom.bomr.version; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; /** * A {@link DependencyVersion} that tolerates leading zeroes. * * @author Andy Wilkinson */ final class LeadingZeroesDependencyVersion extends ArtifactVersionDependencyVersion { private static final Pattern PATTERN = Pattern.compile("0*([0-9]+)\\.0*([0-9]+)\\.0*([0-9]+)"); private final String original; private LeadingZeroesDependencyVersion(ArtifactVersion artifactVersion, String original) { super(artifactVersion); this.original = original; } @Override public String toString() { return this.original; } static LeadingZeroesDependencyVersion parse(String input) { Matcher matcher = PATTERN.matcher(input); if (!matcher.matches()) { return null; } ArtifactVersion artifactVersion = new DefaultArtifactVersion( matcher.group(1) + matcher.group(2) + matcher.group(3)); return new LeadingZeroesDependencyVersion(artifactVersion, input); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/MultipleComponentsDependencyVersion.java ================================================ /* * 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.bom.bomr.version; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.ComparableVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; /** * A fallback {@link DependencyVersion} to handle versions with four or five components * that cannot be handled by {@link ArtifactVersion} because the fourth component is * numeric. * * @author Andy Wilkinson * @author Moritz Halbritter */ final class MultipleComponentsDependencyVersion extends ArtifactVersionDependencyVersion { private final String original; private MultipleComponentsDependencyVersion(ArtifactVersion artifactVersion, String original) { super(artifactVersion, new ComparableVersion(original)); this.original = original; } @Override public String toString() { return this.original; } static MultipleComponentsDependencyVersion parse(String input) { String[] components = input.split("\\."); if (components.length == 4 || components.length == 5) { ArtifactVersion artifactVersion = new DefaultArtifactVersion( components[0] + "." + components[1] + "." + components[2]); if (artifactVersion.getQualifier() != null && artifactVersion.getQualifier().equals(input)) { return null; } return new MultipleComponentsDependencyVersion(artifactVersion, input); } return null; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ReleaseTrainDependencyVersion.java ================================================ /* * 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.bom.bomr.version; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.springframework.util.StringUtils; /** * A {@link DependencyVersion} for a release train such as Spring Data. * * @author Andy Wilkinson */ final class ReleaseTrainDependencyVersion implements DependencyVersion { private static final Pattern VERSION_PATTERN = Pattern .compile("([A-Z][a-z]+)-((BUILD-SNAPSHOT)|([A-Z-]+)([0-9]*))"); private final String releaseTrain; private final String type; private final int version; private final String original; private ReleaseTrainDependencyVersion(String releaseTrain, String type, int version, String original) { this.releaseTrain = releaseTrain; this.type = type; this.version = version; this.original = original; } @Override public int compareTo(DependencyVersion other) { if (!(other instanceof ReleaseTrainDependencyVersion otherReleaseTrain)) { return -1; } int comparison = this.releaseTrain.compareTo(otherReleaseTrain.releaseTrain); if (comparison != 0) { return comparison; } comparison = this.type.compareTo(otherReleaseTrain.type); if (comparison != 0) { return comparison; } return Integer.compare(this.version, otherReleaseTrain.version); } @Override public boolean isUpgrade(DependencyVersion candidate, boolean movingToSnapshots) { if (candidate instanceof ReleaseTrainDependencyVersion candidateReleaseTrain) { return isUpgrade(candidateReleaseTrain, movingToSnapshots); } return true; } private boolean isUpgrade(ReleaseTrainDependencyVersion candidate, boolean movingToSnapshots) { int comparison = this.releaseTrain.compareTo(candidate.releaseTrain); if (comparison != 0) { return comparison < 0; } if (movingToSnapshots && !isSnapshot() && candidate.isSnapshot()) { return true; } comparison = this.type.compareTo(candidate.type); if (comparison != 0) { return comparison < 0; } return Integer.compare(this.version, candidate.version) < 0; } private boolean isSnapshot() { return "BUILD-SNAPSHOT".equals(this.type); } @Override public boolean isSnapshotFor(DependencyVersion candidate) { if (!isSnapshot() || !(candidate instanceof ReleaseTrainDependencyVersion candidateReleaseTrain)) { return false; } return this.releaseTrain.equals(candidateReleaseTrain.releaseTrain); } @Override public boolean isSameMajor(DependencyVersion other) { return isSameReleaseTrain(other); } @Override public boolean isSameMinor(DependencyVersion other) { return isSameReleaseTrain(other); } private boolean isSameReleaseTrain(DependencyVersion other) { if (other instanceof CalendarVersionDependencyVersion) { return false; } if (other instanceof ReleaseTrainDependencyVersion otherReleaseTrain) { return otherReleaseTrain.releaseTrain.equals(this.releaseTrain); } return true; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ReleaseTrainDependencyVersion other = (ReleaseTrainDependencyVersion) obj; return this.original.equals(other.original); } @Override public int hashCode() { return this.original.hashCode(); } @Override public String toString() { return this.original; } static ReleaseTrainDependencyVersion parse(String input) { Matcher matcher = VERSION_PATTERN.matcher(input); if (!matcher.matches()) { return null; } return new ReleaseTrainDependencyVersion(matcher.group(1), StringUtils.hasLength(matcher.group(3)) ? matcher.group(3) : matcher.group(4), (StringUtils.hasLength(matcher.group(5))) ? Integer.parseInt(matcher.group(5)) : 0, input); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/UnstructuredDependencyVersion.java ================================================ /* * 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.bom.bomr.version; import org.apache.maven.artifact.versioning.ComparableVersion; /** * A {@link DependencyVersion} with no structure such that version comparisons are not * possible. * * @author Andy Wilkinson */ final class UnstructuredDependencyVersion extends AbstractDependencyVersion implements DependencyVersion { private final String version; private UnstructuredDependencyVersion(String version) { super(new ComparableVersion(version)); this.version = version; } @Override public boolean isSameMajor(DependencyVersion other) { return true; } @Override public boolean isSameMinor(DependencyVersion other) { return true; } @Override public String toString() { return this.version; } @Override public boolean isSnapshotFor(DependencyVersion candidate) { return false; } static UnstructuredDependencyVersion parse(String version) { return new UnstructuredDependencyVersion(version); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForConflicts.java ================================================ /* * 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.classpath; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.function.Predicate; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.stream.Collectors; import java.util.stream.Stream; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.Task; import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.TaskAction; /** * A {@link Task} for checking the classpath for conflicting classes and resources. * * @author Andy Wilkinson */ public abstract class CheckClasspathForConflicts extends DefaultTask { private final List> ignores = new ArrayList<>(); private FileCollection classpath; public void setClasspath(FileCollection classpath) { this.classpath = classpath; } @Classpath public FileCollection getClasspath() { return this.classpath; } @TaskAction public void checkForConflicts() throws IOException { ClasspathContents classpathContents = new ClasspathContents(); for (File file : this.classpath) { if (file.isDirectory()) { Path root = file.toPath(); try (Stream pathStream = Files.walk(root)) { pathStream.filter(Files::isRegularFile) .forEach((entry) -> classpathContents.add(root.relativize(entry).toString(), root.toString())); } } else { try (JarFile jar = new JarFile(file)) { for (JarEntry entry : Collections.list(jar.entries())) { if (!entry.isDirectory()) { classpathContents.add(entry.getName(), file.getAbsolutePath()); } } } } } Map> conflicts = classpathContents.getConflicts(this.ignores); if (!conflicts.isEmpty()) { StringBuilder message = new StringBuilder(String.format("Found classpath conflicts:%n")); conflicts.forEach((entry, locations) -> { message.append(String.format(" %s%n", entry)); locations.forEach((location) -> message.append(String.format(" %s%n", location))); }); throw new GradleException(message.toString()); } } public void ignore(Predicate predicate) { this.ignores.add(predicate); } private static final class ClasspathContents { private static final Set IGNORED_NAMES = new HashSet<>(Arrays.asList("about.html", "changelog.txt", "LICENSE", "license.txt", "module-info.class", "notice.txt", "readme.txt")); private final Map> classpathContents = new HashMap<>(); private void add(String name, String source) { this.classpathContents.computeIfAbsent(name, (key) -> new ArrayList<>()).add(source); } private Map> getConflicts(List> ignores) { return this.classpathContents.entrySet() .stream() .filter((entry) -> entry.getValue().size() > 1) .filter((entry) -> canConflict(entry.getKey(), ignores)) .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (v1, v2) -> v1, TreeMap::new)); } private boolean canConflict(String name, List> ignores) { if (name.startsWith("META-INF/")) { return false; } for (String ignoredName : IGNORED_NAMES) { if (name.equals(ignoredName)) { return false; } } for (Predicate ignore : ignores) { if (ignore.test(name)) { return false; } } return true; } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForProhibitedDependencies.java ================================================ /* * 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.classpath; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.file.FileCollection; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.TaskAction; /** * A {@link Task} for checking the classpath for prohibited dependencies. * * @author Andy Wilkinson */ public abstract class CheckClasspathForProhibitedDependencies extends DefaultTask { private static final Set PROHIBITED_GROUPS = Set.of("org.codehaus.groovy", "org.eclipse.jetty.toolchain", "org.apache.geronimo.specs", "com.sun.activation"); private static final Set PERMITTED_JAVAX_GROUPS = Set.of("javax.batch", "javax.cache", "javax.money"); private Configuration classpath; public CheckClasspathForProhibitedDependencies() { getOutputs().upToDateWhen((task) -> true); } @Input public abstract SetProperty getPermittedGroups(); public void setClasspath(Configuration classpath) { this.classpath = classpath; } @Classpath public FileCollection getClasspath() { return this.classpath; } @TaskAction public void checkForProhibitedDependencies() { TreeSet prohibited = this.classpath.getResolvedConfiguration() .getResolvedArtifacts() .stream() .map((artifact) -> artifact.getModuleVersion().getId()) .filter(this::prohibited) .map((id) -> id.getGroup() + ":" + id.getName()) .collect(Collectors.toCollection(TreeSet::new)); if (!prohibited.isEmpty()) { StringBuilder message = new StringBuilder(String.format("Found prohibited dependencies:%n")); for (String dependency : prohibited) { message.append(String.format(" %s%n", dependency)); } throw new GradleException(message.toString()); } } private boolean prohibited(ModuleVersionIdentifier id) { return (!getPermittedGroups().get().contains(id.getGroup())) && (PROHIBITED_GROUPS.contains(id.getGroup()) || prohibitedJavax(id) || prohibitedSlf4j(id) || prohibitedJbossSpec(id)); } private boolean prohibitedSlf4j(ModuleVersionIdentifier id) { return id.getGroup().equals("org.slf4j") && id.getName().equals("jcl-over-slf4j"); } private boolean prohibitedJbossSpec(ModuleVersionIdentifier id) { return id.getGroup().startsWith("org.jboss.spec"); } private boolean prohibitedJavax(ModuleVersionIdentifier id) { return id.getGroup().startsWith("javax.") && !PERMITTED_JAVAX_GROUPS.contains(id.getGroup()); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnconstrainedDirectDependencies.java ================================================ /* * 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.classpath; import java.util.Set; import java.util.stream.Collectors; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.component.ModuleComponentSelector; import org.gradle.api.artifacts.result.DependencyResult; import org.gradle.api.artifacts.result.ResolutionResult; import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.TaskAction; /** * Tasks to check that none of classpath's direct dependencies are unconstrained. * * @author Andy Wilkinson */ public abstract class CheckClasspathForUnconstrainedDirectDependencies extends DefaultTask { private Configuration classpath; public CheckClasspathForUnconstrainedDirectDependencies() { getOutputs().upToDateWhen((task) -> true); } @Classpath public FileCollection getClasspath() { return this.classpath; } public void setClasspath(Configuration classpath) { this.classpath = classpath; } @TaskAction void checkForUnconstrainedDirectDependencies() { ResolutionResult resolutionResult = this.classpath.getIncoming().getResolutionResult(); Set dependencies = resolutionResult.getRoot().getDependencies(); Set unconstrainedDependencies = dependencies.stream() .map(DependencyResult::getRequested) .filter(ModuleComponentSelector.class::isInstance) .map(ModuleComponentSelector.class::cast) .map((selector) -> selector.getGroup() + ":" + selector.getModule()) .collect(Collectors.toSet()); Set constraints = resolutionResult.getAllDependencies() .stream() .filter(DependencyResult::isConstraint) .map(DependencyResult::getRequested) .filter(ModuleComponentSelector.class::isInstance) .map(ModuleComponentSelector.class::cast) .map((selector) -> selector.getGroup() + ":" + selector.getModule()) .collect(Collectors.toSet()); unconstrainedDependencies.removeAll(constraints); if (!unconstrainedDependencies.isEmpty()) { throw new GradleException("Found unconstrained direct dependencies: " + unconstrainedDependencies); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnnecessaryExclusions.java ================================================ /* * 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.classpath; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.stream.Collectors; import javax.inject.Inject; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ExcludeRule; import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.artifacts.result.ResolvedArtifactResult; import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.TaskAction; /** * A {@link Task} for checking the classpath for unnecessary exclusions. * * @author Andy Wilkinson */ public abstract class CheckClasspathForUnnecessaryExclusions extends DefaultTask { private static final Map SPRING_BOOT_DEPENDENCIES_PROJECT = Collections.singletonMap("path", ":platform:spring-boot-dependencies"); private final Map> exclusionsByDependencyId = new TreeMap<>(); private final Map dependencyById = new HashMap<>(); private final Dependency platform; private final DependencyHandler dependencies; private final ConfigurationContainer configurations; private Configuration classpath; @Inject public CheckClasspathForUnnecessaryExclusions(DependencyHandler dependencyHandler, ConfigurationContainer configurations) { this.dependencies = getProject().getDependencies(); this.configurations = getProject().getConfigurations(); this.platform = this.dependencies .create(this.dependencies.platform(this.dependencies.project(SPRING_BOOT_DEPENDENCIES_PROJECT))); getOutputs().upToDateWhen((task) -> true); } public void setClasspath(Configuration classpath) { this.classpath = classpath; this.exclusionsByDependencyId.clear(); this.dependencyById.clear(); classpath.getAllDependencies().all(this::processDependency); } @Classpath public FileCollection getClasspath() { return this.classpath; } private void processDependency(Dependency dependency) { if (dependency instanceof ModuleDependency moduleDependency) { processDependency(moduleDependency); } } private void processDependency(ModuleDependency dependency) { String dependencyId = getId(dependency); TreeSet exclusions = dependency.getExcludeRules() .stream() .map(this::getId) .collect(Collectors.toCollection(TreeSet::new)); this.exclusionsByDependencyId.put(dependencyId, exclusions); if (!exclusions.isEmpty()) { this.dependencyById.put(dependencyId, this.dependencies.create(dependencyId)); } } @Input Map> getExclusionsByDependencyId() { return this.exclusionsByDependencyId; } @TaskAction public void checkForUnnecessaryExclusions() { Map> unnecessaryExclusions = new HashMap<>(); this.exclusionsByDependencyId.forEach((dependencyId, exclusions) -> { if (!exclusions.isEmpty()) { Dependency toCheck = this.dependencyById.get(dependencyId); this.configurations.detachedConfiguration(toCheck, this.platform) .getIncoming() .getArtifacts() .getArtifacts() .stream() .map(this::getId) .forEach(exclusions::remove); removeProfileExclusions(dependencyId, exclusions); if (!exclusions.isEmpty()) { unnecessaryExclusions.put(dependencyId, exclusions); } } }); if (!unnecessaryExclusions.isEmpty()) { throw new GradleException(getExceptionMessage(unnecessaryExclusions)); } } private void removeProfileExclusions(String dependencyId, Set exclusions) { if ("org.xmlunit:xmlunit-core".equals(dependencyId)) { exclusions.remove("javax.xml.bind:jaxb-api"); } } private String getExceptionMessage(Map> unnecessaryExclusions) { StringBuilder message = new StringBuilder("Unnecessary exclusions detected:"); for (Entry> entry : unnecessaryExclusions.entrySet()) { message.append(String.format("%n %s", entry.getKey())); for (String exclusion : entry.getValue()) { message.append(String.format("%n %s", exclusion)); } } return message.toString(); } private String getId(ResolvedArtifactResult artifact) { return getId((ModuleComponentIdentifier) artifact.getId().getComponentIdentifier()); } private String getId(ModuleDependency dependency) { return dependency.getGroup() + ":" + dependency.getName(); } private String getId(ExcludeRule rule) { return rule.getGroup() + ":" + rule.getModule(); } private String getId(ModuleComponentIdentifier identifier) { return identifier.getGroup() + ":" + identifier.getModule(); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/cli/HomebrewFormula.java ================================================ /* * 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.cli; import java.io.File; import java.security.MessageDigest; import javax.inject.Inject; import org.apache.commons.codec.digest.DigestUtils; import org.gradle.api.DefaultTask; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileSystemOperations; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.build.artifacts.ArtifactRelease; import org.springframework.boot.build.properties.BuildProperties; import org.springframework.boot.build.properties.BuildType; /** * A {@link Task} for creating a Homebrew formula manifest. * * @author Andy Wilkinson */ public abstract class HomebrewFormula extends DefaultTask { private static final Logger logger = LoggerFactory.getLogger(HomebrewFormula.class); private final FileSystemOperations fileSystemOperations; private final BuildType buildType; @Inject public HomebrewFormula(FileSystemOperations fileSystemOperations) { this.fileSystemOperations = fileSystemOperations; Project project = getProject(); MapProperty properties = getProperties(); properties.put("hash", getArchive().map((archive) -> sha256(archive.getAsFile()))); getProperties().put("repo", ArtifactRelease.forProject(project).getDownloadRepo()); getProperties().put("version", project.getVersion().toString()); this.buildType = BuildProperties.get(getProject()).buildType(); } private String sha256(File file) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); return new DigestUtils(digest).digestAsHex(file); } catch (Exception ex) { throw new TaskExecutionException(this, ex); } } @InputFile @PathSensitive(PathSensitivity.RELATIVE) public abstract RegularFileProperty getArchive(); @InputFile @PathSensitive(PathSensitivity.RELATIVE) public abstract RegularFileProperty getTemplate(); @OutputDirectory public abstract DirectoryProperty getOutputDir(); @Input abstract MapProperty getProperties(); @TaskAction void createFormula() { if (this.buildType != BuildType.OPEN_SOURCE) { logger.debug("Skipping Homebrew formula for non open source build type"); return; } this.fileSystemOperations.copy((copy) -> { copy.from(getTemplate()); copy.into(getOutputDir()); copy.expand(getProperties().get()); }); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/Asciidoc.java ================================================ /* * 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.context.properties; /** * Simple builder to help construct Asciidoc markup. * * @author Phillip Webb */ class Asciidoc { private final StringBuilder content; Asciidoc() { this.content = new StringBuilder(); } Asciidoc appendWithHardLineBreaks(Object... items) { for (Object item : items) { appendln("`+", item, "+` +"); } return this; } Asciidoc appendln(Object... items) { return append(items).newLine(); } Asciidoc append(Object... items) { for (Object item : items) { this.content.append(item); } return this; } Asciidoc newLine() { return append(System.lineSeparator()); } @Override public String toString() { return this.content.toString(); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAdditionalSpringConfigurationMetadata.java ================================================ /* * 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.context.properties; import java.io.File; import java.io.IOException; import org.gradle.api.file.FileTree; import org.gradle.api.file.RegularFileProperty; 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.SourceTask; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.VerificationException; import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Report; /** * {@link SourceTask} that checks additional Spring configuration metadata files. * * @author Andy Wilkinson */ public abstract class CheckAdditionalSpringConfigurationMetadata extends SourceTask { private final File projectDir; public CheckAdditionalSpringConfigurationMetadata() { this.projectDir = getProject().getProjectDir(); } @OutputFile public abstract RegularFileProperty getReportLocation(); @Override @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileTree getSource() { return super.getSource(); } @TaskAction void check() throws IOException { ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(getSource().getFiles()); Report report = new Report(this.projectDir); analyzer.analyzeOrder(report); analyzer.analyzeDuplicates(report); analyzer.analyzeDeprecationSince(report); File reportFile = getReportLocation().get().getAsFile(); report.write(reportFile); if (report.hasProblems()) { throw new VerificationException( "Problems found in additional Spring configuration metadata. See " + reportFile + " for details."); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAggregatedSpringConfigurationMetadata.java ================================================ /* * Copyright 2025 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.context.properties; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.stream.Collectors; 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.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.TaskAction; import org.gradle.api.tasks.VerificationException; /** * {@link Task} that checks aggregated Spring configuration metadata. * * @author Andy Wilkinson */ public abstract class CheckAggregatedSpringConfigurationMetadata extends DefaultTask { private FileCollection configurationPropertyMetadata; @OutputFile public abstract RegularFileProperty getReportLocation(); @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getConfigurationPropertyMetadata() { return this.configurationPropertyMetadata; } public void setConfigurationPropertyMetadata(FileCollection configurationPropertyMetadata) { this.configurationPropertyMetadata = configurationPropertyMetadata; } @TaskAction void check() throws IOException { Report report = createReport(); File reportFile = getReportLocation().get().getAsFile(); Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); if (report.hasProblems()) { throw new VerificationException( "Problems found in aggregated Spring configuration metadata. See " + reportFile + " for details."); } } private Report createReport() { ConfigurationProperties configurationProperties = ConfigurationProperties .fromFiles(this.configurationPropertyMetadata); Set propertyNames = configurationProperties.stream() .map(ConfigurationProperty::getName) .collect(Collectors.toSet()); List missingReplacement = configurationProperties.stream() .filter(ConfigurationProperty::isDeprecated) .filter((deprecated) -> { String replacement = deprecated.getDeprecation().replacement(); return replacement != null && !propertyNames.contains(replacement); }) .toList(); return new Report(missingReplacement); } private static final class Report implements Iterable { private final List propertiesWithMissingReplacement; private Report(List propertiesWithMissingReplacement) { this.propertiesWithMissingReplacement = propertiesWithMissingReplacement; } private boolean hasProblems() { return !this.propertiesWithMissingReplacement.isEmpty(); } @Override public Iterator iterator() { List lines = new ArrayList<>(); if (this.propertiesWithMissingReplacement.isEmpty()) { lines.add("No problems found."); } else { lines.add("The following properties have a replacement that does not exist:"); lines.add(""); lines.addAll(this.propertiesWithMissingReplacement.stream() .map((property) -> "\t" + property.getName() + " (replacement " + property.getDeprecation().replacement() + ")") .toList()); } lines.add(""); return lines.iterator(); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckManualSpringConfigurationMetadata.java ================================================ /* * 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.context.properties; import java.io.File; import java.io.IOException; import java.util.List; import org.gradle.api.DefaultTask; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SourceTask; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.VerificationException; import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Report; /** * {@link SourceTask} that checks manual Spring configuration metadata files. * * @author Andy Wilkinson * @author Stephane Nicoll */ public abstract class CheckManualSpringConfigurationMetadata extends DefaultTask { private final File projectDir; public CheckManualSpringConfigurationMetadata() { this.projectDir = getProject().getProjectDir(); } @OutputFile public abstract RegularFileProperty getReportLocation(); @InputFile @PathSensitive(PathSensitivity.RELATIVE) public abstract Property getMetadataLocation(); @Input public abstract ListProperty getExclusions(); @TaskAction void check() throws IOException { ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer( List.of(getMetadataLocation().get())); Report report = new Report(this.projectDir); analyzer.analyzeOrder(report); analyzer.analyzeDuplicates(report); analyzer.analyzePropertyDescription(report, getExclusions().get()); analyzer.analyzeDeprecationSince(report); File reportFile = getReportLocation().get().getAsFile(); report.write(reportFile); if (report.hasProblems()) { throw new VerificationException( "Problems found in manual Spring configuration metadata. See " + reportFile + " for details."); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckSpringConfigurationMetadata.java ================================================ /* * 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.context.properties; import java.io.File; import java.io.IOException; import java.util.List; import org.gradle.api.DefaultTask; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SourceTask; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.VerificationException; import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Report; /** * {@link SourceTask} that checks {@code spring-configuration-metadata.json} files. * * @author Andy Wilkinson */ public abstract class CheckSpringConfigurationMetadata extends DefaultTask { private final File projectRoot; public CheckSpringConfigurationMetadata() { this.projectRoot = getProject().getProjectDir(); } @OutputFile public abstract RegularFileProperty getReportLocation(); @InputFile @PathSensitive(PathSensitivity.RELATIVE) public abstract RegularFileProperty getMetadataLocation(); @Input public abstract ListProperty getExclusions(); @TaskAction void check() throws IOException { Report report = new Report(this.projectRoot); ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer( List.of(getMetadataLocation().get().getAsFile())); analyzer.analyzePropertyDescription(report, getExclusions().get()); File reportFile = getReportLocation().get().getAsFile(); report.write(reportFile); if (report.hasProblems()) { throw new VerificationException( "Problems found in Spring configuration metadata. See " + reportFile + " for details."); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/CompoundRow.java ================================================ /* * 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.context.properties; import java.util.Set; import java.util.TreeSet; /** * Table row regrouping a list of configuration properties sharing the same description. * * @author Brian Clozel * @author Phillip Webb * @author Moritz Halbritter */ class CompoundRow extends Row { private final Set propertyNames; private final String description; CompoundRow(Snippet snippet, String prefix, String description) { super(snippet, prefix); this.description = description; this.propertyNames = new TreeSet<>(); } void addProperty(ConfigurationProperty property) { this.propertyNames.add(property.getDisplayName()); } boolean isEmpty() { return this.propertyNames.isEmpty(); } @Override void write(Asciidoc asciidoc) { asciidoc.append("|"); asciidoc.append("[[" + getAnchor() + "]]"); asciidoc.append("xref:#" + getAnchor() + "["); this.propertyNames.forEach(asciidoc::appendWithHardLineBreaks); asciidoc.appendln("]"); asciidoc.appendln("|+++", this.description, "+++"); asciidoc.appendln("|"); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationMetadataPlugin.java ================================================ /* * 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.context.properties; import java.io.File; 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.provider.Provider; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.gradle.language.base.plugins.LifecycleBasePlugin; import org.gradle.language.jvm.tasks.ProcessResources; /** * {@link Plugin} for projects that only define manual configuration metadata. * When applied, the plugin registers a {@link CheckManualSpringConfigurationMetadata} * task and configures the {@code check} task to depend upon it. * * @author Andy Wilkinson * @author Stephane Nicoll */ public class ConfigurationMetadataPlugin implements Plugin { private static final String CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME = "configurationPropertiesMetadata"; /** * Name of the {@link CheckAdditionalSpringConfigurationMetadata} task. */ public static final String CHECK_MANUAL_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkManualSpringConfigurationMetadata"; @Override public void apply(Project project) { project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> registerCheckAdditionalMetadataTask(project)); } private void registerCheckAdditionalMetadataTask(Project project) { TaskProvider checkConfigurationMetadata = project.getTasks() .register(CHECK_MANUAL_SPRING_CONFIGURATION_METADATA_TASK_NAME, CheckManualSpringConfigurationMetadata.class); SourceSet mainSourceSet = project.getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets() .getByName(SourceSet.MAIN_SOURCE_SET_NAME); Provider manualMetadataLocation = project.getTasks() .named(mainSourceSet.getProcessResourcesTaskName(), ProcessResources.class) .map((processResources) -> new File(processResources.getDestinationDir(), "META-INF/spring-configuration-metadata.json")); checkConfigurationMetadata.configure((check) -> { check.getMetadataLocation().set(manualMetadataLocation); check.getReportLocation() .set(project.getLayout() .getBuildDirectory() .file("reports/manual-spring-configuration-metadata/check.txt")); }); addMetadataArtifact(project, manualMetadataLocation); project.getTasks() .named(LifecycleBasePlugin.CHECK_TASK_NAME) .configure((check) -> check.dependsOn(checkConfigurationMetadata)); } private void addMetadataArtifact(Project project, Provider metadataLocation) { project.getConfigurations() .consumable(CONFIGURATION_PROPERTIES_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, "configuration-properties-metadata")); }); }); project.getArtifacts().add(CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME, metadataLocation); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperties.java ================================================ /* * 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.context.properties; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Stream; import tools.jackson.databind.json.JsonMapper; /** * Configuration properties read from one or more * {@code META-INF/spring-configuration-metadata.json} files. * * @author Andy Wilkinson * @author Phillip Webb */ final class ConfigurationProperties { private final Map byName; private ConfigurationProperties(List properties) { Map byName = new LinkedHashMap<>(); for (ConfigurationProperty property : properties) { byName.put(property.getName(), property); } this.byName = Collections.unmodifiableMap(byName); } ConfigurationProperty get(String propertyName) { return this.byName.get(propertyName); } Stream stream() { return this.byName.values().stream(); } @SuppressWarnings("unchecked") static ConfigurationProperties fromFiles(Iterable files) { JsonMapper jsonMapper = new JsonMapper(); List properties = new ArrayList<>(); for (File file : files) { Map json = jsonMapper.readValue(file, Map.class); for (Map property : (List>) json.get("properties")) { properties.add(ConfigurationProperty.fromJsonProperties(property)); } } return new ConfigurationProperties(properties); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesAnalyzer.java ================================================ /* * 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.context.properties; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; import tools.jackson.core.StreamReadFeature; import tools.jackson.databind.json.JsonMapper; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.function.SingletonSupplier; /** * Check configuration metadata for inconsistencies. The available checks are: *
    *
  • Metadata elements {@link #analyzeOrder(Report) must be sorted alphabetically}
  • *
  • Metadata elements {@link #analyzeDuplicates(Report) must not be duplicates}
  • *
  • Properties {@link #analyzePropertyDescription(Report, List) must have a * description}
  • *
* * @author Stephane Nicoll */ class ConfigurationPropertiesAnalyzer { private static final List ELEMENT_TYPES = List.of("groups", "properties", "hints"); private final Collection sources; private final SingletonSupplier jsonMapperSupplier; ConfigurationPropertiesAnalyzer(Collection sources) { if (sources.isEmpty()) { throw new IllegalArgumentException("At least one source should be provided"); } this.sources = sources; this.jsonMapperSupplier = SingletonSupplier .of(() -> JsonMapper.builder().enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION).build()); } void analyzeOrder(Report report) { for (File source : this.sources) { report.registerAnalysis(source, analyzeOrder(source)); } } private Analysis analyzeOrder(File source) { Map json = readJsonContent(source); Analysis analysis = new Analysis("Metadata element order:"); for (String elementType : ELEMENT_TYPES) { analyzeMetadataElementOrder(elementType, json, analysis); } return analysis; } @SuppressWarnings("unchecked") private void analyzeMetadataElementOrder(String key, Map json, Analysis analysis) { List> groups = (List>) json.getOrDefault(key, Collections.emptyList()); List names = groups.stream().map((group) -> (String) group.get("name")).toList(); List sortedNames = names.stream().sorted().toList(); for (int i = 0; i < names.size(); i++) { String actual = names.get(i); String expected = sortedNames.get(i); if (!actual.equals(expected)) { analysis.addItem("Wrong order at $." + key + "[" + i + "].name - expected '" + expected + "' but found '" + actual + "'"); } } } void analyzeDuplicates(Report report) { for (File source : this.sources) { report.registerAnalysis(source, analyzeDuplicates(source)); } } private Analysis analyzeDuplicates(File source) { Map json = readJsonContent(source); Analysis analysis = new Analysis("Metadata element duplicates:"); for (String elementType : ELEMENT_TYPES) { analyzeMetadataElementDuplicates(elementType, json, analysis); } return analysis; } @SuppressWarnings("unchecked") private void analyzeMetadataElementDuplicates(String key, Map json, Analysis analysis) { List> elements = (List>) json.getOrDefault(key, Collections.emptyList()); List names = elements.stream().map((group) -> (String) group.get("name")).toList(); Set uniqueNames = new HashSet<>(); for (int i = 0; i < names.size(); i++) { String name = names.get(i); if (!uniqueNames.add(name)) { analysis.addItem("Duplicate name '" + name + "' at $." + key + "[" + i + "]"); } } } void analyzePropertyDescription(Report report, List exclusions) { for (File source : this.sources) { report.registerAnalysis(source, analyzePropertyDescription(source, exclusions)); } } @SuppressWarnings("unchecked") private Analysis analyzePropertyDescription(File source, List exclusions) { Map json = readJsonContent(source); Analysis analysis = new Analysis("The following properties have no description:"); List> properties = (List>) json.get("properties"); for (Map property : properties) { String name = (String) property.get("name"); if (!isDeprecated(property) && !isDescribed(property) && !isExcluded(exclusions, name)) { analysis.addItem(name); } } return analysis; } private boolean isExcluded(List exclusions, String propertyName) { for (String exclusion : exclusions) { if (propertyName.equals(exclusion)) { return true; } if (exclusion.endsWith(".*")) { if (propertyName.startsWith(exclusion.substring(0, exclusion.length() - 2))) { return true; } } } return false; } private boolean isDeprecated(Map property) { return property.get("deprecation") != null; } private boolean isDescribed(Map property) { return property.get("description") != null; } void analyzeDeprecationSince(Report report) throws IOException { for (File source : this.sources) { report.registerAnalysis(source, analyzeDeprecationSince(source)); } } @SuppressWarnings("unchecked") private Analysis analyzeDeprecationSince(File source) throws IOException { Analysis analysis = new Analysis("The following properties are deprecated without a 'since' version:"); Map json = readJsonContent(source); List> properties = (List>) json.get("properties"); properties.stream().filter((property) -> property.containsKey("deprecation")).forEach((property) -> { Map deprecation = (Map) property.get("deprecation"); if (!deprecation.containsKey("since")) { analysis.addItem(property.get("name").toString()); } }); return analysis; } @SuppressWarnings("unchecked") private Map readJsonContent(File source) { return this.jsonMapperSupplier.obtain().readValue(source, Map.class); } private static void writeAll(PrintWriter writer, Iterable elements, Consumer itemWriter) { Iterator it = elements.iterator(); while (it.hasNext()) { itemWriter.accept(it.next()); if (it.hasNext()) { writer.println(); } } } static class Report { private final File baseDirectory; private final MultiValueMap analyses = new LinkedMultiValueMap<>(); Report(File baseDirectory) { this.baseDirectory = baseDirectory; } void registerAnalysis(File path, Analysis analysis) { this.analyses.add(path, analysis); } boolean hasProblems() { return this.analyses.values() .stream() .anyMatch((candidates) -> candidates.stream().anyMatch(Analysis::hasProblems)); } List getAnalyses(File source) { return this.analyses.getOrDefault(source, Collections.emptyList()); } /** * Write this report to the given {@code file}. * @param file the file to write the report to */ void write(File file) throws IOException { Files.writeString(file.toPath(), createContent(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } private String createContent() { if (this.analyses.isEmpty()) { return "No problems found."; } StringWriter out = new StringWriter(); try (PrintWriter writer = new PrintWriter(out)) { writeAll(writer, this.analyses.entrySet(), (entry) -> { writer.println(this.baseDirectory.toPath().relativize(entry.getKey().toPath())); boolean hasProblems = entry.getValue().stream().anyMatch(Analysis::hasProblems); if (hasProblems) { writeAll(writer, entry.getValue(), (analysis) -> analysis.createDetails(writer)); } else { writer.println("No problems found."); } }); } return out.toString(); } } static class Analysis { private final String header; private final List items; Analysis(String header) { this.header = header; this.items = new ArrayList<>(); } void addItem(String item) { this.items.add(item); } boolean hasProblems() { return !this.items.isEmpty(); } List getItems() { return this.items; } void createDetails(PrintWriter writer) { writer.println(this.header); if (this.items.isEmpty()) { writer.println("No problems found."); } else { for (String item : this.items) { writer.println("\t- " + item); } } } @Override public String toString() { StringWriter out = new StringWriter(); PrintWriter writer = new PrintWriter(out); createDetails(writer); return out.toString(); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesPlugin.java ================================================ /* * 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.context.properties; import java.util.Map; import java.util.stream.Collectors; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.attributes.Category; import org.gradle.api.attributes.Usage; import org.gradle.api.file.RegularFile; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.language.base.plugins.LifecycleBasePlugin; import org.springframework.util.StringUtils; /** * {@link Plugin} for projects that define {@code @ConfigurationProperties}. When applied, * the plugin reacts to the presence of the {@link JavaPlugin} by: * *
    *
  • Adding a dependency on the configuration properties annotation processor. *
  • Disables incremental compilation to avoid property descriptions being lost. *
  • Configuring the additional metadata locations annotation processor compiler * argument. *
  • Adding the outputs of the processResources task as inputs of the compileJava task * to ensure that the additional metadata is available when the annotation processor runs. *
  • Registering a {@link CheckAdditionalSpringConfigurationMetadata} task and * configuring the {@code check} task to depend upon it. *
  • Defining an artifact for the resulting configuration property metadata so that it * can be consumed by downstream projects. *
* * @author Andy Wilkinson */ public class ConfigurationPropertiesPlugin implements Plugin { private static final String CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME = "configurationPropertiesMetadata"; /** * Name of the {@link CheckAdditionalSpringConfigurationMetadata} task. */ public static final String CHECK_ADDITIONAL_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkAdditionalSpringConfigurationMetadata"; /** * Name of the {@link CheckSpringConfigurationMetadata} task. */ public static final String CHECK_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkSpringConfigurationMetadata"; @Override public void apply(Project project) { project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { configureConfigurationPropertiesAnnotationProcessor(project); disableIncrementalCompilation(project); configureAdditionalMetadataLocationsCompilerArgument(project); registerCheckAdditionalMetadataTask(project); registerCheckMetadataTask(project); addMetadataArtifact(project); }); } private void configureConfigurationPropertiesAnnotationProcessor(Project project) { Configuration annotationProcessors = project.getConfigurations() .getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME); annotationProcessors.getDependencies() .add(project.getDependencies() .project(Map.of("path", ":configuration-metadata:spring-boot-configuration-processor"))); } private void disableIncrementalCompilation(Project project) { SourceSet mainSourceSet = project.getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets() .getByName(SourceSet.MAIN_SOURCE_SET_NAME); project.getTasks() .named(mainSourceSet.getCompileJavaTaskName(), JavaCompile.class) .configure((compileJava) -> compileJava.getOptions().setIncremental(false)); } private void addMetadataArtifact(Project project) { SourceSet mainSourceSet = project.getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets() .getByName(SourceSet.MAIN_SOURCE_SET_NAME); project.getConfigurations() .consumable(CONFIGURATION_PROPERTIES_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, "configuration-properties-metadata")); }); }); project.afterEvaluate((evaluatedProject) -> evaluatedProject.getArtifacts() .add(CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME, mainSourceSet.getJava() .getDestinationDirectory() .dir("META-INF/spring-configuration-metadata.json"), (artifact) -> artifact .builtBy(evaluatedProject.getTasks().getByName(mainSourceSet.getClassesTaskName())))); } private void configureAdditionalMetadataLocationsCompilerArgument(Project project) { JavaCompile compileJava = project.getTasks() .withType(JavaCompile.class) .getByName(JavaPlugin.COMPILE_JAVA_TASK_NAME); compileJava.getInputs() .files(project.getTasks().getByName(JavaPlugin.PROCESS_RESOURCES_TASK_NAME)) .withPathSensitivity(PathSensitivity.RELATIVE) .withPropertyName("processed resources"); SourceSet mainSourceSet = project.getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets() .getByName(SourceSet.MAIN_SOURCE_SET_NAME); compileJava.getOptions() .getCompilerArgs() .add("-Aorg.springframework.boot.configurationprocessor.additionalMetadataLocations=" + StringUtils.collectionToCommaDelimitedString(mainSourceSet.getResources() .getSourceDirectories() .getFiles() .stream() .map(project.getRootProject()::relativePath) .collect(Collectors.toSet()))); } private void registerCheckAdditionalMetadataTask(Project project) { TaskProvider checkConfigurationMetadata = project.getTasks() .register(CHECK_ADDITIONAL_SPRING_CONFIGURATION_METADATA_TASK_NAME, CheckAdditionalSpringConfigurationMetadata.class); checkConfigurationMetadata.configure((check) -> { SourceSet mainSourceSet = project.getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets() .getByName(SourceSet.MAIN_SOURCE_SET_NAME); check.setSource(mainSourceSet.getResources()); check.include("META-INF/additional-spring-configuration-metadata.json"); check.getReportLocation() .set(project.getLayout() .getBuildDirectory() .file("reports/additional-spring-configuration-metadata/check.txt")); }); project.getTasks() .named(LifecycleBasePlugin.CHECK_TASK_NAME) .configure((check) -> check.dependsOn(checkConfigurationMetadata)); } private void registerCheckMetadataTask(Project project) { TaskProvider checkConfigurationMetadata = project.getTasks() .register(CHECK_SPRING_CONFIGURATION_METADATA_TASK_NAME, CheckSpringConfigurationMetadata.class); checkConfigurationMetadata.configure((check) -> { SourceSet mainSourceSet = project.getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets() .getByName(SourceSet.MAIN_SOURCE_SET_NAME); Provider metadataLocation = project.getTasks() .named(mainSourceSet.getCompileJavaTaskName(), JavaCompile.class) .flatMap((javaCompile) -> javaCompile.getDestinationDirectory() .file("META-INF/spring-configuration-metadata.json")); check.getMetadataLocation().set(metadataLocation); check.getReportLocation() .set(project.getLayout().getBuildDirectory().file("reports/spring-configuration-metadata/check.txt")); }); project.getTasks() .named(LifecycleBasePlugin.CHECK_TASK_NAME) .configure((check) -> check.dependsOn(checkConfigurationMetadata)); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperty.java ================================================ /* * 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.context.properties; import java.util.Map; /** * A configuration property. * * @author Andy Wilkinson * @author Phillip Webb */ class ConfigurationProperty { private final String name; private final String type; private final Object defaultValue; private final String description; private final boolean deprecated; private final Deprecation deprecation; ConfigurationProperty(String name, String type) { this(name, type, null, null, false, null); } ConfigurationProperty(String name, String type, Object defaultValue, String description, boolean deprecated, Deprecation deprecation) { this.name = name; this.type = type; this.defaultValue = defaultValue; this.description = description; this.deprecated = deprecated; this.deprecation = deprecation; } String getName() { return this.name; } String getDisplayName() { return (getType() != null && getType().startsWith("java.util.Map")) ? getName() + ".*" : getName(); } String getType() { return this.type; } Object getDefaultValue() { return this.defaultValue; } String getDescription() { return this.description; } boolean isDeprecated() { return this.deprecated; } Deprecation getDeprecation() { return this.deprecation; } @Override public String toString() { return "ConfigurationProperty [name=" + this.name + ", type=" + this.type + "]"; } @SuppressWarnings("unchecked") static ConfigurationProperty fromJsonProperties(Map property) { String name = (String) property.get("name"); String type = (String) property.get("type"); Object defaultValue = property.get("defaultValue"); String description = (String) property.get("description"); boolean deprecated = property.containsKey("deprecated"); Map deprecation = (Map) property.get("deprecation"); return new ConfigurationProperty(name, type, defaultValue, description, deprecated, Deprecation.fromJsonProperties(deprecation)); } record Deprecation(String reason, String replacement, String since, String level) { static Deprecation fromJsonProperties(Map property) { if (property == null) { return null; } String reason = (String) property.get("reason"); String replacement = (String) property.get("replacement"); String since = (String) property.get("since"); String level = (String) property.get("level"); return new Deprecation(reason, replacement, since, level); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java ================================================ /* * 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.context.properties; import java.io.IOException; import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; 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.TaskAction; import org.springframework.boot.build.context.properties.Snippet.Config; /** * {@link Task} used to document auto-configuration classes. * * @author Andy Wilkinson * @author Phillip Webb */ public abstract class DocumentConfigurationProperties extends DefaultTask { private FileCollection configurationPropertyMetadata; @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getConfigurationPropertyMetadata() { return this.configurationPropertyMetadata; } public void setConfigurationPropertyMetadata(FileCollection configurationPropertyMetadata) { this.configurationPropertyMetadata = configurationPropertyMetadata; } @Input public abstract Property getDeprecated(); @OutputDirectory public abstract DirectoryProperty getOutputDir(); @TaskAction void documentConfigurationProperties() throws IOException { Snippets snippets = new Snippets(this.configurationPropertyMetadata, getDeprecated().getOrElse(false)); snippets.add("application-properties.core", "Core Properties", this::corePrefixes); snippets.add("application-properties.cache", "Cache Properties", this::cachePrefixes); snippets.add("application-properties.grpc", "gRPC Properties", this::grpcPrefixes); snippets.add("application-properties.mail", "Mail Properties", this::mailPrefixes); snippets.add("application-properties.json", "JSON Properties", this::jsonPrefixes); snippets.add("application-properties.data", "Data Properties", this::dataPrefixes); snippets.add("application-properties.transaction", "Transaction Properties", this::transactionPrefixes); snippets.add("application-properties.data-migration", "Data Migration Properties", this::dataMigrationPrefixes); snippets.add("application-properties.integration", "Integration Properties", this::integrationPrefixes); snippets.add("application-properties.web", "Web Properties", this::webPrefixes); snippets.add("application-properties.templating", "Templating Properties", this::templatePrefixes); snippets.add("application-properties.server", "Server Properties", this::serverPrefixes); snippets.add("application-properties.security", "Security Properties", this::securityPrefixes); snippets.add("application-properties.rsocket", "RSocket Properties", this::rsocketPrefixes); snippets.add("application-properties.actuator", "Actuator Properties", this::actuatorPrefixes); snippets.add("application-properties.devtools", "Devtools Properties", this::devtoolsPrefixes); snippets.add("application-properties.docker-compose", "Docker Compose Properties", this::dockerComposePrefixes); snippets.add("application-properties.testcontainers", "Testcontainers Properties", this::testcontainersPrefixes); snippets.add("application-properties.testing", "Testing Properties", this::testingPrefixes); snippets.writeTo(getOutputDir().getAsFile().get().toPath()); } private void corePrefixes(Config config) { config.accept("debug"); config.accept("trace"); config.accept("logging"); config.accept("spring.aop"); config.accept("spring.application"); config.accept("spring.autoconfigure"); config.accept("spring.banner"); config.accept("spring.beaninfo"); config.accept("spring.config"); config.accept("spring.info"); config.accept("spring.jmx"); config.accept("spring.lifecycle"); config.accept("spring.main"); config.accept("spring.messages"); config.accept("spring.pid"); config.accept("spring.profiles"); config.accept("spring.quartz"); config.accept("spring.reactor"); config.accept("spring.ssl"); config.accept("spring.task"); config.accept("spring.threads"); config.accept("spring.validation"); config.accept("spring.mandatory-file-encoding"); config.accept("info"); config.accept("spring.output.ansi.enabled"); } private void cachePrefixes(Config config) { config.accept("spring.cache"); } private void grpcPrefixes(Config prefix) { prefix.accept("spring.grpc"); } private void mailPrefixes(Config config) { config.accept("spring.mail"); config.accept("spring.sendgrid"); } private void jsonPrefixes(Config config) { config.accept("spring.jackson"); config.accept("spring.gson"); config.accept("spring.kotlinx.serialization.json"); } private void dataPrefixes(Config config) { config.accept("spring.couchbase"); config.accept("spring.cassandra"); config.accept("spring.elasticsearch"); config.accept("spring.h2"); config.accept("spring.influx"); config.accept("spring.ldap"); config.accept("spring.mongodb"); config.accept("spring.neo4j"); config.accept("spring.persistence"); config.accept("spring.data"); config.accept("spring.datasource"); config.accept("spring.jooq"); config.accept("spring.jdbc"); config.accept("spring.jpa"); config.accept("spring.r2dbc"); config.accept("spring.datasource.oracleucp", "Oracle UCP specific settings bound to an instance of Oracle UCP's PoolDataSource"); config.accept("spring.datasource.dbcp2", "Commons DBCP2 specific settings bound to an instance of DBCP2's BasicDataSource"); config.accept("spring.datasource.tomcat", "Tomcat datasource specific settings bound to an instance of Tomcat JDBC's DataSource"); config.accept("spring.datasource.hikari", "Hikari specific settings bound to an instance of Hikari's HikariDataSource"); } private void transactionPrefixes(Config prefix) { prefix.accept("spring.jta"); prefix.accept("spring.transaction"); } private void dataMigrationPrefixes(Config prefix) { prefix.accept("spring.flyway"); prefix.accept("spring.liquibase"); prefix.accept("spring.sql.init"); } private void integrationPrefixes(Config prefix) { prefix.accept("spring.activemq"); prefix.accept("spring.artemis"); prefix.accept("spring.batch"); prefix.accept("spring.integration"); prefix.accept("spring.jms"); prefix.accept("spring.kafka"); prefix.accept("spring.pulsar"); prefix.accept("spring.rabbitmq"); prefix.accept("spring.hazelcast"); prefix.accept("spring.webservices"); } private void webPrefixes(Config prefix) { prefix.accept("spring.graphql"); prefix.accept("spring.hateoas"); prefix.accept("spring.http"); prefix.accept("spring.jersey"); prefix.accept("spring.mvc"); prefix.accept("spring.netty"); prefix.accept("spring.resources"); prefix.accept("spring.servlet"); prefix.accept("spring.session"); prefix.accept("spring.web"); prefix.accept("spring.webflux"); } private void templatePrefixes(Config prefix) { prefix.accept("spring.freemarker"); prefix.accept("spring.groovy"); prefix.accept("spring.mustache"); prefix.accept("spring.thymeleaf"); } private void serverPrefixes(Config prefix) { prefix.accept("server"); } private void securityPrefixes(Config prefix) { prefix.accept("spring.security"); } private void rsocketPrefixes(Config prefix) { prefix.accept("spring.rsocket"); } private void actuatorPrefixes(Config prefix) { prefix.accept("management"); prefix.accept("micrometer"); } private void dockerComposePrefixes(Config prefix) { prefix.accept("spring.docker.compose"); } private void devtoolsPrefixes(Config prefix) { prefix.accept("spring.devtools"); } private void testingPrefixes(Config prefix) { prefix.accept("spring.test."); } private void testcontainersPrefixes(Config prefix) { prefix.accept("spring.testcontainers."); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/Row.java ================================================ /* * 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.context.properties; /** * Abstract class for rows in {@link Table}. * * @author Brian Clozel * @author Phillip Webb */ abstract class Row implements Comparable { private final Snippet snippet; private final String id; protected Row(Snippet snippet, String id) { this.snippet = snippet; this.id = id; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } Row other = (Row) obj; return this.id.equals(other.id); } @Override public int hashCode() { return this.id.hashCode(); } @Override public int compareTo(Row other) { return this.id.compareTo(other.id); } String getAnchor() { return this.snippet.getAnchor() + "." + this.id; } abstract void write(Asciidoc asciidoc); } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/SingleRow.java ================================================ /* * 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.context.properties; import java.util.Arrays; import java.util.stream.Collectors; import org.springframework.boot.build.context.properties.ConfigurationProperty.Deprecation; /** * Table row containing a single configuration property. * * @author Brian Clozel * @author Phillip Webb * @author Moritz Halbritter */ class SingleRow extends Row { private final Snippets snippets; private final String defaultValue; private final ConfigurationProperty property; SingleRow(Snippet snippet, ConfigurationProperty property) { this(null, snippet, property); } SingleRow(Snippets snippets, Snippet snippet, ConfigurationProperty property) { super(snippet, property.getName()); this.snippets = snippets; this.defaultValue = getDefaultValue(property.getDefaultValue()); this.property = property; } private String getDefaultValue(Object defaultValue) { if (defaultValue == null) { return null; } if (defaultValue.getClass().isArray()) { return Arrays.stream((Object[]) defaultValue) .map(Object::toString) .collect(Collectors.joining("," + System.lineSeparator())); } return defaultValue.toString(); } @Override void write(Asciidoc asciidoc) { asciidoc.append("|"); asciidoc.append("[[" + getAnchor() + "]]"); asciidoc.appendln("xref:#" + getAnchor() + "[`+", this.property.getDisplayName(), "+`]"); writeDescription(asciidoc); writeDefaultValue(asciidoc); } private void writeDescription(Asciidoc builder) { builder.append("|"); if (this.property.isDeprecated()) { Deprecation deprecation = this.property.getDeprecation(); String replacement = (deprecation != null) ? deprecation.replacement() : null; String reason = (deprecation != null) ? deprecation.reason() : null; if (replacement != null && !replacement.isEmpty()) { String xref = (this.snippets != null) ? this.snippets.findXref(deprecation.replacement()) : null; if (xref != null) { builder.append("Replaced by xref:" + xref + "[`+" + deprecation.replacement() + "+`]"); } else { builder.append("Replaced by `+" + deprecation.replacement() + "+`"); } } else if (reason != null && !reason.isEmpty()) { builder.append("+++", clean(reason), "+++"); } } else { String description = this.property.getDescription(); if (description != null && !description.isEmpty()) { builder.append("+++", clean(description), "+++"); } } builder.appendln(); } private String clean(String text) { return text.replace("|", "\\|").replace("<", "<").replace(">", ">"); } private void writeDefaultValue(Asciidoc builder) { String defaultValue = (this.defaultValue != null) ? this.defaultValue : ""; if (defaultValue.isEmpty()) { builder.appendln("|"); } else { defaultValue = defaultValue.replace("\\", "\\\\").replace("|", "\\|"); builder.appendln("|`+", defaultValue, "+`"); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippet.java ================================================ /* * 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.context.properties; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; /** * A configuration properties snippet. * * @author Brian Clozed * @author Phillip Webb */ class Snippet { private final String anchor; private final String title; private final Set prefixes; private final Map overrides; Snippet(String anchor, String title, Consumer config) { Set prefixes = new LinkedHashSet<>(); Map overrides = new LinkedHashMap<>(); if (config != null) { config.accept(new Config() { @Override public void accept(String prefix) { prefixes.add(prefix); } @Override public void accept(String prefix, String description) { overrides.put(prefix, description); } }); } this.anchor = anchor; this.title = title; this.prefixes = prefixes; this.overrides = overrides; } String getAnchor() { return this.anchor; } String getTitle() { return this.title; } Set getPrefixes() { return this.prefixes; } Map getOverrides() { return this.overrides; } void forEachPrefix(Consumer action) { this.prefixes.forEach(action); } void forEachOverride(BiConsumer action) { this.overrides.forEach(action); } /** * Callback to configure the snippet. */ interface Config { /** * Accept the given prefix using the meta-data description. * @param prefix the prefix to accept */ void accept(String prefix); /** * Accept the given prefix with a defined description. * @param prefix the prefix to accept * @param description the description to use */ void accept(String prefix, String description); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java ================================================ /* * 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.context.properties; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; import org.gradle.api.file.FileCollection; import org.springframework.boot.build.context.properties.ConfigurationProperty.Deprecation; /** * Configuration properties snippets. * * @author Brian Clozed * @author Phillip Webb */ class Snippets { private final ConfigurationProperties properties; private final List snippets = new ArrayList<>(); private final boolean deprecated; Snippets(FileCollection configurationPropertyMetadata, boolean deprecated) { this.properties = ConfigurationProperties.fromFiles(configurationPropertyMetadata); this.deprecated = deprecated; } void add(String anchor, String title, Consumer config) { this.snippets.add(new Snippet(anchor, title, config)); } void writeTo(Path outputDirectory) throws IOException { createDirectory(outputDirectory); Set remaining = this.properties.stream() .filter(this::shouldAdd) .map(ConfigurationProperty::getName) .collect(Collectors.toSet()); for (Snippet snippet : this.snippets) { Set written = writeSnippet(outputDirectory, snippet, remaining); remaining.removeAll(written); } if (!this.deprecated && !remaining.isEmpty()) { throw new IllegalStateException( "The following keys were not written to the documentation: " + String.join(", ", remaining)); } } private Set writeSnippet(Path outputDirectory, Snippet snippet, Set remaining) throws IOException { Table table = new Table(); Set added = new HashSet<>(); snippet.forEachOverride((prefix, description) -> { CompoundRow row = new CompoundRow(snippet, prefix, (!this.deprecated) ? description : ""); remaining.stream().filter((candidate) -> candidate.startsWith(prefix)).forEach((name) -> { ConfigurationProperty property = this.properties.get(name); if (shouldAdd(property) && added.add(name)) { row.addProperty(property); } }); if (!row.isEmpty()) { table.addRow(row); } }); snippet.forEachPrefix((prefix) -> { remaining.stream().filter((candidate) -> candidate.startsWith(prefix)).forEach((name) -> { ConfigurationProperty property = this.properties.get(name); if (shouldAdd(property) && added.add(name)) { table.addRow(new SingleRow(this, snippet, property)); } }); }); Asciidoc asciidoc = getAsciidoc(snippet, table); writeAsciidoc(outputDirectory, snippet, asciidoc); return added; } String findXref(String name) { ConfigurationProperty property = this.properties.get(name); if (property == null || property.isDeprecated()) { return null; } for (Snippet snippet : this.snippets) { for (String prefix : snippet.getOverrides().keySet()) { if (name.startsWith(prefix)) { return null; } } for (String prefix : snippet.getPrefixes()) { if (name.startsWith(prefix)) { return "appendix:application-properties/index.adoc#%s.%s".formatted(snippet.getAnchor(), name); } } } return null; } private boolean shouldAdd(ConfigurationProperty property) { return (property == null || (property.isDeprecated() == this.deprecated && !deprecatedAtErrorLevel(property))); } private boolean deprecatedAtErrorLevel(ConfigurationProperty property) { Deprecation deprecation = property.getDeprecation(); return deprecation != null && "error".equals(deprecation.level()); } private Asciidoc getAsciidoc(Snippet snippet, Table table) { Asciidoc asciidoc = new Asciidoc(); if (!table.isEmpty()) { // We have to prepend 'appendix.' as a section id here, otherwise the // spring-asciidoctor-extensions:section-id asciidoctor extension complains asciidoc.appendln("[[appendix." + ((this.deprecated) ? "deprecated-" : "") + snippet.getAnchor() + "]]"); asciidoc.appendln("== ", ((this.deprecated) ? "Deprecated " : "") + snippet.getTitle()); table.write(asciidoc); } return asciidoc; } private void writeAsciidoc(Path outputDirectory, Snippet snippet, Asciidoc asciidoc) throws IOException { String[] parts = (snippet.getAnchor()).split("\\."); Path path = outputDirectory.resolve(parts[parts.length - 1] + ".adoc"); createDirectory(path.getParent()); Files.deleteIfExists(path); try (OutputStream outputStream = Files.newOutputStream(path)) { outputStream.write(asciidoc.toString().getBytes(StandardCharsets.UTF_8)); } } private void createDirectory(Path path) throws IOException { assertValidOutputDirectory(path); if (!Files.exists(path)) { Files.createDirectory(path); } } private void assertValidOutputDirectory(Path path) { if (path == null) { throw new IllegalArgumentException("Directory path should not be null"); } if (Files.exists(path) && !Files.isDirectory(path)) { throw new IllegalArgumentException("Path already exists and is not a directory"); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/context/properties/Table.java ================================================ /* * 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.context.properties; import java.util.Set; import java.util.TreeSet; /** * Asciidoctor table listing configuration properties sharing to a common theme. * * @author Brian Clozel */ class Table { private final Set rows = new TreeSet<>(); void addRow(Row row) { this.rows.add(row); } void write(Asciidoc asciidoc) { asciidoc.appendln("[cols=\"4,3,3\", options=\"header\"]"); asciidoc.appendln("|==="); asciidoc.appendln("|Name|Description|Default Value"); asciidoc.appendln(); this.rows.forEach((entry) -> { entry.write(asciidoc); asciidoc.appendln(); }); asciidoc.appendln("|==="); } boolean isEmpty() { return this.rows.isEmpty(); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/devtools/DocumentDevtoolsPropertyDefaults.java ================================================ /* * 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.devtools; import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Map; import java.util.Properties; import java.util.TreeMap; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import org.gradle.api.DefaultTask; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; 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.TaskAction; /** * Task for documenting Devtools' property defaults. * * @author Andy Wilkinson */ public abstract class DocumentDevtoolsPropertyDefaults extends DefaultTask { private FileCollection defaults; public DocumentDevtoolsPropertyDefaults() { getOutputFile().convention(getProject().getLayout() .getBuildDirectory() .file("generated/docs/using/devtools-property-defaults.adoc")); } @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getDefaults() { return this.defaults; } public void setDefaults(FileCollection defaults) { this.defaults = defaults; } @OutputFile public abstract RegularFileProperty getOutputFile(); @TaskAction void documentPropertyDefaults() throws IOException { Map propertyDefaults = loadPropertyDefaults(); documentPropertyDefaults(propertyDefaults); } private Map loadPropertyDefaults() throws IOException, FileNotFoundException { Properties properties = new Properties(); Map propertyDefaults = new TreeMap<>(); for (File contribution : this.defaults.getFiles()) { if (contribution.isFile()) { try (JarFile jar = new JarFile(contribution)) { ZipEntry entry = jar.getEntry("META-INF/spring-devtools.properties"); if (entry != null) { properties.load(jar.getInputStream(entry)); } } } else if (contribution.exists()) { throw new IllegalStateException( "Unexpected Devtools default properties contribution from '" + contribution + "'"); } } for (String name : properties.stringPropertyNames()) { if (name.startsWith("defaults.")) { propertyDefaults.put(name.substring("defaults.".length()), properties.getProperty(name)); } } return propertyDefaults; } private void documentPropertyDefaults(Map properties) throws IOException { try (PrintWriter writer = new PrintWriter(new FileWriter(getOutputFile().getAsFile().get()))) { writer.println("[cols=\"3,1\"]"); writer.println("|==="); writer.println("| Name | Default Value"); properties.forEach((name, value) -> { writer.println(); writer.printf("| `%s`%n", name); writer.printf("| `%s`%n", value); }); writer.println("|==="); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/docs/ApplicationRunner.java ================================================ /* * 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.docs; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; 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.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.gradle.internal.jvm.Jvm; /** * {@link Task} to run an application for the purpose of capturing its output for * inclusion in the reference documentation. * * @author Andy Wilkinson */ public abstract class ApplicationRunner extends DefaultTask { private FileCollection classpath; public ApplicationRunner() { getApplicationJar().convention("/opt/apps/myapp.jar"); } @OutputFile public abstract RegularFileProperty getOutput(); @Classpath public FileCollection getClasspath() { return this.classpath; } public void setClasspath(FileCollection classpath) { this.classpath = classpath; } @Input public abstract ListProperty getArgs(); @Input public abstract Property getMainClass(); @Input public abstract Property getExpectedLogging(); @Input abstract MapProperty getNormalizations(); @Input abstract Property getApplicationJar(); public void normalizeTomcatPort() { getNormalizations().put("(Tomcat started on port )[\\d]+( \\(http\\))", "$18080$2"); getNormalizations().put("(Tomcat initialized with port )[\\d]+( \\(http\\))", "$18080$2"); } public void normalizeLiveReloadPort() { getNormalizations().put("(LiveReload server is running on port )[\\d]+", "$135729"); } @TaskAction void runApplication() throws IOException { List command = new ArrayList<>(); File executable = Jvm.current().getExecutable("java"); command.add(executable.getAbsolutePath()); command.add("-cp"); command.add(this.classpath.getFiles() .stream() .map(File::getAbsolutePath) .collect(Collectors.joining(File.pathSeparator))); command.add(getMainClass().get()); command.addAll(getArgs().get()); File outputFile = getOutput().getAsFile().get(); Process process = new ProcessBuilder().redirectOutput(outputFile) .redirectError(outputFile) .command(command) .start(); awaitLogging(process); process.destroy(); normalizeLogging(); } private void awaitLogging(Process process) { long end = System.currentTimeMillis() + 60000; String expectedLogging = getExpectedLogging().get(); while (System.currentTimeMillis() < end) { for (String line : outputLines()) { if (line.contains(expectedLogging)) { return; } } if (!process.isAlive()) { throw new IllegalStateException("Process exited before '" + expectedLogging + "' was logged"); } } throw new IllegalStateException("'" + expectedLogging + "' was not logged within 60 seconds"); } private List outputLines() { Path outputPath = getOutput().get().getAsFile().toPath(); try { return Files.readAllLines(outputPath); } catch (IOException ex) { throw new RuntimeException("Failed to read lines of output from '" + outputPath + "'", ex); } } private void normalizeLogging() { List outputLines = outputLines(); List normalizedLines = normalize(outputLines); Path outputPath = getOutput().get().getAsFile().toPath(); try { Files.write(outputPath, normalizedLines); } catch (IOException ex) { throw new RuntimeException("Failed to write normalized lines of output to '" + outputPath + "'", ex); } } private List normalize(List lines) { List normalizedLines = lines; Map normalizations = new HashMap<>(getNormalizations().get()); normalizations.put("(Starting .* using Java .* with PID [\\d]+ \\().*( started by ).*( in ).*(\\))", "$1" + getApplicationJar().get() + "$2myuser$3/opt/apps/$4"); for (Entry normalization : normalizations.entrySet()) { Pattern pattern = Pattern.compile(normalization.getKey()); normalizedLines = normalize(normalizedLines, pattern, normalization.getValue()); } return normalizedLines; } private List normalize(List lines, Pattern pattern, String replacement) { boolean matched = false; List normalizedLines = new ArrayList<>(); for (String line : lines) { Matcher matcher = pattern.matcher(line); StringBuilder transformed = new StringBuilder(); while (matcher.find()) { matched = true; matcher.appendReplacement(transformed, replacement); } matcher.appendTail(transformed); normalizedLines.add(transformed.toString()); } if (!matched) { reportUnmatchedNormalization(lines, pattern); } return normalizedLines; } private void reportUnmatchedNormalization(List lines, Pattern pattern) { StringBuilder message = new StringBuilder( "'" + pattern + "' did not match any of the following lines of output:"); message.append(String.format("%n")); for (String line : lines) { message.append(String.format("%s%n", line)); } throw new IllegalStateException(message.toString()); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/docs/ConfigureJavadocLinks.java ================================================ /* * 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.docs; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.gradle.api.Action; import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.javadoc.Javadoc; import org.gradle.external.javadoc.StandardJavadocDocletOptions; import org.springframework.boot.build.bom.ResolvedBom; import org.springframework.boot.build.bom.ResolvedBom.JavadocLink; /** * An {@link Action} to configure the links option of a {@link Javadoc} task. * * @author Andy Wilkinson */ public class ConfigureJavadocLinks implements Action { private final FileCollection resolvedBoms; private final Collection includedLibraries; public ConfigureJavadocLinks(FileCollection resolvedBoms, Collection includedLibraries) { this.resolvedBoms = resolvedBoms; this.includedLibraries = includedLibraries; } @Override public void execute(Javadoc javadoc) { javadoc.options((options) -> { if (options instanceof StandardJavadocDocletOptions standardOptions) { configureLinks(standardOptions); } }); } private void configureLinks(StandardJavadocDocletOptions options) { ResolvedBom resolvedBom = ResolvedBom.readFrom(this.resolvedBoms.getSingleFile()); List links = new ArrayList<>(); links.add("https://docs.oracle.com/en/java/javase/17/docs/api/"); links.add("https://jakarta.ee/specifications/platform/11/apidocs/"); resolvedBom.libraries() .stream() .filter((candidate) -> this.includedLibraries.contains(candidate.name())) .flatMap((library) -> library.links().javadoc().stream()) .map(JavadocLink::uri) .map(URI::toString) .forEach(links::add); options.setLinks(links); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/docs/DocumentManagedDependencies.java ================================================ /* * 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.docs; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Set; import java.util.TreeSet; import org.gradle.api.DefaultTask; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; 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.TaskAction; import org.springframework.boot.build.bom.ResolvedBom; import org.springframework.boot.build.bom.ResolvedBom.Bom; import org.springframework.boot.build.bom.ResolvedBom.Id; import org.springframework.boot.build.bom.ResolvedBom.ResolvedLibrary; /** * Task for documenting {@link ResolvedBom boms'} managed dependencies. * * @author Andy Wilkinson */ public abstract class DocumentManagedDependencies extends DefaultTask { private FileCollection resolvedBoms; @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getResolvedBoms() { return this.resolvedBoms; } public void setResolvedBoms(FileCollection resolvedBoms) { this.resolvedBoms = resolvedBoms; } @OutputFile public abstract RegularFileProperty getOutputFile(); @TaskAction public void documentConstrainedVersions() throws IOException { File outputFile = getOutputFile().get().getAsFile(); outputFile.getParentFile().mkdirs(); try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) { writer.println("|==="); writer.println("| Group ID | Artifact ID | Version"); Set managedCoordinates = new TreeSet<>((id1, id2) -> { int result = id1.groupId().compareTo(id2.groupId()); if (result != 0) { return result; } return id1.artifactId().compareTo(id2.artifactId()); }); for (File file : getResolvedBoms().getFiles()) { managedCoordinates.addAll(process(ResolvedBom.readFrom(file))); } for (Id id : managedCoordinates) { writer.println(); writer.printf("| `%s`%n", id.groupId()); writer.printf("| `%s`%n", id.artifactId()); writer.printf("| `%s`%n", id.version()); } writer.println("|==="); } } private Set process(ResolvedBom resolvedBom) { TreeSet managedCoordinates = new TreeSet<>(); for (ResolvedLibrary library : resolvedBom.libraries()) { for (Id managedDependency : library.managedDependencies()) { managedCoordinates.add(managedDependency); } for (Bom importedBom : library.importedBoms()) { managedCoordinates.addAll(process(importedBom)); } } return managedCoordinates; } private Set process(Bom bom) { TreeSet managedCoordinates = new TreeSet<>(); bom.managedDependencies().stream().forEach(managedCoordinates::add); Bom parent = bom.parent(); if (parent != null) { managedCoordinates.addAll(process(parent)); } return managedCoordinates; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/docs/DocumentVersionProperties.java ================================================ /* * 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.docs; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import org.gradle.api.DefaultTask; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; 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.TaskAction; import org.springframework.boot.build.bom.ResolvedBom; import org.springframework.boot.build.bom.ResolvedBom.ResolvedLibrary; /** * Task for documenting {@link ResolvedBom boms'} version properties. * * @author Christoph Dreis * @author Andy Wilkinson */ public abstract class DocumentVersionProperties extends DefaultTask { private FileCollection resolvedBoms; @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getResolvedBoms() { return this.resolvedBoms; } public void setResolvedBoms(FileCollection resolvedBoms) { this.resolvedBoms = resolvedBoms; } @OutputFile public abstract RegularFileProperty getOutputFile(); @TaskAction public void documentVersionProperties() throws IOException { List libraries = this.resolvedBoms.getFiles() .stream() .map(ResolvedBom::readFrom) .flatMap((resolvedBom) -> resolvedBom.libraries().stream()) .sorted((l1, l2) -> l1.name().compareToIgnoreCase(l2.name())) .toList(); File outputFile = getOutputFile().getAsFile().get(); outputFile.getParentFile().mkdirs(); try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) { writer.println("|==="); writer.println("| Library | Version Property"); for (ResolvedLibrary library : libraries) { writer.println(); writer.printf("| `%s`%n", library.name()); writer.printf("| `%s`%n", library.versionProperty()); } writer.println("|==="); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/DocumentPluginGoals.java ================================================ /* * 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.mavenplugin; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import org.springframework.boot.build.mavenplugin.PluginXmlParser.Mojo; import org.springframework.boot.build.mavenplugin.PluginXmlParser.Parameter; import org.springframework.boot.build.mavenplugin.PluginXmlParser.Plugin; /** * A {@link Task} to document the plugin's goals. * * @author Andy Wilkinson */ public abstract class DocumentPluginGoals extends DefaultTask { private final PluginXmlParser parser = new PluginXmlParser(); @OutputDirectory public abstract DirectoryProperty getOutputDir(); @Input public abstract MapProperty getGoalSections(); @InputFile public abstract RegularFileProperty getPluginXml(); @TaskAction public void documentPluginGoals() throws IOException { Plugin plugin = this.parser.parse(getPluginXml().getAsFile().get()); writeOverview(plugin); for (Mojo mojo : plugin.getMojos()) { documentMojo(plugin, mojo); } } private void writeOverview(Plugin plugin) throws IOException { try (PrintWriter writer = new PrintWriter( new FileWriter(new File(getOutputDir().getAsFile().get(), "overview.adoc")))) { writer.println("[cols=\"1,3\"]"); writer.println("|==="); writer.println("| Goal | Description"); writer.println(); for (Mojo mojo : plugin.getMojos()) { writer.printf("| xref:%s[%s:%s]%n", goalSectionId(mojo, false), plugin.getGoalPrefix(), mojo.getGoal()); writer.printf("| %s%n", mojo.getDescription()); writer.println(); } writer.println("|==="); } } private void documentMojo(Plugin plugin, Mojo mojo) throws IOException { try (PrintWriter writer = new PrintWriter( new FileWriter(new File(getOutputDir().getAsFile().get(), mojo.getGoal() + ".adoc")))) { String sectionId = goalSectionId(mojo, true); writer.printf("[[%s]]%n", sectionId); writer.printf("= `%s:%s`%n%n", plugin.getGoalPrefix(), mojo.getGoal()); writer.printf("`%s:%s:%s`%n", plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion()); writer.println(); writer.println(mojo.getDescription()); List parameters = mojo.getParameters().stream().filter(Parameter::isEditable).toList(); List requiredParameters = parameters.stream().filter(Parameter::isRequired).toList(); String detailsSectionId = sectionId + ".parameter-details"; if (!requiredParameters.isEmpty()) { writer.println(); writer.println(); writer.println(); writer.printf("[[%s.required-parameters]]%n", sectionId); writer.println("== Required parameters"); writer.println(); writeParametersTable(writer, detailsSectionId, requiredParameters); } List optionalParameters = parameters.stream() .filter((parameter) -> !parameter.isRequired()) .toList(); if (!optionalParameters.isEmpty()) { writer.println(); writer.println(); writer.println(); writer.printf("[[%s.optional-parameters]]%n", sectionId); writer.println("== Optional parameters"); writer.println(); writeParametersTable(writer, detailsSectionId, optionalParameters); } writer.println(); writer.println(); writer.println(); writer.printf("[[%s]]%n", detailsSectionId); writer.println("== Parameter details"); writer.println(); writeParameterDetails(writer, parameters, detailsSectionId); } } private String goalSectionId(Mojo mojo, boolean innerReference) { String goalSection = getGoalSections().getting(mojo.getGoal()).get(); if (goalSection == null) { throw new IllegalStateException("Goal '" + mojo.getGoal() + "' has not be assigned to a section"); } String sectionId = goalSection + "." + mojo.getGoal() + "-goal"; return (!innerReference) ? goalSection + "#" + sectionId : sectionId; } private void writeParametersTable(PrintWriter writer, String detailsSectionId, List parameters) { writer.println("[cols=\"3,2,3\"]"); writer.println("|==="); writer.println("| Name | Type | Default"); writer.println(); for (Parameter parameter : parameters) { String name = parameter.getName(); writer.printf("| xref:#%s.%s[%s]%n", detailsSectionId, parameterId(name), name); writer.printf("| `%s`%n", typeNameToJavadocLink(shortTypeName(parameter.getType()), parameter.getType())); String defaultValue = parameter.getDefaultValue(); if (defaultValue != null) { writer.printf("| `%s`%n", defaultValue); } else { writer.println("|"); } writer.println(); } writer.println("|==="); } private void writeParameterDetails(PrintWriter writer, List parameters, String sectionId) { for (Parameter parameter : parameters) { String name = parameter.getName(); writer.println(); writer.println(); writer.printf("[[%s.%s]]%n", sectionId, parameterId(name)); writer.printf("=== `%s`%n", name); writer.println(parameter.getDescription()); writer.println(); writer.println("[cols=\"10h,90\"]"); writer.println("|==="); writer.println(); writeDetail(writer, "Name", name); writeDetail(writer, "Type", typeNameToJavadocLink(parameter.getType())); writeOptionalDetail(writer, "Default value", parameter.getDefaultValue()); writeOptionalDetail(writer, "User property", parameter.getUserProperty()); writeOptionalDetail(writer, "Since", parameter.getSince()); writer.println("|==="); } } private String parameterId(String name) { StringBuilder id = new StringBuilder(name.length() + 4); for (char c : name.toCharArray()) { if (Character.isLowerCase(c)) { id.append(c); } else { id.append("-"); id.append(Character.toLowerCase(c)); } } return id.toString(); } private void writeDetail(PrintWriter writer, String name, String value) { writer.printf("| %s%n", name); writer.printf("| `%s`%n", value); writer.println(); } private void writeOptionalDetail(PrintWriter writer, String name, String value) { writer.printf("| %s%n", name); if (value != null) { writer.printf("| `%s`%n", value); } else { writer.println("|"); } writer.println(); } private String shortTypeName(String name) { if (name.lastIndexOf('.') >= 0) { name = name.substring(name.lastIndexOf('.') + 1); } if (name.lastIndexOf('$') >= 0) { name = name.substring(name.lastIndexOf('$') + 1); } return name; } private String typeNameToJavadocLink(String name) { return typeNameToJavadocLink(name, name); } private String typeNameToJavadocLink(String shortName, String name) { if (name.startsWith("org.springframework.boot.maven")) { return "xref:maven-plugin:api/java/" + typeNameToJavadocPath(name) + ".html[" + shortName + "]"; } if (name.startsWith("org.springframework.boot")) { return "xref:api:java/" + typeNameToJavadocPath(name) + ".html[" + shortName + "]"; } return shortName; } private String typeNameToJavadocPath(String name) { return name.replace(".", "/").replace("$", "."); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenExec.java ================================================ /* * 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.mavenplugin; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.TaskExecutionException; import org.gradle.process.internal.ExecException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A custom {@link JavaExec} {@link Task task} for running Maven. * * @author Andy Wilkinson */ public abstract class MavenExec extends JavaExec { private final Logger logger = LoggerFactory.getLogger(MavenExec.class); public MavenExec() { setClasspath(mavenConfiguration(getProject())); args("--batch-mode"); getMainClass().set("org.apache.maven.cli.MavenCli"); getPom().set(getProjectDir().file("pom.xml")); } @Internal public abstract DirectoryProperty getProjectDir(); @InputFile @PathSensitive(PathSensitivity.RELATIVE) abstract RegularFileProperty getPom(); @Override public void exec() { File workingDir = getProjectDir().getAsFile().get(); workingDir(workingDir); systemProperty("maven.multiModuleProjectDirectory", workingDir.getAbsolutePath()); try { Path logFile = Files.createTempFile(getName(), ".log"); try { args("--log-file", logFile.toFile().getAbsolutePath()); super.exec(); if (this.logger.isInfoEnabled()) { Files.readAllLines(logFile).forEach(this.logger::info); } } catch (ExecException ex) { System.out.println("Exec exception! Dumping log"); Files.readAllLines(logFile).forEach(System.out::println); throw ex; } } catch (IOException ex) { throw new TaskExecutionException(this, ex); } } private Configuration mavenConfiguration(Project project) { Configuration existing = project.getConfigurations().findByName("maven"); if (existing != null) { return existing; } return project.getConfigurations().create("maven", (maven) -> { maven.getDependencies().add(project.getDependencies().create("org.apache.maven:maven-embedder:3.6.3")); maven.getDependencies().add(project.getDependencies().create("org.apache.maven:maven-compat:3.6.3")); maven.getDependencies().add(project.getDependencies().create("org.slf4j:slf4j-simple:1.7.5")); maven.getDependencies() .add(project.getDependencies() .create("org.apache.maven.resolver:maven-resolver-connector-basic:1.4.1")); maven.getDependencies() .add(project.getDependencies().create("org.apache.maven.resolver:maven-resolver-transport-http:1.4.1")); }); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java ================================================ /* * 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.mavenplugin; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Properties; import java.util.Set; import javax.inject.Inject; import io.spring.javaformat.formatter.FileEdit; import io.spring.javaformat.formatter.FileFormatter; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.ComponentMetadataContext; import org.gradle.api.artifacts.ComponentMetadataRule; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.artifacts.VariantMetadata; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.artifacts.result.ResolvedArtifactResult; import org.gradle.api.attributes.DocsType; import org.gradle.api.attributes.Usage; import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.component.ConfigurationVariantDetails; import org.gradle.api.component.SoftwareComponent; import org.gradle.api.file.CopySpec; import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.file.ProjectLayout; import org.gradle.api.file.RegularFile; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.JavaLibraryPlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Provider; import org.gradle.api.publish.PublishingExtension; import org.gradle.api.publish.maven.MavenPublication; import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; import org.gradle.api.publish.tasks.GenerateModuleMetadata; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputDirectory; 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.SourceSetContainer; import org.gradle.api.tasks.Sync; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskExecutionException; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.javadoc.Javadoc; import org.gradle.external.javadoc.StandardJavadocDocletOptions; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.springframework.boot.build.DeployedPlugin; import org.springframework.boot.build.MavenRepositoryPlugin; import org.springframework.boot.build.bom.ResolvedBom; import org.springframework.boot.build.bom.ResolvedBom.ResolvedLibrary; import org.springframework.boot.build.optional.OptionalDependenciesPlugin; import org.springframework.boot.build.test.DockerTestPlugin; import org.springframework.boot.build.test.IntegrationTestPlugin; import org.springframework.core.CollectionFactory; /** * Plugin for building Spring Boot's Maven Plugin. * * @author Andy Wilkinson * @author Phillip Webb * @author Moritz Halbritter */ public class MavenPluginPlugin implements Plugin { @Override public void apply(Project project) { project.getPlugins().apply(JavaLibraryPlugin.class); project.getPlugins().apply(MavenPublishPlugin.class); project.getPlugins().apply(DeployedPlugin.class); project.getPlugins().apply(MavenRepositoryPlugin.class); project.getPlugins().apply(IntegrationTestPlugin.class); Jar jarTask = (Jar) project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME); configurePomPackaging(project); addPopulateIntTestMavenRepositoryTask(project); TaskProvider generateHelpMojoTask = addGenerateHelpMojoTask(project, jarTask); TaskProvider generatePluginDescriptorTask = addGeneratePluginDescriptorTask(project, jarTask, generateHelpMojoTask); addDocumentPluginGoalsTask(project, generatePluginDescriptorTask); addPrepareMavenBinariesTask(project); TaskProvider extractVersionPropertiesTask = addExtractVersionPropertiesTask(project); project.getTasks() .named(IntegrationTestPlugin.INT_TEST_TASK_NAME) .configure((task) -> task.getInputs() .file(extractVersionPropertiesTask.map(ExtractVersionProperties::getDestination)) .withPathSensitivity(PathSensitivity.RELATIVE) .withPropertyName("versionProperties")); publishOptionalDependenciesInPom(project); project.getTasks().withType(GenerateModuleMetadata.class).configureEach((task) -> task.setEnabled(false)); } private void publishOptionalDependenciesInPom(Project project) { project.getPlugins().withType(OptionalDependenciesPlugin.class, (optionalDependencies) -> { SoftwareComponent component = project.getComponents().findByName("java"); if (component instanceof AdhocComponentWithVariants componentWithVariants) { componentWithVariants.addVariantsFromConfiguration( project.getConfigurations().getByName(OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME), ConfigurationVariantDetails::mapToOptional); } }); MavenPublication publication = (MavenPublication) project.getExtensions() .getByType(PublishingExtension.class) .getPublications() .getByName("maven"); publication.getPom().withXml((xml) -> { Element root = xml.asElement(); NodeList children = root.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if ("dependencyManagement".equals(child.getNodeName())) { root.removeChild(child); } } }); } private void configurePomPackaging(Project project) { PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); publishing.getPublications().withType(MavenPublication.class, this::setPackaging); } private void setPackaging(MavenPublication mavenPublication) { mavenPublication.pom((pom) -> pom.setPackaging("maven-plugin")); } private void addPopulateIntTestMavenRepositoryTask(Project project) { Configuration repositoryContents = project.getConfigurations().create("repositoryContents"); repositoryContents.extendsFrom( project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME), project.getConfigurations().getByName("mavenRepository")); repositoryContents.attributes((attributes) -> attributes.attribute(DocsType.DOCS_TYPE_ATTRIBUTE, project.getObjects().named(DocsType.class, "maven-repository"))); repositoryContents.setCanBeConsumed(false); TaskProvider populateMavenRepository = project.getTasks() .register("populateResolvedDependenciesMavenRepository", ResolvedConfigurationMavenRepository.class, (task) -> { task.setConfiguration(repositoryContents); task.getOutputDir() .set(project.getLayout().getBuildDirectory().dir("resolved-dependencies-maven-repository")); }); project.getDependencies() .components((components) -> components.all(MavenRepositoryComponentMetadataRule.class)); TaskProvider populateRepository = project.getTasks() .register("populateTestMavenRepository", Sync.class, (task) -> { task.setDestinationDir( project.getLayout().getBuildDirectory().dir("test-maven-repository").get().getAsFile()); task.with(copyIntTestMavenRepositoryFiles(project, populateMavenRepository)); task.dependsOn( project.getTasks().getByName(MavenRepositoryPlugin.PUBLISH_TO_PROJECT_REPOSITORY_TASK_NAME)); }); project.getTasks().getByName(IntegrationTestPlugin.INT_TEST_TASK_NAME).dependsOn(populateRepository); project.getPlugins() .withType(DockerTestPlugin.class) .all((dockerTestPlugin) -> project.getTasks() .named(DockerTestPlugin.DOCKER_TEST_TASK_NAME, (dockerTest) -> dockerTest.dependsOn(populateRepository))); } private CopySpec copyIntTestMavenRepositoryFiles(Project project, TaskProvider runtimeClasspathMavenRepository) { CopySpec copySpec = project.copySpec(); copySpec.from(project.getConfigurations().getByName(MavenRepositoryPlugin.MAVEN_REPOSITORY_CONFIGURATION_NAME)); copySpec.from(project.getLayout().getBuildDirectory().dir("maven-repository")); copySpec.from(runtimeClasspathMavenRepository); return copySpec; } private void addDocumentPluginGoalsTask(Project project, TaskProvider generatePluginDescriptorTask) { project.getTasks().register("documentPluginGoals", DocumentPluginGoals.class, (task) -> { ProjectLayout layout = project.getLayout(); Provider pluginXml = layout.file(generatePluginDescriptorTask .map((generateDescriptor) -> new File(generateDescriptor.getOutputs().getFiles().getSingleFile(), "plugin.xml"))); task.getPluginXml().set(pluginXml); task.getOutputDir().set(layout.getBuildDirectory().dir("docs/generated/goals/")); task.dependsOn(generatePluginDescriptorTask); }); } private TaskProvider addGenerateHelpMojoTask(Project project, Jar jarTask) { Provider helpMojoDir = project.getLayout().getBuildDirectory().dir("help-mojo"); TaskProvider syncHelpMojoInputs = createSyncHelpMojoInputsTask(project, helpMojoDir); TaskProvider task = createGenerateHelpMojoTask(project, helpMojoDir, syncHelpMojoInputs); includeHelpMojoInJar(jarTask, task); return task; } private TaskProvider createGenerateHelpMojoTask(Project project, Provider helpMojoDir, TaskProvider syncHelpMojoInputs) { return project.getTasks().register("generateHelpMojo", MavenExec.class, (task) -> { task.getProjectDir().set(helpMojoDir); task.args("org.apache.maven.plugins:maven-plugin-plugin:3.6.1:helpmojo"); task.getOutputs().dir(helpMojoDir.map((directory) -> directory.dir("target/generated-sources/plugin"))); task.dependsOn(syncHelpMojoInputs); }); } private TaskProvider createSyncHelpMojoInputsTask(Project project, Provider helpMojoDir) { return project.getTasks().register("syncHelpMojoInputs", Sync.class, (task) -> { task.setDestinationDir(helpMojoDir.get().getAsFile()); File pomFile = new File(project.getProjectDir(), "src/maven/resources/pom.xml"); task.from(pomFile, (copy) -> replaceVersionPlaceholder(copy, project)); }); } private void includeHelpMojoInJar(Jar jarTask, TaskProvider generateHelpMojoTask) { jarTask.from(generateHelpMojoTask).exclude("**/*.java"); jarTask.dependsOn(generateHelpMojoTask); } private TaskProvider addGeneratePluginDescriptorTask(Project project, Jar jarTask, TaskProvider generateHelpMojoTask) { Provider pluginDescriptorDir = project.getLayout().getBuildDirectory().dir("plugin-descriptor"); Provider generatedHelpMojoDir = project.getLayout() .getBuildDirectory() .dir("generated/sources/helpMojo"); SourceSet mainSourceSet = getMainSourceSet(project); project.getTasks().withType(Javadoc.class, this::setJavadocOptions); TaskProvider formattedHelpMojoSource = createFormatHelpMojoSource(project, generateHelpMojoTask, generatedHelpMojoDir); project.getTasks().getByName(mainSourceSet.getCompileJavaTaskName()).dependsOn(formattedHelpMojoSource); mainSourceSet.java((javaSources) -> javaSources.srcDir(formattedHelpMojoSource)); TaskProvider pluginDescriptorInputs = createSyncPluginDescriptorInputs(project, pluginDescriptorDir, mainSourceSet); TaskProvider task = createGeneratePluginDescriptorTask(project, pluginDescriptorDir, pluginDescriptorInputs); includeDescriptorInJar(jarTask, task); return task; } private SourceSet getMainSourceSet(Project project) { SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets(); return sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); } private void setJavadocOptions(Javadoc javadoc) { StandardJavadocDocletOptions options = (StandardJavadocDocletOptions) javadoc.getOptions(); options.addMultilineStringsOption("tag").setValue(Arrays.asList("goal:X", "requiresProject:X", "threadSafe:X")); } private TaskProvider createFormatHelpMojoSource(Project project, TaskProvider generateHelpMojoTask, Provider generatedHelpMojoDir) { return project.getTasks().register("formatHelpMojoSource", FormatHelpMojoSource.class, (task) -> { task.setGenerator(generateHelpMojoTask); task.getOutputDir().set(generatedHelpMojoDir); }); } private TaskProvider createSyncPluginDescriptorInputs(Project project, Provider destination, SourceSet sourceSet) { return project.getTasks().register("syncPluginDescriptorInputs", Sync.class, (task) -> { task.setDestinationDir(destination.get().getAsFile()); File pomFile = new File(project.getProjectDir(), "src/maven/resources/pom.xml"); task.from(pomFile, (copy) -> replaceVersionPlaceholder(copy, project)); task.from(sourceSet.getOutput().getClassesDirs(), (sync) -> sync.into("target/classes")); task.from(sourceSet.getAllJava().getSrcDirs(), (sync) -> sync.into("src/main/java")); task.getInputs().property("version", project.getVersion()); task.dependsOn(sourceSet.getClassesTaskName()); }); } private TaskProvider createGeneratePluginDescriptorTask(Project project, Provider mavenDir, TaskProvider pluginDescriptorInputs) { return project.getTasks().register("generatePluginDescriptor", MavenExec.class, (task) -> { task.args("org.apache.maven.plugins:maven-plugin-plugin:3.6.1:descriptor"); task.getOutputs().dir(mavenDir.map((directory) -> directory.dir("target/classes/META-INF/maven"))); task.getInputs() .dir(mavenDir.map((directory) -> directory.dir("target/classes/org"))) .withPathSensitivity(PathSensitivity.RELATIVE) .withPropertyName("plugin classes"); task.getProjectDir().set(mavenDir); task.dependsOn(pluginDescriptorInputs); }); } private void includeDescriptorInJar(Jar jar, TaskProvider generatePluginDescriptorTask) { jar.from(generatePluginDescriptorTask, (copy) -> copy.into("META-INF/maven/")); jar.dependsOn(generatePluginDescriptorTask); } private void addPrepareMavenBinariesTask(Project project) { TaskProvider task = project.getTasks() .register("prepareMavenBinaries", PrepareMavenBinaries.class, (prepareMavenBinaries) -> prepareMavenBinaries.getOutputDir() .set(project.getLayout().getBuildDirectory().dir("maven-binaries"))); project.getTasks() .getByName(IntegrationTestPlugin.INT_TEST_TASK_NAME) .getInputs() .dir(task.map(PrepareMavenBinaries::getOutputDir)) .withPathSensitivity(PathSensitivity.RELATIVE) .withPropertyName("mavenBinaries"); } private void replaceVersionPlaceholder(CopySpec copy, Project project) { copy.filter((input) -> replaceVersionPlaceholder(project, input)); } private String replaceVersionPlaceholder(Project project, String input) { return input.replace("{{version}}", project.getVersion().toString()); } private TaskProvider addExtractVersionPropertiesTask(Project project) { return project.getTasks().register("extractVersionProperties", ExtractVersionProperties.class, (task) -> { task.setResolvedBoms(project.getConfigurations().create("versionProperties")); task.getDestination() .set(project.getLayout() .getBuildDirectory() .dir("generated-resources") .map((dir) -> dir.file("extracted-versions.properties"))); }); } public abstract static class FormatHelpMojoSource extends DefaultTask { private final ObjectFactory objectFactory; @Inject public FormatHelpMojoSource(ObjectFactory objectFactory) { this.objectFactory = objectFactory; } private TaskProvider generator; void setGenerator(TaskProvider generator) { this.generator = generator; getInputs().files(this.generator) .withPathSensitivity(PathSensitivity.RELATIVE) .withPropertyName("generated source"); } @OutputDirectory public abstract DirectoryProperty getOutputDir(); @TaskAction void syncAndFormat() { FileFormatter formatter = new FileFormatter(); for (File output : this.generator.get().getOutputs().getFiles()) { formatter.formatFiles(this.objectFactory.fileTree().from(output), StandardCharsets.UTF_8) .forEach((edit) -> save(output, edit)); } } private void save(File output, FileEdit edit) { Path relativePath = output.toPath().relativize(edit.getFile().toPath()); Path outputLocation = getOutputDir().getAsFile().get().toPath().resolve(relativePath); try { Files.createDirectories(outputLocation.getParent()); String content = edit.getFormattedContent(); content = addNullAwaySuppression(content); Files.writeString(outputLocation, content); } catch (Exception ex) { throw new TaskExecutionException(this, ex); } } private String addNullAwaySuppression(String content) { String separator = System.lineSeparator(); String[] lines = content.split(separator); StringBuilder result = new StringBuilder(); for (String line : lines) { if (line.startsWith("public class ")) { result.append("@SuppressWarnings(\"NullAway\")").append(separator); } result.append(line).append(separator); } return result.toString(); } } public static class MavenRepositoryComponentMetadataRule implements ComponentMetadataRule { private final ObjectFactory objects; @javax.inject.Inject public MavenRepositoryComponentMetadataRule(ObjectFactory objects) { this.objects = objects; } @Override public void execute(ComponentMetadataContext context) { context.getDetails() .maybeAddVariant("compileWithMetadata", "compile", (variant) -> configureVariant(context, variant)); context.getDetails() .maybeAddVariant("apiElementsWithMetadata", "apiElements", (variant) -> configureVariant(context, variant)); } private void configureVariant(ComponentMetadataContext context, VariantMetadata variant) { variant.attributes((attributes) -> { attributes.attribute(DocsType.DOCS_TYPE_ATTRIBUTE, this.objects.named(DocsType.class, "maven-repository")); attributes.attribute(Usage.USAGE_ATTRIBUTE, this.objects.named(Usage.class, "maven-repository")); }); variant.withFiles((files) -> { ModuleVersionIdentifier id = context.getDetails().getId(); files.addFile(id.getName() + "-" + id.getVersion() + ".pom"); }); } } public abstract static class ResolvedConfigurationMavenRepository extends DefaultTask { private Configuration configuration; @OutputDirectory public abstract DirectoryProperty getOutputDir(); @Classpath public Configuration getConfiguration() { return this.configuration; } public void setConfiguration(Configuration configuration) { this.configuration = configuration; } @TaskAction public void createRepository() { for (ResolvedArtifactResult result : this.configuration.getIncoming().getArtifacts()) { if (result.getId().getComponentIdentifier() instanceof ModuleComponentIdentifier identifier) { String fileName = result.getFile() .getName() .replace(identifier.getVersion() + "-" + identifier.getVersion(), identifier.getVersion()); File repositoryLocation = getOutputDir() .dir(identifier.getGroup().replace('.', '/') + "/" + identifier.getModule() + "/" + identifier.getVersion() + "/" + fileName) .get() .getAsFile(); repositoryLocation.getParentFile().mkdirs(); try { Files.copy(result.getFile().toPath(), repositoryLocation.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException ex) { throw new RuntimeException("Failed to copy artifact '" + result + "'", ex); } } } } } public abstract static class ExtractVersionProperties extends DefaultTask { private FileCollection resolvedBoms; @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getResolvedBoms() { return this.resolvedBoms; } public void setResolvedBoms(FileCollection resolvedBoms) { this.resolvedBoms = resolvedBoms; } @OutputFile public abstract RegularFileProperty getDestination(); @TaskAction public void extractVersionProperties() { ResolvedBom resolvedBom = ResolvedBom.readFrom(this.resolvedBoms.getSingleFile()); Properties versions = extractVersionProperties(resolvedBom); writeProperties(versions); } private void writeProperties(Properties versions) { File outputFile = getDestination().getAsFile().get(); outputFile.getParentFile().mkdirs(); try (Writer writer = new FileWriter(outputFile)) { versions.store(writer, null); } catch (IOException ex) { throw new GradleException("Failed to write extracted version properties", ex); } } private Properties extractVersionProperties(ResolvedBom resolvedBom) { Properties versions = CollectionFactory.createSortedProperties(true); versions.setProperty("project.version", resolvedBom.id().version()); Set versionProperties = Set.of("log4j2.version", "maven-jar-plugin.version", "maven-war-plugin.version", "build-helper-maven-plugin.version", "spring-framework.version", "jakarta-servlet.version", "kotlin.version", "assertj.version", "junit-jupiter.version"); for (ResolvedLibrary library : resolvedBom.libraries()) { if (library.versionProperty() != null && versionProperties.contains(library.versionProperty())) { versions.setProperty(library.versionProperty(), library.version()); } } return versions; } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/PluginXmlParser.java ================================================ /* * 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.mavenplugin; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * A parser for a Maven plugin's {@code plugin.xml} file. * * @author Andy Wilkinson * @author Mike Smithson */ class PluginXmlParser { private final XPath xpath; PluginXmlParser() { this.xpath = XPathFactory.newInstance().newXPath(); } Plugin parse(File pluginXml) { try { Node root = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(pluginXml); List mojos = parseMojos(root); return new Plugin(textAt("//plugin/groupId", root), textAt("//plugin/artifactId", root), textAt("//plugin/version", root), textAt("//plugin/goalPrefix", root), mojos); } catch (Exception ex) { throw new RuntimeException(ex); } } private String textAt(String path, Node source) throws XPathExpressionException { String text = this.xpath.evaluate(path + "/text()", source); return text.isEmpty() ? null : text; } private List parseMojos(Node plugin) throws XPathExpressionException { List mojos = new ArrayList<>(); for (Node mojoNode : nodesAt("//plugin/mojos/mojo", plugin)) { mojos.add(new Mojo(textAt("goal", mojoNode), format(textAt("description", mojoNode)), parseParameters(mojoNode))); } return mojos; } private Iterable nodesAt(String path, Node source) throws XPathExpressionException { return IterableNodeList.of((NodeList) this.xpath.evaluate(path, source, XPathConstants.NODESET)); } private List parseParameters(Node mojoNode) throws XPathExpressionException { Map defaultValues = new HashMap<>(); Map userProperties = new HashMap<>(); for (Node parameterConfigurationNode : nodesAt("configuration/*", mojoNode)) { String userProperty = parameterConfigurationNode.getTextContent(); if (userProperty != null && !userProperty.isEmpty()) { userProperties.put(parameterConfigurationNode.getNodeName(), userProperty.replace("${", "`").replace("}", "`")); } Node defaultValueAttribute = parameterConfigurationNode.getAttributes().getNamedItem("default-value"); if (defaultValueAttribute != null && !defaultValueAttribute.getTextContent().isEmpty()) { defaultValues.put(parameterConfigurationNode.getNodeName(), defaultValueAttribute.getTextContent()); } } List parameters = new ArrayList<>(); for (Node parameterNode : nodesAt("parameters/parameter", mojoNode)) { parameters.add(parseParameter(parameterNode, defaultValues, userProperties)); } return parameters; } private Parameter parseParameter(Node parameterNode, Map defaultValues, Map userProperties) throws XPathExpressionException { String description = textAt("description", parameterNode); return new Parameter(textAt("name", parameterNode), textAt("type", parameterNode), booleanAt("required", parameterNode), booleanAt("editable", parameterNode), (description != null) ? format(description) : "", defaultValues.get(textAt("name", parameterNode)), userProperties.get(textAt("name", parameterNode)), textAt("since", parameterNode)); } private boolean booleanAt(String path, Node node) throws XPathExpressionException { return Boolean.parseBoolean(textAt(path, node)); } private String format(String input) { return input.replace("", "`") .replace("", "`") .replace("<", "<") .replace(">", ">") .replace("
", " ") .replace("

", " ") .replace("\n", " ") .replace(""", "\"") .replaceAll("\\{@code (.*?)}", "`$1`") .replaceAll("\\{@link (.*?)}", "`$1`") .replaceAll("\\{@literal (.*?)}", "`$1`") .replaceAll("(.*?)", "$1[$2]"); } private static final class IterableNodeList implements Iterable { private final NodeList nodeList; private IterableNodeList(NodeList nodeList) { this.nodeList = nodeList; } private static Iterable of(NodeList nodeList) { return new IterableNodeList(nodeList); } @Override public Iterator iterator() { return new Iterator<>() { private int index; @Override public boolean hasNext() { return this.index < IterableNodeList.this.nodeList.getLength(); } @Override public Node next() { return IterableNodeList.this.nodeList.item(this.index++); } }; } } static final class Plugin { private final String groupId; private final String artifactId; private final String version; private final String goalPrefix; private final List mojos; private Plugin(String groupId, String artifactId, String version, String goalPrefix, List mojos) { this.groupId = groupId; this.artifactId = artifactId; this.version = version; this.goalPrefix = goalPrefix; this.mojos = mojos; } String getGroupId() { return this.groupId; } String getArtifactId() { return this.artifactId; } String getVersion() { return this.version; } String getGoalPrefix() { return this.goalPrefix; } List getMojos() { return this.mojos; } } static final class Mojo { private final String goal; private final String description; private final List parameters; private Mojo(String goal, String description, List parameters) { this.goal = goal; this.description = description; this.parameters = parameters; } String getGoal() { return this.goal; } String getDescription() { return this.description; } List getParameters() { return this.parameters; } } static final class Parameter { private final String name; private final String type; private final boolean required; private final boolean editable; private final String description; private final String defaultValue; private final String userProperty; private final String since; private Parameter(String name, String type, boolean required, boolean editable, String description, String defaultValue, String userProperty, String since) { this.name = name; this.type = type; this.required = required; this.editable = editable; this.description = description; this.defaultValue = defaultValue; this.userProperty = userProperty; this.since = since; } String getName() { return this.name; } String getType() { return this.type; } boolean isRequired() { return this.required; } boolean isEditable() { return this.editable; } String getDescription() { return this.description; } String getDefaultValue() { return this.defaultValue; } String getUserProperty() { return this.userProperty; } String getSince() { return this.since; } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/PrepareMavenBinaries.java ================================================ /* * 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.mavenplugin; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.file.ArchiveOperations; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileSystemOperations; import org.gradle.api.file.FileTree; import org.gradle.api.provider.Provider; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; /** * {@link Task} to make Maven binaries available for integration testing. * * @author Andy Wilkinson */ public abstract class PrepareMavenBinaries extends DefaultTask { private final FileSystemOperations fileSystemOperations; private final Provider> binaries; @Inject public PrepareMavenBinaries(FileSystemOperations fileSystemOperations, ArchiveOperations archiveOperations) { this.fileSystemOperations = fileSystemOperations; ConfigurationContainer configurations = getProject().getConfigurations(); DependencyHandler dependencies = getProject().getDependencies(); this.binaries = getVersions().map((versions) -> versions.stream() .map((version) -> configurations .detachedConfiguration(dependencies.create("org.apache.maven:apache-maven:" + version + ":bin@zip"))) .map(Configuration::getSingleFile) .map(archiveOperations::zipTree) .collect(Collectors.toSet())); } @OutputDirectory public abstract DirectoryProperty getOutputDir(); @Input public abstract SetProperty getVersions(); @TaskAction public void prepareBinaries() { this.fileSystemOperations.sync((sync) -> { sync.into(getOutputDir()); this.binaries.get().forEach(sync::from); }); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/optional/OptionalDependenciesPlugin.java ================================================ /* * 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.optional; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSetContainer; /** * A {@code Plugin} that adds support for Maven-style optional dependencies. Creates a new * {@code optional} configuration. The {@code optional} configuration is part of the * project's compile and runtime classpaths but does not affect the classpath of dependent * projects. * * @author Andy Wilkinson */ public class OptionalDependenciesPlugin implements Plugin { /** * Name of the {@code optional} configuration. */ public static final String OPTIONAL_CONFIGURATION_NAME = "optional"; @Override public void apply(Project project) { Configuration optional = project.getConfigurations().create("optional"); optional.setCanBeConsumed(true); optional.setCanBeResolved(false); project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { SourceSetContainer sourceSets = project.getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets(); sourceSets.all((sourceSet) -> { project.getConfigurations() .getByName(sourceSet.getCompileClasspathConfigurationName()) .extendsFrom(optional); project.getConfigurations() .getByName(sourceSet.getRuntimeClasspathConfigurationName()) .extendsFrom(optional); }); }); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/processors/AnnotationProcessorPlugin.java ================================================ /* * 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.processors; import java.util.Map; import java.util.TreeMap; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.tasks.bundling.Jar; /** * A {@link Plugin} for an annotation processor project. * * @author Christoph Dreis */ public class AnnotationProcessorPlugin implements Plugin { private static final String JAR_TYPE = "annotation-processor"; @Override public void apply(Project project) { project.getTasks().withType(Jar.class, (jar) -> project.afterEvaluate((evaluated) -> { jar.manifest((manifest) -> { Map attributes = new TreeMap<>(); attributes.put("Spring-Boot-Jar-Type", JAR_TYPE); manifest.attributes(attributes); }); })); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/properties/BuildProperties.java ================================================ /* * 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.properties; import org.gradle.api.Project; /** * Properties that can influence the build. * * @param buildType the build type * @param gitHub GitHub details * @author Phillip Webb */ public record BuildProperties(BuildType buildType, GitHub gitHub) { private static final String PROPERTY_NAME = BuildProperties.class.getName(); /** * Get the {@link BuildProperties} for the given {@link Project}. * @param project the source project * @return the build properties */ public static BuildProperties get(Project project) { BuildProperties buildProperties = (BuildProperties) project.findProperty(PROPERTY_NAME); if (buildProperties == null) { buildProperties = load(project); project.getExtensions().getExtraProperties().set(PROPERTY_NAME, buildProperties); } return buildProperties; } private static BuildProperties load(Project project) { BuildType buildType = buildType(project.findProperty("spring.build-type")); return switch (buildType) { case OPEN_SOURCE -> new BuildProperties(buildType, GitHub.OPEN_SOURCE); case COMMERCIAL -> new BuildProperties(buildType, GitHub.COMMERCIAL); }; } private static BuildType buildType(Object value) { if (value == null || "oss".equals(value.toString())) { return BuildType.OPEN_SOURCE; } if ("commercial".equals(value.toString())) { return BuildType.COMMERCIAL; } throw new IllegalStateException("Unknown build type property '" + value + "'"); } /** * GitHub properties. * * @param organization the GitHub organization * @param repository the GitHub repository */ public record GitHub(String organization, String repository) { static final GitHub OPEN_SOURCE = new GitHub("spring-projects", "spring-boot"); static final GitHub COMMERCIAL = new GitHub("spring-projects", "spring-boot-commercial"); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/properties/BuildType.java ================================================ /* * 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.properties; import java.util.Locale; /** * The type of build being performed. * * @author Phillip Webb */ public enum BuildType { /** * An open source build. */ OPEN_SOURCE, /** * A commercial build. */ COMMERCIAL; public String toIdentifier() { return toString().replace("_", "").toLowerCase(Locale.ROOT); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/springframework/CheckAotFactories.java ================================================ /* * 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.springframework; import org.gradle.api.Task; /** * {@link Task} that checks {@code META-INF/spring/aot.factories}. * * @author Andy Wilkinson */ public abstract class CheckAotFactories extends CheckFactoriesFile { public CheckAotFactories() { super("META-INF/spring/aot.factories"); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/springframework/CheckFactoriesFile.java ================================================ /* * 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.springframework; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.gradle.api.DefaultTask; import org.gradle.api.Task; 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.core.io.support.SpringFactoriesLoader; import org.springframework.util.StringUtils; /** * {@link Task} that checks files loaded by {@link SpringFactoriesLoader}. * * @author Andy Wilkinson */ public abstract class CheckFactoriesFile extends DefaultTask { private final String path; private FileCollection sourceFiles = getProject().getObjects().fileCollection(); private FileCollection classpath = getProject().getObjects().fileCollection(); protected CheckFactoriesFile(String path) { this.path = path; 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(this.path)); } 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() { getSource().forEach(this::check); } private void check(File factoriesFile) { Properties properties = load(factoriesFile); Map> problems = new LinkedHashMap<>(); for (String name : properties.stringPropertyNames()) { String value = properties.getProperty(name); List classNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray(value)); collectProblems(problems, name, classNames); List sortedValues = new ArrayList<>(classNames); Collections.sort(sortedValues); if (!sortedValues.equals(classNames)) { List problemsForClassName = problems.computeIfAbsent(name, (k) -> new ArrayList<>()); problemsForClassName.add("Entries should be sorted alphabetically"); } } File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); writeReport(factoriesFile, problems, outputFile); if (!problems.isEmpty()) { throw new VerificationException("%s check failed. See '%s' for details".formatted(this.path, outputFile)); } } private void collectProblems(Map> problems, String key, List classNames) { for (String className : classNames) { if (!find(className)) { addNoFoundProblem(className, problems.computeIfAbsent(key, (k) -> new ArrayList<>())); } } } private void addNoFoundProblem(String className, List problemsForClassName) { String binaryName = binaryNameOf(className); boolean foundBinaryForm = find(binaryName); problemsForClassName.add(!foundBinaryForm ? "'%s' was not found".formatted(className) : "'%s' should be listed using its binary name '%s'".formatted(className, binaryName)); } private boolean find(String className) { for (File root : this.classpath.getFiles()) { String classFilePath = className.replace(".", "/") + ".class"; if (new File(root, classFilePath).isFile()) { return true; } } return false; } private String binaryNameOf(String className) { int lastDotIndex = className.lastIndexOf('.'); return className.substring(0, lastDotIndex) + "$" + className.substring(lastDotIndex + 1); } private Properties load(File aotFactories) { Properties properties = new Properties(); try (FileInputStream input = new FileInputStream(aotFactories)) { properties.load(input); return properties; } catch (IOException ex) { throw new UncheckedIOException(ex); } } private void writeReport(File factoriesFile, Map> problems, File outputFile) { outputFile.getParentFile().mkdirs(); StringBuilder report = new StringBuilder(); if (!problems.isEmpty()) { report.append("Found problems in '%s':%n".formatted(factoriesFile)); problems.forEach((key, problemsForKey) -> { report.append(" - %s:%n".formatted(key)); problemsForKey.forEach((problem) -> report.append(" - %s%n".formatted(problem))); }); } try { Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } catch (IOException ex) { throw new UncheckedIOException(ex); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/springframework/CheckSpringFactories.java ================================================ /* * 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.springframework; import org.gradle.api.Task; /** * {@link Task} that checks {@code META-INF/spring.factories}. * * @author Andy Wilkinson */ public abstract class CheckSpringFactories extends CheckFactoriesFile { public CheckSpringFactories() { super("META-INF/spring.factories"); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/starters/DocumentStarters.java ================================================ /* * 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.starters; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; import java.util.stream.Stream; import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; 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.TaskAction; import org.springframework.util.StringUtils; /** * {@link Task} to document all starter projects. * * @author Andy Wilkinson */ public abstract class DocumentStarters extends DefaultTask { private final Configuration starters; public DocumentStarters() { this.starters = getProject().getConfigurations().create("starters"); getProject().getGradle().projectsEvaluated((gradle) -> { gradle.allprojects((project) -> { if (project.getPlugins().hasPlugin(StarterPlugin.class)) { Map dependency = new HashMap<>(); dependency.put("path", project.getPath()); dependency.put("configuration", "starterMetadata"); this.starters.getDependencies().add(project.getDependencies().project(dependency)); } }); }); } @OutputDirectory public abstract DirectoryProperty getOutputDir(); @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getStarters() { return this.starters; } @TaskAction void documentStarters() { Set starters = this.starters.getFiles() .stream() .map(this::loadStarter) .collect(Collectors.toCollection(TreeSet::new)); writeTable("application-starters", starters.stream().filter(Starter::isApplication)); writeTable("production-starters", starters.stream().filter(Starter::isProduction)); writeTable("technical-starters", starters.stream().filter(Starter::isTechnical)); } private Starter loadStarter(File metadata) { Properties properties = new Properties(); try (FileReader reader = new FileReader(metadata)) { properties.load(reader); return new Starter(properties.getProperty("name"), properties.getProperty("description"), StringUtils.commaDelimitedListToSet(properties.getProperty("dependencies"))); } catch (IOException ex) { throw new RuntimeException(ex); } } private void writeTable(String name, Stream starters) { File output = new File(getOutputDir().getAsFile().get(), name + ".adoc"); output.getParentFile().mkdirs(); try (PrintWriter writer = new PrintWriter(new FileWriter(output))) { writer.println("|==="); writer.println("| Name | Description"); starters.forEach((starter) -> { writer.println(); writer.printf("| [[%s]]`%s`%n", starter.name, starter.name); writer.printf("| %s%n", postProcessDescription(starter.description)); }); writer.println("|==="); } catch (IOException ex) { throw new RuntimeException(ex); } } private String postProcessDescription(String description) { return addStarterCrossLinks(description); } private String addStarterCrossLinks(String input) { return input.replaceAll("(spring-boot-starter[A-Za-z0-9-]*)", "xref:#$1[`$1`]"); } private static final class Starter implements Comparable { private final String name; private final String description; private final Set dependencies; private Starter(String name, String description, Set dependencies) { this.name = name; this.description = description; this.dependencies = dependencies; } private boolean isProduction() { return this.name.equals("spring-boot-starter-actuator"); } private boolean isTechnical() { return !Arrays.asList("spring-boot-starter", "spring-boot-starter-test").contains(this.name) && !isProduction() && !this.dependencies.contains("spring-boot-starter"); } private boolean isApplication() { return !isProduction() && !isTechnical(); } @Override public int compareTo(Starter other) { return this.name.compareTo(other.name); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/starters/StarterMetadata.java ================================================ /* * 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.starters; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.Properties; import java.util.stream.Collectors; import org.gradle.api.DefaultTask; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.springframework.core.CollectionFactory; /** * A {@link Task} for generating metadata that describes a starter. * * @author Andy Wilkinson */ public abstract class StarterMetadata extends DefaultTask { private Configuration dependencies; public StarterMetadata() { Project project = getProject(); getStarterName().convention(project.provider(project::getName)); getStarterDescription().convention(project.provider(project::getDescription)); } @Input public abstract Property getStarterName(); @Input public abstract Property getStarterDescription(); @Classpath public FileCollection getDependencies() { return this.dependencies; } public void setDependencies(Configuration dependencies) { this.dependencies = dependencies; } @OutputFile public abstract RegularFileProperty getDestination(); @TaskAction void generateMetadata() throws IOException { Properties properties = CollectionFactory.createSortedProperties(true); properties.setProperty("name", getStarterName().get()); properties.setProperty("description", getStarterDescription().get()); properties.setProperty("dependencies", String.join(",", this.dependencies.getResolvedConfiguration() .getResolvedArtifacts() .stream() .map(ResolvedArtifact::getName) .collect(Collectors.toSet()))); File destination = getDestination().getAsFile().get(); destination.getParentFile().mkdirs(); try (FileWriter writer = new FileWriter(destination)) { properties.store(writer, null); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/starters/StarterPlugin.java ================================================ /* * 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.starters; import java.util.Map; import java.util.TreeMap; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.file.RegularFile; import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.plugins.JavaLibraryPlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.PluginContainer; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; import org.springframework.boot.build.ConventionsPlugin; import org.springframework.boot.build.DeployedPlugin; import org.springframework.boot.build.classpath.CheckClasspathForConflicts; import org.springframework.boot.build.classpath.CheckClasspathForUnconstrainedDirectDependencies; import org.springframework.boot.build.classpath.CheckClasspathForUnnecessaryExclusions; import org.springframework.util.StringUtils; /** * A {@link Plugin} for a starter project. * * @author Andy Wilkinson */ public class StarterPlugin implements Plugin { private static final String JAR_TYPE = "dependencies-starter"; @Override public void apply(Project project) { PluginContainer plugins = project.getPlugins(); plugins.apply(DeployedPlugin.class); plugins.apply(JavaLibraryPlugin.class); plugins.apply(ConventionsPlugin.class); ConfigurationContainer configurations = project.getConfigurations(); Configuration runtimeClasspath = configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); TaskProvider starterMetadata = project.getTasks() .register("starterMetadata", StarterMetadata.class, (task) -> { task.setDependencies(runtimeClasspath); Provider destination = project.getLayout() .getBuildDirectory() .file("starter-metadata.properties"); task.getDestination().set(destination); }); configurations.create("starterMetadata"); project.getArtifacts() .add("starterMetadata", starterMetadata.map(StarterMetadata::getDestination), (artifact) -> artifact.builtBy(starterMetadata)); createClasspathConflictsCheck(runtimeClasspath, project); createUnnecessaryExclusionsCheck(runtimeClasspath, project); createUnconstrainedDirectDependenciesCheck(runtimeClasspath, project); configureJarManifest(project); } private void createClasspathConflictsCheck(Configuration classpath, Project project) { TaskProvider checkClasspathForConflicts = project.getTasks() .register("check" + StringUtils.capitalize(classpath.getName() + "ForConflicts"), CheckClasspathForConflicts.class, (task) -> task.setClasspath(classpath)); project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForConflicts); } private void createUnnecessaryExclusionsCheck(Configuration classpath, Project project) { TaskProvider checkClasspathForUnnecessaryExclusions = project.getTasks() .register("check" + StringUtils.capitalize(classpath.getName() + "ForUnnecessaryExclusions"), CheckClasspathForUnnecessaryExclusions.class, (task) -> task.setClasspath(classpath)); project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForUnnecessaryExclusions); } private void createUnconstrainedDirectDependenciesCheck(Configuration classpath, Project project) { TaskProvider checkClasspathForUnconstrainedDirectDependencies = project .getTasks() .register("check" + StringUtils.capitalize(classpath.getName() + "ForUnconstrainedDirectDependencies"), CheckClasspathForUnconstrainedDirectDependencies.class, (task) -> task.setClasspath(classpath)); project.getTasks() .getByName(JavaBasePlugin.CHECK_TASK_NAME) .dependsOn(checkClasspathForUnconstrainedDirectDependencies); } private void configureJarManifest(Project project) { project.getTasks().withType(Jar.class, (jar) -> project.afterEvaluate((evaluated) -> { jar.manifest((manifest) -> { Map attributes = new TreeMap<>(); attributes.put("Spring-Boot-Jar-Type", JAR_TYPE); manifest.attributes(attributes); }); })); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestBuildService.java ================================================ /* * 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; import org.gradle.api.Project; import org.gradle.api.provider.Provider; import org.gradle.api.services.BuildService; import org.gradle.api.services.BuildServiceParameters; /** * Build service for Docker-based tests. The maximum number of {@code dockerTest} tasks * that can run in parallel can be configured using * {@code org.springframework.boot.dockertest.max-parallel-tasks}. By default, only a * single {@code dockerTest} task will run at a time. * * @author Andy Wilkinson */ abstract class DockerTestBuildService implements BuildService { static Provider registerIfNecessary(Project project) { return project.getGradle() .getSharedServices() .registerIfAbsent("dockerTest", DockerTestBuildService.class, (spec) -> spec.getMaxParallelUsages().set(maxParallelTasks(project))); } private static int maxParallelTasks(Project project) { Object property = project.findProperty("org.springframework.boot.dockertest.max-parallel-tasks"); if (property == null) { return 1; } return Integer.parseInt(property.toString()); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java ================================================ /* * 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; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Provider; import org.gradle.api.services.BuildService; import org.gradle.api.tasks.Exec; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.testing.Test; import org.gradle.language.base.plugins.LifecycleBasePlugin; import org.gradle.plugins.ide.eclipse.EclipsePlugin; import org.gradle.plugins.ide.eclipse.model.EclipseModel; /** * Plugin for Docker-based tests. Creates a {@link SourceSet source set}, {@link Test * test} task, and {@link BuildService shared service} named {@code dockerTest}. The build * service is configured to only allow serial usage and the {@code dockerTest} task is * configured to use the build service. In a parallel build, this ensures that only a * single {@code dockerTest} task can run at any given time. * * @author Andy Wilkinson */ public class DockerTestPlugin implements Plugin { /** * Name of the {@code dockerTest} task. */ public static final String DOCKER_TEST_TASK_NAME = "dockerTest"; /** * Name of the {@code dockerTest} source set. */ public static final String DOCKER_TEST_SOURCE_SET_NAME = "dockerTest"; /** * Name of the {@code dockerTest} shared service. */ public static final String DOCKER_TEST_SERVICE_NAME = "dockerTest"; private static final String RECLAIM_DOCKER_SPACE_TASK_NAME = "reclaimDockerSpace"; @Override public void apply(Project project) { project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> configureDockerTesting(project)); } private void configureDockerTesting(Project project) { Provider buildService = DockerTestBuildService.registerIfNecessary(project); SourceSet dockerTestSourceSet = createSourceSet(project); Provider dockerTest = createTestTask(project, dockerTestSourceSet, buildService); project.getTasks().getByName(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(dockerTest); project.getPlugins().withType(EclipsePlugin.class, (eclipsePlugin) -> { EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class); eclipse.classpath((classpath) -> classpath.getPlusConfigurations() .add(project.getConfigurations() .getByName(dockerTestSourceSet.getRuntimeClasspathConfigurationName()))); }); project.getDependencies() .add(dockerTestSourceSet.getRuntimeOnlyConfigurationName(), "org.junit.platform:junit-platform-launcher"); Provider reclaimDockerSpace = createReclaimDockerSpaceTask(project, buildService); project.getTasks().getByName(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(reclaimDockerSpace); } private SourceSet createSourceSet(Project project) { SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets(); SourceSet dockerTestSourceSet = sourceSets.create(DOCKER_TEST_SOURCE_SET_NAME); SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); SourceSet test = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME); dockerTestSourceSet.setCompileClasspath(dockerTestSourceSet.getCompileClasspath() .plus(main.getOutput()) .plus(main.getCompileClasspath()) .plus(test.getOutput())); dockerTestSourceSet.setRuntimeClasspath(dockerTestSourceSet.getRuntimeClasspath() .plus(main.getOutput()) .plus(main.getRuntimeClasspath()) .plus(test.getOutput())); project.getPlugins().withType(IntegrationTestPlugin.class, (integrationTestPlugin) -> { SourceSet intTest = sourceSets.getByName(IntegrationTestPlugin.INT_TEST_SOURCE_SET_NAME); dockerTestSourceSet .setCompileClasspath(dockerTestSourceSet.getCompileClasspath().plus(intTest.getOutput())); dockerTestSourceSet .setRuntimeClasspath(dockerTestSourceSet.getRuntimeClasspath().plus(intTest.getOutput())); }); return dockerTestSourceSet; } private Provider createTestTask(Project project, SourceSet dockerTestSourceSet, Provider buildService) { return project.getTasks().register(DOCKER_TEST_TASK_NAME, Test.class, (task) -> { task.usesService(buildService); task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); task.setDescription("Runs Docker-based tests."); task.setTestClassesDirs(dockerTestSourceSet.getOutput().getClassesDirs()); task.setClasspath(dockerTestSourceSet.getRuntimeClasspath()); task.shouldRunAfter(JavaPlugin.TEST_TASK_NAME); }); } private Provider createReclaimDockerSpaceTask(Project project, Provider buildService) { return project.getTasks().register(RECLAIM_DOCKER_SPACE_TASK_NAME, Exec.class, (task) -> { task.usesService(buildService); task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); task.setDescription("Reclaims Docker space on CI."); task.shouldRunAfter(DOCKER_TEST_TASK_NAME); task.onlyIf(this::shouldReclaimDockerSpace); task.executable("bash"); task.args("-c", project.getRootDir() .toPath() .resolve(".github/scripts/reclaim-docker-diskspace.sh") .toAbsolutePath()); }); } private boolean shouldReclaimDockerSpace(Task task) { if (System.getProperty("os.name").startsWith("Windows")) { return false; } return System.getenv("GITHUB_ACTIONS") != null || System.getenv("RECLAIM_DOCKER_SPACE") != null; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/test/IntegrationTestPlugin.java ================================================ /* * 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; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.testing.Test; import org.gradle.language.base.plugins.LifecycleBasePlugin; import org.gradle.plugins.ide.eclipse.EclipsePlugin; import org.gradle.plugins.ide.eclipse.model.EclipseModel; /** * A {@link Plugin} to configure integration testing support in a {@link Project}. * * @author Andy Wilkinson */ public class IntegrationTestPlugin implements Plugin { /** * Name of the {@code intTest} task. */ public static String INT_TEST_TASK_NAME = "intTest"; /** * Name of the {@code intTest} source set. */ public static String INT_TEST_SOURCE_SET_NAME = "intTest"; @Override public void apply(Project project) { project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> configureIntegrationTesting(project)); } private void configureIntegrationTesting(Project project) { SourceSet intTestSourceSet = createSourceSet(project); TaskProvider intTest = createTestTask(project, intTestSourceSet); project.getTasks().getByName(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(intTest); project.getPlugins().withType(EclipsePlugin.class, (eclipsePlugin) -> { EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class); eclipse.classpath((classpath) -> classpath.getPlusConfigurations() .add(project.getConfigurations().getByName(intTestSourceSet.getRuntimeClasspathConfigurationName()))); }); project.getDependencies() .add(intTestSourceSet.getRuntimeOnlyConfigurationName(), "org.junit.platform:junit-platform-launcher"); } private SourceSet createSourceSet(Project project) { SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets(); SourceSet intTestSourceSet = sourceSets.create(INT_TEST_SOURCE_SET_NAME); SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); intTestSourceSet.setCompileClasspath(intTestSourceSet.getCompileClasspath().plus(main.getOutput())); intTestSourceSet.setRuntimeClasspath(intTestSourceSet.getRuntimeClasspath().plus(main.getOutput())); return intTestSourceSet; } private TaskProvider createTestTask(Project project, SourceSet intTestSourceSet) { return project.getTasks().register(INT_TEST_TASK_NAME, Test.class, (task) -> { task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); task.setDescription("Runs integration tests."); task.setTestClassesDirs(intTestSourceSet.getOutput().getClassesDirs()); task.setClasspath(intTestSourceSet.getRuntimeClasspath()); task.shouldRunAfter(JavaPlugin.TEST_TASK_NAME); }); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/test/SystemTestPlugin.java ================================================ /* * 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; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.testing.Test; import org.gradle.language.base.plugins.LifecycleBasePlugin; import org.gradle.plugins.ide.eclipse.EclipsePlugin; import org.gradle.plugins.ide.eclipse.model.EclipseModel; /** * A {@link Plugin} to configure system testing support in a {@link Project}. * * @author Andy Wilkinson * @author Scott Frederick */ public class SystemTestPlugin implements Plugin { private static final Spec NEVER = (task) -> false; /** * Name of the {@code systemTest} task. */ public static String SYSTEM_TEST_TASK_NAME = "systemTest"; /** * Name of the {@code systemTest} source set. */ public static String SYSTEM_TEST_SOURCE_SET_NAME = "systemTest"; @Override public void apply(Project project) { project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> configureSystemTesting(project)); } private void configureSystemTesting(Project project) { SourceSet systemTestSourceSet = createSourceSet(project); createTestTask(project, systemTestSourceSet); project.getPlugins().withType(EclipsePlugin.class, (eclipsePlugin) -> { EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class); eclipse.classpath((classpath) -> classpath.getPlusConfigurations() .add(project.getConfigurations() .getByName(systemTestSourceSet.getRuntimeClasspathConfigurationName()))); }); project.getDependencies() .add(systemTestSourceSet.getRuntimeOnlyConfigurationName(), "org.junit.platform:junit-platform-launcher"); } private SourceSet createSourceSet(Project project) { SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets(); SourceSet systemTestSourceSet = sourceSets.create(SYSTEM_TEST_SOURCE_SET_NAME); SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); systemTestSourceSet .setCompileClasspath(systemTestSourceSet.getCompileClasspath().plus(mainSourceSet.getOutput())); systemTestSourceSet .setRuntimeClasspath(systemTestSourceSet.getRuntimeClasspath().plus(mainSourceSet.getOutput())); return systemTestSourceSet; } private TaskProvider createTestTask(Project project, SourceSet systemTestSourceSet) { return project.getTasks().register(SYSTEM_TEST_TASK_NAME, Test.class, (task) -> { task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); task.setDescription("Runs system tests."); task.setTestClassesDirs(systemTestSourceSet.getOutput().getClassesDirs()); task.setClasspath(systemTestSourceSet.getRuntimeClasspath()); task.shouldRunAfter(JavaPlugin.TEST_TASK_NAME); if (isCi()) { task.getOutputs().upToDateWhen(NEVER); task.getOutputs().doNotCacheIf("System tests are always rerun on CI", (spec) -> true); } }); } private boolean isCi() { return Boolean.parseBoolean(System.getenv("CI")); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/CheckAutoConfigureImports.java ================================================ /* * 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> allProblems = new TreeMap<>(); for (AutoConfigureImports autoConfigureImports : loadImports()) { List 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 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 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 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 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> 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 imports, String fileName) { } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/DocumentTestSlices.java ================================================ /* * 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.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; 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.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.TaskAction; import tools.jackson.databind.json.JsonMapper; import org.springframework.boot.build.test.autoconfigure.TestSliceMetadata.TestSlice; /** * {@link Task} used to document test slices. * * @author Andy Wilkinson */ public abstract class DocumentTestSlices extends DefaultTask { private FileCollection testSliceMetadata; @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public FileCollection getTestSlices() { return this.testSliceMetadata; } public void setTestSlices(FileCollection testSlices) { this.testSliceMetadata = testSlices; } @OutputFile public abstract RegularFileProperty getOutputFile(); @TaskAction void documentTestSlices() throws IOException { Map> testSlices = readTestSlices(); writeTable(testSlices); } private Map> readTestSlices() { Map> testSlices = new TreeMap<>(); for (File metadataFile : this.testSliceMetadata) { JsonMapper mapper = JsonMapper.builder().build(); TestSliceMetadata metadata = mapper.readValue(metadataFile, TestSliceMetadata.class); List slices = new ArrayList<>(metadata.testSlices()); Collections.sort(slices, (s1, s2) -> s1.annotation().compareTo(s2.annotation())); testSlices.put(metadata.module(), slices); } return testSlices; } private void writeTable(Map> testSlicesByModule) throws IOException { File outputFile = getOutputFile().getAsFile().get(); outputFile.getParentFile().mkdirs(); try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) { writer.println("[cols=\"d,d,a\"]"); writer.println("|==="); writer.println("|Module | Test slice | Imported auto-configuration"); testSlicesByModule.forEach((module, testSlices) -> { testSlices.forEach((testSlice) -> { writer.println(); writer.printf("| `%s`%n", module); writer.printf("| javadoc:%s[format=annotation]%n", testSlice.annotation()); writer.println("| "); for (String importedAutoConfiguration : testSlice.importedAutoConfigurations()) { writer.printf("`%s`%n", importedAutoConfiguration); } }); }); writer.println("|==="); } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/GenerateTestSliceMetadata.java ================================================ /* * 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.provider.Property; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; 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; this.getModuleName().convention(getProject().getName()); } 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(); @Input public abstract Property getModuleName(); @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 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(getModuleName().get(), 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 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 removeComments(List lines) { List 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 readTestSlices(File classesDir, MetadataReaderFactory metadataReaderFactory, Properties springFactories) throws IOException { try (Stream 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 importedAutoConfiguration = getImportedAutoConfiguration(springFactories, metadataReader.getAnnotationMetadata()); return new TestSlice(annotationName, importedAutoConfiguration); } private List getImportedAutoConfiguration(Properties springFactories, AnnotationMetadata annotationMetadata) { Stream 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 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"); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/TestAutoConfigurationPlugin.java ================================================ /* * 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: * *

    *
  • Add checks to ensure AutoConfigure*.import files and related annotations are * correct
  • *
* * @author Andy Wilkinson */ public class TestAutoConfigurationPlugin implements Plugin { @Override public void apply(Project target) { target.getPlugins().withType(JavaPlugin.class, (plugin) -> { TaskProvider 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)); }); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/TestSliceMetadata.java ================================================ /* * 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 tools.jackson.databind.SerializationFeature; import tools.jackson.databind.json.JsonMapper; /** * Metadata describing a module's test slices. * * @param module the module's name * @param testSlices the module's test slices * @author Andy Wilkinson */ record TestSliceMetadata(String module, List testSlices) { static TestSliceMetadata readFrom(File file) { return JsonMapper.builder().build().readValue(file, TestSliceMetadata.class); } void writeTo(File file) { JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build().writeValue(file, this); } record TestSlice(String annotation, List importedAutoConfigurations) { } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/TestSlicePlugin.java ================================================ /* * 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: * *
    *
  • Applies the {@link TestAutoConfigurationPlugin} *
* Additionally, when the {@link JavaPlugin} is applied it: * *
    *
  • Defines a task that produces metadata describing the test slices. The metadata is * made available as an artifact in the {@code testSliceMetadata} configuration *
* * @author Andy Wilkinson */ public class TestSlicePlugin implements Plugin { 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 = 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 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); } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/testing/TestFailuresPlugin.java ================================================ /* * 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.testing; import java.util.ArrayList; import java.util.List; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.testing.Test; import org.gradle.api.tasks.testing.TestDescriptor; import org.gradle.api.tasks.testing.TestListener; import org.gradle.api.tasks.testing.TestResult; /** * Plugin for recording test failures and reporting them at the end of the build. * * @author Andy Wilkinson */ public class TestFailuresPlugin implements Plugin { @Override public void apply(Project project) { Provider testResultsOverview = project.getGradle() .getSharedServices() .registerIfAbsent("testResultsOverview", TestResultsOverview.class, (spec) -> { }); project.getTasks().withType(Test.class, (test) -> { test.usesService(testResultsOverview); test.addTestListener(new FailureRecordingTestListener(testResultsOverview, test)); }); } private final class FailureRecordingTestListener implements TestListener { private final List failures = new ArrayList<>(); private final Provider testResultsOverview; private final Test test; private FailureRecordingTestListener(Provider testResultOverview, Test test) { this.testResultsOverview = testResultOverview; this.test = test; } @Override public void afterSuite(TestDescriptor descriptor, TestResult result) { if (!this.failures.isEmpty()) { this.testResultsOverview.get().addFailures(this.test, this.failures); } } @Override public void afterTest(TestDescriptor descriptor, TestResult result) { if (result.getFailedTestCount() > 0) { this.failures.add(descriptor); } } @Override public void beforeSuite(TestDescriptor descriptor) { } @Override public void beforeTest(TestDescriptor descriptor) { } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/testing/TestResultsOverview.java ================================================ /* * 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.testing; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.gradle.api.DefaultTask; import org.gradle.api.services.BuildService; import org.gradle.api.services.BuildServiceParameters; import org.gradle.api.tasks.testing.Test; import org.gradle.api.tasks.testing.TestDescriptor; import org.gradle.tooling.events.FinishEvent; import org.gradle.tooling.events.OperationCompletionListener; /** * {@link BuildService} that provides an overview of all the test failures in the build. * * @author Andy Wilkinson */ public abstract class TestResultsOverview implements BuildService, OperationCompletionListener, AutoCloseable { private final Map> testFailures = new TreeMap<>(Comparator.comparing(DefaultTask::getPath)); private final Object monitor = new Object(); void addFailures(Test test, List failureDescriptors) { List testFailures = failureDescriptors.stream().map(TestFailure::new).sorted().toList(); synchronized (this.monitor) { this.testFailures.put(test, testFailures); } } @Override public void onFinish(FinishEvent event) { // OperationCompletionListener is implemented to defer close until the build ends } @Override public void close() { synchronized (this.monitor) { if (this.testFailures.isEmpty()) { return; } System.err.println(); System.err.println("Found test failures in " + this.testFailures.size() + " test task" + ((this.testFailures.size() == 1) ? ":" : "s:")); this.testFailures.forEach((task, failures) -> { System.err.println(); System.err.println(task.getPath()); failures.forEach((failure) -> System.err .println(" " + failure.descriptor.getClassName() + " > " + failure.descriptor.getName())); }); } } private static final class TestFailure implements Comparable { private final TestDescriptor descriptor; private TestFailure(TestDescriptor descriptor) { this.descriptor = descriptor; } @Override public int compareTo(TestFailure other) { int comparison = this.descriptor.getClassName().compareTo(other.descriptor.getClassName()); if (comparison == 0) { comparison = this.descriptor.getName().compareTo(other.descriptor.getName()); } return comparison; } } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainExtension.java ================================================ /* * 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.toolchain; import org.gradle.api.Project; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.jvm.toolchain.JavaLanguageVersion; import org.springframework.boot.build.SystemRequirementsExtension; /** * DSL extension for {@link ToolchainPlugin}. * * @author Christoph Dreis */ public abstract class ToolchainExtension { private final JavaLanguageVersion javaVersion; public ToolchainExtension(Project project) { String toolchainVersion = (String) project.findProperty("toolchainVersion"); this.javaVersion = (toolchainVersion != null) ? JavaLanguageVersion.of(toolchainVersion) : null; SystemRequirementsExtension systemRequirements = project.getExtensions() .getByType(SystemRequirementsExtension.class); getMinimumCompatibleJavaVersion() .convention(project.provider(() -> JavaLanguageVersion.of(systemRequirements.getJava().getVersion()))); } public abstract Property getMinimumCompatibleJavaVersion(); public abstract Property getMaximumCompatibleJavaVersion(); public abstract ListProperty getTestJvmArgs(); JavaLanguageVersion getJavaVersion() { return this.javaVersion; } } ================================================ FILE: buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainPlugin.java ================================================ /* * 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.toolchain; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.tasks.testing.Test; import org.gradle.jvm.toolchain.JavaLanguageVersion; import org.gradle.jvm.toolchain.JavaToolchainService; /** * {@link Plugin} for customizing Gradle's toolchain support. * * @author Christoph Dreis * @author Andy Wilkinson */ public class ToolchainPlugin implements Plugin { @Override public void apply(Project project) { configureToolchain(project); } private void configureToolchain(Project project) { ToolchainExtension toolchain = project.getExtensions().create("toolchain", ToolchainExtension.class, project); JavaLanguageVersion toolchainVersion = toolchain.getJavaVersion(); if (toolchainVersion != null) { project.afterEvaluate((evaluated) -> configure(evaluated, toolchain)); } } private void configure(Project project, ToolchainExtension toolchain) { if (!isJavaVersionSupported(toolchain, toolchain.getJavaVersion())) { disableToolchainTasks(project); } else { configureTestToolchain(project, toolchain.getJavaVersion()); } } private boolean isJavaVersionSupported(ToolchainExtension toolchain, JavaLanguageVersion toolchainVersion) { JavaLanguageVersion minimumVersion = toolchain.getMinimumCompatibleJavaVersion().getOrNull(); if (minimumVersion == null || toolchainVersion.canCompileOrRun(minimumVersion)) { return toolchain.getMaximumCompatibleJavaVersion() .map((version) -> version.canCompileOrRun(toolchainVersion)) .getOrElse(true); } return false; } private void disableToolchainTasks(Project project) { project.getTasks().withType(Test.class, (task) -> task.setEnabled(false)); } private void configureTestToolchain(Project project, JavaLanguageVersion toolchainVersion) { JavaToolchainService javaToolchains = project.getExtensions().getByType(JavaToolchainService.class); project.getTasks() .withType(Test.class, (test) -> test.getJavaLauncher() .set(javaToolchains.launcherFor((spec) -> spec.getLanguageVersion().set(toolchainVersion)))); } } ================================================ FILE: buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties ================================================ # === INCLUDE-CODE LOCATIONS === include-java=ROOT:example$java/org/springframework/boot/docs include-kotlin= ROOT:example$kotlin/org/springframework/boot/docs # === URLs === url-ant-docs=https://ant.apache.org/manual url-buildpacks-docs=https://buildpacks.io/docs url-cyclonedx-docs-gradle-plugin=https://github.com/CycloneDX/cyclonedx-gradle-plugin url-cyclonedx-docs-maven-plugin=https://github.com/CycloneDX/cyclonedx-maven-plugin url-git-commit-id-maven-plugin=https://github.com/git-commit-id/git-commit-id-maven-plugin url-download-liberica-nik=https://bell-sw.com/pages/downloads/native-image-kit/#/nik-22-17 url-dynatrace-docs=https://docs.dynatrace.com/docs url-dynatrace-docs-shortlink={url-dynatrace-docs}/shortlink url-github-raw=https://raw.githubusercontent.com/{github-repo}/{github-ref} url-github-issues=https://github.com/{github-repo}/issues url-github-wiki=https://github.com/{github-repo}/wiki url-github=https://github.com/{github-repo} url-graal-docs=https://www.graalvm.org/{version-graal}/reference-manual url-graal-docs-native-image={url-graal-docs}/native-image url-gradle-docs=https://docs.gradle.org/current/userguide url-gradle-docs-application-plugin={url-gradle-docs}/application_plugin.html url-gradle-docs-groovy-plugin={url-gradle-docs}/groovy_plugin.html url-gradle-docs-java-plugin={url-gradle-docs}/java_plugin.html url-gradle-docs-war-plugin={url-gradle-docs}/war_plugin.html url-gradle-dsl=https://docs.gradle.org/current/dsl url-gradle-javadoc=https://docs.gradle.org/current/javadoc url-kotlin-docs-kotlin-plugin={url-kotlin-docs}/using-gradle.html url-micrometer-docs-concepts={url-micrometer-docs}/concepts url-micrometer-docs-implementations={url-micrometer-docs}/implementations url-micrometer-docs-observation={url-micrometer-docs}/observation url-native-build-tools-docs=https://graalvm.github.io/native-build-tools/{version-native-build-tools} url-native-build-tools-docs-gradle-plugin={url-native-build-tools-docs}/gradle-plugin.html url-native-build-tools-docs-maven-plugin={url-native-build-tools-docs}/maven-plugin.html url-paketo-docs=https://paketo.io/docs url-paketo-docs-java-buildpack={url-paketo-docs}/buildpacks/language-family-buildpacks/java url-protobuf-docs-gradle-plugin=https://github.com/google/protobuf-gradle-plugin url-pulsar-client-api-javadoc=https://javadoc.io/doc/org.apache.pulsar/pulsar-client-api/{version-pulsar-client-api} url-spring-boot-for-apache-geode-docs=https://docs.spring.io/spring-boot-data-geode-build/2.0.x/reference/html5 url-spring-boot-for-apache-geode-site=https://github.com/spring-projects/spring-boot-data-geode url-spring-data-cassandra-docs=https://docs.spring.io/spring-data/cassandra/reference/{antoraversion-spring-data-cassandra} url-spring-data-cassandra-site=https://spring.io/projects/spring-data-cassandra url-spring-data-cassandra-javadoc=https://docs.spring.io/spring-data/cassandra/docs/{dotxversion-spring-data-cassandra}/api url-spring-data-commons-javadoc=https://docs.spring.io/spring-data/commons/docs/{dotxversion-spring-data-commons}/api url-spring-data-couchbase-docs=https://docs.spring.io/spring-data/couchbase/reference/{antoraversion-spring-data-couchbase} url-spring-data-couchbase-site=https://spring.io/projects/spring-data-couchbase url-spring-data-couchbase-javadoc=https://docs.spring.io/spring-data/couchbase/docs/{dotxversion-spring-data-couchbase}/api url-spring-data-elasticsearch-docs=https://docs.spring.io/spring-data/elasticsearch/reference/{antoraversion-spring-data-elasticsearch} url-spring-data-elasticsearch-site=https://spring.io/projects/spring-data-elasticsearch url-spring-data-elasticsearch-javadoc=https://docs.spring.io/spring-data/elasticsearch/docs/{dotxversion-spring-data-elasticsearch}/api url-spring-data-envers-site=https://spring.io/projects/spring-data-envers url-spring-data-geode-site=https://spring.io/projects/spring-data-geode url-spring-data-jdbc-docs=https://docs.spring.io/spring-data/relational/reference/{antoraversion-spring-data-jdbc} url-spring-data-jdbc-site=https://spring.io/projects/spring-data-jdbc url-spring-data-jdbc-javadoc=https://docs.spring.io/spring-data/jdbc/docs/{dotxversion-spring-data-jdbc}/api url-spring-data-jpa-docs=https://docs.spring.io/spring-data/jpa/reference/{antoraversion-spring-data-jpa} url-spring-data-jpa-site=https://spring.io/projects/spring-data-jpa url-spring-data-jpa-javadoc=https://docs.spring.io/spring-data/jpa/docs/{dotxversion-spring-data-jpa}/api url-spring-data-ldap-docs=https://docs.spring.io/spring-data/ldap/reference/{antoraversion-spring-data-ldap} url-spring-data-ldap-site=https://spring.io/projects/spring-data-ldap url-spring-data-ldap-javadoc=https://docs.spring.io/spring-data/ldap/docs/{dotxversion-spring-data-ldap}/api url-spring-data-mongodb-docs=https://docs.spring.io/spring-data/mongodb/reference/{antoraversion-spring-data-mongodb} url-spring-data-mongodb-site=https://spring.io/projects/spring-data-mongodb url-spring-data-mongodb-javadoc=https://docs.spring.io/spring-data/mongodb/docs/{dotxversion-spring-data-mongodb}/api url-spring-data-neo4j-docs=https://docs.spring.io/spring-data/neo4j/reference/{antoraversion-spring-data-neo4j} url-spring-data-neo4j-site=https://spring.io/projects/spring-data-neo4j url-spring-data-neo4j-javadoc=https://docs.spring.io/spring-data/neo4j/docs/{dotxversion-spring-data-neo4j}/api url-spring-data-r2dbc-docs=https://docs.spring.io/spring-data/relational/reference/{antoraversion-spring-data-r2dbc} url-spring-data-r2dbc-site=https://spring.io/projects/spring-data-r2dbc url-spring-data-r2dbc-javadoc=https://docs.spring.io/spring-data/r2dbc/docs/{dotxversion-spring-data-r2dbc}/api url-spring-data-redis-docs=https://docs.spring.io/spring-data/redis/reference/{antoraversion-spring-data-redis} url-spring-data-redis-site=https://spring.io/projects/spring-data-redis url-spring-data-redis-javadoc=https://docs.spring.io/spring-data/redis/docs/{dotxversion-spring-data-redis}/api url-spring-data-rest-docs=https://docs.spring.io/spring-data/rest/reference/{antoraversion-spring-data-rest} url-spring-data-rest-site=https://spring.io/projects/spring-data-rest url-spring-data-rest-javadoc=https://docs.spring.io/spring-data/rest/docs/{dotxversion-spring-data-rest}/api url-spring-data-site=https://spring.io/projects/spring-data url-jackson-annotations-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/{version-jackson-annotations} url-jackson-core-javadoc=https://javadoc.io/doc/tools.jackson.core/jackson-core/{version-jackson-core} url-jackson-databind-javadoc=https://javadoc.io/doc/tools.jackson.core/jackson-databind/{version-jackson-databind} url-jackson-dataformat-xml-javadoc=https://javadoc.io/doc/tools.jackson.dataformat/jackson-dataformat-xml/{version-jackson-dataformat-xml} url-jackson2-databind-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/{version-jackson2-databind} # === Javadoc Locations === javadoc-location-com-fasterxml-jackson-annotation={url-jackson-annotations-javadoc} javadoc-location-com-fasterxml-jackson-databind={url-jackson2-databind-javadoc} javadoc-location-org-apache-pulsar-client-api={url-pulsar-client-api-javadoc} javadoc-location-org-springframework-data-cassandra={url-spring-data-cassandra-javadoc} javadoc-location-org-springframework-data-convert={url-spring-data-commons-javadoc} javadoc-location-org-springframework-data-querydsl={url-spring-data-commons-javadoc} javadoc-location-org-springframework-data-repository={url-spring-data-commons-javadoc} javadoc-location-org-springframework-data-couchbase={url-spring-data-couchbase-javadoc} javadoc-location-org-springframework-data-elasticsearch={url-spring-data-elasticsearch-javadoc} javadoc-location-org-springframework-data-jdbc={url-spring-data-jdbc-javadoc} javadoc-location-org-springframework-data-jpa={url-spring-data-jpa-javadoc} javadoc-location-org-springframework-data-ldap={url-spring-data-ldap-javadoc} javadoc-location-org-springframework-data-mongodb={url-spring-data-mongodb-javadoc} javadoc-location-org-springframework-data-neo4j={url-spring-data-neo4j-javadoc} javadoc-location-org-springframework-data-r2dbc={url-spring-data-r2dbc-javadoc} javadoc-location-org-springframework-data-redis={url-spring-data-redis-javadoc} javadoc-location-org-springframework-data-rest={url-spring-data-rest-javadoc} javadoc-location-tools-jackson-core={url-jackson-core-javadoc} javadoc-location-tools-jackson-databind={url-jackson-databind-javadoc} javadoc-location-tools-jackson-dataformat-xml={url-jackson-dataformat-xml-javadoc} # === API References === apiref-gradle-plugin-boot-build-image=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.html apiref-gradle-plugin-boot-jar=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/bundling/BootJar.html apiref-gradle-plugin-boot-run=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/run/BootRun.html apiref-gradle-plugin-boot-war=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/bundling/BootWar.html apiref-gradle-plugin-boot-build-info=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfo.html apiref-openjdk=https://docs.oracle.com/en/java/javase/17/docs/api # === Code Links === 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-batch-data-mongodb-src={code-spring-boot}/module/spring-boot-batch-data-mongodb/src/main/java/org/springframework/boot/batch/mongodb 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-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 ================================================ FILE: buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-playbook-template.yml ================================================ antora: extensions: site: title: Spring Boot content: sources: [] asciidoc: sourcemap: true attributes: chomp: all hide-uri-scheme: '@' javadoc-location: xref:api:java/ page-pagination: '' page-stackoverflow-url: https://stackoverflow.com/tags/spring-boot tabs-sync-option: '@' extensions: urls: latest_version_segment: '' runtime: log: failure_level: warn ================================================ FILE: buildSrc/src/main/resources/org/springframework/boot/build/legal/LICENSE.txt ================================================ Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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. ================================================ FILE: buildSrc/src/main/resources/org/springframework/boot/build/legal/NOTICE.txt ================================================ Spring Boot ${version} Copyright (c) 2012-2026 VMware, Inc. This product is licensed to you under the Apache License, Version 2.0 (the "License"). You may not use this product except in compliance with the License. ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/ConventionsPluginTests.java ================================================ /* * 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; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.Map; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link ConventionsPlugin}. * * @author Christoph Dreis */ class ConventionsPluginTests { private File projectDir; private File buildFile; @BeforeEach void setup(@TempDir File projectDir) throws IOException { this.projectDir = projectDir; this.buildFile = new File(this.projectDir, "build.gradle"); File settingsFile = new File(this.projectDir, "settings.gradle"); try (PrintWriter out = new PrintWriter(new FileWriter(settingsFile))) { out.println("plugins {"); out.println(" id 'com.gradle.develocity'"); out.println("}"); out.println("include ':platform:spring-boot-internal-dependencies'"); } File internalDependencies = new File(this.projectDir, "platform/spring-boot-internal-dependencies/build.gradle"); internalDependencies.getParentFile().mkdirs(); try (PrintWriter out = new PrintWriter(new FileWriter(internalDependencies))) { out.println("plugins {"); out.println(" id 'java-platform'"); out.println("}"); } } @Test void jarIncludesLegalFiles() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'java'"); out.println(" id 'org.springframework.boot.conventions'"); out.println("}"); out.println("version = '1.2.3'"); out.println("java {"); out.println(" sourceCompatibility = '17'"); out.println("}"); out.println("description 'Test project for manifest customization'"); out.println("jar.archiveFileName = 'test.jar'"); } runGradle("jar"); File file = new File(this.projectDir, "/build/libs/test.jar"); assertThat(file).exists(); try (JarFile jar = new JarFile(file)) { assertThatLicenseIsPresent(jar); assertThatNoticeIsPresent(jar); Attributes mainAttributes = jar.getManifest().getMainAttributes(); assertThat(mainAttributes.getValue("Implementation-Title")) .isEqualTo("Test project for manifest customization"); assertThat(mainAttributes.getValue("Automatic-Module-Name")) .isEqualTo(this.projectDir.getName().replace("-", ".")); assertThat(mainAttributes.getValue("Implementation-Version")).isEqualTo("1.2.3"); assertThat(mainAttributes.getValue("Built-By")).isEqualTo("Spring"); assertThat(mainAttributes.getValue("Build-Jdk-Spec")).isEqualTo("17"); } } @Test void sourceJarIsBuilt() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'java'"); out.println(" id 'maven-publish'"); out.println(" id 'org.springframework.boot.conventions'"); out.println("}"); out.println("version = '1.2.3'"); out.println("java {"); out.println(" sourceCompatibility = '17'"); out.println("}"); out.println("description 'Test'"); } runGradle("assemble"); File file = new File(this.projectDir, "/build/libs/" + this.projectDir.getName() + "-1.2.3-sources.jar"); assertThat(file).exists(); try (JarFile jar = new JarFile(file)) { assertThatLicenseIsPresent(jar); assertThatNoticeIsPresent(jar); Attributes mainAttributes = jar.getManifest().getMainAttributes(); assertThat(mainAttributes.getValue("Implementation-Title")) .isEqualTo("Source for " + this.projectDir.getName()); assertThat(mainAttributes.getValue("Automatic-Module-Name")) .isEqualTo(this.projectDir.getName().replace("-", ".")); assertThat(mainAttributes.getValue("Implementation-Version")).isEqualTo("1.2.3"); assertThat(mainAttributes.getValue("Built-By")).isEqualTo("Spring"); assertThat(mainAttributes.getValue("Build-Jdk-Spec")).isEqualTo("17"); } } @Test void javadocJarIsBuilt() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'java'"); out.println(" id 'maven-publish'"); out.println(" id 'org.springframework.boot.conventions'"); out.println("}"); out.println("version = '1.2.3'"); out.println("java {"); out.println(" sourceCompatibility = '17'"); out.println("}"); out.println("description 'Test'"); } runGradle("assemble"); File file = new File(this.projectDir, "/build/libs/" + this.projectDir.getName() + "-1.2.3-javadoc.jar"); assertThat(file).exists(); try (JarFile jar = new JarFile(file)) { assertThatLicenseIsPresent(jar); assertThatNoticeIsPresent(jar); Attributes mainAttributes = jar.getManifest().getMainAttributes(); assertThat(mainAttributes.getValue("Implementation-Title")) .isEqualTo("Javadoc for " + this.projectDir.getName()); assertThat(mainAttributes.getValue("Automatic-Module-Name")) .isEqualTo(this.projectDir.getName().replace("-", ".")); assertThat(mainAttributes.getValue("Implementation-Version")).isEqualTo("1.2.3"); assertThat(mainAttributes.getValue("Built-By")).isEqualTo("Spring"); assertThat(mainAttributes.getValue("Build-Jdk-Spec")).isEqualTo("17"); } } private void assertThatLicenseIsPresent(JarFile jar) throws IOException { JarEntry license = jar.getJarEntry("META-INF/LICENSE.txt"); assertThat(license).isNotNull(); String licenseContent = FileCopyUtils.copyToString(new InputStreamReader(jar.getInputStream(license))); assertThat(licenseContent).isEqualTo(Files.readString(Path.of("src", "main", "resources", "org", "springframework", "boot", "build", "legal", "LICENSE.txt"))); } private void assertThatNoticeIsPresent(JarFile jar) throws IOException { JarEntry notice = jar.getJarEntry("META-INF/NOTICE.txt"); assertThat(notice).isNotNull(); String noticeContent = FileCopyUtils.copyToString(new InputStreamReader(jar.getInputStream(notice))); // Test that variables were replaced assertThat(noticeContent).doesNotContain("${"); } @Test void testRetryIsConfiguredWithThreeRetriesOnCI() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'java'"); out.println(" id 'org.springframework.boot.conventions'"); out.println("}"); out.println("description 'Test'"); out.println("task retryConfig {"); out.println(" doLast {"); out.println(" test.develocity.testRetry {"); out.println(" println \"maxRetries: ${maxRetries.get()}\""); out.println(" println \"failOnPassedAfterRetry: ${failOnPassedAfterRetry.get()}\""); out.println(" }"); out.println(" }"); out.println("}"); } assertThat(runGradle(Collections.singletonMap("CI", "true"), "retryConfig", "--stacktrace").getOutput()) .contains("maxRetries: 3") .contains("failOnPassedAfterRetry: false"); } @Test void testRetryIsConfiguredWithZeroRetriesLocally() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'java'"); out.println(" id 'org.springframework.boot.conventions'"); out.println("}"); out.println("description 'Test'"); out.println("task retryConfig {"); out.println(" doLast {"); out.println(" test.develocity.testRetry {"); out.println(" println \"maxRetries: ${maxRetries.get()}\""); out.println(" println \"failOnPassedAfterRetry: ${failOnPassedAfterRetry.get()}\""); out.println(" }"); out.println(" }"); out.println("}"); } assertThat(runGradle(Collections.singletonMap("CI", "local"), "retryConfig", "--stacktrace").getOutput()) .contains("maxRetries: 0") .contains("failOnPassedAfterRetry: false"); } private BuildResult runGradle(String... args) { return runGradle(Collections.emptyMap(), args); } private BuildResult runGradle(Map environment, String... args) { return GradleRunner.create() .withProjectDir(this.projectDir) .withEnvironment(environment) .withArguments(args) .withPluginClasspath() .build(); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java ================================================ /* * 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.antora; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import org.junit.jupiter.api.Test; import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.Library.BomAlignment; import org.springframework.boot.build.bom.Library.Group; import org.springframework.boot.build.bom.Library.LibraryVersion; import org.springframework.boot.build.bom.Library.Link; import org.springframework.boot.build.bom.Library.ProhibitedVersion; import org.springframework.boot.build.bom.Library.VersionAlignment; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.boot.build.properties.BuildType; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link AntoraAsciidocAttributes}. * * @author Phillip Webb * @author Stephane Nicoll */ class AntoraAsciidocAttributesTests { @Test void buildTypeWhenOpenSource() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("build-type", "opensource"); } @Test void buildTypeWhenCommercial() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.COMMERCIAL, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("build-type", "commercial"); } @Test void githubRefWhenReleasedVersionIsTag() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("github-ref", "v1.2.3"); } @Test void githubRefWhenLatestSnapshotVersionIsMainBranch() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("github-ref", "main"); } @Test void githubRefWhenOlderSnapshotVersionIsBranch() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", false, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("github-ref", "1.2.x"); } @Test void githubRefWhenOlderSnapshotHotFixVersionIsBranch() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3.1-SNAPSHOT", false, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("github-ref", "1.2.3.x"); } @Test void versionReferenceFromLibrary() { Library library = mockLibrary(Collections.emptyMap()); AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3.1-SNAPSHOT", false, BuildType.OPEN_SOURCE, List.of(library), mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("version-spring-framework", "1.2.3"); } @Test void versionReferenceFromSpringDataDependencyReleaseVersion() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions("3.2.5"), null); assertThat(attributes.get()).containsEntry("version-spring-data-mongodb", "3.2.5"); assertThat(attributes.get()).containsEntry("url-spring-data-mongodb-docs", "https://docs.spring.io/spring-data/mongodb/reference/3.2"); assertThat(attributes.get()).containsEntry("url-spring-data-mongodb-javadoc", "https://docs.spring.io/spring-data/mongodb/docs/3.2.x/api"); } @Test void versionReferenceFromSpringDataDependencySnapshotVersion() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions("3.2.0-SNAPSHOT"), null); assertThat(attributes.get()).containsEntry("version-spring-data-mongodb", "3.2.0-SNAPSHOT"); assertThat(attributes.get()).containsEntry("url-spring-data-mongodb-docs", "https://docs.spring.io/spring-data/mongodb/reference/3.2-SNAPSHOT"); assertThat(attributes.get()).containsEntry("url-spring-data-mongodb-javadoc", "https://docs.spring.io/spring-data/mongodb/docs/3.2.x/api"); } @Test void versionNativeBuildTools() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), Map.of("nativeBuildToolsVersion", "3.4.5")); assertThat(attributes.get()).containsEntry("version-native-build-tools", "3.4.5"); } @Test void urlArtifactRepositoryWhenRelease() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.maven.apache.org/maven2"); } @Test void urlArtifactRepositoryWhenMilestone() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-M1", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.maven.apache.org/maven2"); } @Test void urlArtifactRepositoryWhenSnapshot() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.spring.io/snapshot"); } @Test void artifactReleaseTypeWhenOpenSourceRelease() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("artifact-release-type", "release"); assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "opensource-release"); } @Test void artifactReleaseTypeWhenOpenSourceMilestone() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-M1", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("artifact-release-type", "milestone"); assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "opensource-milestone"); } @Test void artifactReleaseTypeWhenOpenSourceSnapshot() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("artifact-release-type", "snapshot"); assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "opensource-snapshot"); } @Test void artifactReleaseTypeWhenCommercialRelease() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.COMMERCIAL, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("artifact-release-type", "release"); assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "commercial-release"); } @Test void artifactReleaseTypeWhenCommercialMilestone() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-M1", true, BuildType.COMMERCIAL, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("artifact-release-type", "milestone"); assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "commercial-milestone"); } @Test void artifactReleaseTypeWhenCommercialSnapshot() { AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, BuildType.COMMERCIAL, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("artifact-release-type", "snapshot"); assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "commercial-snapshot"); } @Test void urlLinksFromLibrary() { Map> links = new LinkedHashMap<>(); links.put("site", singleLink((version) -> "https://example.com/site/" + version)); links.put("docs", singleLink((version) -> "https://example.com/docs/" + version)); links.put("javadoc", singleLink((version) -> "https://example.com/api/" + version, "org.springframework.[core|util]")); Library library = mockLibrary(links); AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3.1-SNAPSHOT", false, BuildType.OPEN_SOURCE, List.of(library), mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("url-spring-framework-site", "https://example.com/site/1.2.3") .containsEntry("url-spring-framework-docs", "https://example.com/docs/1.2.3") .containsEntry("url-spring-framework-javadoc", "https://example.com/api/1.2.3"); assertThat(attributes.get()) .containsEntry("javadoc-location-org-springframework-core", "{url-spring-framework-javadoc}") .containsEntry("javadoc-location-org-springframework-util", "{url-spring-framework-javadoc}"); } private List singleLink(Function factory, String... packages) { Link link = new Link(null, factory, List.of(packages)); return List.of(link); } @Test void linksFromProperties() { Map attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null) .get(); assertThat(attributes).containsEntry("include-java", "ROOT:example$java/org/springframework/boot/docs"); assertThat(attributes).containsEntry("url-spring-data-cassandra-site", "https://spring.io/projects/spring-data-cassandra"); List keys = new ArrayList<>(attributes.keySet()); assertThat(keys.indexOf("include-java")).isLessThan(keys.indexOf("code-spring-boot-latest")); } private Library mockLibrary(Map> links) { String name = "Spring Framework"; String calendarName = null; LibraryVersion version = new LibraryVersion(DependencyVersion.parse("1.2.3")); List groups = Collections.emptyList(); List prohibitedVersion = Collections.emptyList(); boolean considerSnapshots = false; VersionAlignment versionAlignment = null; BomAlignment alignsWithBom = null; String linkRootName = null; Library library = new Library(name, calendarName, version, groups, null, prohibitedVersion, considerSnapshots, versionAlignment, alignsWithBom, linkRootName, links); return library; } private Map mockDependencyVersions() { return mockDependencyVersions("1.2.3"); } private Map mockDependencyVersions(String version) { Map versions = new LinkedHashMap<>(); addMockSpringDataVersion(versions, "spring-data-commons", version); addMockSpringDataVersion(versions, "spring-data-cassandra", version); addMockSpringDataVersion(versions, "spring-data-couchbase", version); addMockSpringDataVersion(versions, "spring-data-elasticsearch", version); addMockSpringDataVersion(versions, "spring-data-jdbc", version); addMockSpringDataVersion(versions, "spring-data-jpa", version); addMockSpringDataVersion(versions, "spring-data-mongodb", version); addMockSpringDataVersion(versions, "spring-data-neo4j", version); addMockSpringDataVersion(versions, "spring-data-r2dbc", version); addMockSpringDataVersion(versions, "spring-data-redis", version); addMockSpringDataVersion(versions, "spring-data-rest-core", version); addMockSpringDataVersion(versions, "spring-data-ldap", version); addMockTestcontainersVersion(versions, "activemq", version); addMockTestcontainersVersion(versions, "cassandra", version); addMockTestcontainersVersion(versions, "clickhouse", version); addMockTestcontainersVersion(versions, "couchbase", version); addMockTestcontainersVersion(versions, "elasticsearch", version); addMockTestcontainersVersion(versions, "grafana", version); addMockTestcontainersVersion(versions, "jdbc", version); addMockTestcontainersVersion(versions, "kafka", version); addMockTestcontainersVersion(versions, "mariadb", version); addMockTestcontainersVersion(versions, "mongodb", version); addMockTestcontainersVersion(versions, "mssqlserver", version); addMockTestcontainersVersion(versions, "mysql", version); addMockTestcontainersVersion(versions, "neo4j", version); addMockTestcontainersVersion(versions, "oracle-xe", version); addMockTestcontainersVersion(versions, "oracle-free", version); addMockTestcontainersVersion(versions, "postgresql", version); addMockTestcontainersVersion(versions, "pulsar", version); addMockTestcontainersVersion(versions, "rabbitmq", version); addMockTestcontainersVersion(versions, "redpanda", version); addMockTestcontainersVersion(versions, "r2dbc", version); addMockJackson2CoreVersion(versions, "jackson-annotations", version); addMockJackson2CoreVersion(versions, "jackson-databind", version); addMockJacksonCoreVersion(versions, "jackson-core", version); addMockJacksonCoreVersion(versions, "jackson-databind", version); addMockJacksonCoreVersion(versions, "jackson-databind", version); versions.put("org.apache.pulsar:pulsar-client-api", version); versions.put("tools.jackson.dataformat:jackson-dataformat-xml", version); return versions; } private void addMockSpringDataVersion(Map versions, String artifactId, String version) { versions.put("org.springframework.data:" + artifactId, version); } private void addMockTestcontainersVersion(Map versions, String artifactId, String version) { versions.put("org.testcontainers:" + artifactId, version); } private void addMockJackson2CoreVersion(Map versions, String artifactId, String version) { versions.put("com.fasterxml.jackson.core:" + artifactId, version); } private void addMockJacksonCoreVersion(Map versions, String artifactId, String version) { versions.put("tools.jackson.core:" + artifactId, version); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/antora/GenerateAntoraPlaybookTests.java ================================================ /* * 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.antora; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import org.gradle.api.Project; import org.gradle.testfixtures.ProjectBuilder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.build.antora.Extensions.AntoraExtensionsConfiguration.ZipContentsCollector.AlwaysInclude; import org.springframework.boot.build.antora.GenerateAntoraPlaybook.AntoraExtensions.ZipContentsCollector; import org.springframework.util.function.ThrowingConsumer; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link GenerateAntoraPlaybook}. * * @author Phillip Webb */ class GenerateAntoraPlaybookTests { @TempDir File temp; @Test void writePlaybookGeneratesExpectedContent() throws Exception { writePlaybookYml((task) -> { task.getAntoraExtensions().getXref().getStubs().addAll("appendix:.*", "api:.*", "reference:.*"); ZipContentsCollector zipContentsCollector = task.getAntoraExtensions().getZipContentsCollector(); zipContentsCollector.getAlwaysInclude().set(List.of(new AlwaysInclude("test", "local-aggregate-content"))); zipContentsCollector.getDependencies().add("test-dependency"); }); String actual = Files.readString(this.temp.toPath() .resolve("rootproject/project/build/generated/docs/antora-playbook/antora-playbook.yml")); String expected = Files .readString(Path.of("src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml")); assertThat(actual.replace('\\', '/')).isEqualToNormalizingNewlines(expected.replace('\\', '/')); } @Test void writePlaybookWhenHasJavadocExcludeGeneratesExpectedContent() throws Exception { writePlaybookYml((task) -> { task.getAntoraExtensions().getXref().getStubs().addAll("appendix:.*", "api:.*", "reference:.*"); ZipContentsCollector zipContentsCollector = task.getAntoraExtensions().getZipContentsCollector(); zipContentsCollector.getAlwaysInclude().set(List.of(new AlwaysInclude("test", "local-aggregate-content"))); zipContentsCollector.getDependencies().add("test-dependency"); task.getAsciidocExtensions().getExcludeJavadocExtension().set(true); }); String actual = Files.readString(this.temp.toPath() .resolve("rootproject/project/build/generated/docs/antora-playbook/antora-playbook.yml")); assertThat(actual).doesNotContain("javadoc-extension"); } private void writePlaybookYml(ThrowingConsumer customizer) throws Exception { File rootProjectDir = new File(this.temp, "rootproject").getCanonicalFile(); rootProjectDir.mkdirs(); Project rootProject = ProjectBuilder.builder().withProjectDir(rootProjectDir).build(); File projectDir = new File(rootProjectDir, "project"); projectDir.mkdirs(); Project project = ProjectBuilder.builder().withProjectDir(projectDir).withParent(rootProject).build(); project.getTasks() .register("generateAntoraPlaybook", GenerateAntoraPlaybook.class, customizer::accept) .get() .writePlaybookYml(); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java ================================================ /* * 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; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import org.gradle.api.tasks.SourceSet; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; import org.gradle.testkit.runner.TaskOutcome; import org.gradle.testkit.runner.UnexpectedBuildFailure; import org.gradle.testkit.runner.UnexpectedBuildSuccess; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.boot.build.architecture.annotations.TestConditionalOnClass; import org.springframework.boot.build.architecture.annotations.TestConditionalOnMissingBean; import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties; import org.springframework.boot.build.architecture.annotations.TestConfigurationPropertiesBinding; import org.springframework.boot.build.architecture.annotations.TestDeprecatedConfigurationProperty; import org.springframework.util.ClassUtils; import org.springframework.util.FileSystemUtils; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ArchitectureCheck}. * * @author Andy Wilkinson * @author Scott Frederick * @author Ivan Malutin * @author Dmytro Nosan * @author Stefano Cordio */ class ArchitectureCheckTests { private static final String ASSERTJ_CORE = "org.assertj:assertj-core:3.27.4"; private static final String JUNIT_JUPITER = "org.junit.jupiter:junit-jupiter:5.12.0"; private static final String SPRING_CONTEXT = "org.springframework:spring-context:6.2.15"; private static final String SPRING_CORE = "org.springframework:spring-core:6.2.15"; private static final String SPRING_INTEGRATION_JMX = "org.springframework.integration:spring-integration-jmx:6.5.1"; private GradleBuild gradleBuild; @BeforeEach void setup(@TempDir Path projectDir) { this.gradleBuild = new GradleBuild(projectDir); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenPackagesAreTangledShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "tangled"); buildAndFail(this.gradleBuild, task, "slices matching '(**)' should be free of cycles"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenPackagesAreNotTangledShouldSucceedAndWriteEmptyReport(Task task) throws IOException { prepareTask(task, "untangled"); build(this.gradleBuild, task); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenBeanPostProcessorBeanMethodIsNotStaticShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "bpp/nonstatic"); buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task, "methods that are annotated with @Bean and have raw return type assignable" + " to org.springframework.beans.factory.config.BeanPostProcessor"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenBeanPostProcessorBeanMethodIsStaticAndHasUnsafeParametersShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "bpp/unsafeparameters"); buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task, "methods that are annotated with @Bean and have raw return type assignable" + " to org.springframework.beans.factory.config.BeanPostProcessor"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenBeanPostProcessorBeanMethodIsStaticAndHasSafeParametersShouldSucceedAndWriteEmptyReport(Task task) throws IOException { prepareTask(task, "bpp/safeparameters"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenBeanPostProcessorBeanMethodIsStaticAndHasNoParametersShouldSucceedAndWriteEmptyReport(Task task) throws IOException { prepareTask(task, "bpp/noparameters"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenBeanFactoryPostProcessorBeanMethodIsNotStaticShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "bfpp/nonstatic"); buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task, "methods that are annotated with @Bean and have raw return type assignable" + " to org.springframework.beans.factory.config.BeanFactoryPostProcessor"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasParametersShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "bfpp/parameters"); buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task, "methods that are annotated with @Bean and have raw return type assignable" + " to org.springframework.beans.factory.config.BeanFactoryPostProcessor"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasNoParametersShouldSucceedAndWriteEmptyReport(Task task) throws IOException { prepareTask(task, "bfpp/noparameters"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassLoadsResourceUsingResourceUtilsShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "resources/loads"); buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task, "no classes should call method where target owner type" + " org.springframework.util.ResourceUtils and target name 'getURL'"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassUsesResourceUtilsWithoutLoadingResourcesShouldSucceedAndWriteEmptyReport(Task task) throws IOException { prepareTask(task, "resources/noloads"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassDoesNotCallObjectsRequireNonNullShouldSucceedAndWriteEmptyReport(Task task) throws IOException { prepareTask(task, "objects/noRequireNonNull"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassCallsObjectsRequireNonNullWithMessageShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "objects/requireNonNullWithString"); buildAndFail(this.gradleBuild, task, "no classes should call method Objects.requireNonNull(Object, String)"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassCallsObjectsRequireNonNullWithMessageAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport( Task task) throws IOException { prepareTask(task, "objects/requireNonNullWithString"); build(this.gradleBuild.withProhibitObjectsRequireNonNull(false), task); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassCallsObjectsRequireNonNullWithSupplierShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "objects/requireNonNullWithSupplier"); buildAndFail(this.gradleBuild, task, "no classes should call method Objects.requireNonNull(Object, Supplier)"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassCallsObjectsRequireNonNullWithSupplierAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport( Task task) throws IOException { prepareTask(task, "objects/requireNonNullWithSupplier"); build(this.gradleBuild.withProhibitObjectsRequireNonNull(false), task); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassCallsCollectorsToListShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "collectors/toList"); buildAndFail(this.gradleBuild, task, "because java.util.stream.Stream.toList() should be used instead"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassCallsUrlEncoderWithStringEncodingShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "url/encode"); buildAndFail(this.gradleBuild, task, "because java.net.URLEncoder.encode(String s, Charset charset) should be used instead"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassCallsUrlDecoderWithStringEncodingShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "url/decode"); buildAndFail(this.gradleBuild, task, "because java.net.URLDecoder.decode(String s, Charset charset) should be used instead"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassCallsStringToUpperCaseWithoutLocaleShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "string/toUpperCase"); buildAndFail(this.gradleBuild, task, "because String.toUpperCase(Locale.ROOT) should be used instead"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassCallsStringToLowerCaseWithoutLocaleShouldFailAndWriteReport(Task task) throws IOException { prepareTask(task, "string/toLowerCase"); buildAndFail(this.gradleBuild, task, "because String.toLowerCase(Locale.ROOT) should be used instead"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassCallsStringToLowerCaseWithLocaleShouldSucceedAndWriteEmptyReport(Task task) throws IOException { prepareTask(task, "string/toLowerCaseWithLocale"); build(this.gradleBuild, task); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenClassCallsStringToUpperCaseWithLocaleShouldSucceedAndWriteEmptyReport(Task task) throws IOException { prepareTask(task, "string/toUpperCaseWithLocale"); build(this.gradleBuild, task); } @Test void whenConditionalOnMissingBeanWithTypeSameAsMethodReturnTypeShouldFailAndWriteReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "conditionalonmissingbean/valueonly", "annotations"); buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConditionalOnMissingBeanAnnotation(), Task.CHECK_ARCHITECTURE_MAIN, "should not specify only a value that is the same as the method's return type"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenConditionalOnMissingBeanWithTypeAttributeShouldSucceedAndWriteEmptyReport(Task task) throws IOException { prepareTask(task, "conditionalonmissingbean/withtype", "annotations"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenConditionalOnMissingBeanWithNameAttributeShouldSucceedAndWriteEmptyReport(Task task) throws IOException { prepareTask(task, "conditionalonmissingbean/withname", "annotations"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task); } @Test void whenClassLevelConfigurationPropertiesContainsOnlyPrefixShouldFailAndWriteReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/classprefixonly", "annotations"); buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesAnnotation(), Task.CHECK_ARCHITECTURE_MAIN, "should specify implicit 'value' attribute other than explicit 'prefix' attribute"); } @Test void whenClassLevelConfigurationPropertiesContainsPrefixAndIgnoreShouldSucceedAndWriteEmptyReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/classprefixandignore", "annotations"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesAnnotation(), Task.CHECK_ARCHITECTURE_MAIN); } @Test void whenClassLevelConfigurationPropertiesContainsOnlyValueShouldSucceedAndWriteEmptyReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/classvalueonly", "annotations"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesAnnotation(), Task.CHECK_ARCHITECTURE_MAIN); } @Test void whenMethodLevelConfigurationPropertiesContainsOnlyPrefixShouldFailAndWriteReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/methodprefixonly", "annotations"); buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesAnnotation(), Task.CHECK_ARCHITECTURE_MAIN, "should specify implicit 'value' attribute other than explicit 'prefix' attribute"); } @Test void whenMethodLevelConfigurationPropertiesContainsPrefixAndIgnoreShouldSucceedAndWriteEmptyReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/methodprefixandignore", "annotations"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesAnnotation(), Task.CHECK_ARCHITECTURE_MAIN); } @Test void whenMethodLevelConfigurationPropertiesContainsOnlyValueShouldSucceedAndWriteEmptyReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/methodvalueonly", "annotations"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesAnnotation(), Task.CHECK_ARCHITECTURE_MAIN); } @Test void whenConfigurationPropertiesBindingBeanMethodIsNotStaticShouldFailAndWriteReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/bindingnonstatic", "annotations"); buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesBindingAnnotation(), Task.CHECK_ARCHITECTURE_MAIN, "does not have modifier STATIC"); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenBeanPostProcessorBeanMethodIsNotStaticWithExternalClassShouldFailAndWriteReport(Task task) throws IOException { Path sourceDirectory = task.getSourceDirectory(this.gradleBuild.getProjectDir()) .resolve(ClassUtils.classPackageAsResourcePath(getClass())); Files.createDirectories(sourceDirectory); Files.writeString(sourceDirectory.resolve("TestClass.java"), """ package %s; import org.springframework.context.annotation.Bean; import org.springframework.integration.monitor.IntegrationMBeanExporter; public class TestClass { @Bean IntegrationMBeanExporter integrationMBeanExporter() { return new IntegrationMBeanExporter(); } } """.formatted(ClassUtils.getPackageName(getClass()))); buildAndFail(this.gradleBuild.withDependencies(SPRING_INTEGRATION_JMX), task, "methods that are annotated with @Bean and have raw return type assignable " + "to org.springframework.beans.factory.config.BeanPostProcessor"); } @Test void whenBeanMethodExposesPrivateTypeWithMainSourcesShouldFailAndWriteReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "beans/privatebean"); buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), Task.CHECK_ARCHITECTURE_MAIN, "methods that are annotated with @Bean should not return types declared " + "with the PRIVATE modifier, as such types are incompatible with Spring AOT processing", "returns Class " + " which is declared as [PRIVATE, STATIC, FINAL]"); } @Test void whenBeanMethodExposesPrivateTypeWithTestsSourcesShouldSucceedAndWriteEmptyReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_TEST, "beans/privatebean"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT), Task.CHECK_ARCHITECTURE_TEST); } @ParameterizedTest(name = "{0}") @EnumSource(Task.class) void whenBeanMethodExposesNonPrivateTypeShouldSucceedAndWriteEmptyReport(Task task) throws IOException { prepareTask(task, "beans/regular"); build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task); } @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 ", "should not have a value that is the same as the type of the method's first parameter"); } @Test void whenConditionalOnClassUsedOnBeanMethodsWithMainSourcesShouldFailAndWriteReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "conditionalonclass", "annotations"); GradleBuild gradleBuild = this.gradleBuild.withDependencies(SPRING_CONTEXT).withConditionalOnClassAnnotation(); buildAndFail(gradleBuild, Task.CHECK_ARCHITECTURE_MAIN, "because @ConditionalOnClass on @Bean methods is ineffective - it doesn't prevent" + " the method signature from being loaded. Such condition need to be placed" + " on a @Configuration class, allowing the condition to back off before the type is loaded"); } @Test void whenConditionalOnClassUsedOnBeanMethodsWithTestSourcesShouldSucceedAndWriteEmptyReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_TEST, "conditionalonclass", "annotations"); GradleBuild gradleBuild = this.gradleBuild.withDependencies(SPRING_CONTEXT).withConditionalOnClassAnnotation(); build(gradleBuild, Task.CHECK_ARCHITECTURE_TEST); } @Test void whenDeprecatedConfigurationPropertyIsMissingSinceShouldFailAndWriteReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/deprecatedsince", "annotations"); GradleBuild gradleBuild = this.gradleBuild.withDependencies(SPRING_CONTEXT) .withDeprecatedConfigurationPropertyAnnotation(); buildAndFail(gradleBuild, Task.CHECK_ARCHITECTURE_MAIN, "should include a non-empty 'since' attribute of @DeprecatedConfigurationProperty", "DeprecatedConfigurationPropertySince.getProperty"); } @Test void whenCustomAssertionMethodNotReturningSelfIsAnnotatedWithCheckReturnValueShouldSucceedAndWriteEmptyReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "assertj/checkReturnValue"); build(this.gradleBuild.withDependencies(ASSERTJ_CORE, SPRING_CORE), Task.CHECK_ARCHITECTURE_MAIN); } @Test void whenCustomAssertionMethodNotReturningSelfIsNotAnnotatedWithCheckReturnValueShouldFailAndWriteReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "assertj/noCheckReturnValue"); buildAndFail(this.gradleBuild.withDependencies(ASSERTJ_CORE), Task.CHECK_ARCHITECTURE_MAIN, "methods that are declared in classes that implement org.assertj.core.api.Assert and " + "are public and do not have modifier BRIDGE and do not return self type should be annotated " + "with @CheckReturnValue"); } private void prepareTask(Task task, String... sourceDirectories) throws IOException { for (String sourceDirectory : sourceDirectories) { FileSystemUtils.copyRecursively( Paths.get("src/test/java") .resolve(ClassUtils.classPackageAsResourcePath(getClass())) .resolve(sourceDirectory), task.getSourceDirectory(this.gradleBuild.getProjectDir()) .resolve(ClassUtils.classPackageAsResourcePath(getClass())) .resolve(sourceDirectory)); } } private void build(GradleBuild gradleBuild, Task task) throws IOException { try { BuildResult buildResult = gradleBuild.build(task.toString()); assertThat(buildResult.taskPaths(TaskOutcome.SUCCESS)).as(buildResult.getOutput()).contains(":" + task); assertThat(task.getFailureReport(gradleBuild.getProjectDir())).isEmpty(); } catch (UnexpectedBuildFailure ex) { StringBuilder message = new StringBuilder("Expected build to succeed but it failed"); if (Files.exists(task.getFailureReportFile(gradleBuild.getProjectDir()))) { message.append('\n').append(task.getFailureReport(gradleBuild.getProjectDir())); } message.append('\n').append(ex.getBuildResult().getOutput()); throw new AssertionError(message.toString(), ex); } } private void buildAndFail(GradleBuild gradleBuild, Task task, String... messages) throws IOException { try { BuildResult buildResult = gradleBuild.buildAndFail(task.toString()); assertThat(buildResult.taskPaths(TaskOutcome.FAILED)).as(buildResult.getOutput()).contains(":" + task); try { assertThat(task.getFailureReport(gradleBuild.getProjectDir())).contains(messages); } catch (NoSuchFileException ex) { throw new AssertionError("Expected failure report not found\n" + buildResult.getOutput()); } } catch (UnexpectedBuildSuccess ex) { throw new AssertionError("Expected build to fail but it succeeded\n" + ex.getBuildResult().getOutput(), ex); } } private enum Task { CHECK_ARCHITECTURE_MAIN(SourceSet.MAIN_SOURCE_SET_NAME), CHECK_ARCHITECTURE_TEST(SourceSet.TEST_SOURCE_SET_NAME); private final String sourceSetName; Task(String sourceSetName) { this.sourceSetName = sourceSetName; } String getFailureReport(Path projectDir) throws IOException { return Files.readString(getFailureReportFile(projectDir), StandardCharsets.UTF_8); } Path getFailureReportFile(Path projectDir) { return projectDir.resolve("build/%s/failure-report.txt".formatted(toString())); } Path getSourceDirectory(Path projectDir) { return projectDir.resolve("src/%s/java".formatted(this.sourceSetName)); } @Override public String toString() { return "checkArchitecture" + StringUtils.capitalize(this.sourceSetName) + "Java"; } } private static final class GradleBuild { private final Path projectDir; private final Set dependencies = new LinkedHashSet<>(); private final Map taskConfigurations = new LinkedHashMap<>(); private GradleBuild(Path projectDir) { this.projectDir = projectDir; } Path getProjectDir() { return this.projectDir; } GradleBuild withProhibitObjectsRequireNonNull(Boolean prohibitObjectsRequireNonNull) { for (Task task : Task.values()) { configureTask(task, (configuration) -> configuration .withProhibitObjectsRequireNonNull(prohibitObjectsRequireNonNull)); } return this; } GradleBuild withConditionalOnClassAnnotation() { configureTasks(ArchitectureCheckAnnotation.CONDITIONAL_ON_CLASS.name(), TestConditionalOnClass.class.getName()); return this; } GradleBuild withConditionalOnMissingBeanAnnotation() { configureTasks(ArchitectureCheckAnnotation.CONDITIONAL_ON_MISSING_BEAN.name(), TestConditionalOnMissingBean.class.getName()); return this; } GradleBuild withConfigurationPropertiesAnnotation() { configureTasks(ArchitectureCheckAnnotation.CONFIGURATION_PROPERTIES.name(), TestConfigurationProperties.class.getName()); return this; } GradleBuild withConfigurationPropertiesBindingAnnotation() { configureTasks(ArchitectureCheckAnnotation.CONFIGURATION_PROPERTIES_BINDING.name(), TestConfigurationPropertiesBinding.class.getName()); return this; } GradleBuild withDeprecatedConfigurationPropertyAnnotation() { configureTasks(ArchitectureCheckAnnotation.DEPRECATED_CONFIGURATION_PROPERTY.name(), TestDeprecatedConfigurationProperty.class.getName()); return this; } private void configureTasks(String annotationName, String annotationClass) { for (Task task : Task.values()) { configureTask(task, (configuration) -> configuration.withAnnotation(annotationName, annotationClass)); } } private void configureTask(Task task, UnaryOperator configurer) { this.taskConfigurations.computeIfAbsent(task, (key) -> new TaskConfiguration(null, null)); this.taskConfigurations.compute(task, (key, value) -> configurer.apply(value)); } GradleBuild withDependencies(String... dependencies) { this.dependencies.clear(); this.dependencies.addAll(Arrays.asList(dependencies)); return this; } BuildResult build(String... arguments) throws IOException { return prepareRunner(arguments).build(); } BuildResult buildAndFail(String... arguments) throws IOException { return prepareRunner(arguments).buildAndFail(); } private GradleRunner prepareRunner(String... arguments) throws IOException { StringBuilder buildFile = new StringBuilder(); buildFile.append("plugins {\n") .append(" id 'java'\n") .append(" id 'org.springframework.boot.architecture'\n") .append("}\n\n") .append("repositories {\n") .append(" mavenCentral()\n") .append("}\n\n") .append("java {\n") .append(" sourceCompatibility = '17'\n") .append(" targetCompatibility = '17'\n") .append("}\n\n"); if (!this.dependencies.isEmpty()) { buildFile.append("dependencies {\n"); for (String dependency : this.dependencies) { buildFile.append("\n implementation ").append(StringUtils.quote(dependency)); } buildFile.append("\n}\n\n"); } this.taskConfigurations.forEach((task, configuration) -> { buildFile.append(task).append(" {"); if (configuration.prohibitObjectsRequireNonNull() != null) { buildFile.append("\n prohibitObjectsRequireNonNull = ") .append(configuration.prohibitObjectsRequireNonNull()); } if (configuration.annotations() != null && !configuration.annotations().isEmpty()) { buildFile.append("\n annotationClasses = ") .append(toGroovyMapString(configuration.annotations())); } buildFile.append("\n}\n"); }); Files.writeString(this.projectDir.resolve("build.gradle"), buildFile, StandardCharsets.UTF_8); return GradleRunner.create() .withProjectDir(this.projectDir.toFile()) .withArguments(arguments) .withPluginClasspath(); } static String toGroovyMapString(Map map) { return map.entrySet() .stream() .map((entry) -> "'" + entry.getKey() + "' : '" + entry.getValue() + "'") .collect(Collectors.joining(", ", "[", "]")); } private record TaskConfiguration(Boolean prohibitObjectsRequireNonNull, Map annotations) { public TaskConfiguration { if (annotations == null) { annotations = new HashMap<>(); } } private TaskConfiguration withProhibitObjectsRequireNonNull(Boolean prohibitObjectsRequireNonNull) { return new TaskConfiguration(prohibitObjectsRequireNonNull, this.annotations); } private TaskConfiguration withAnnotation(String name, String annotationClass) { Map map = new HashMap<>(this.annotations); map.put(name, annotationClass); return new TaskConfiguration(this.prohibitObjectsRequireNonNull, map); } } } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConditionalOnClass.java ================================================ /* * 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.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * {@code @ConditionalOnClass} analogue for architecture checks. * */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface TestConditionalOnClass { Class[] value() default {}; String[] name() default {}; } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConditionalOnMissingBean.java ================================================ /* * 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.annotations; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * {@code @ConditionalOnMissingBean} analogue for architecture checks. */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface TestConditionalOnMissingBean { Class[] value() default {}; String[] type() default {}; Class[] ignored() default {}; String[] ignoredType() default {}; Class[] annotation() default {}; String[] name() default {}; Class[] parameterizedContainer() default {}; } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConfigurationProperties.java ================================================ /* * 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.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; import org.springframework.stereotype.Indexed; @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Indexed public @interface TestConfigurationProperties { @AliasFor("prefix") String value() default ""; @AliasFor("value") String prefix() default ""; boolean ignoreInvalidFields() default false; boolean ignoreUnknownFields() default true; } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConfigurationPropertiesBinding.java ================================================ /* * 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.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface TestConfigurationPropertiesBinding { } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestDeprecatedConfigurationProperty.java ================================================ /* * 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.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * {@code @DeprecatedConfigurationProperty} analogue for architecture checks. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TestDeprecatedConfigurationProperty { /** * The reason for the deprecation. * @return the deprecation reason */ String reason() default ""; /** * The field that should be used instead (if any). * @return the replacement field */ String replacement() default ""; /** * The version in which the property became deprecated. * @return the version */ String since() default ""; } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/assertj/checkReturnValue/WithCheckReturnValue.java ================================================ /* * 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.assertj.checkReturnValue; import org.assertj.core.api.AbstractAssert; import org.springframework.lang.CheckReturnValue; public class WithCheckReturnValue extends AbstractAssert { WithCheckReturnValue() { super(null, WithCheckReturnValue.class); } @CheckReturnValue public Object notReturningSelf() { return new Object(); } @Override public WithCheckReturnValue isEqualTo(Object expected) { return super.isEqualTo(expected); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/assertj/noCheckReturnValue/NoCheckReturnValue.java ================================================ /* * 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.assertj.noCheckReturnValue; import org.assertj.core.api.AbstractAssert; public class NoCheckReturnValue extends AbstractAssert { NoCheckReturnValue() { super(null, NoCheckReturnValue.class); } public Object notReturningSelf() { return new Object(); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/beans/privatebean/PrivateBean.java ================================================ /* * 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.beans.privatebean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) class PrivateBean { @Bean static MyBean myBean() { return new MyBean(); } private static final class MyBean { } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/beans/regular/RegularBean.java ================================================ /* * 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.beans.regular; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) class RegularBean { @Bean static PackagePrivate packagePrivateBean() { return new PackagePrivate(); } @Bean static Protected protectedBean() { return new Protected(); } @Bean static Public publicBean() { return new Public(); } static final class PackagePrivate { } protected static final class Protected { } public static final class Public { } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/nonstatic/NonStaticBeanFactoryPostProcessorConfiguration.java ================================================ /* * 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.bfpp.nonstatic; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.context.annotation.Bean; class NonStaticBeanFactoryPostProcessorConfiguration { @Bean BeanFactoryPostProcessor nonStaticBeanFactoryPostProcessor() { return (beanFactory) -> { }; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/noparameters/NoParametersBeanFactoryPostProcessorConfiguration.java ================================================ /* * 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.bfpp.noparameters; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.context.annotation.Bean; class NoParametersBeanFactoryPostProcessorConfiguration { @Bean static BeanFactoryPostProcessor noParametersBeanFactoryPostProcessor() { return (beanFactory) -> { }; } @Bean Integer beanOne() { return 1; } @Bean String beanTwo() { return "test"; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/parameters/ParametersBeanFactoryPostProcessorConfiguration.java ================================================ /* * 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.bfpp.parameters; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.context.annotation.Bean; class ParametersBeanFactoryPostProcessorConfiguration { @Bean static BeanFactoryPostProcessor parametersBeanFactoryPostProcessor(Integer param) { return (beanFactory) -> { }; } @Bean Integer beanOne() { return 1; } @Bean String beanTwo() { return "test"; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/nonstatic/NonStaticBeanPostProcessorConfiguration.java ================================================ /* * 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.bpp.nonstatic; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; class NonStaticBeanPostProcessorConfiguration { @Bean BeanPostProcessor nonStaticBeanPostProcessor() { return new BeanPostProcessor() { }; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/noparameters/NoParametersBeanPostProcessorConfiguration.java ================================================ /* * 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.bpp.noparameters; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; class NoParametersBeanPostProcessorConfiguration { @Bean static BeanPostProcessor noParametersBeanPostProcessor() { return new BeanPostProcessor() { }; } @Bean Integer beanOne() { return 1; } @Bean String beanTwo() { return "test"; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/safeparameters/SafeParametersBeanPostProcessorConfiguration.java ================================================ /* * 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.bpp.safeparameters; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Lazy; import org.springframework.core.env.Environment; class SafeParametersBeanPostProcessorConfiguration { @Bean static BeanPostProcessor safeParametersBeanPostProcessor(ApplicationContext context, ObjectProvider beanOne, ObjectProvider beanTwo, Environment environment, @Lazy String beanThree) { return new BeanPostProcessor() { }; } @Bean Integer beanOne() { return 1; } @Bean String beanTwo() { return "test"; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/unsafeparameters/UnsafeParametersBeanPostProcessorConfiguration.java ================================================ /* * 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.bpp.unsafeparameters; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; class UnsafeParametersBeanPostProcessorConfiguration { @Bean static BeanPostProcessor unsafeParametersBeanPostProcessor(ApplicationContext context, Integer beanOne, String beanTwo) { return new BeanPostProcessor() { }; } @Bean Integer beanOne() { return 1; } @Bean String beanTwo() { return "test"; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/collectors/toList/CollectorsToList.java ================================================ /* * 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.collectors.toList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; class CollectorsToList { void exampleMethod() { List strings = Stream.of("a", "b", "c").collect(Collectors.toList()); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonclass/OnBeanMethod.java ================================================ /* * 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.conditionalonclass; import org.springframework.boot.build.architecture.annotations.TestConditionalOnClass; import org.springframework.context.annotation.Bean; class OnBeanMethod { @Bean @TestConditionalOnClass(String.class) String helloWorld() { return "Hello World"; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonmissingbean/valueonly/TypeSameAsMethodReturnType.java ================================================ /* * 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.conditionalonmissingbean.valueonly; import org.springframework.boot.build.architecture.annotations.TestConditionalOnMissingBean; import org.springframework.context.annotation.Bean; class TypeSameAsMethodReturnType { @Bean @TestConditionalOnMissingBean(String.class) String helloWorld() { return "Hello World"; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonmissingbean/withname/WithNameAttribute.java ================================================ /* * 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.conditionalonmissingbean.withname; import org.springframework.boot.build.architecture.annotations.TestConditionalOnMissingBean; import org.springframework.context.annotation.Bean; class WithNameAttribute { @Bean @TestConditionalOnMissingBean(name = "myBean") String helloWorld() { return "Hello World"; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonmissingbean/withtype/WithTypeAttribute.java ================================================ /* * 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.conditionalonmissingbean.withtype; import org.springframework.boot.build.architecture.annotations.TestConditionalOnMissingBean; import org.springframework.context.annotation.Bean; class WithTypeAttribute { @Bean @TestConditionalOnMissingBean(type = "String") String helloWorld() { return "Hello World"; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/bindingnonstatic/BindingMethodNonStatic.java ================================================ /* * 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.configurationproperties.bindingnonstatic; import java.util.List; import org.springframework.boot.build.architecture.annotations.TestConfigurationPropertiesBinding; import org.springframework.context.annotation.Bean; public class BindingMethodNonStatic { @Bean @TestConfigurationPropertiesBinding public List binder() { return List.of("hello", "world"); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/classprefixandignore/ConfigurationPropertiesWithPrefixAndIgnore.java ================================================ /* * 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.configurationproperties.classprefixandignore; import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties; @TestConfigurationProperties(prefix = "testing", ignoreUnknownFields = false) public class ConfigurationPropertiesWithPrefixAndIgnore { private String property; public String getProperty() { return this.property; } public void setProperty(String property) { this.property = property; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/classprefixonly/ConfigurationPropertiesWithPrefixOnly.java ================================================ /* * 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.configurationproperties.classprefixonly; import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties; @TestConfigurationProperties(prefix = "testing") public class ConfigurationPropertiesWithPrefixOnly { private String property; public String getProperty() { return this.property; } public void setProperty(String property) { this.property = property; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/classvalueonly/ConfigurationPropertiesWithValueOnly.java ================================================ /* * 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.configurationproperties.classvalueonly; import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties; @TestConfigurationProperties("testing") public class ConfigurationPropertiesWithValueOnly { private String property; public String getProperty() { return this.property; } public void setProperty(String property) { this.property = property; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/deprecatedsince/DeprecatedConfigurationPropertySince.java ================================================ /* * 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.configurationproperties.deprecatedsince; import org.springframework.boot.build.architecture.annotations.TestDeprecatedConfigurationProperty; public class DeprecatedConfigurationPropertySince { private String property; @TestDeprecatedConfigurationProperty(reason = "no longer used") @Deprecated public String getProperty() { return this.property; } public void setProperty(String property) { this.property = property; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/methodprefixandignore/ConfigurationPropertiesWithPrefixAndIgnore.java ================================================ /* * 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.configurationproperties.methodprefixandignore; import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties; public class ConfigurationPropertiesWithPrefixAndIgnore { private String property; @TestConfigurationProperties(prefix = "testing", ignoreInvalidFields = true) public String getProperty() { return this.property; } public void setProperty(String property) { this.property = property; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/methodprefixonly/ConfigurationPropertiesWithPrefixOnly.java ================================================ /* * 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.configurationproperties.methodprefixonly; import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties; public class ConfigurationPropertiesWithPrefixOnly { private String property; @TestConfigurationProperties(prefix = "testing") public String getProperty() { return this.property; } public void setProperty(String property) { this.property = property; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/methodvalueonly/ConfigurationPropertiesWithValueOnly.java ================================================ /* * 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.configurationproperties.methodvalueonly; import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties; public class ConfigurationPropertiesWithValueOnly { private String property; @TestConfigurationProperties("testing") public String getProperty() { return this.property; } public void setProperty(String property) { this.property = property; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/inferredfromparametertype/EnumSourceInferredFromParameterType.java ================================================ /* * 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 } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/sameasparametertype/EnumSourceSameAsParameterType.java ================================================ /* * 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 } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/valuenecessary/EnumSourceValueNecessary.java ================================================ /* * 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 } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/nullmarked/notannotated/TestClass.java ================================================ /* * 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.nullmarked.notannotated; public class TestClass { } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/objects/noRequireNonNull/NoRequireNonNull.java ================================================ /* * 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.objects.noRequireNonNull; import java.util.Collections; import org.springframework.util.Assert; class NoRequireNonNull { void exampleMethod() { Assert.notNull(new Object(), "Object must not be null"); // Compilation of a method reference generates code that uses // Objects.requireNonNull(Object). Check that it doesn't cause a failure. Collections.emptyList().forEach(System.out::println); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/objects/requireNonNullWithString/RequireNonNullWithString.java ================================================ /* * 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.objects.requireNonNullWithString; import java.util.Objects; class RequireNonNullWithString { void exampleMethod() { Objects.requireNonNull(new Object(), "Object cannot be null"); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/objects/requireNonNullWithSupplier/RequireNonNullWithSupplier.java ================================================ /* * 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.objects.requireNonNullWithSupplier; import java.util.Objects; class RequireNonNullWithSupplier { void exampleMethod() { Objects.requireNonNull(new Object(), () -> "Object cannot be null"); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/resources/loads/ResourceUtilsResourceLoader.java ================================================ /* * 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.resources.loads; import java.io.FileNotFoundException; import org.springframework.util.ResourceUtils; public class ResourceUtilsResourceLoader { void getResource() throws FileNotFoundException { ResourceUtils.getURL("gradle.properties"); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/resources/noloads/ResourceUtilsWithoutLoading.java ================================================ /* * 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.resources.noloads; import java.net.MalformedURLException; import java.net.URL; import org.springframework.util.ResourceUtils; public class ResourceUtilsWithoutLoading { void inspectResourceLocation() throws MalformedURLException { ResourceUtils.isUrl("gradle.properties"); ResourceUtils.isFileURL(new URL("gradle.properties")); "test".startsWith(ResourceUtils.FILE_URL_PREFIX); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/string/toLowerCase/ToLowerCase.java ================================================ /* * 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.string.toLowerCase; class ToLowerCase { void exampleMethod() { String test = "Object must not be null"; System.out.println(test.toLowerCase()); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/string/toLowerCaseWithLocale/ToLowerCaseWithLocale.java ================================================ /* * 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.string.toLowerCaseWithLocale; import java.util.Locale; class ToLowerCaseWithLocale { void exampleMethod() { String test = "Object must not be null"; System.out.println(test.toLowerCase(Locale.ENGLISH)); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/string/toUpperCase/ToUpperCase.java ================================================ /* * 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.string.toUpperCase; class ToUpperCase { void exampleMethod() { String test = "Object must not be null"; System.out.println(test.toUpperCase()); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/string/toUpperCaseWithLocale/ToUpperCaseWithLocale.java ================================================ /* * 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.string.toUpperCaseWithLocale; import java.util.Locale; class ToUpperCaseWithLocale { void exampleMethod() { String test = "Object must not be null"; System.out.println(test.toUpperCase(Locale.ROOT)); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/tangled/TangledOne.java ================================================ /* * 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.tangled; import org.springframework.boot.build.architecture.tangled.sub.TangledTwo; public final class TangledOne { public static final String ID = TangledTwo.class.getName() + "One"; private TangledOne() { } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/tangled/sub/TangledTwo.java ================================================ /* * 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.tangled.sub; import org.springframework.boot.build.architecture.tangled.TangledOne; public final class TangledTwo { public static final String ID = TangledOne.ID + "-Two"; private TangledTwo() { } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/untangled/UntangledOne.java ================================================ /* * 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.untangled; import org.springframework.boot.build.architecture.untangled.sub.UntangledTwo; public final class UntangledOne { public static final String ID = UntangledTwo.class.getName() + "One"; private UntangledOne() { } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/untangled/sub/UntangledTwo.java ================================================ /* * 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.untangled.sub; public final class UntangledTwo { public static final String ID = "Two"; private UntangledTwo() { } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/url/decode/UrlDecodeWithStringEncoding.java ================================================ /* * 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.url.decode; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; class UrlDecodeWithStringEncoding { void exampleMethod() throws UnsupportedEncodingException { URLDecoder.decode("https://example.com", "UTF-8"); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/architecture/url/encode/UrlEncodeWithStringEncoding.java ================================================ /* * 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.url.encode; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; class UrlEncodeWithStringEncoding { void exampleMethod() throws UnsupportedEncodingException { URLEncoder.encode("https://example.com", "UTF-8"); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/artifacts/ArtifactReleaseTests.java ================================================ /* * 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.artifacts; import org.gradle.api.Project; import org.gradle.testfixtures.ProjectBuilder; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ArtifactRelease}. * * @author Andy Wilkinson * @author Scott Frederick */ class ArtifactReleaseTests { @Test void whenProjectVersionIsSnapshotThenTypeIsSnapshot() { Project project = ProjectBuilder.builder().build(); project.setVersion("1.2.3-SNAPSHOT"); assertThat(ArtifactRelease.forProject(project).getType()).isEqualTo("snapshot"); } @Test void whenProjectVersionIsMilestoneThenTypeIsMilestone() { Project project = ProjectBuilder.builder().build(); project.setVersion("1.2.3-M1"); assertThat(ArtifactRelease.forProject(project).getType()).isEqualTo("milestone"); } @Test void whenProjectVersionIsReleaseCandidateThenTypeIsMilestone() { Project project = ProjectBuilder.builder().build(); project.setVersion("1.2.3-RC1"); assertThat(ArtifactRelease.forProject(project).getType()).isEqualTo("milestone"); } @Test void whenProjectVersionIsReleaseThenTypeIsRelease() { Project project = ProjectBuilder.builder().build(); project.setVersion("1.2.3"); assertThat(ArtifactRelease.forProject(project).getType()).isEqualTo("release"); } @Test void whenProjectVersionIsSnapshotThenRepositoryIsArtifactorySnapshot() { Project project = ProjectBuilder.builder().build(); project.setVersion("1.2.3-SNAPSHOT"); assertThat(ArtifactRelease.forProject(project).getDownloadRepo()).contains("repo.spring.io/snapshot"); } @Test void whenProjectVersionIsMilestoneThenRepositoryIsMavenCentral() { Project project = ProjectBuilder.builder().build(); project.setVersion("4.0.0-M1"); assertThat(ArtifactRelease.forProject(project).getDownloadRepo()) .contains("https://repo.maven.apache.org/maven2"); } @Test void whenProjectVersionIsReleaseCandidateThenRepositoryIsMavenCentral() { Project project = ProjectBuilder.builder().build(); project.setVersion("4.0.0-RC1"); assertThat(ArtifactRelease.forProject(project).getDownloadRepo()) .contains("https://repo.maven.apache.org/maven2"); } @Test void whenProjectVersionIsReleaseThenRepositoryIsMavenCentral() { Project project = ProjectBuilder.builder().build(); project.setVersion("1.2.3"); assertThat(ArtifactRelease.forProject(project).getDownloadRepo()) .contains("https://repo.maven.apache.org/maven2"); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/assertj/NodeAssert.java ================================================ /* * 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.assertj; import java.io.File; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.AssertProvider; import org.assertj.core.api.StringAssert; import org.w3c.dom.Document; import org.w3c.dom.Node; /** * AssertJ {@link AssertProvider} for {@link Node} assertions. * * @author Andy Wilkinson */ public class NodeAssert extends AbstractAssert implements AssertProvider { private static final DocumentBuilderFactory FACTORY = DocumentBuilderFactory.newInstance(); private final XPathFactory xpathFactory = XPathFactory.newInstance(); private final XPath xpath = this.xpathFactory.newXPath(); public NodeAssert(File xmlFile) { this(read(xmlFile)); } public NodeAssert(Node actual) { super(actual, NodeAssert.class); } private static Document read(File xmlFile) { try { return FACTORY.newDocumentBuilder().parse(xmlFile); } catch (Exception ex) { throw new RuntimeException(ex); } } public NodeAssert nodeAtPath(String xpath) { try { return new NodeAssert((Node) this.xpath.evaluate(xpath, this.actual, XPathConstants.NODE)); } catch (XPathExpressionException ex) { throw new RuntimeException(ex); } } public StringAssert textAtPath(String xpath) { try { return new StringAssert( (String) this.xpath.evaluate(xpath + "/text()", this.actual, XPathConstants.STRING)); } catch (XPathExpressionException ex) { throw new RuntimeException(ex); } } @Override public NodeAssert assertThat() { return this; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClassesTests.java ================================================ /* * 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.autoconfigure; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; import java.util.Properties; import java.util.function.Consumer; import java.util.stream.Stream; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.testfixtures.ProjectBuilder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link DocumentAutoConfigurationClasses}. * * @author Andy Wilkinson */ class DocumentAutoConfigurationClassesTests { @TempDir private File temp; @Test void classesAreDocumented() throws IOException { File output = documentAutoConfigurationClasses((metadataDir) -> { writeAutoConfigurationMetadata("spring-boot-one", List.of("org.springframework.boot.one.AAutoConfiguration", "org.springframework.boot.one.BAutoConfiguration"), metadataDir); writeAutoConfigurationMetadata("spring-boot-two", List.of("org.springframework.boot.two.CAutoConfiguration", "org.springframework.boot.two.DAutoConfiguration"), metadataDir); }); assertThat(output).isNotEmptyDirectory(); assertThat(output.listFiles()).extracting(File::getName) .containsExactlyInAnyOrder("spring-boot-one.adoc", "spring-boot-two.adoc", "nav.adoc"); } @Test void whenMetadataIsRemovedThenOutputForThatMetadataIsNoLongerPresent() throws IOException { documentAutoConfigurationClasses((metadataDir) -> { writeAutoConfigurationMetadata("spring-boot-one", List.of("org.springframework.boot.one.AAutoConfiguration", "org.springframework.boot.one.BAutoConfiguration"), metadataDir); writeAutoConfigurationMetadata("spring-boot-two", List.of("org.springframework.boot.two.CAutoConfiguration", "org.springframework.boot.two.DAutoConfiguration"), metadataDir); }); File output = documentAutoConfigurationClasses( (metadataDir) -> assertThat(new File(metadataDir, "spring-boot-two.properties").delete()).isTrue()); assertThat(output).isNotEmptyDirectory(); assertThat(output.listFiles()).extracting(File::getName) .containsExactlyInAnyOrder("spring-boot-one.adoc", "nav.adoc"); } private File documentAutoConfigurationClasses(Consumer metadataDir) throws IOException { Project project = ProjectBuilder.builder().build(); DocumentAutoConfigurationClasses task = project.getTasks() .register("documentAutoConfigurationClasses", DocumentAutoConfigurationClasses.class) .get(); File output = new File(this.temp, "output"); File input = new File(this.temp, "input"); input.mkdirs(); metadataDir.accept(input); ConfigurableFileCollection autoConfiguration = project.files(); Stream.of(input.listFiles()).forEach(autoConfiguration::from); task.getOutputDir().set(output); task.setAutoConfiguration(autoConfiguration); task.documentAutoConfigurationClasses(); return output; } private void writeAutoConfigurationMetadata(String module, List classes, File outputDir) { File metadata = new File(outputDir, module + ".properties"); Properties properties = new Properties(); properties.setProperty("autoConfigurationClassNames", String.join(",", classes)); properties.setProperty("module", module); try (FileOutputStream out = new FileOutputStream(metadata)) { properties.store(out, null); } catch (IOException ex) { throw new java.io.UncheckedIOException(ex); } } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java ================================================ /* * 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.bom; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.function.Consumer; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.build.DeployedPlugin; import org.springframework.boot.build.assertj.NodeAssert; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link BomPlugin}. * * @author Andy Wilkinson */ class BomPluginIntegrationTests { private File projectDir; private File buildFile; @BeforeEach void setup(@TempDir File projectDir) { this.projectDir = projectDir; this.buildFile = new File(this.projectDir, "build.gradle"); } @Test void libraryModulesAreIncludedInDependencyManagementOfGeneratedPom() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'org.springframework.boot.bom'"); out.println(" id 'org.springframework.boot.deployed'"); out.println("}"); out.println("bom {"); out.println(" library('ActiveMQ', '5.15.10') {"); out.println(" group('org.apache.activemq') {"); out.println(" modules = ["); out.println(" 'activemq-amqp',"); out.println(" 'activemq-blueprint'"); out.println(" ]"); out.println(" }"); out.println(" }"); out.println("}"); } generatePom((pom) -> { assertThat(pom).textAtPath("//properties/activemq.version").isEqualTo("5.15.10"); NodeAssert dependency = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[1]"); assertThat(dependency).textAtPath("groupId").isEqualTo("org.apache.activemq"); assertThat(dependency).textAtPath("artifactId").isEqualTo("activemq-amqp"); assertThat(dependency).textAtPath("version").isEqualTo("${activemq.version}"); assertThat(dependency).textAtPath("scope").isNullOrEmpty(); assertThat(dependency).textAtPath("type").isNullOrEmpty(); assertThat(dependency).textAtPath("classifier").isNullOrEmpty(); dependency = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[2]"); assertThat(dependency).textAtPath("groupId").isEqualTo("org.apache.activemq"); assertThat(dependency).textAtPath("artifactId").isEqualTo("activemq-blueprint"); assertThat(dependency).textAtPath("version").isEqualTo("${activemq.version}"); assertThat(dependency).textAtPath("scope").isNullOrEmpty(); assertThat(dependency).textAtPath("type").isNullOrEmpty(); assertThat(dependency).textAtPath("classifier").isNullOrEmpty(); }); } @Test void libraryPluginsAreIncludedInPluginManagementOfGeneratedPom() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'org.springframework.boot.bom'"); out.println(" id 'org.springframework.boot.deployed'"); out.println("}"); out.println("bom {"); out.println(" library('Flyway', '6.0.8') {"); out.println(" group('org.flywaydb') {"); out.println(" plugins = ["); out.println(" 'flyway-maven-plugin'"); out.println(" ]"); out.println(" }"); out.println(" }"); out.println("}"); } generatePom((pom) -> { assertThat(pom).textAtPath("//properties/flyway.version").isEqualTo("6.0.8"); NodeAssert plugin = pom.nodeAtPath("//pluginManagement/plugins/plugin"); assertThat(plugin).textAtPath("groupId").isEqualTo("org.flywaydb"); assertThat(plugin).textAtPath("artifactId").isEqualTo("flyway-maven-plugin"); assertThat(plugin).textAtPath("version").isEqualTo("${flyway.version}"); assertThat(plugin).textAtPath("scope").isNullOrEmpty(); assertThat(plugin).textAtPath("type").isNullOrEmpty(); }); } @Test void libraryImportsAreIncludedInDependencyManagementOfGeneratedPom() throws Exception { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'org.springframework.boot.bom'"); out.println(" id 'org.springframework.boot.deployed'"); out.println("}"); out.println("bom {"); out.println(" library('Jackson Bom', '2.10.0') {"); out.println(" group('com.fasterxml.jackson') {"); out.println(" bom('jackson-bom')"); out.println(" }"); out.println(" }"); out.println("}"); } generatePom((pom) -> { assertThat(pom).textAtPath("//properties/jackson-bom.version").isEqualTo("2.10.0"); NodeAssert dependency = pom.nodeAtPath("//dependencyManagement/dependencies/dependency"); assertThat(dependency).textAtPath("groupId").isEqualTo("com.fasterxml.jackson"); assertThat(dependency).textAtPath("artifactId").isEqualTo("jackson-bom"); assertThat(dependency).textAtPath("version").isEqualTo("${jackson-bom.version}"); assertThat(dependency).textAtPath("scope").isEqualTo("import"); assertThat(dependency).textAtPath("type").isEqualTo("pom"); assertThat(dependency).textAtPath("classifier").isNullOrEmpty(); }); } @Test void moduleExclusionsAreIncludedInDependencyManagementOfGeneratedPom() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'org.springframework.boot.bom'"); out.println(" id 'org.springframework.boot.deployed'"); out.println("}"); out.println("bom {"); out.println(" library('MySQL', '8.0.18') {"); out.println(" group('mysql') {"); out.println(" modules = ["); out.println(" 'mysql-connector-java' {"); out.println(" exclude group: 'com.google.protobuf', module: 'protobuf-java'"); out.println(" }"); out.println(" ]"); out.println(" }"); out.println(" }"); out.println("}"); } generatePom((pom) -> { assertThat(pom).textAtPath("//properties/mysql.version").isEqualTo("8.0.18"); NodeAssert dependency = pom.nodeAtPath("//dependencyManagement/dependencies/dependency"); assertThat(dependency).textAtPath("groupId").isEqualTo("mysql"); assertThat(dependency).textAtPath("artifactId").isEqualTo("mysql-connector-java"); assertThat(dependency).textAtPath("version").isEqualTo("${mysql.version}"); assertThat(dependency).textAtPath("scope").isNullOrEmpty(); assertThat(dependency).textAtPath("type").isNullOrEmpty(); assertThat(dependency).textAtPath("classifier").isNullOrEmpty(); NodeAssert exclusion = dependency.nodeAtPath("exclusions/exclusion"); assertThat(exclusion).textAtPath("groupId").isEqualTo("com.google.protobuf"); assertThat(exclusion).textAtPath("artifactId").isEqualTo("protobuf-java"); }); } @Test void moduleTypesAreIncludedInDependencyManagementOfGeneratedPom() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'org.springframework.boot.bom'"); out.println(" id 'org.springframework.boot.deployed'"); out.println("}"); out.println("bom {"); out.println(" library('Elasticsearch', '7.15.2') {"); out.println(" group('org.elasticsearch.distribution.integ-test-zip') {"); out.println(" modules = ["); out.println(" 'elasticsearch' {"); out.println(" type = 'zip'"); out.println(" }"); out.println(" ]"); out.println(" }"); out.println(" }"); out.println("}"); } generatePom((pom) -> { assertThat(pom).textAtPath("//properties/elasticsearch.version").isEqualTo("7.15.2"); NodeAssert dependency = pom.nodeAtPath("//dependencyManagement/dependencies/dependency"); assertThat(dependency).textAtPath("groupId").isEqualTo("org.elasticsearch.distribution.integ-test-zip"); assertThat(dependency).textAtPath("artifactId").isEqualTo("elasticsearch"); assertThat(dependency).textAtPath("version").isEqualTo("${elasticsearch.version}"); assertThat(dependency).textAtPath("scope").isNullOrEmpty(); assertThat(dependency).textAtPath("type").isEqualTo("zip"); assertThat(dependency).textAtPath("classifier").isNullOrEmpty(); assertThat(dependency).nodeAtPath("exclusions").isNull(); }); } @Test void moduleClassifiersAreIncludedInDependencyManagementOfGeneratedPom() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'org.springframework.boot.bom'"); out.println(" id 'org.springframework.boot.deployed'"); out.println("}"); out.println("bom {"); out.println(" library('Kafka', '2.7.2') {"); out.println(" group('org.apache.kafka') {"); out.println(" modules = ["); out.println(" 'connect-api',"); out.println(" 'generator',"); out.println(" 'generator' {"); out.println(" classifier = 'test'"); out.println(" },"); out.println(" 'kafka-tools',"); out.println(" ]"); out.println(" }"); out.println(" }"); out.println("}"); } generatePom((pom) -> { assertThat(pom).textAtPath("//properties/kafka.version").isEqualTo("2.7.2"); NodeAssert connectApi = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[1]"); assertThat(connectApi).textAtPath("groupId").isEqualTo("org.apache.kafka"); assertThat(connectApi).textAtPath("artifactId").isEqualTo("connect-api"); assertThat(connectApi).textAtPath("version").isEqualTo("${kafka.version}"); assertThat(connectApi).textAtPath("scope").isNullOrEmpty(); assertThat(connectApi).textAtPath("type").isNullOrEmpty(); assertThat(connectApi).textAtPath("classifier").isNullOrEmpty(); assertThat(connectApi).nodeAtPath("exclusions").isNull(); NodeAssert generator = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[2]"); assertThat(generator).textAtPath("groupId").isEqualTo("org.apache.kafka"); assertThat(generator).textAtPath("artifactId").isEqualTo("generator"); assertThat(generator).textAtPath("version").isEqualTo("${kafka.version}"); assertThat(generator).textAtPath("scope").isNullOrEmpty(); assertThat(generator).textAtPath("type").isNullOrEmpty(); assertThat(generator).textAtPath("classifier").isNullOrEmpty(); assertThat(generator).nodeAtPath("exclusions").isNull(); NodeAssert generatorTest = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[3]"); assertThat(generatorTest).textAtPath("groupId").isEqualTo("org.apache.kafka"); assertThat(generatorTest).textAtPath("artifactId").isEqualTo("generator"); assertThat(generatorTest).textAtPath("version").isEqualTo("${kafka.version}"); assertThat(generatorTest).textAtPath("scope").isNullOrEmpty(); assertThat(generatorTest).textAtPath("type").isNullOrEmpty(); assertThat(generatorTest).textAtPath("classifier").isEqualTo("test"); assertThat(generatorTest).nodeAtPath("exclusions").isNull(); NodeAssert kafkaTools = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[4]"); assertThat(kafkaTools).textAtPath("groupId").isEqualTo("org.apache.kafka"); assertThat(kafkaTools).textAtPath("artifactId").isEqualTo("kafka-tools"); assertThat(kafkaTools).textAtPath("version").isEqualTo("${kafka.version}"); assertThat(kafkaTools).textAtPath("scope").isNullOrEmpty(); assertThat(kafkaTools).textAtPath("type").isNullOrEmpty(); assertThat(kafkaTools).textAtPath("classifier").isNullOrEmpty(); assertThat(kafkaTools).nodeAtPath("exclusions").isNull(); }); } @Test void libraryNamedSpringBootHasNoVersionProperty() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'org.springframework.boot.bom'"); out.println(" id 'org.springframework.boot.deployed'"); out.println("}"); out.println("bom {"); out.println(" library('Spring Boot', '1.2.3') {"); out.println(" group('org.springframework.boot') {"); out.println(" modules = ["); out.println(" 'spring-boot'"); out.println(" ]"); out.println(" }"); out.println(" }"); out.println("}"); } generatePom((pom) -> { assertThat(pom).textAtPath("//properties/spring-boot.version").isEmpty(); NodeAssert dependency = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[1]"); assertThat(dependency).textAtPath("groupId").isEqualTo("org.springframework.boot"); assertThat(dependency).textAtPath("artifactId").isEqualTo("spring-boot"); assertThat(dependency).textAtPath("version").isEqualTo("1.2.3"); assertThat(dependency).textAtPath("scope").isNullOrEmpty(); assertThat(dependency).textAtPath("type").isNullOrEmpty(); }); } private BuildResult runGradle(String... args) { return GradleRunner.create() .withDebug(true) .withProjectDir(this.projectDir) .withArguments(args) .withPluginClasspath() .build(); } private void generatePom(Consumer consumer) { runGradle(DeployedPlugin.GENERATE_POM_TASK_NAME, "-s"); File generatedPomXml = new File(this.projectDir, "build/publications/maven/pom-default.xml"); assertThat(generatedPomXml).isFile(); consumer.accept(new NodeAssert(generatedPomXml)); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/bom/LibraryTests.java ================================================ /* * 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.bom; import java.util.Collections; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.boot.build.bom.Library.BomAlignment; import org.springframework.boot.build.bom.Library.Group; import org.springframework.boot.build.bom.Library.LibraryVersion; import org.springframework.boot.build.bom.Library.Link; import org.springframework.boot.build.bom.Library.ProhibitedVersion; import org.springframework.boot.build.bom.Library.VersionAlignment; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link Library}. * * @author Phillip Webb */ class LibraryTests { @Test void getLinkRootNameWhenNoneSpecified() { String name = "Spring Framework"; String calendarName = null; LibraryVersion version = new LibraryVersion(DependencyVersion.parse("1.2.3")); List groups = Collections.emptyList(); List prohibitedVersion = Collections.emptyList(); boolean considerSnapshots = false; VersionAlignment versionAlignment = null; BomAlignment alignsWithBom = null; String linkRootName = null; Map> links = Collections.emptyMap(); Library library = new Library(name, calendarName, version, groups, null, prohibitedVersion, considerSnapshots, versionAlignment, alignsWithBom, linkRootName, links); assertThat(library.getLinkRootName()).isEqualTo("spring-framework"); } @Test void getLinkRootNameWhenSpecified() { String name = "Spring Data BOM"; String calendarName = null; LibraryVersion version = new LibraryVersion(DependencyVersion.parse("1.2.3")); List groups = Collections.emptyList(); List prohibitedVersion = Collections.emptyList(); boolean considerSnapshots = false; VersionAlignment versionAlignment = null; BomAlignment alignsWithBom = null; String linkRootName = "spring-data"; Map> links = Collections.emptyMap(); Library library = new Library(name, calendarName, version, groups, null, prohibitedVersion, considerSnapshots, versionAlignment, alignsWithBom, linkRootName, links); assertThat(library.getLinkRootName()).isEqualTo("spring-data"); } @Test void toMajorMinorGenerationWithRelease() { LibraryVersion version = new LibraryVersion(DependencyVersion.parse("1.2.3")); assertThat(version.forMajorMinorGeneration()).isEqualTo("1.2.x"); } @Test void toMajorMinorGenerationWithSnapshot() { LibraryVersion version = new LibraryVersion(DependencyVersion.parse("2.0.0-SNAPSHOT")); assertThat(version.forMajorMinorGeneration()).isEqualTo("2.0.x-SNAPSHOT"); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolverTests.java ================================================ /* * 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.bom.bomr; import java.util.ArrayList; import java.util.List; import org.gradle.api.internal.tasks.userinput.UserInputHandler; import org.gradle.api.provider.Provider; import org.junit.jupiter.api.Test; import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.Library.LibraryVersion; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link InteractiveUpgradeResolver}. * * @author Phillip Webb */ class InteractiveUpgradeResolverTests { @Test void resolveUpgradeUpdateVersionNumberInLibrary() { UserInputHandler userInputHandler = mock(UserInputHandler.class); LibraryUpdateResolver libaryUpdateResolver = mock(LibraryUpdateResolver.class); InteractiveUpgradeResolver upgradeResolver = new InteractiveUpgradeResolver(userInputHandler, libaryUpdateResolver); List libraries = new ArrayList<>(); DependencyVersion version = DependencyVersion.parse("1.0.0"); LibraryVersion libraryVersion = new LibraryVersion(version); Library library = new Library("test", null, libraryVersion, null, null, null, false, null, null, null, null); libraries.add(library); List librariesToUpgrade = new ArrayList<>(); librariesToUpgrade.add(library); List updates = new ArrayList<>(); DependencyVersion updateVersion = DependencyVersion.parse("1.0.1"); VersionOption versionOption = new VersionOption(updateVersion); updates.add(new LibraryWithVersionOptions(library, List.of(versionOption))); given(libaryUpdateResolver.findLibraryUpdates(any(), any())).willReturn(updates); Provider providerOfVersionOption = providerOf(versionOption); given(userInputHandler.askUser(any())).willReturn(providerOfVersionOption); List upgrades = upgradeResolver.resolveUpgrades(librariesToUpgrade, libraries); assertThat(upgrades.get(0).to().getVersion().getVersion()).isEqualTo(updateVersion); } @SuppressWarnings({ "unchecked", "rawtypes" }) private Provider providerOf(VersionOption versionOption) { Provider provider = mock(Provider.class); given(provider.get()).willReturn(versionOption); return provider; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/ReleaseScheduleTests.java ================================================ /* * 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.bom.bomr; import java.time.OffsetDateTime; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.boot.build.bom.bomr.ReleaseSchedule.Release; import org.springframework.core.io.ClassPathResource; import org.springframework.http.MediaType; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; /** * Tests for {@link ReleaseSchedule}. * * @author Andy Wilkinson */ class ReleaseScheduleTests { private final RestTemplate rest = new RestTemplate(); private final ReleaseSchedule releaseSchedule = new ReleaseSchedule(this.rest); private final MockRestServiceServer server = MockRestServiceServer.bindTo(this.rest).build(); @Test void releasesBetween() { this.server .expect(requestTo("https://calendar.spring.io/releases?start=2023-09-01T00:00Z&end=2023-09-21T23:59Z")) .andRespond(withSuccess(new ClassPathResource("releases.json"), MediaType.APPLICATION_JSON)); Map> releases = this.releaseSchedule .releasesBetween(OffsetDateTime.parse("2023-09-01T00:00Z"), OffsetDateTime.parse("2023-09-21T23:59Z")); assertThat(releases).hasSize(23); assertThat(releases.get("Spring Framework")).hasSize(3); assertThat(releases.get("Spring Boot")).hasSize(4); assertThat(releases.get("Spring Modulith")).hasSize(1); assertThat(releases.get("spring graphql")).hasSize(3); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java ================================================ /* * 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.bom.bomr; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.util.Collections; import java.util.Properties; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.Library.LibraryVersion; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; /** * Tests for {@link UpgradeApplicator}. * * @author Andy Wilkinson */ class UpgradeApplicatorTests { @TempDir File temp; @Test void whenUpgradeIsAppliedToLibraryWithVersionThenBomIsUpdated() throws IOException { File bom = new File(this.temp, "bom.gradle"); FileCopyUtils.copy(new File("src/test/resources/bom.gradle"), bom); String originalContents = Files.readString(bom.toPath()); File gradleProperties = new File(this.temp, "gradle.properties"); FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties); Library activeMq = new Library("ActiveMQ", null, new LibraryVersion(DependencyVersion.parse("5.15.11")), null, null, null, false, null, null, null, Collections.emptyMap()); new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()) .apply(new Upgrade(activeMq, activeMq.withVersion(new LibraryVersion(DependencyVersion.parse("5.16"))))); String bomContents = Files.readString(bom.toPath()); assertThat(bomContents).hasSize(originalContents.length() - 3); } @Test void whenUpgradeIsAppliedToLibraryWithVersionPropertyThenGradlePropertiesIsUpdated() throws IOException { File bom = new File(this.temp, "bom.gradle"); FileCopyUtils.copy(new File("src/test/resources/bom.gradle"), bom); File gradleProperties = new File(this.temp, "gradle.properties"); FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties); Library kotlin = new Library("Kotlin", null, new LibraryVersion(DependencyVersion.parse("1.3.70")), null, null, null, false, null, null, null, Collections.emptyMap()); new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()) .apply(new Upgrade(kotlin, kotlin.withVersion(new LibraryVersion(DependencyVersion.parse("1.4"))))); Properties properties = new Properties(); try (InputStream in = new FileInputStream(gradleProperties)) { properties.load(in); } assertThat(properties).containsOnly(entry("a", "alpha"), entry("b", "bravo"), entry("kotlinVersion", "1.4"), entry("t", "tango")); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeTests.java ================================================ /* * 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.bom.bomr; import org.junit.jupiter.api.Test; import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.Library.LibraryVersion; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link Upgrade}. * * @author Phillip Webb */ class UpgradeTests { @Test void createToRelease() { Library from = new Library("Test", null, new LibraryVersion(DependencyVersion.parse("1.0.0")), null, null, null, false, null, null, null, null); Upgrade upgrade = new Upgrade(from, from.withVersion(new LibraryVersion(DependencyVersion.parse("1.0.1")))); assertThat(upgrade.from().getNameAndVersion()).isEqualTo("Test 1.0.0"); assertThat(upgrade.to().getNameAndVersion()).isEqualTo("Test 1.0.1"); assertThat(upgrade.toRelease().getNameAndVersion()).isEqualTo("Test 1.0.1"); } @Test void createToSnapshot() { Library from = new Library("Test", null, new LibraryVersion(DependencyVersion.parse("1.0.0")), null, null, null, false, null, null, null, null); Upgrade upgrade = new Upgrade(from, from.withVersion(new LibraryVersion(DependencyVersion.parse("1.0.1-SNAPSHOT"))), from.withVersion(new LibraryVersion(DependencyVersion.parse("1.0.1")))); assertThat(upgrade.from().getNameAndVersion()).isEqualTo("Test 1.0.0"); assertThat(upgrade.to().getNameAndVersion()).isEqualTo("Test 1.0.1-SNAPSHOT"); assertThat(upgrade.toRelease().getNameAndVersion()).isEqualTo("Test 1.0.1"); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/ArtifactVersionDependencyVersionTests.java ================================================ /* * 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.bom.bomr.version; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ArtifactVersionDependencyVersion}. * * @author Andy Wilkinson */ class ArtifactVersionDependencyVersionTests { @Test void parseWhenVersionIsNotAMavenVersionShouldReturnNull() { assertThat(version("1.2.3.1")).isNull(); } @Test void parseWhenVersionIsAMavenVersionShouldReturnAVersion() { assertThat(version("1.2.3")).isNotNull(); } @Test void isSameMajorWhenSameMajorAndMinorShouldReturnTrue() { assertThat(version("1.10.2").isSameMajor(version("1.10.0"))).isTrue(); } @Test void isSameMajorWhenSameMajorShouldReturnTrue() { assertThat(version("1.10.2").isSameMajor(version("1.9.0"))).isTrue(); } @Test void isSameMajorWhenDifferentMajorShouldReturnFalse() { assertThat(version("2.0.2").isSameMajor(version("1.9.0"))).isFalse(); } @Test void isSameMinorWhenSameMinorShouldReturnTrue() { assertThat(version("1.10.2").isSameMinor(version("1.10.1"))).isTrue(); } @Test void isSameMinorWhenDifferentMinorShouldReturnFalse() { assertThat(version("1.10.2").isSameMinor(version("1.9.1"))).isFalse(); } @Test void isSnapshotForWhenSnapshotForReleaseShouldReturnTrue() { assertThat(version("1.10.2-SNAPSHOT").isSnapshotFor(version("1.10.2"))).isTrue(); } @Test void isSnapshotForWhenBuildSnapshotForReleaseShouldReturnTrue() { assertThat(version("1.10.2.BUILD-SNAPSHOT").isSnapshotFor(version("1.10.2.RELEASE"))).isTrue(); } @Test void isSnapshotForWhenSnapshotForReleaseCandidateShouldReturnTrue() { assertThat(version("1.10.2-SNAPSHOT").isSnapshotFor(version("1.10.2-RC2"))).isTrue(); } @Test void isSnapshotForWhenBuildSnapshotForReleaseCandidateShouldReturnTrue() { assertThat(version("1.10.2.BUILD-SNAPSHOT").isSnapshotFor(version("1.10.2.RC2"))).isTrue(); } @Test void isSnapshotForWhenSnapshotForMilestoneShouldReturnTrue() { assertThat(version("1.10.2-SNAPSHOT").isSnapshotFor(version("1.10.2-M1"))).isTrue(); } @Test void isSnapshotForWhenBuildSnapshotForMilestoneShouldReturnTrue() { assertThat(version("1.10.2.BUILD-SNAPSHOT").isSnapshotFor(version("1.10.2.M1"))).isTrue(); } @Test void isSnapshotForWhenSnapshotForDifferentReleaseShouldReturnFalse() { assertThat(version("1.10.1-SNAPSHOT").isSnapshotFor(version("1.10.2"))).isFalse(); } @Test void isSnapshotForWhenBuildSnapshotForDifferentReleaseShouldReturnTrue() { assertThat(version("1.10.1.BUILD-SNAPSHOT").isSnapshotFor(version("1.10.2.RELEASE"))).isFalse(); } @Test void isSnapshotForWhenSnapshotForDifferentReleaseCandidateShouldReturnTrue() { assertThat(version("1.10.1-SNAPSHOT").isSnapshotFor(version("1.10.2-RC2"))).isFalse(); } @Test void isSnapshotForWhenBuildSnapshotForDifferentReleaseCandidateShouldReturnTrue() { assertThat(version("1.10.1.BUILD-SNAPSHOT").isSnapshotFor(version("1.10.2.RC2"))).isFalse(); } @Test void isSnapshotForWhenSnapshotForDifferentMilestoneShouldReturnTrue() { assertThat(version("1.10.1-SNAPSHOT").isSnapshotFor(version("1.10.2-M1"))).isFalse(); } @Test void isSnapshotForWhenBuildSnapshotForDifferentMilestoneShouldReturnTrue() { assertThat(version("1.10.1.BUILD-SNAPSHOT").isSnapshotFor(version("1.10.2.M1"))).isFalse(); } @Test void isSnapshotForWhenNotSnapshotShouldReturnFalse() { assertThat(version("1.10.1-M1").isSnapshotFor(version("1.10.1"))).isFalse(); } private ArtifactVersionDependencyVersion version(String version) { return ArtifactVersionDependencyVersion.parse(version); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/CalendarVersionDependencyVersionTests.java ================================================ /* * 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.bom.bomr.version; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link CalendarVersionDependencyVersion}. * * @author Andy Wilkinson */ class CalendarVersionDependencyVersionTests { @Test void parseWhenVersionIsNotACalendarVersionShouldReturnNull() { assertThat(version("1.2.3")).isNull(); } @Test void parseWhenVersionIsACalendarVersionShouldReturnAVersion() { assertThat(version("2020.0.0")).isNotNull(); } @Test void isSameMajorWhenSameMajorAndMinorShouldReturnTrue() { assertThat(version("2020.0.0").isSameMajor(version("2020.0.1"))).isTrue(); } @Test void isSameMajorWhenSameMajorShouldReturnTrue() { assertThat(version("2020.0.0").isSameMajor(version("2020.1.0"))).isTrue(); } @Test void isSameMajorWhenDifferentMajorShouldReturnFalse() { assertThat(version("2020.0.0").isSameMajor(version("2021.0.0"))).isFalse(); } @Test void isSameMinorWhenSameMinorShouldReturnTrue() { assertThat(version("2020.0.0").isSameMinor(version("2020.0.1"))).isTrue(); } @Test void isSameMinorWhenDifferentMinorShouldReturnFalse() { assertThat(version("2020.0.0").isSameMinor(version("2020.1.0"))).isFalse(); } @Test void calendarVersionIsNotSameMajorAsReleaseTrainVersion() { assertThat(version("2020.0.0").isSameMajor(releaseTrainVersion("Aluminium-RELEASE"))).isFalse(); } @Test void calendarVersionIsNotSameMinorAsReleaseTrainVersion() { assertThat(version("2020.0.0").isSameMinor(releaseTrainVersion("Aluminium-RELEASE"))).isFalse(); } private ReleaseTrainDependencyVersion releaseTrainVersion(String version) { return ReleaseTrainDependencyVersion.parse(version); } private CalendarVersionDependencyVersion version(String version) { return CalendarVersionDependencyVersion.parse(version); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/DependencyVersionTests.java ================================================ /* * 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.bom.bomr.version; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link DependencyVersion}. * * @author Andy Wilkinson * @author Moritz Halbritter */ class DependencyVersionTests { @Test void parseWhenValidMavenVersionShouldReturnArtifactVersionDependencyVersion() { assertThat(DependencyVersion.parse("1.2.3.Final")).isExactlyInstanceOf(ArtifactVersionDependencyVersion.class); } @Test void parseWhenReleaseTrainShouldReturnReleaseTrainDependencyVersion() { assertThat(DependencyVersion.parse("Ingalls-SR5")).isInstanceOf(ReleaseTrainDependencyVersion.class); } @Test void parseWhenMavenLikeVersionWithNumericQualifierShouldReturnNumericQualifierDependencyVersion() { assertThat(DependencyVersion.parse("1.2.3.4")).isInstanceOf(MultipleComponentsDependencyVersion.class); } @Test void parseWhen5ComponentsShouldReturnNumericQualifierDependencyVersion() { assertThat(DependencyVersion.parse("1.2.3.4.5")).isInstanceOf(MultipleComponentsDependencyVersion.class); } @Test void parseWhenVersionWithLeadingZeroesShouldReturnLeadingZeroesDependencyVersion() { assertThat(DependencyVersion.parse("1.4.01")).isInstanceOf(LeadingZeroesDependencyVersion.class); } @Test void parseWhenVersionWithCombinedPatchAndQualifierShouldReturnCombinedPatchAndQualifierDependencyVersion() { assertThat(DependencyVersion.parse("4.0.0M4")).isInstanceOf(CombinedPatchAndQualifierDependencyVersion.class); } @Test void parseWhenCalendarVersionShouldReturnArtifactVersionDependencyVersion() { assertThat(DependencyVersion.parse("2020.0.0")).isInstanceOf(CalendarVersionDependencyVersion.class); } @Test void parseWhenCalendarVersionWithModifierShouldReturnArtifactVersionDependencyVersion() { assertThat(DependencyVersion.parse("2020.0.0-M1")).isInstanceOf(CalendarVersionDependencyVersion.class); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/DependencyVersionUpgradeTests.java ================================================ /* * 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.bom.bomr.version; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.util.function.Function; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.support.ParameterDeclarations; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link DependencyVersion#isUpgrade} of {@link DependencyVersion} * implementations. * * @author Andy Wilkinson */ class DependencyVersionUpgradeTests { @ParameterizedTest @ArtifactVersion(current = "1.2.3", candidate = "1.2.3") @ArtifactVersion(current = "1.2.3.RELEASE", candidate = "1.2.3.RELEASE") @CalendarVersion(current = "2023.0.0", candidate = "2023.0.0") @ReleaseTrain(current = "Kay-RELEASE", candidate = "Kay-RELEASE") void isUpgradeWhenSameVersionShouldReturnFalse(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, false)).isFalse(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3-SNAPSHOT", candidate = "1.2.3-SNAPSHOT") @ArtifactVersion(current = "1.2.3.BUILD-SNAPSHOT", candidate = "1.2.3.BUILD-SNAPSHOT") @CalendarVersion(current = "2023.0.0-SNAPSHOT", candidate = "2023.0.0-SNAPSHOT") @ReleaseTrain(current = "Kay-BUILD-SNAPSHOT", candidate = "Kay-BUILD-SNAPSHOT") void isUpgradeWhenSameSnapshotVersionShouldReturnFalse(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, false)).isFalse(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3-SNAPSHOT", candidate = "1.2.3-SNAPSHOT") @ArtifactVersion(current = "1.2.3.BUILD-SNAPSHOT", candidate = "1.2.3.BUILD-SNAPSHOT") @CalendarVersion(current = "2023.0.0-SNAPSHOT", candidate = "2023.0.0-SNAPSHOT") @ReleaseTrain(current = "Kay-BUILD-SNAPSHOT", candidate = "Kay-BUILD-SNAPSHOT") void isUpgradeWhenSameSnapshotVersionAndMovingToSnapshotsShouldReturnFalse(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, true)).isFalse(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3", candidate = "1.2.4") @ArtifactVersion(current = "1.2.3.RELEASE", candidate = "1.2.4.RELEASE") @CalendarVersion(current = "2023.0.0", candidate = "2023.0.1") @ReleaseTrain(current = "Kay-RELEASE", candidate = "Kay-SR1") void isUpgradeWhenLaterPatchReleaseShouldReturnTrue(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, false)).isTrue(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3", candidate = "1.2.4-SNAPSHOT") @ArtifactVersion(current = "1.2.3.RELEASE", candidate = "1.2.4.BUILD-SNAPSHOT") @CalendarVersion(current = "2023.0.0", candidate = "2023.0.1-SNAPSHOT") void isUpgradeWhenSnapshotOfLaterPatchReleaseShouldReturnTrue(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, false)).isTrue(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3", candidate = "1.2.4-SNAPSHOT") @ArtifactVersion(current = "1.2.3.RELEASE", candidate = "1.2.4.BUILD-SNAPSHOT") @CalendarVersion(current = "2023.0.0", candidate = "2023.0.1-SNAPSHOT") @ReleaseTrain(current = "Kay-RELEASE", candidate = "Kay-BUILD-SNAPSHOT") void isUpgradeWhenSnapshotOfLaterPatchReleaseAndMovingToSnapshotsShouldReturnTrue(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, true)).isTrue(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3", candidate = "1.2.3-SNAPSHOT") @ArtifactVersion(current = "1.2.3.RELEASE", candidate = "1.2.3.BUILD-SNAPSHOT") @CalendarVersion(current = "2023.0.0", candidate = "2023.0.0-SNAPSHOT") @ReleaseTrain(current = "Kay-RELEASE", candidate = "Kay-BUILD-SNAPSHOT") void isUpgradeWhenSnapshotOfSameVersionShouldReturnFalse(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, false)).isFalse(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3-SNAPSHOT", candidate = "1.2.3-M2") @ArtifactVersion(current = "1.2.3.BUILD-SNAPSHOT", candidate = "1.2.3.M2") @CalendarVersion(current = "2023.0.0-SNAPSHOT", candidate = "2023.0.0-M2") @ReleaseTrain(current = "Kay-BUILD-SNAPSHOT", candidate = "Kay-M2") void isUpgradeWhenSnapshotToMilestoneShouldReturnTrue(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, false)).isTrue(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3-SNAPSHOT", candidate = "1.2.3-RC1") @ArtifactVersion(current = "1.2.3.BUILD-SNAPSHOT", candidate = "1.2.3.RC1") @CalendarVersion(current = "2023.0.0-SNAPSHOT", candidate = "2023.0.0-RC1") @ReleaseTrain(current = "Kay-BUILD-SNAPSHOT", candidate = "Kay-RC1") void isUpgradeWhenSnapshotToReleaseCandidateShouldReturnTrue(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, false)).isTrue(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3-SNAPSHOT", candidate = "1.2.3") @ArtifactVersion(current = "1.2.3.BUILD-SNAPSHOT", candidate = "1.2.3.RELEASE") @CalendarVersion(current = "2023.0.0-SNAPSHOT", candidate = "2023.0.0") @ReleaseTrain(current = "Kay-BUILD-SNAPSHOT", candidate = "Kay-RELEASE") void isUpgradeWhenSnapshotToReleaseShouldReturnTrue(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, false)).isTrue(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3-M1", candidate = "1.2.3-SNAPSHOT") @ArtifactVersion(current = "1.2.3.M1", candidate = "1.2.3.BUILD-SNAPSHOT") @CalendarVersion(current = "2023.0.0-M1", candidate = "2023.0.0-SNAPSHOT") @ReleaseTrain(current = "Kay-M1", candidate = "Kay-BUILD-SNAPSHOT") void isUpgradeWhenMilestoneToSnapshotShouldReturnFalse(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, false)).isFalse(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3-RC1", candidate = "1.2.3-SNAPSHOT") @ArtifactVersion(current = "1.2.3.RC1", candidate = "1.2.3.BUILD-SNAPSHOT") @CalendarVersion(current = "2023.0.0-RC1", candidate = "2023.0.0-SNAPSHOT") @ReleaseTrain(current = "Kay-RC1", candidate = "Kay-BUILD-SNAPSHOT") void isUpgradeWhenReleaseCandidateToSnapshotShouldReturnFalse(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, false)).isFalse(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3", candidate = "1.2.3-SNAPSHOT") @ArtifactVersion(current = "1.2.3.RELEASE", candidate = "1.2.3.BUILD-SNAPSHOT") @CalendarVersion(current = "2023.0.0", candidate = "2023.0.0-SNAPSHOT") @ReleaseTrain(current = "Kay-RELEASE", candidate = "Kay-BUILD-SNAPSHOT") void isUpgradeWhenReleaseToSnapshotShouldReturnFalse(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, false)).isFalse(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3-M1", candidate = "1.2.3-SNAPSHOT") @ArtifactVersion(current = "1.2.3.M1", candidate = "1.2.3.BUILD-SNAPSHOT") @CalendarVersion(current = "2023.0.0-M1", candidate = "2023.0.0-SNAPSHOT") @ReleaseTrain(current = "Kay-M1", candidate = "Kay-BUILD-SNAPSHOT") void isUpgradeWhenMilestoneToSnapshotAndMovingToSnapshotsShouldReturnTrue(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, true)).isTrue(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3-RC1", candidate = "1.2.3-SNAPSHOT") @ArtifactVersion(current = "1.2.3.RC1", candidate = "1.2.3.BUILD-SNAPSHOT") @CalendarVersion(current = "2023.0.0-RC1", candidate = "2023.0.0-SNAPSHOT") @ReleaseTrain(current = "Kay-RC1", candidate = "Kay-BUILD-SNAPSHOT") void isUpgradeWhenReleaseCandidateToSnapshotAndMovingToSnapshotsShouldReturnTrue(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, true)).isTrue(); } @ParameterizedTest @ArtifactVersion(current = "1.2.3", candidate = "1.2.3-SNAPSHOT") @ArtifactVersion(current = "1.2.3.RELEASE", candidate = "1.2.3.BUILD-SNAPSHOT") @CalendarVersion(current = "2023.0.0", candidate = "2023.0.0-SNAPSHOT") void isUpgradeWhenReleaseToSnapshotAndMovingToSnapshotsShouldReturnFalse(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, true)).isFalse(); } @ParameterizedTest @ReleaseTrain(current = "Kay-RELEASE", candidate = "Kay-BUILD-SNAPSHOT") void isUpgradeWhenReleaseTrainToSnapshotAndMovingToSnapshotsShouldReturnTrue(DependencyVersion current, DependencyVersion candidate) { assertThat(current.isUpgrade(candidate, true)).isTrue(); } @Repeatable(ArtifactVersions.class) @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @ArgumentsSource(InputProvider.class) @interface ArtifactVersion { String current(); String candidate(); } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface ArtifactVersions { ArtifactVersion[] value(); } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @ArgumentsSource(InputProvider.class) @interface ReleaseTrain { String current(); String candidate(); } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @ArgumentsSource(InputProvider.class) @interface CalendarVersion { String current(); String candidate(); } static class InputProvider implements ArgumentsProvider { @Override public Stream provideArguments(ParameterDeclarations parameterDeclarations, ExtensionContext context) { Method testMethod = context.getRequiredTestMethod(); Stream artifactVersions = artifactVersions(testMethod) .map((artifactVersion) -> Arguments.of(VersionType.ARTIFACT_VERSION.parse(artifactVersion.current()), VersionType.ARTIFACT_VERSION.parse(artifactVersion.candidate()))); Stream releaseTrains = releaseTrains(testMethod) .map((releaseTrain) -> Arguments.of(VersionType.RELEASE_TRAIN.parse(releaseTrain.current()), VersionType.RELEASE_TRAIN.parse(releaseTrain.candidate()))); Stream calendarVersions = calendarVersions(testMethod) .map((calendarVersion) -> Arguments.of(VersionType.CALENDAR_VERSION.parse(calendarVersion.current()), VersionType.CALENDAR_VERSION.parse(calendarVersion.candidate()))); return Stream.concat(Stream.concat(artifactVersions, releaseTrains), calendarVersions); } private Stream artifactVersions(Method testMethod) { ArtifactVersions artifactVersions = testMethod.getAnnotation(ArtifactVersions.class); if (artifactVersions != null) { return Stream.of(artifactVersions.value()); } return versions(testMethod, ArtifactVersion.class); } private Stream releaseTrains(Method testMethod) { return versions(testMethod, ReleaseTrain.class); } private Stream calendarVersions(Method testMethod) { return versions(testMethod, CalendarVersion.class); } private Stream versions(Method testMethod, Class type) { T annotation = testMethod.getAnnotation(type); return (annotation != null) ? Stream.of(annotation) : Stream.empty(); } } enum VersionType { ARTIFACT_VERSION(ArtifactVersionDependencyVersion::parse), CALENDAR_VERSION(CalendarVersionDependencyVersion::parse), RELEASE_TRAIN(ReleaseTrainDependencyVersion::parse); private final Function parser; VersionType(Function parser) { this.parser = parser; } DependencyVersion parse(String version) { return this.parser.apply(version); } } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/MultipleComponentsDependencyVersionTests.java ================================================ /* * 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.bom.bomr.version; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link MultipleComponentsDependencyVersion}. * * @author Andy Wilkinson * @author Moritz Halbritter */ class MultipleComponentsDependencyVersionTests { @Test void isSameMajorOfFiveComponentVersionWithSameMajorShouldReturnTrue() { assertThat(version("21.4.0.0.1").isSameMajor(version("21.1.0.0"))).isTrue(); } @Test void isSameMajorOfFiveComponentVersionWithDifferentMajorShouldReturnFalse() { assertThat(version("21.4.0.0.1").isSameMajor(version("22.1.0.0"))).isFalse(); } @Test void isSameMinorOfFiveComponentVersionWithSameMinorShouldReturnTrue() { assertThat(version("21.4.0.0.1").isSameMinor(version("21.4.0.0"))).isTrue(); } @Test void isSameMinorOfFiveComponentVersionWithDifferentMinorShouldReturnFalse() { assertThat(version("21.4.0.0.1").isSameMinor(version("21.5.0.0"))).isFalse(); } private MultipleComponentsDependencyVersion version(String version) { return MultipleComponentsDependencyVersion.parse(version); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/ReleaseTrainDependencyVersionTests.java ================================================ /* * 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.bom.bomr.version; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ReleaseTrainDependencyVersion}. * * @author Andy Wilkinson */ class ReleaseTrainDependencyVersionTests { @Test void parsingOfANonReleaseTrainVersionReturnsNull() { assertThat(version("5.1.4.RELEASE")).isNull(); } @Test void parsingOfAReleaseTrainVersionReturnsVersion() { assertThat(version("Lovelace-SR3")).isNotNull(); } @Test void isSameMajorWhenReleaseTrainIsDifferentShouldReturnFalse() { assertThat(version("Lovelace-RELEASE").isSameMajor(version("Kay-SR5"))).isFalse(); } @Test void isSameMajorWhenReleaseTrainIsTheSameShouldReturnTrue() { assertThat(version("Lovelace-RELEASE").isSameMajor(version("Lovelace-SR5"))).isTrue(); } @Test void isSameMinorWhenReleaseTrainIsDifferentShouldReturnFalse() { assertThat(version("Lovelace-RELEASE").isSameMajor(version("Kay-SR5"))).isFalse(); } @Test void isSameMinorWhenReleaseTrainIsTheSameShouldReturnTrue() { assertThat(version("Lovelace-RELEASE").isSameMajor(version("Lovelace-SR5"))).isTrue(); } @Test void releaseTrainVersionIsNotSameMajorAsCalendarTrainVersion() { assertThat(version("Kay-SR6").isSameMajor(calendarVersion("2020.0.0"))).isFalse(); } @Test void releaseTrainVersionIsNotSameMinorAsCalendarVersion() { assertThat(version("Kay-SR6").isSameMinor(calendarVersion("2020.0.0"))).isFalse(); } @Test void isSnapshotForWhenSnapshotForServiceReleaseShouldReturnTrue() { assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Kay-SR2"))).isTrue(); } @Test void isSnapshotForWhenSnapshotForReleaseShouldReturnTrue() { assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Kay-RELEASE"))).isTrue(); } @Test void isSnapshotForWhenSnapshotForReleaseCandidateShouldReturnTrue() { assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Kay-RC1"))).isTrue(); } @Test void isSnapshotForWhenSnapshotForMilestoneShouldReturnTrue() { assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Kay-M2"))).isTrue(); } @Test void isSnapshotForWhenSnapshotForDifferentReleaseShouldReturnFalse() { assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Lovelace-RELEASE"))).isFalse(); } @Test void isSnapshotForWhenSnapshotForDifferentReleaseCandidateShouldReturnTrue() { assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Lovelace-RC2"))).isFalse(); } @Test void isSnapshotForWhenSnapshotForDifferentMilestoneShouldReturnTrue() { assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Lovelace-M1"))).isFalse(); } @Test void isSnapshotForWhenNotSnapshotShouldReturnFalse() { assertThat(version("Kay-M1").isSnapshotFor(version("Kay-RELEASE"))).isFalse(); } private static ReleaseTrainDependencyVersion version(String input) { return ReleaseTrainDependencyVersion.parse(input); } private CalendarVersionDependencyVersion calendarVersion(String version) { return CalendarVersionDependencyVersion.parse(version); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/context/properties/CompoundRowTests.java ================================================ /* * 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.context.properties; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link CompoundRow}. * * @author Brian Clozel * @author Moritz Halbritter */ class CompoundRowTests { private static final String NEWLINE = System.lineSeparator(); private static final Snippet SNIPPET = new Snippet("my", "title", null); @Test void simpleProperty() { CompoundRow row = new CompoundRow(SNIPPET, "spring.test", "This is a description."); row.addProperty(new ConfigurationProperty("spring.test.first", "java.lang.String")); row.addProperty(new ConfigurationProperty("spring.test.second", "java.lang.String")); row.addProperty(new ConfigurationProperty("spring.test.third", "java.lang.String")); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); assertThat(asciidoc).hasToString("|[[my.spring.test]]xref:#my.spring.test[`+spring.test.first+` +" + NEWLINE + "`+spring.test.second+` +" + NEWLINE + "`+spring.test.third+` +" + NEWLINE + "]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|" + NEWLINE); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesAnalyzerTests.java ================================================ /* * 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.context.properties; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Analysis; import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Report; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link ConfigurationPropertiesAnalyzer}. * * @author Stephane Nicoll */ class ConfigurationPropertiesAnalyzerTests { @Test void createAnalyzerWithNoSource() { assertThatIllegalArgumentException() .isThrownBy(() -> new ConfigurationPropertiesAnalyzer(Collections.emptyList())) .withMessage("At least one source should be provided"); } @Test void analyzeOrderWithAlphabeticalOrder(@TempDir File tempDir) throws IOException { File metadata = new File(tempDir, "metadata.json"); Files.writeString(metadata.toPath(), """ { "properties": [ { "name": "abc"}, {"name": "def"}, {"name": "xyz"} ] }"""); Report report = new Report(tempDir); ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(List.of(metadata)); analyzer.analyzeOrder(report); assertThat(report.hasProblems()).isFalse(); assertThat(report.getAnalyses(metadata)).singleElement() .satisfies(((analysis) -> assertThat(analysis.getItems()).isEmpty())); } @Test void analyzeOrderWithViolations(@TempDir File tempDir) throws IOException { File metadata = new File(tempDir, "metadata.json"); Files.writeString(metadata.toPath(), """ { "properties": [ { "name": "def"}, {"name": "abc"}, {"name": "xyz"} ] }"""); Report report = new Report(tempDir); ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(List.of(metadata)); analyzer.analyzeOrder(report); assertThat(report.hasProblems()).isTrue(); assertThat(report.getAnalyses(metadata)).singleElement() .satisfies((analysis) -> assertThat(analysis.getItems()).containsExactly( "Wrong order at $.properties[0].name - expected 'abc' but found 'def'", "Wrong order at $.properties[1].name - expected 'def' but found 'abc'")); } @Test void analyzeDuplicatesWithNoDuplicates(@TempDir File tempDir) throws IOException { File metadata = new File(tempDir, "metadata.json"); Files.writeString(metadata.toPath(), """ { "properties": [ { "name": "abc"}, {"name": "def"}, {"name": "xyz"} ] }"""); Report report = new Report(tempDir); ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(List.of(metadata)); analyzer.analyzeOrder(report); assertThat(report.hasProblems()).isFalse(); assertThat(report.getAnalyses(metadata)).singleElement() .satisfies(((analysis) -> assertThat(analysis.getItems()).isEmpty())); } @Test void analyzeDuplicatesWithDuplicate(@TempDir File tempDir) throws IOException { File metadata = new File(tempDir, "metadata.json"); Files.writeString(metadata.toPath(), """ { "properties": [ { "name": "abc"}, {"name": "abc"}, {"name": "def"} ] }"""); Report report = new Report(tempDir); ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(List.of(metadata)); analyzer.analyzeDuplicates(report); assertThat(report.hasProblems()).isTrue(); assertThat(report.getAnalyses(metadata)).singleElement() .satisfies((analysis) -> assertThat(analysis.getItems()) .containsExactly("Duplicate name 'abc' at $.properties[1]")); } @Test void analyzePropertyDescription(@TempDir File tempDir) throws IOException { File metadata = new File(tempDir, "metadata.json"); Files.writeString(metadata.toPath(), """ { "properties": [ { "name": "abc", "description": "This is abc." }, { "name": "def", "description": "This is def." }, { "name": "xyz", "description": "This is xyz." } ] }"""); Report report = new Report(tempDir); ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(List.of(metadata)); analyzer.analyzePropertyDescription(report, List.of()); assertThat(report.hasProblems()).isFalse(); assertThat(report.getAnalyses(metadata)).singleElement() .satisfies(((analysis) -> assertThat(analysis.getItems()).isEmpty())); } @Test void analyzePropertyDescriptionWithMissingDescription(@TempDir File tempDir) throws IOException { File metadata = new File(tempDir, "metadata.json"); Files.writeString(metadata.toPath(), """ { "properties": [ { "name": "abc", "description": "This is abc." }, { "name": "def" }, { "name": "xyz", "description": "This is xyz." } ] }"""); Report report = new Report(tempDir); ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(List.of(metadata)); analyzer.analyzePropertyDescription(report, List.of()); assertThat(report.hasProblems()).isTrue(); assertThat(report.getAnalyses(metadata)).singleElement() .satisfies(((analysis) -> assertThat(analysis.getItems()).containsExactly("def"))); } @Test void analyzeDeprecatedPropertyWithMissingSince(@TempDir File tempDir) throws IOException { File metadata = new File(tempDir, "metadata.json"); Files.writeString(metadata.toPath(), """ { "properties": [ { "name": "abc", "description": "This is abc.", "deprecation": { "reason": "abc reason", "since": "3.0.0" } }, { "name": "def", "description": "This is def." }, { "name": "xyz", "description": "This is xyz.", "deprecation": { "reason": "xyz reason" } } ] }"""); Report report = new Report(tempDir); ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(List.of(metadata)); analyzer.analyzeDeprecationSince(report); assertThat(report.hasProblems()).isTrue(); assertThat(report.getAnalyses(metadata)).singleElement() .satisfies(((analysis) -> assertThat(analysis.getItems()).containsExactly("xyz"))); } @Test void writeEmptyReport(@TempDir File tempDir) throws IOException { assertThat(writeToFile(tempDir, new Report(tempDir))).hasContent("No problems found."); } @Test void writeReportWithNoProblemsFound(@TempDir File tempDir) throws IOException { Report report = new Report(tempDir); File first = new File(tempDir, "metadata-1.json"); report.registerAnalysis(first, new Analysis("Check for things:")); File second = new File(tempDir, "metadata-2.json"); report.registerAnalysis(second, new Analysis("Check for other things:")); assertThat(writeToFile(tempDir, report)).content().isEqualToIgnoringNewLines(""" metadata-1.json No problems found. metadata-2.json No problems found. """); } @Test void writeReportWithOneProblem(@TempDir File tempDir) throws IOException { Report report = new Report(tempDir); File metadata = new File(tempDir, "metadata-1.json"); Analysis analysis = new Analysis("Check for things:"); analysis.addItem("Should not be deprecated"); report.registerAnalysis(metadata, analysis); report.registerAnalysis(metadata, new Analysis("Check for other things:")); assertThat(writeToFile(tempDir, report)).content().isEqualToIgnoringNewLines(""" metadata-1.json Check for things: - Should not be deprecated Check for other things: No problems found. """); } @Test void writeReportWithSeveralProblems(@TempDir File tempDir) throws IOException { Report report = new Report(tempDir); File metadata = new File(tempDir, "metadata-1.json"); Analysis firstAnalysis = new Analysis("Check for things:"); firstAnalysis.addItem("Should not be deprecated"); firstAnalysis.addItem("Should not be public"); report.registerAnalysis(metadata, firstAnalysis); Analysis secondAnalysis = new Analysis("Check for other things:"); secondAnalysis.addItem("Field 'this' not expected"); report.registerAnalysis(metadata, secondAnalysis); assertThat(writeToFile(tempDir, report)).content().isEqualToIgnoringNewLines(""" metadata-1.json Check for things: - Should not be deprecated - Should not be public Check for other things: - Field 'this' not expected """); } private File writeToFile(File directory, Report report) throws IOException { File file = new File(directory, "report.txt"); report.write(file); return file; } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesTests.java ================================================ /* * 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.context.properties; import java.io.File; import java.util.Arrays; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ConfigurationProperties} * * @author Andy Wilkinson */ class ConfigurationPropertiesTests { @Test void whenJsonHasAnIntegerDefaultValueThenItRemainsAnIntegerWhenRead() { ConfigurationProperties properties = ConfigurationProperties .fromFiles(Arrays.asList(new File("src/test/resources/spring-configuration-metadata.json"))); assertThat(properties.get("example.counter").getDefaultValue()).isEqualTo(0); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/context/properties/SingleRowTests.java ================================================ /* * 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.context.properties; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link SingleRow}. * * @author Brian Clozel * @author Moritz Halbritter */ class SingleRowTests { private static final String NEWLINE = System.lineSeparator(); private static final Snippet SNIPPET = new Snippet("my", "title", null); @Test void simpleProperty() { ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", "something", "This is a description.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+something+`" + NEWLINE); } @Test void noDefaultValue() { ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null, "This is a description.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|" + NEWLINE); } @Test void defaultValueWithPipes() { ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", "first|second", "This is a description.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+first\\|second+`" + NEWLINE); } @Test void defaultValueWithBackslash() { ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", "first\\second", "This is a description.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+first\\\\second+`" + NEWLINE); } @Test void descriptionWithPipe() { ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null, "This is a description with a | pipe.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description with a \\| pipe.+++" + NEWLINE + "|" + NEWLINE); } @Test void mapProperty() { ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.util.Map", null, "This is a description.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop.*+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|" + NEWLINE); } @Test void listProperty() { String[] defaultValue = new String[] { "first", "second", "third" }; ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.util.List", defaultValue, "This is a description.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); assertThat(asciidoc).hasToString("|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+first," + NEWLINE + "second," + NEWLINE + "third+`" + NEWLINE); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/context/properties/TableTests.java ================================================ /* * 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.context.properties; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link Table}. * * @author Brian Clozel * @author Moritz Halbritter */ class TableTests { private static final String NEWLINE = System.lineSeparator(); private static final Snippet SNIPPET = new Snippet("my", "title", null); @Test void simpleTable() { Table table = new Table(); table.addRow(new SingleRow(SNIPPET, new ConfigurationProperty("spring.test.prop", "java.lang.String", "something", "This is a description.", false, null))); table.addRow(new SingleRow(SNIPPET, new ConfigurationProperty("spring.test.other", "java.lang.String", "other value", "This is another description.", false, null))); Asciidoc asciidoc = new Asciidoc(); table.write(asciidoc); // @formatter:off assertThat(asciidoc).hasToString("[cols=\"4,3,3\", options=\"header\"]" + NEWLINE + "|===" + NEWLINE + "|Name|Description|Default Value" + NEWLINE + NEWLINE + "|[[my.spring.test.other]]xref:#my.spring.test.other[`+spring.test.other+`]" + NEWLINE + "|+++This is another description.+++" + NEWLINE + "|`+other value+`" + NEWLINE + NEWLINE + "|[[my.spring.test.prop]]xref:#my.spring.test.prop[`+spring.test.prop+`]" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+something+`" + NEWLINE + NEWLINE + "|===" + NEWLINE); // @formatter:on } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/groovyscripts/SpringRepositoriesExtensionTests.java ================================================ /* * 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.groovyscripts; import java.io.File; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.UnaryOperator; import groovy.lang.Closure; import groovy.lang.GroovyClassLoader; import org.gradle.api.Action; import org.gradle.api.artifacts.dsl.RepositoryHandler; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.artifacts.repositories.MavenRepositoryContentDescriptor; import org.gradle.api.artifacts.repositories.PasswordCredentials; import org.gradle.api.artifacts.repositories.RepositoryContentDescriptor; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.mockito.invocation.InvocationOnMock; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; /** * Tests for {@code SpringRepositorySupport.groovy}. * * @author Phillip Webb */ class SpringRepositoriesExtensionTests { private static GroovyClassLoader groovyClassLoader; private static Class supportClass; @BeforeAll static void loadGroovyClass() throws Exception { groovyClassLoader = new GroovyClassLoader(SpringRepositoriesExtensionTests.class.getClassLoader()); supportClass = groovyClassLoader.parseClass(new File("SpringRepositorySupport.groovy")); } @AfterAll static void cleanup() throws Exception { groovyClassLoader.close(); } private final List repositories = new ArrayList<>(); private final List contents = new ArrayList<>(); private final List credentials = new ArrayList<>(); private final List mavenContent = new ArrayList<>(); @Test void mavenRepositoriesWhenNotCommercialSnapshot() { SpringRepositoriesExtension extension = createExtension("0.0.0-SNAPSHOT", "oss"); extension.mavenRepositories(); assertThat(this.repositories).hasSize(1); verify(this.repositories.get(0)).setName("spring-oss-snapshot"); verify(this.repositories.get(0)).setUrl("https://repo.spring.io/snapshot"); verify(this.mavenContent.get(0)).snapshotsOnly(); } @Test void mavenRepositoriesWhenCommercialSnapshot() { SpringRepositoriesExtension extension = createExtension("0.0.0-SNAPSHOT", "commercial"); extension.mavenRepositories(); assertThat(this.repositories).hasSize(3); verify(this.repositories.get(0)).setName("spring-commercial-release"); verify(this.repositories.get(0)) .setUrl("https://usw1.packages.broadcom.com/spring-enterprise-maven-prod-local"); verify(this.mavenContent.get(0)).releasesOnly(); verify(this.repositories.get(1)).setName("spring-commercial-snapshot"); verify(this.repositories.get(1)).setUrl("https://usw1.packages.broadcom.com/spring-enterprise-maven-dev-local"); verify(this.mavenContent.get(1)).snapshotsOnly(); verify(this.repositories.get(2)).setName("spring-oss-snapshot"); verify(this.repositories.get(2)).setUrl("https://repo.spring.io/snapshot"); verify(this.mavenContent.get(2)).snapshotsOnly(); } @Test void mavenRepositoriesWhenNotCommercialMilestone() { SpringRepositoriesExtension extension = createExtension("0.0.0-M1", "oss"); extension.mavenRepositories(); assertThat(this.repositories).isEmpty(); } @Test void mavenRepositoriesWhenCommercialMilestone() { SpringRepositoriesExtension extension = createExtension("0.0.0-M1", "commercial"); extension.mavenRepositories(); assertThat(this.repositories).hasSize(1); verify(this.repositories.get(0)).setName("spring-commercial-release"); verify(this.repositories.get(0)) .setUrl("https://usw1.packages.broadcom.com/spring-enterprise-maven-prod-local"); verify(this.mavenContent.get(0)).releasesOnly(); } @Test void mavenRepositoriesWhenNotCommercialRelease() { SpringRepositoriesExtension extension = createExtension("0.0.1", "oss"); extension.mavenRepositories(); assertThat(this.repositories).isEmpty(); } @Test void mavenRepositoriesWhenCommercialRelease() { SpringRepositoriesExtension extension = createExtension("0.0.1", "commercial"); extension.mavenRepositories(); assertThat(this.repositories).hasSize(1); verify(this.repositories.get(0)).setName("spring-commercial-release"); verify(this.repositories.get(0)) .setUrl("https://usw1.packages.broadcom.com/spring-enterprise-maven-prod-local"); verify(this.mavenContent.get(0)).releasesOnly(); } @Test void mavenRepositoriesWhenConditionMatches() { SpringRepositoriesExtension extension = createExtension("0.0.0-SNAPSHOT", "oss"); extension.mavenRepositoriesFor("1.2.3-SNAPSHOT"); assertThat(this.repositories).hasSize(1); } @Test void mavenRepositoriesWhenConditionDoesNotMatch() { SpringRepositoriesExtension extension = createExtension("0.0.0-SNAPSHOT", "oss"); extension.mavenRepositoriesFor("1.2.3"); assertThat(this.repositories).isEmpty(); } @Test void mavenRepositoriesExcludingBootGroup() { SpringRepositoriesExtension extension = createExtension("0.0.0-SNAPSHOT", "oss"); extension.mavenRepositoriesExcludingBootGroup(); assertThat(this.contents).hasSize(1); verify(this.contents.get(0)).excludeGroup("org.springframework.boot"); } @Test void mavenRepositoriesWithRepositorySpecificEnvironmentVariables() { Map environment = new HashMap<>(); environment.put("COMMERCIAL_RELEASE_REPO_URL", "curl"); environment.put("COMMERCIAL_RELEASE_REPO_USERNAME", "cuser"); environment.put("COMMERCIAL_RELEASE_REPO_PASSWORD", "cpass"); environment.put("COMMERCIAL_SNAPSHOT_REPO_URL", "surl"); environment.put("COMMERCIAL_SNAPSHOT_REPO_USERNAME", "suser"); environment.put("COMMERCIAL_SNAPSHOT_REPO_PASSWORD", "spass"); SpringRepositoriesExtension extension = createExtension("0.0.0-SNAPSHOT", "commercial", environment::get); extension.mavenRepositories(); assertThat(this.repositories).hasSize(3); verify(this.repositories.get(0)).setUrl("curl"); verify(this.repositories.get(1)).setUrl("surl"); assertThat(this.credentials).hasSize(2); verify(this.credentials.get(0)).setUsername("cuser"); verify(this.credentials.get(0)).setPassword("cpass"); verify(this.credentials.get(1)).setUsername("suser"); verify(this.credentials.get(1)).setPassword("spass"); } @Test void mavenRepositoriesWhenRepositoryEnvironmentVariables() { Map environment = new HashMap<>(); environment.put("COMMERCIAL_REPO_URL", "url"); environment.put("COMMERCIAL_REPO_USERNAME", "user"); environment.put("COMMERCIAL_REPO_PASSWORD", "pass"); SpringRepositoriesExtension extension = createExtension("0.0.0-SNAPSHOT", "commercial", environment::get); extension.mavenRepositories(); assertThat(this.repositories).hasSize(3); verify(this.repositories.get(0)).setUrl("url"); verify(this.repositories.get(1)).setUrl("url"); assertThat(this.credentials).hasSize(2); verify(this.credentials.get(0)).setUsername("user"); verify(this.credentials.get(0)).setPassword("pass"); verify(this.credentials.get(1)).setUsername("user"); verify(this.credentials.get(1)).setPassword("pass"); } private SpringRepositoriesExtension createExtension(String version, String buildType) { return createExtension(version, buildType, (name) -> null); } @SuppressWarnings({ "unchecked", "unchecked" }) private SpringRepositoriesExtension createExtension(String version, String buildType, UnaryOperator environment) { RepositoryHandler repositoryHandler = mock(RepositoryHandler.class); given(repositoryHandler.maven(any(Closure.class))).willAnswer(this::mavenClosure); return SpringRepositoriesExtension.get(repositoryHandler, version, buildType, environment); } @SuppressWarnings({ "unchecked", "unchecked" }) private Object mavenClosure(InvocationOnMock invocation) { MavenArtifactRepository repository = mock(MavenArtifactRepository.class); willAnswer(this::contentAction).given(repository).content(any(Action.class)); willAnswer(this::credentialsAction).given(repository).credentials(any(Action.class)); willAnswer(this::mavenContentAction).given(repository).mavenContent(any(Action.class)); Closure closure = invocation.getArgument(0); closure.call(repository); this.repositories.add(repository); return null; } private Object contentAction(InvocationOnMock invocation) { RepositoryContentDescriptor content = mock(RepositoryContentDescriptor.class); Action action = invocation.getArgument(0); action.execute(content); this.contents.add(content); return null; } private Object credentialsAction(InvocationOnMock invocation) { PasswordCredentials credentials = mock(PasswordCredentials.class); Action action = invocation.getArgument(0); action.execute(credentials); this.credentials.add(credentials); return null; } private Object mavenContentAction(InvocationOnMock invocation) { MavenRepositoryContentDescriptor mavenContent = mock(MavenRepositoryContentDescriptor.class); Action action = invocation.getArgument(0); action.execute(mavenContent); this.mavenContent.add(mavenContent); return null; } interface SpringRepositoriesExtension { void mavenRepositories(); void mavenRepositoriesFor(Object version); void mavenRepositoriesExcludingBootGroup(); static SpringRepositoriesExtension get(RepositoryHandler repositoryHandler, String version, String buildType, UnaryOperator environment) { try { Class extensionClass = supportClass.getClassLoader().loadClass("SpringRepositoriesExtension"); Object extension = extensionClass .getDeclaredConstructor(Object.class, Object.class, Object.class, Object.class) .newInstance(repositoryHandler, version, buildType, environment); return (SpringRepositoriesExtension) Proxy.newProxyInstance( SpringRepositoriesExtensionTests.class.getClassLoader(), new Class[] { SpringRepositoriesExtension.class }, (instance, method, args) -> { Class[] params = new Class[(args != null) ? args.length : 0]; Arrays.fill(params, Object.class); Method groovyMethod = extension.getClass().getDeclaredMethod(method.getName(), params); return groovyMethod.invoke(extension, args); }); } catch (Exception ex) { throw new RuntimeException(ex); } } } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/mavenplugin/PluginXmlParserTests.java ================================================ /* * 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.mavenplugin; import java.io.File; import java.io.FileNotFoundException; import org.junit.jupiter.api.Test; import org.springframework.boot.build.mavenplugin.PluginXmlParser.Plugin; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link PluginXmlParser}. * * @author Andy Wilkinson * @author Mike Smithson */ class PluginXmlParserTests { private final PluginXmlParser parser = new PluginXmlParser(); @Test void parseExistingDescriptorReturnPluginDescriptor() { Plugin plugin = this.parser.parse(new File("src/test/resources/plugin.xml")); assertThat(plugin.getGroupId()).isEqualTo("org.springframework.boot"); assertThat(plugin.getArtifactId()).isEqualTo("spring-boot-maven-plugin"); assertThat(plugin.getVersion()).isEqualTo("2.2.0.GRADLE-SNAPSHOT"); assertThat(plugin.getGoalPrefix()).isEqualTo("spring-boot"); assertThat(plugin.getMojos().stream().map(PluginXmlParser.Mojo::getGoal)).containsExactly("build-info", "help", "repackage", "run", "start", "stop"); } @Test void parseNonExistingFileThrowException() { assertThatExceptionOfType(RuntimeException.class) .isThrownBy(() -> this.parser.parse(new File("src/test/resources/nonexistent.xml"))) .withCauseInstanceOf(FileNotFoundException.class); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/optional/OptionalDependenciesPluginIntegrationTests.java ================================================ /* * 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.optional; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link OptionalDependenciesPlugin}. * * @author Andy Wilkinson */ class OptionalDependenciesPluginIntegrationTests { private File projectDir; private File buildFile; @BeforeEach void setup(@TempDir File projectDir) { this.projectDir = projectDir; this.buildFile = new File(this.projectDir, "build.gradle"); } @Test void optionalConfigurationIsCreated() throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins { id 'org.springframework.boot.optional-dependencies' }"); out.println("task printConfigurations {"); out.println(" doLast {"); out.println(" configurations.all { println it.name }"); out.println(" }"); out.println("}"); } BuildResult buildResult = runGradle("printConfigurations"); assertThat(buildResult.getOutput()).contains(OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME); } @Test void optionalDependenciesAreAddedToMainSourceSetsCompileClasspath() throws IOException { optionalDependenciesAreAddedToSourceSetClasspath("main", "compileClasspath"); } @Test void optionalDependenciesAreAddedToMainSourceSetsRuntimeClasspath() throws IOException { optionalDependenciesAreAddedToSourceSetClasspath("main", "runtimeClasspath"); } @Test void optionalDependenciesAreAddedToTestSourceSetsCompileClasspath() throws IOException { optionalDependenciesAreAddedToSourceSetClasspath("test", "compileClasspath"); } @Test void optionalDependenciesAreAddedToTestSourceSetsRuntimeClasspath() throws IOException { optionalDependenciesAreAddedToSourceSetClasspath("test", "runtimeClasspath"); } private void optionalDependenciesAreAddedToSourceSetClasspath(String sourceSet, String classpath) throws IOException { try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) { out.println("plugins {"); out.println(" id 'org.springframework.boot.optional-dependencies'"); out.println(" id 'java'"); out.println("}"); out.println("repositories {"); out.println(" mavenCentral()"); out.println("}"); out.println("dependencies {"); out.println(" optional 'org.springframework:spring-jcl:5.1.2.RELEASE'"); out.println("}"); out.println("task printClasspath {"); out.println(" doLast {"); out.println(" println sourceSets." + sourceSet + "." + classpath + ".files"); out.println(" }"); out.println("}"); } BuildResult buildResult = runGradle("printClasspath"); assertThat(buildResult.getOutput()).contains("spring-jcl"); } private BuildResult runGradle(String... args) { return GradleRunner.create().withProjectDir(this.projectDir).withArguments(args).withPluginClasspath().build(); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/test/autoconfigure/TestSliceMetadataTests.java ================================================ /* * 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); } } ================================================ FILE: buildSrc/src/test/java/org/springframework/boot/build/testing/TestFailuresPluginIntegrationTests.java ================================================ /* * 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.testing; import java.io.BufferedReader; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; import java.util.List; import java.util.function.Consumer; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; /** * Integrations tests for {@link TestFailuresPlugin}. * * @author Andy Wilkinson */ class TestFailuresPluginIntegrationTests { private File projectDir; @BeforeEach void setup(@TempDir File projectDir) { this.projectDir = projectDir; } @Test void singleProject() { createProject(this.projectDir); BuildResult result = GradleRunner.create() .withDebug(true) .withProjectDir(this.projectDir) .withArguments("build") .withPluginClasspath() .buildAndFail(); assertThat(readLines(result.getOutput())).containsSequence("Found test failures in 1 test task:", "", ":test", " example.ExampleTests > bad()", " example.ExampleTests > fail()", " example.MoreTests > bad()", " example.MoreTests > fail()"); } @Test void multiProject() { createMultiProjectBuild(); BuildResult result = GradleRunner.create() .withDebug(true) .withProjectDir(this.projectDir) .withArguments("build") .withPluginClasspath() .buildAndFail(); assertThat(readLines(result.getOutput())).containsSequence("Found test failures in 1 test task:", "", ":project-one:test", " example.ExampleTests > bad()", " example.ExampleTests > fail()", " example.MoreTests > bad()", " example.MoreTests > fail()"); } @Test void multiProjectContinue() { createMultiProjectBuild(); BuildResult result = GradleRunner.create() .withDebug(true) .withProjectDir(this.projectDir) .withArguments("build", "--continue") .withPluginClasspath() .buildAndFail(); assertThat(readLines(result.getOutput())).containsSequence("Found test failures in 2 test tasks:", "", ":project-one:test", " example.ExampleTests > bad()", " example.ExampleTests > fail()", " example.MoreTests > bad()", " example.MoreTests > fail()", "", ":project-two:test", " example.ExampleTests > bad()", " example.ExampleTests > fail()", " example.MoreTests > bad()", " example.MoreTests > fail()"); } @Test void multiProjectParallel() { createMultiProjectBuild(); BuildResult result = GradleRunner.create() .withDebug(true) .withProjectDir(this.projectDir) .withArguments("build", "--parallel", "--stacktrace") .withPluginClasspath() .buildAndFail(); assertThat(readLines(result.getOutput())).containsSequence("Found test failures in 2 test tasks:", "", ":project-one:test", " example.ExampleTests > bad()", " example.ExampleTests > fail()", " example.MoreTests > bad()", " example.MoreTests > fail()", "", ":project-two:test", " example.ExampleTests > bad()", " example.ExampleTests > fail()", " example.MoreTests > bad()", " example.MoreTests > fail()"); } private void createProject(File dir) { File examplePackage = new File(dir, "src/test/java/example"); examplePackage.mkdirs(); createTestSource("ExampleTests", examplePackage); createTestSource("MoreTests", examplePackage); createBuildScript(dir); } private void createMultiProjectBuild() { createProject(new File(this.projectDir, "project-one")); createProject(new File(this.projectDir, "project-two")); withPrintWriter(new File(this.projectDir, "settings.gradle"), (writer) -> { writer.println("include 'project-one'"); writer.println("include 'project-two'"); }); } private void createTestSource(String name, File dir) { withPrintWriter(new File(dir, name + ".java"), (writer) -> { writer.println("package example;"); writer.println(); writer.println("import org.junit.jupiter.api.Test;"); writer.println(); writer.println("import static org.assertj.core.api.Assertions.assertThat;"); writer.println(); writer.println("class " + name + "{"); writer.println(); writer.println(" @Test"); writer.println(" void fail() {"); writer.println(" assertThat(true).isFalse();"); writer.println(" }"); writer.println(); writer.println(" @Test"); writer.println(" void bad() {"); writer.println(" assertThat(5).isLessThan(4);"); writer.println(" }"); writer.println(); writer.println(" @Test"); writer.println(" void ok() {"); writer.println(" }"); writer.println(); writer.println("}"); }); } private void createBuildScript(File dir) { withPrintWriter(new File(dir, "build.gradle"), (writer) -> { writer.println("plugins {"); writer.println(" id 'java'"); writer.println(" id 'org.springframework.boot.test-failures'"); writer.println("}"); writer.println(); writer.println("repositories {"); writer.println(" mavenCentral()"); writer.println("}"); writer.println(); writer.println("dependencies {"); writer.println(" testImplementation 'org.junit.jupiter:junit-jupiter:5.6.0'"); 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("test {"); writer.println(" useJUnitPlatform()"); writer.println("}"); }); } private void withPrintWriter(File file, Consumer consumer) { try (PrintWriter writer = new PrintWriter(new FileWriter(file))) { consumer.accept(writer); } catch (IOException ex) { throw new RuntimeException(ex); } } private List readLines(String output) { try (BufferedReader reader = new BufferedReader(new StringReader(output))) { return reader.lines().toList(); } catch (IOException ex) { throw new RuntimeException(ex); } } } ================================================ FILE: buildSrc/src/test/resources/bom.gradle ================================================ /* * 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. */ bom { library("ActiveMQ", "5.15.11") { group("org.apache.activemq") { modules = [ "activemq-amqp", "activemq-blueprint", "activemq-broker", "activemq-camel", "activemq-client", "activemq-console", "activemq-http", "activemq-jaas", "activemq-jdbc-store", "activemq-jms-pool", "activemq-kahadb-store", "activemq-karaf", "activemq-leveldb-store", "activemq-log4j-appender", "activemq-mqtt", "activemq-openwire-generator", "activemq-openwire-legacy", "activemq-osgi", "activemq-partition", "activemq-pool", "activemq-ra", "activemq-run", "activemq-runtime-config", "activemq-shiro", "activemq-spring", "activemq-stomp", "activemq-web" ] } } library("Kotlin", "${kotlinVersion}") { group("org.jetbrains.kotlin") { imports = [ "kotlin-bom" ] plugins = [ "kotlin-maven-plugin" ] } } library("OAuth2 OIDC SDK") { version("8.36.1") { shouldAlignWithVersionFrom("Spring Security") } group("com.nimbusds") { modules = [ "oauth2-oidc-sdk" ] } } } ================================================ FILE: buildSrc/src/test/resources/gradle.properties ================================================ a=alpha b=bravo kotlinVersion=1.3.70 t=tango ================================================ FILE: buildSrc/src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml ================================================ antora: extensions: - require: '@springio/antora-extensions/override-navigation-builder-extension' - require: '@springio/antora-extensions/static-page-extension' - require: '@springio/antora-xref-extension' stub: - appendix:.* - api:.* - reference:.* - require: '@springio/antora-zip-contents-collector-extension' always_include: - classifier: local-aggregate-content name: test locations: - project/build/generated/docs/antora-content/test-${version}-${name}-${classifier}.zip - project/build/generated/docs/antora-dependencies-content/test-dependency/test-${version}-${name}-${classifier}.zip version_file: gradle.properties - require: '@springio/antora-extensions/root-component-extension' root_component_name: boot site: title: Spring Boot content: sources: - url: ./../../../../.. branches: HEAD version: unspecified start_paths: - project/src/docs/antora asciidoc: sourcemap: true attributes: chomp: all hide-uri-scheme: '@' javadoc-location: xref:api:java/ page-pagination: '' page-stackoverflow-url: https://stackoverflow.com/tags/spring-boot tabs-sync-option: '@' extensions: - '@asciidoctor/tabs' - '@springio/asciidoctor-extensions' - '@springio/asciidoctor-extensions/configuration-properties-extension' - '@springio/asciidoctor-extensions/javadoc-extension' - '@springio/asciidoctor-extensions/section-ids-extension' urls: latest_version_segment: '' runtime: log: failure_level: warn output: dir: ./../../../site ================================================ FILE: buildSrc/src/test/resources/plugin.xml ================================================ Spring Boot Maven Plugin org.springframework.boot spring-boot-maven-plugin 2.2.0.GRADLE-SNAPSHOT spring-boot false true build-info Generate a {@code build-info.properties} file based on the content of the current {@link MavenProject}. false true false false false true generate-resources org.springframework.boot.maven.BuildInfoMojo java per-lookup once-per-session 1.4.0 true additionalProperties java.util.Map false true Additional properties to store in the build-info.properties. Each entry is prefixed by {@code build.} in the generated build-info.properties. outputFile java.io.File false true The location of the generated build-info.properties. project org.apache.maven.project.MavenProject true false The Maven project. session org.apache.maven.execution.MavenSession true false The Maven session. time java.lang.String 2.2.0 false true The value used for the {@code build.time} property in a form suitable for {@link Instant#parse(CharSequence)}. Defaults to {@code session.request.startTime}. To disable the {@code build.time} property entirely, use {@code 'off'}. org.sonatype.plexus.build.incremental.BuildContext buildContext help Display help information on spring-boot-maven-plugin.<br> Call <code>mvn spring-boot:help -Ddetail=true -Dgoal=&lt;goal-name&gt;</code> to display parameter details. false false false false false true org.springframework.boot.maven.HelpMojo java per-lookup once-per-session true detail boolean false true If <code>true</code>, display all settable properties for each goal. goal java.lang.String false true The name of the goal for which to show help. If unspecified, all goals will be displayed. indentSize int false true The number of spaces per indentation level, should be positive. lineLength int false true The maximum length of a display line, should be positive. ${detail} ${goal} ${indentSize} ${lineLength} repackage Repackages existing JAR and WAR archives so that they can be executed from the command line using {@literal java -jar}. With <code>layout=NONE</code> can also be used simply to package a JAR with nested dependencies (and no main class, so not executable). compile+runtime false true false false false true package org.springframework.boot.maven.RepackageMojo java per-lookup once-per-session 1.0.0 compile+runtime true attach boolean 1.4.0 false true Attach the repackaged archive to be installed and deployed. classifier java.lang.String 1.0.0 false true Classifier to add to the repackaged archive. If not given, the main artifact will be replaced by the repackaged archive. If given, the classifier will also be used to determine the source archive to repackage: if an artifact with that classifier already exists, it will be used as source and replaced. If no such artifact exists, the main artifact will be used as source and the repackaged archive will be attached as a supplemental artifact with that classifier. Attaching the artifact allows to deploy it alongside to the original one, see <a href= "https://maven.apache.org/plugins/maven-deploy-plugin/examples/deploying-with-classifiers.html" >the Maven documentation for more details</a>. embeddedLaunchScript java.io.File 1.3.0 false true The embedded launch script to prepend to the front of the jar if it is fully executable. If not specified the 'Spring Boot' default script will be used. embeddedLaunchScriptProperties java.util.Properties 1.3.0 false true Properties that should be expanded in the embedded launch script. excludeDevtools boolean 1.3.0 false true Exclude Spring Boot devtools from the repackaged archive. excludeGroupIds java.lang.String 1.1.0 false true Comma separated list of groupId names to exclude (exact match). excludes java.util.List 1.1.0 false true Collection of artifact definitions to exclude. The {@link Exclude} element defines a {@code groupId} and {@code artifactId} mandatory properties and an optional {@code classifier} property. executable boolean 1.3.0 false true Make a fully executable jar for *nix machines by prepending a launch script to the jar. <p> Currently, some tools do not accept this format so you may not always be able to use this technique. For example, {@code jar -xf} may silently fail to extract a jar or war that has been made fully-executable. It is recommended that you only enable this option if you intend to execute it directly, rather than running it with {@code java -jar} or deploying it to a servlet container. finalName java.lang.String 1.0.0 false false Name of the generated archive. includeSystemScope boolean 1.4.0 false true Include system scoped dependencies. includes java.util.List 1.2.0 false true Collection of artifact definitions to include. The {@link Include} element defines a {@code groupId} and {@code artifactId} mandatory properties and an optional {@code classifier} property. layout org.springframework.boot.maven.RepackageMojo$LayoutType 1.0.0 false true The type of archive (which corresponds to how the dependencies are laid out inside it). Possible values are JAR, WAR, ZIP, DIR, NONE. Defaults to a guess based on the archive type. layoutFactory org.springframework.boot.loader.tools.LayoutFactory 1.5.0 false true 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 parties. mainClass java.lang.String 1.0.0 false true The name of the main class. If not specified the first compiled class found that contains a 'main' method will be used. outputDirectory java.io.File 1.0.0 true true Directory containing the generated archive. project org.apache.maven.project.MavenProject 1.0.0 true false The Maven project. requiresUnpack java.util.List 1.1.0 false true A list of the libraries that must be unpacked from fat jars in order to run. Specify each library as a {@code <dependency>} with a {@code <groupId>} and a {@code <artifactId>} and they will be unpacked at runtime. skip boolean 1.2.0 false true Skip the execution. ${spring-boot.repackage.excludeDevtools} ${spring-boot.excludeGroupIds} ${spring-boot.excludes} ${spring-boot.includes} ${spring-boot.repackage.layout} ${spring-boot.repackage.skip} org.apache.maven.project.MavenProjectHelper projectHelper run Run an executable archive application. test false true false false false true validate test-compile org.springframework.boot.maven.RunMojo java per-lookup once-per-session 1.0.0 false addResources boolean 1.0.0 false true Add maven resources to the classpath directly, this allows live in-place editing of resources. Duplicate resources are removed from {@code target/classes} to prevent them to appear twice if {@code ClassLoader.getResources()} is called. Please consider adding {@code spring-boot-devtools} to your project instead as it provides this feature and many more. agent java.io.File[] 1.0.0 since 2.2.0 in favor of {@code agents} false true Path to agent jar. NOTE: a forked process is required to use this feature. agents java.io.File[] 2.2.0 false true Path to agent jars. NOTE: a forked process is required to use this feature. arguments java.lang.String[] 1.0.0 false true Arguments that should be passed to the application. On command line use commas to separate multiple arguments. classesDirectory java.io.File 1.0.0 true true Directory containing the classes and resource files that should be packaged into the archive. environmentVariables java.util.Map 2.1.0 false true List of Environment variables that should be associated with the forked process used to run the application. NOTE: a forked process is required to use this feature. excludeGroupIds java.lang.String 1.1.0 false true Comma separated list of groupId names to exclude (exact match). excludes java.util.List 1.1.0 false true Collection of artifact definitions to exclude. The {@link Exclude} element defines a {@code groupId} and {@code artifactId} mandatory properties and an optional {@code classifier} property. folders java.lang.String[] 1.0.0 false true Additional folders besides the classes directory that should be added to the classpath. fork boolean 1.2.0 false true Flag to indicate if the run processes should be forked. Disabling forking will disable some features such as an agent, custom JVM arguments, devtools or specifying the working directory to use. includes java.util.List 1.2.0 false true Collection of artifact definitions to include. The {@link Include} element defines a {@code groupId} and {@code artifactId} mandatory properties and an optional {@code classifier} property. jvmArguments java.lang.String 1.1.0 false true JVM arguments that should be associated with the forked process used to run the application. On command line, make sure to wrap multiple values between quotes. NOTE: a forked process is required to use this feature. mainClass java.lang.String 1.0.0 false true The name of the main class. If not specified the first compiled class found that contains a 'main' method will be used. noverify boolean 1.0.0 false true Flag to say that the agent requires -noverify. optimizedLaunch boolean 2.2.0 false true Whether the JVM's launch should be optimized. profiles java.lang.String[] 1.3.0 false true The spring profiles to activate. Convenience shortcut of specifying the 'spring.profiles.active' argument. On command line use commas to separate multiple profiles. project org.apache.maven.project.MavenProject 1.0.0 true false The Maven project. skip boolean 1.3.2 false true Skip the execution. systemPropertyVariables java.util.Map 2.1.0 false true List of JVM system properties to pass to the process. NOTE: a forked process is required to use this feature. useTestClasspath java.lang.Boolean 1.3.0 false true Flag to include the test classpath when running. workingDirectory java.io.File 1.5.0 false true Current working directory to use for the application. If not specified, basedir will be used. NOTE: a forked process is required to use this feature. ${spring-boot.run.addResources} ${spring-boot.run.agent} ${spring-boot.run.agents} ${spring-boot.run.arguments} ${spring-boot.excludeGroupIds} ${spring-boot.excludes} ${spring-boot.run.folders} ${spring-boot.run.fork} ${spring-boot.includes} ${spring-boot.run.jvmArguments} ${spring-boot.run.main-class} ${spring-boot.run.noverify} ${spring-boot.run.optimizedLaunch} ${spring-boot.run.profiles} ${spring-boot.run.skip} ${spring-boot.run.useTestClasspath} ${spring-boot.run.workingDirectory} start Start a spring application. Contrary to the {@code run} goal, this does not block and allows other goal to operate on the application. This goal is typically used in integration test scenario where the application is started before a test suite and stopped after. test false true false false false true pre-integration-test org.springframework.boot.maven.StartMojo java per-lookup once-per-session 1.3.0 false addResources boolean 1.0.0 false true Add maven resources to the classpath directly, this allows live in-place editing of resources. Duplicate resources are removed from {@code target/classes} to prevent them to appear twice if {@code ClassLoader.getResources()} is called. Please consider adding {@code spring-boot-devtools} to your project instead as it provides this feature and many more. agent java.io.File[] 1.0.0 since 2.2.0 in favor of {@code agents} false true Path to agent jar. NOTE: a forked process is required to use this feature. agents java.io.File[] 2.2.0 false true Path to agent jars. NOTE: a forked process is required to use this feature. arguments java.lang.String[] 1.0.0 false true Arguments that should be passed to the application. On command line use commas to separate multiple arguments. classesDirectory java.io.File 1.0.0 true true Directory containing the classes and resource files that should be packaged into the archive. environmentVariables java.util.Map 2.1.0 false true List of Environment variables that should be associated with the forked process used to run the application. NOTE: a forked process is required to use this feature. excludeGroupIds java.lang.String 1.1.0 false true Comma separated list of groupId names to exclude (exact match). excludes java.util.List 1.1.0 false true Collection of artifact definitions to exclude. The {@link Exclude} element defines a {@code groupId} and {@code artifactId} mandatory properties and an optional {@code classifier} property. folders java.lang.String[] 1.0.0 false true Additional folders besides the classes directory that should be added to the classpath. fork boolean 1.2.0 false true Flag to indicate if the run processes should be forked. Disabling forking will disable some features such as an agent, custom JVM arguments, devtools or specifying the working directory to use. includes java.util.List 1.2.0 false true Collection of artifact definitions to include. The {@link Include} element defines a {@code groupId} and {@code artifactId} mandatory properties and an optional {@code classifier} property. jmxName java.lang.String false true The JMX name of the automatically deployed MBean managing the lifecycle of the spring application. jmxPort int false true The port to use to expose the platform MBeanServer if the application is forked. jvmArguments java.lang.String 1.1.0 false true JVM arguments that should be associated with the forked process used to run the application. On command line, make sure to wrap multiple values between quotes. NOTE: a forked process is required to use this feature. mainClass java.lang.String 1.0.0 false true The name of the main class. If not specified the first compiled class found that contains a 'main' method will be used. maxAttempts int false true The maximum number of attempts to check if the spring application is ready. Combined with the "wait" argument, this gives a global timeout value (30 sec by default) noverify boolean 1.0.0 false true Flag to say that the agent requires -noverify. profiles java.lang.String[] 1.3.0 false true The spring profiles to activate. Convenience shortcut of specifying the 'spring.profiles.active' argument. On command line use commas to separate multiple profiles. project org.apache.maven.project.MavenProject 1.0.0 true false The Maven project. skip boolean 1.3.2 false true Skip the execution. systemPropertyVariables java.util.Map 2.1.0 false true List of JVM system properties to pass to the process. NOTE: a forked process is required to use this feature. useTestClasspath java.lang.Boolean 1.3.0 false true Flag to include the test classpath when running. wait long false true The number of milliseconds to wait between each attempt to check if the spring application is ready. workingDirectory java.io.File 1.5.0 false true Current working directory to use for the application. If not specified, basedir will be used. NOTE: a forked process is required to use this feature. ${spring-boot.run.addResources} ${spring-boot.run.agent} ${spring-boot.run.agents} ${spring-boot.run.arguments} ${spring-boot.excludeGroupIds} ${spring-boot.excludes} ${spring-boot.run.folders} ${spring-boot.run.fork} ${spring-boot.includes} ${spring-boot.run.jvmArguments} ${spring-boot.run.main-class} ${spring-boot.run.noverify} ${spring-boot.run.profiles} ${spring-boot.run.skip} ${spring-boot.run.useTestClasspath} ${spring-boot.run.workingDirectory} stop Stop a spring application that has been started by the "start" goal. Typically invoked once a test suite has completed. false true false false false true post-integration-test org.springframework.boot.maven.StopMojo java per-lookup once-per-session 1.3.0 false fork java.lang.Boolean 1.3.0 false true Flag to indicate if process to stop was forked. By default, the value is inherited from the {@link MavenProject}. If it is set, it must match the value used to {@link StartMojo start} the process. jmxName java.lang.String false true The JMX name of the automatically deployed MBean managing the lifecycle of the application. jmxPort int false true The port to use to look up the platform MBeanServer if the application has been forked. project org.apache.maven.project.MavenProject 1.4.1 true false The Maven project. skip boolean 1.3.2 false true Skip the execution. ${spring-boot.stop.fork} ${spring-boot.stop.skip} ================================================ FILE: buildSrc/src/test/resources/releases.json ================================================ [ { "allDay": true, "start": "2023-09-22", "title": "Spring Modulith 1.0.1", "url": "https://github.com/spring-projects/spring-modulith/milestone/15" }, { "allDay": true, "start": "2023-09-22", "title": "Spring Modulith 1.1 M1", "url": "https://github.com/spring-projects/spring-modulith/milestone/16" }, { "allDay": true, "start": "2023-09-12", "title": "Reactor 2020.0.36", "url": "https://github.com/reactor/reactor/milestone/51" }, { "allDay": true, "start": "2023-09-12", "title": "Reactor 2022.0.11", "url": "https://github.com/reactor/reactor/milestone/52" }, { "allDay": true, "start": "2023-09-12", "title": "Reactor 2023.0.0-M3", "url": "https://github.com/reactor/reactor/milestone/53" }, { "allDay": true, "start": "2023-09-12", "title": "Reactor Core 3.4.33", "url": "https://github.com/reactor/reactor-core/milestone/158" }, { "allDay": true, "start": "2023-09-12", "title": "Reactor Core 3.5.10", "url": "https://github.com/reactor/reactor-core/milestone/159" }, { "allDay": true, "start": "2023-09-12", "title": "Reactor Core 3.6.0-M3", "url": "https://github.com/reactor/reactor-core/milestone/160" }, { "allDay": true, "start": "2023-09-13", "title": "Sts4 4.20.0.RELEASE", "url": "https://github.com/spring-projects/sts4/milestone/66" }, { "allDay": true, "start": "2023-09-20", "title": "Spring Batch 5.1.0-M3", "url": "https://github.com/spring-projects/spring-batch/milestone/150" }, { "allDay": true, "start": "2023-09-19", "title": "Spring Integration 6.2.0-M3", "url": "https://github.com/spring-projects/spring-integration/milestone/306" }, { "allDay": true, "start": "2023-09-19", "title": "Spring Integration 5.5.19", "url": "https://github.com/spring-projects/spring-integration/milestone/309" }, { "allDay": true, "start": "2023-09-19", "title": "Spring Integration 6.1.3", "url": "https://github.com/spring-projects/spring-integration/milestone/310" }, { "allDay": true, "start": "2023-09-15", "title": "Spring Data Release 2023.1.0-M3", "url": "https://github.com/spring-projects/spring-data-release/milestone/30" }, { "allDay": true, "start": "2023-09-15", "title": "Spring Data Release 2021.2.16", "url": "https://github.com/spring-projects/spring-data-release/milestone/39" }, { "allDay": true, "start": "2023-09-15", "title": "Spring Data Release 2022.0.10", "url": "https://github.com/spring-projects/spring-data-release/milestone/40" }, { "allDay": true, "start": "2023-09-15", "title": "Spring Data Release 2023.0.4", "url": "https://github.com/spring-projects/spring-data-release/milestone/41" }, { "allDay": true, "start": "2023-09-19", "title": "Spring Graphql 1.0.5", "url": "https://github.com/spring-projects/spring-graphql/milestone/27" }, { "allDay": true, "start": "2023-09-19", "title": "Spring Graphql 1.1.6", "url": "https://github.com/spring-projects/spring-graphql/milestone/33" }, { "allDay": true, "start": "2023-09-19", "title": "Spring Graphql 1.2.3", "url": "https://github.com/spring-projects/spring-graphql/milestone/34" }, { "allDay": true, "start": "2023-09-19", "title": "Spring Authorization Server 1.2.0-M1", "url": "https://github.com/spring-projects/spring-authorization-server/milestone/34" }, { "allDay": true, "start": "2023-09-18", "title": "Spring Kafka 3.1.0-M1", "url": "https://github.com/spring-projects/spring-kafka/milestone/225" }, { "allDay": true, "start": "2023-09-14", "title": "Spring Cloud Dataflow 2.11.0", "url": "https://github.com/spring-cloud/spring-cloud-dataflow/milestone/159" }, { "allDay": true, "start": "2023-09-11", "title": "Micrometer 1.9.15", "url": "https://github.com/micrometer-metrics/micrometer/milestone/217" }, { "allDay": true, "start": "2023-09-11", "title": "Micrometer 1.10.11", "url": "https://github.com/micrometer-metrics/micrometer/milestone/218" }, { "allDay": true, "start": "2023-09-11", "title": "Micrometer 1.11.4", "url": "https://github.com/micrometer-metrics/micrometer/milestone/219" }, { "allDay": true, "start": "2023-09-11", "title": "Micrometer 1.12.0-M3", "url": "https://github.com/micrometer-metrics/micrometer/milestone/220" }, { "allDay": true, "start": "2023-09-11", "title": "Tracing 1.0.10", "url": "https://github.com/micrometer-metrics/tracing/milestone/33" }, { "allDay": true, "start": "2023-09-11", "title": "Tracing 1.1.5", "url": "https://github.com/micrometer-metrics/tracing/milestone/34" }, { "allDay": true, "start": "2023-09-26", "title": "Spring Cloud Release 2023.0.0-M2", "url": "https://github.com/spring-cloud/spring-cloud-release/milestone/134" }, { "allDay": true, "start": "2023-09-11", "title": "Context Propagation 1.0.6", "url": "https://github.com/micrometer-metrics/context-propagation/milestone/19" }, { "allDay": true, "start": "2023-09-14", "title": "Spring Ldap 3.2.0-M3", "url": "https://github.com/spring-projects/spring-ldap/milestone/63" }, { "allDay": true, "start": "2023-09-21", "title": "Spring Boot 3.2.0-M3", "url": "https://github.com/spring-projects/spring-boot/milestone/306" }, { "allDay": true, "start": "2023-09-21", "title": "Spring Boot 2.7.16", "url": "https://github.com/spring-projects/spring-boot/milestone/315" }, { "allDay": true, "start": "2023-09-21", "title": "Spring Boot 3.0.11", "url": "https://github.com/spring-projects/spring-boot/milestone/316" }, { "allDay": true, "start": "2023-09-21", "title": "Spring Boot 3.1.4", "url": "https://github.com/spring-projects/spring-boot/milestone/317" }, { "allDay": true, "start": "2023-09-14", "title": "Spring Cloud Deployer 2.9.0", "url": "https://github.com/spring-cloud/spring-cloud-deployer/milestone/116" }, { "allDay": true, "start": "2023-09-12", "title": "Reactor Kafka 1.3.21", "url": "https://github.com/reactor/reactor-kafka/milestone/38" }, { "allDay": true, "start": "2023-09-18", "title": "Spring Security 6.2.0-M3", "url": "https://github.com/spring-projects/spring-security/milestone/308" }, { "allDay": true, "start": "2023-09-22", "title": "Stream Applications 4.0.0", "url": "https://github.com/spring-cloud/stream-applications/milestone/7" }, { "allDay": true, "start": "2023-09-12", "title": "Reactor Netty 1.1.11", "url": "https://github.com/reactor/reactor-netty/milestone/153" }, { "allDay": true, "start": "2023-09-12", "title": "Reactor Netty 1.0.36", "url": "https://github.com/reactor/reactor-netty/milestone/154" }, { "allDay": true, "start": "2023-09-14", "title": "Spring Framework 6.0.12", "url": "https://github.com/spring-projects/spring-framework/milestone/331" }, { "allDay": true, "start": "2023-09-14", "title": "Spring Framework 5.3.30", "url": "https://github.com/spring-projects/spring-framework/milestone/332" }, { "allDay": true, "start": "2023-09-14", "title": "Spring Framework 6.1.0-RC1", "url": "https://github.com/spring-projects/spring-framework/milestone/333" } ] ================================================ FILE: buildSrc/src/test/resources/spring-configuration-metadata.json ================================================ { "properties": [ { "name": "example.counter", "type": "java.lang.Integer", "defaultValue": 0 } ] } ================================================ FILE: buildpack/spring-boot-buildpack-platform/build.gradle ================================================ /* * 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. */ plugins { id "java-library" id "org.springframework.boot.deployed" id "org.springframework.boot.docker-test" } description = "Spring Boot Buildpack Platform" dependencies { dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) dockerTestImplementation("org.junit.jupiter:junit-jupiter") dockerTestRuntimeOnly("org.testcontainers:testcontainers") implementation("net.java.dev.jna:jna-platform") implementation("org.apache.commons:commons-compress") implementation("org.apache.httpcomponents.client5:httpclient5") implementation("org.springframework:spring-core") implementation("org.tomlj:tomlj:1.0.0") implementation("tools.jackson.core:jackson-databind") testImplementation(project(":test-support:spring-boot-test-support")) } tasks.named("compileTestJava") { options.nullability.checking = "tests" } tasks.named("compileDockerTestJava") { options.nullability.checking = "tests" } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/dockerTest/java/org/springframework/boot/buildpack/platform/docker/DockerApiIntegrationTests.java ================================================ /* * 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.buildpack.platform.docker; import java.io.IOException; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; /** * Integration tests for {@link DockerApi}. * * @author Phillip Webb */ @DisabledIfDockerUnavailable class DockerApiIntegrationTests { private final DockerApi docker = new DockerApi(); @Test void pullImage() throws IOException { this.docker.image() .pull(ImageReference.of("docker.io/paketobuildpacks/builder:base"), null, new TotalProgressPullListener(new TotalProgressBar("Pulling: "))); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java ================================================ /* * 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.buildpack.platform.build; import java.util.List; import java.util.function.Consumer; 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.TotalProgressEvent; 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.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; /** * Base class for {@link BuildLog} implementations. * * @author Phillip Webb * @author Scott Frederick * @author Andrey Shlykov * @author Rafael Ceccone * @since 2.3.0 */ public abstract class AbstractBuildLog implements BuildLog { @Override public void start(BuildRequest request) { log("Building image '" + request.getName() + "'"); log(); } @Override public Consumer pullingImage(ImageReference imageReference, @Nullable ImagePlatform platform, ImageType imageType) { return (platform != null) ? getProgressConsumer(" > Pulling %s '%s' for platform '%s'".formatted(imageType.getDescription(), imageReference, platform)) : getProgressConsumer(" > Pulling %s '%s'".formatted(imageType.getDescription(), imageReference)); } @Override public void pulledImage(Image image, ImageType imageType) { log(String.format(" > Pulled %s '%s'", imageType.getDescription(), getDigest(image))); } @Override public Consumer pushingImage(ImageReference imageReference) { return getProgressConsumer(String.format(" > Pushing image '%s'", imageReference)); } @Override public void pushedImage(ImageReference imageReference) { log(String.format(" > Pushed image '%s'", imageReference)); } @Override public void executingLifecycle(BuildRequest request, LifecycleVersion version, VolumeName buildCacheVolume) { log(" > Executing lifecycle version " + version); log(" > Using build cache volume '" + buildCacheVolume + "'"); } @Override public void executingLifecycle(BuildRequest request, LifecycleVersion version, Cache buildCache) { log(" > Executing lifecycle version " + version); log(" > Using build cache " + buildCache); } @Override public Consumer runningPhase(BuildRequest request, String name) { log(); log(" > Running " + name); String prefix = String.format(" %-14s", "[" + name + "] "); return (event) -> log(prefix + event); } @Override public void skippingPhase(String name, String reason) { log(); log(" > Skipping " + name + " " + reason); log(); } @Override public void executedLifecycle(BuildRequest request) { log(); log("Successfully built image '" + request.getName() + "'"); log(); } @Override public void taggedImage(ImageReference tag) { log("Successfully created image tag '" + tag + "'"); log(); } @Override public void failedCleaningWorkDir(Cache cache, @Nullable Exception exception) { StringBuilder message = new StringBuilder("Warning: Working location " + cache + " could not be cleaned"); if (exception != null) { message.append(": ").append(exception.getMessage()); } log(); log(message.toString()); log(); } @Override public void sensitiveTargetBindingDetected(Binding binding) { log("Warning: Binding '%s' uses a container path which is used by buildpacks while building. Binding to it can cause problems!" .formatted(binding)); log(); } private String getDigest(Image image) { List digests = image.getDigests(); return (digests.isEmpty() ? "" : digests.get(0)); } protected void log() { log(""); } protected abstract void log(String message); protected abstract Consumer getProgressConsumer(String message); } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ApiVersions.java ================================================ /* * 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.buildpack.platform.build; import java.util.Arrays; import java.util.stream.IntStream; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.ApiVersion; import org.springframework.util.StringUtils; /** * A set of API Version numbers comprised of major and minor values. * * @author Scott Frederick */ final class ApiVersions { /** * The platform API versions supported by this release. */ static final ApiVersions SUPPORTED_PLATFORMS = ApiVersions.of(0, IntStream.rangeClosed(3, 14)); private final ApiVersion[] apiVersions; private ApiVersions(ApiVersion... versions) { this.apiVersions = versions; } /** * Find the latest version among the specified versions that is supported by these API * versions. * @param others the versions to check against * @return the version */ ApiVersion findLatestSupported(String... others) { for (int versionsIndex = this.apiVersions.length - 1; versionsIndex >= 0; versionsIndex--) { ApiVersion apiVersion = this.apiVersions[versionsIndex]; for (int otherIndex = others.length - 1; otherIndex >= 0; otherIndex--) { ApiVersion other = ApiVersion.parse(others[otherIndex]); if (apiVersion.supports(other)) { return apiVersion; } } } throw new IllegalStateException( "Detected platform API versions '" + StringUtils.arrayToCommaDelimitedString(others) + "' are not included in supported versions '" + this + "'"); } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } ApiVersions other = (ApiVersions) obj; return Arrays.equals(this.apiVersions, other.apiVersions); } @Override public int hashCode() { return Arrays.hashCode(this.apiVersions); } @Override public String toString() { return StringUtils.arrayToCommaDelimitedString(this.apiVersions); } /** * Factory method to parse strings into an {@link ApiVersions} instance. * @param values the values to parse. * @return the corresponding {@link ApiVersions} * @throws IllegalArgumentException if any values could not be parsed */ static ApiVersions parse(String... values) { return new ApiVersions(Arrays.stream(values).map(ApiVersion::parse).toArray(ApiVersion[]::new)); } static ApiVersions of(int major, IntStream minorsInclusive) { return new ApiVersions( minorsInclusive.mapToObj((minor) -> ApiVersion.of(major, minor)).toArray(ApiVersion[]::new)); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java ================================================ /* * 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.buildpack.platform.build; import java.io.PrintStream; import java.util.function.Consumer; 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.TotalProgressEvent; 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.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; /** * Callback interface used to provide {@link Builder} output logging. * * @author Phillip Webb * @author Scott Frederick * @author Andrey Shlykov * @author Rafael Ceccone * @since 2.3.0 * @see #toSystemOut() */ public interface BuildLog { /** * Log that a build is starting. * @param request the build request */ void start(BuildRequest request); /** * Log that an image is being pulled. * @param imageReference the image reference * @param platform the platform of the image * @param imageType the image type * @return a consumer for progress update events */ Consumer pullingImage(ImageReference imageReference, @Nullable ImagePlatform platform, ImageType imageType); /** * Log that an image has been pulled. * @param image the image that was pulled * @param imageType the image type that was pulled */ void pulledImage(Image image, ImageType imageType); /** * Log that an image is being pushed. * @param imageReference the image reference * @return a consumer for progress update events */ Consumer pushingImage(ImageReference imageReference); /** * Log that an image has been pushed. * @param imageReference the image reference */ void pushedImage(ImageReference imageReference); /** * Log that the lifecycle is executing. * @param request the build request * @param version the lifecycle version * @param buildCacheVolume the name of the build cache volume in use */ void executingLifecycle(BuildRequest request, LifecycleVersion version, VolumeName buildCacheVolume); /** * Log that the lifecycle is executing. * @param request the build request * @param version the lifecycle version * @param buildCache the build cache in use */ void executingLifecycle(BuildRequest request, LifecycleVersion version, Cache buildCache); /** * Log that a specific phase is running. * @param request the build request * @param name the name of the phase * @return a consumer for log updates */ Consumer runningPhase(BuildRequest request, String name); /** * Log that a specific phase is being skipped. * @param name the name of the phase * @param reason the reason the phase is skipped */ void skippingPhase(String name, String reason); /** * Log that the lifecycle has executed. * @param request the build request */ void executedLifecycle(BuildRequest request); /** * Log that a tag has been created. * @param tag the tag reference */ void taggedImage(ImageReference tag); /** * Log that a cache cleanup step was not completed successfully. * @param cache the cache * @param exception any exception that caused the failure * @since 3.2.6 */ void failedCleaningWorkDir(Cache cache, @Nullable Exception exception); /** * Log that a binding with a sensitive target has been detected. * @param binding the binding * @since 3.4.0 */ void sensitiveTargetBindingDetected(Binding binding); /** * Factory method that returns a {@link BuildLog} the outputs to {@link System#out}. * @return a build log instance that logs to system out */ static BuildLog toSystemOut() { return to(System.out); } /** * Factory method that returns a {@link BuildLog} the outputs to a given * {@link PrintStream}. * @param out the print stream used to output the log * @return a build log instance that logs to the given print stream */ static BuildLog to(PrintStream out) { return new PrintStreamBuildLog(out); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildOwner.java ================================================ /* * 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.buildpack.platform.build; import java.util.Map; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * The {@link Owner} that should perform the build. * * @author Phillip Webb * @author Andy Wilkinson */ class BuildOwner implements Owner { private static final String USER_PROPERTY_NAME = "CNB_USER_ID"; private static final String GROUP_PROPERTY_NAME = "CNB_GROUP_ID"; private final long uid; private final long gid; BuildOwner(Map env) { this.uid = getValue(env, USER_PROPERTY_NAME); this.gid = getValue(env, GROUP_PROPERTY_NAME); } BuildOwner(long uid, long gid) { this.uid = uid; this.gid = gid; } private long getValue(Map env, String name) { String value = env.get(name); Assert.state(StringUtils.hasText(value), () -> "Missing '" + name + "' value from the builder environment '" + env + "'"); try { return Long.parseLong(value); } catch (NumberFormatException ex) { throw new IllegalStateException( "Malformed '" + name + "' value '" + value + "' in the builder environment '" + env + "'", ex); } } @Override public long getUid() { return this.uid; } @Override public long getGid() { return this.gid; } @Override public String toString() { return this.uid + "/" + this.gid; } /** * Factory method to create the {@link BuildOwner} by inspecting the image env for * {@code CNB_USER_ID}/{@code CNB_GROUP_ID} variables. * @param env the env to parse * @return a {@link BuildOwner} instance extracted from the env * @throws IllegalStateException if the env does not contain the correct CNB variables */ static BuildOwner fromEnv(Map env) { Assert.notNull(env, "'env' must not be null"); return new BuildOwner(env); } /** * Factory method to create a new {@link BuildOwner} with specified user/group * identifier. * @param uid the user identifier * @param gid the group identifier * @return a new {@link BuildOwner} instance */ static BuildOwner of(long uid, long gid) { return new BuildOwner(uid, gid); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java ================================================ /* * 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.buildpack.platform.build; import java.io.File; import java.time.Instant; import java.time.format.DateTimeParseException; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Function; 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.ImageReference; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.util.Assert; /** * A build request to be handled by the {@link Builder}. * * @author Phillip Webb * @author Scott Frederick * @author Andrey Shlykov * @author Jeroen Meijer * @author Rafael Ceccone * @author Julian Liebig * @since 2.3.0 */ public class BuildRequest { static final String DEFAULT_BUILDER_IMAGE_NAME = "paketobuildpacks/builder-noble-java-tiny"; static final String DEFAULT_BUILDER_IMAGE_REF = DEFAULT_BUILDER_IMAGE_NAME + ":latest"; static final List KNOWN_TRUSTED_BUILDERS = List.of( ImageReference.of("paketobuildpacks/builder-noble-java-tiny"), ImageReference.of("paketobuildpacks/builder-jammy-java-tiny"), ImageReference.of("paketobuildpacks/builder-jammy-tiny"), ImageReference.of("paketobuildpacks/builder-jammy-base"), ImageReference.of("paketobuildpacks/builder-jammy-full"), ImageReference.of("paketobuildpacks/builder-jammy-buildpackless-tiny"), ImageReference.of("paketobuildpacks/builder-jammy-buildpackless-base"), ImageReference.of("paketobuildpacks/builder-jammy-buildpackless-full"), ImageReference.of("gcr.io/buildpacks/builder"), ImageReference.of("heroku/builder")); private static final ImageReference DEFAULT_BUILDER = ImageReference.of(DEFAULT_BUILDER_IMAGE_REF); private final ImageReference name; private final Function applicationContent; private final ImageReference builder; private final @Nullable Boolean trustBuilder; private final @Nullable ImageReference runImage; private final Creator creator; private final Map env; private final boolean cleanCache; private final boolean verboseLogging; private final PullPolicy pullPolicy; private final boolean publish; private final List buildpacks; private final List bindings; private final @Nullable String network; private final List tags; private final @Nullable Cache buildWorkspace; private final @Nullable Cache buildCache; private final @Nullable Cache launchCache; private final @Nullable Instant createdDate; private final @Nullable String applicationDirectory; private final @Nullable List securityOptions; private final @Nullable ImagePlatform platform; BuildRequest(ImageReference name, Function applicationContent) { Assert.notNull(name, "'name' must not be null"); Assert.notNull(applicationContent, "'applicationContent' must not be null"); this.name = name.inTaggedForm(); this.applicationContent = applicationContent; this.builder = DEFAULT_BUILDER; this.trustBuilder = null; this.runImage = null; this.env = Collections.emptyMap(); this.cleanCache = false; this.verboseLogging = false; this.pullPolicy = PullPolicy.ALWAYS; this.publish = false; this.creator = Creator.withVersion(""); this.buildpacks = Collections.emptyList(); this.bindings = Collections.emptyList(); this.network = null; this.tags = Collections.emptyList(); this.buildWorkspace = null; this.buildCache = null; this.launchCache = null; this.createdDate = null; this.applicationDirectory = null; this.securityOptions = null; this.platform = null; } BuildRequest(ImageReference name, Function applicationContent, ImageReference builder, @Nullable Boolean trustBuilder, @Nullable ImageReference runImage, Creator creator, Map env, boolean cleanCache, boolean verboseLogging, PullPolicy pullPolicy, boolean publish, List buildpacks, List bindings, @Nullable String network, List tags, @Nullable Cache buildWorkspace, @Nullable Cache buildCache, @Nullable Cache launchCache, @Nullable Instant createdDate, @Nullable String applicationDirectory, @Nullable List securityOptions, @Nullable ImagePlatform platform) { this.name = name; this.applicationContent = applicationContent; this.builder = builder; this.trustBuilder = trustBuilder; this.runImage = runImage; this.creator = creator; this.env = env; this.cleanCache = cleanCache; this.verboseLogging = verboseLogging; this.pullPolicy = pullPolicy; this.publish = publish; this.buildpacks = buildpacks; this.bindings = bindings; this.network = network; this.tags = tags; this.buildWorkspace = buildWorkspace; this.buildCache = buildCache; this.launchCache = launchCache; this.createdDate = createdDate; this.applicationDirectory = applicationDirectory; this.securityOptions = securityOptions; this.platform = platform; } /** * Return a new {@link BuildRequest} with an updated builder. * @param builder the new builder to use * @return an updated build request */ public BuildRequest withBuilder(ImageReference builder) { Assert.notNull(builder, "'builder' must not be null"); return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated trust builder setting. * @param trustBuilder {@code true} if the builder should be treated as trusted, * {@code false} otherwise * @return an updated build request * @since 3.4.0 */ public BuildRequest withTrustBuilder(boolean trustBuilder) { return new BuildRequest(this.name, this.applicationContent, this.builder, trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated run image. * @param runImageName the run image to use * @return an updated build request */ public BuildRequest withRunImage(ImageReference runImageName) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, runImageName.inTaggedOrDigestForm(), this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated creator. * @param creator the new {@code Creator} to use * @return an updated build request */ public BuildRequest withCreator(Creator creator) { Assert.notNull(creator, "'creator' must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an additional env variable. * @param name the variable name * @param value the variable value * @return an updated build request */ public BuildRequest withEnv(String name, String value) { Assert.hasText(name, "'name' must not be empty"); Assert.hasText(value, "'value' must not be empty"); Map env = new LinkedHashMap<>(this.env); env.put(name, value); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with additional env variables. * @param env the additional variables * @return an updated build request */ public BuildRequest withEnv(Map env) { Assert.notNull(env, "'env' must not be null"); Map updatedEnv = new LinkedHashMap<>(this.env); updatedEnv.putAll(env); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated clean cache setting. * @param cleanCache if the cache should be cleaned * @return an updated build request */ public BuildRequest withCleanCache(boolean cleanCache) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated verbose logging setting. * @param verboseLogging if verbose logging should be used * @return an updated build request */ public BuildRequest withVerboseLogging(boolean verboseLogging) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with the updated image pull policy. * @param pullPolicy image pull policy {@link PullPolicy} * @return an updated build request */ public BuildRequest withPullPolicy(PullPolicy pullPolicy) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated publish setting. * @param publish if the built image should be pushed to a registry * @return an updated build request */ public BuildRequest withPublish(boolean publish) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated buildpacks setting. * @param buildpacks a collection of buildpacks to use when building the image * @return an updated build request * @since 2.5.0 */ public BuildRequest withBuildpacks(BuildpackReference... buildpacks) { Assert.notEmpty(buildpacks, "'buildpacks' must not be empty"); return withBuildpacks(Arrays.asList(buildpacks)); } /** * Return a new {@link BuildRequest} with an updated buildpacks setting. * @param buildpacks a collection of buildpacks to use when building the image * @return an updated build request * @since 2.5.0 */ public BuildRequest withBuildpacks(List buildpacks) { Assert.notNull(buildpacks, "'buildpacks' must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with updated bindings. * @param bindings a collection of bindings to mount to the build container * @return an updated build request * @since 2.5.0 */ public BuildRequest withBindings(Binding... bindings) { Assert.notEmpty(bindings, "'bindings' must not be empty"); return withBindings(Arrays.asList(bindings)); } /** * Return a new {@link BuildRequest} with updated bindings. * @param bindings a collection of bindings to mount to the build container * @return an updated build request * @since 2.5.0 */ public BuildRequest withBindings(List bindings) { Assert.notNull(bindings, "'bindings' must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated network setting. * @param network the network the build container will connect to * @return an updated build request * @since 2.6.0 */ public BuildRequest withNetwork(@Nullable String network) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with updated tags. * @param tags a collection of tags to be created for the built image * @return an updated build request */ public BuildRequest withTags(ImageReference... tags) { Assert.notEmpty(tags, "'tags' must not be empty"); return withTags(Arrays.asList(tags)); } /** * Return a new {@link BuildRequest} with updated tags. * @param tags a collection of tags to be created for the built image * @return an updated build request */ public BuildRequest withTags(List tags) { Assert.notNull(tags, "'tags' must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated build workspace. * @param buildWorkspace the build workspace * @return an updated build request * @since 3.2.0 */ public BuildRequest withBuildWorkspace(Cache buildWorkspace) { Assert.notNull(buildWorkspace, "'buildWorkspace' must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated build cache. * @param buildCache the build cache * @return an updated build request */ public BuildRequest withBuildCache(Cache buildCache) { Assert.notNull(buildCache, "'buildCache' must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated launch cache. * @param launchCache the cache * @return an updated build request */ public BuildRequest withLaunchCache(Cache launchCache) { Assert.notNull(launchCache, "'launchCache' must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated created date. * @param createdDate the created date * @return an updated build request */ public BuildRequest withCreatedDate(String createdDate) { Assert.notNull(createdDate, "'createdDate' must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, parseCreatedDate(createdDate), this.applicationDirectory, this.securityOptions, this.platform); } private Instant parseCreatedDate(String createdDate) { if ("now".equalsIgnoreCase(createdDate)) { return Instant.now(); } try { return Instant.parse(createdDate); } catch (DateTimeParseException ex) { throw new IllegalArgumentException("Error parsing '" + createdDate + "' as an image created date", ex); } } /** * Return a new {@link BuildRequest} with an updated application directory. * @param applicationDirectory the application directory * @return an updated build request */ public BuildRequest withApplicationDirectory(String applicationDirectory) { Assert.notNull(applicationDirectory, "'applicationDirectory' must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, applicationDirectory, this.securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated security options. * @param securityOptions the security options * @return an updated build request * @since 3.2.0 */ public BuildRequest withSecurityOptions(List securityOptions) { Assert.notNull(securityOptions, "'securityOptions' must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, securityOptions, this.platform); } /** * Return a new {@link BuildRequest} with an updated image platform. * @param platform the image platform * @return an updated build request * @since 3.4.0 */ public BuildRequest withImagePlatform(String platform) { Assert.notNull(platform, "'platform' must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, ImagePlatform.of(platform)); } /** * Return the name of the image that should be created. * @return the name of the image */ public ImageReference getName() { return this.name; } /** * Return a {@link TarArchive} containing the application content that the buildpack * should package. This is typically the contents of the Jar. * @param owner the owner of the tar entries * @return the application content * @see TarArchive#fromZip(File, Owner) */ public TarArchive getApplicationContent(Owner owner) { return this.applicationContent.apply(owner); } /** * Return the builder that should be used. * @return the builder to use */ public ImageReference getBuilder() { return this.builder; } /** * Return whether the builder should be treated as trusted. * @return the trust builder flag * @since 3.4.0 */ public boolean isTrustBuilder() { return (this.trustBuilder != null) ? this.trustBuilder : isBuilderKnownAndTrusted(); } private boolean isBuilderKnownAndTrusted() { return KNOWN_TRUSTED_BUILDERS.stream().anyMatch((builder) -> builder.getName().equals(this.builder.getName())); } /** * Return the run image that should be used, if provided. * @return the run image */ public @Nullable ImageReference getRunImage() { return this.runImage; } /** * Return the {@link Creator} the builder should use. * @return the {@code Creator} */ public Creator getCreator() { return this.creator; } /** * Return any env variable that should be passed to the builder. * @return the builder env */ public Map getEnv() { return this.env; } /** * Return if caches should be cleaned before packaging. * @return if caches should be cleaned */ public boolean isCleanCache() { return this.cleanCache; } /** * Return if verbose logging output should be used. * @return if verbose logging should be used */ public boolean isVerboseLogging() { return this.verboseLogging; } /** * Return if the built image should be pushed to a registry. * @return if the built image should be pushed to a registry */ public boolean isPublish() { return this.publish; } /** * Return the image {@link PullPolicy} that the builder should use. * @return image pull policy */ public PullPolicy getPullPolicy() { return this.pullPolicy; } /** * Return the collection of buildpacks to use when building the image, if provided. * @return the buildpacks */ public List getBuildpacks() { return this.buildpacks; } /** * Return the collection of bindings to mount to the build container. * @return the bindings * @since 2.5.0 */ public List getBindings() { return this.bindings; } /** * Return the network the build container will connect to. * @return the network * @since 2.6.0 */ public @Nullable String getNetwork() { return this.network; } /** * Return the collection of tags that should be created. * @return the tags */ public List getTags() { return this.tags; } /** * Return the build workspace that should be used by the lifecycle. * @return the build workspace or {@code null} * @since 3.2.0 */ public @Nullable Cache getBuildWorkspace() { return this.buildWorkspace; } /** * Return the custom build cache that should be used by the lifecycle. * @return the build cache */ public @Nullable Cache getBuildCache() { return this.buildCache; } /** * Return the custom launch cache that should be used by the lifecycle. * @return the launch cache */ public @Nullable Cache getLaunchCache() { return this.launchCache; } /** * Return the custom created date that should be used by the lifecycle. * @return the created date */ public @Nullable Instant getCreatedDate() { return this.createdDate; } /** * Return the application directory that should be used by the lifecycle. * @return the application directory */ public @Nullable String getApplicationDirectory() { return this.applicationDirectory; } /** * Return the security options that should be used by the lifecycle. * @return the security options or {@code null} * @since 3.2.0 */ public @Nullable List getSecurityOptions() { return this.securityOptions; } /** * Return the platform that should be used when pulling images. * @return the platform or {@code null} * @since 3.4.0 */ public @Nullable ImagePlatform getImagePlatform() { return this.platform; } /** * Factory method to create a new {@link BuildRequest} from a JAR file. * @param jarFile the source jar file * @return a new build request instance */ public static BuildRequest forJarFile(File jarFile) { assertJarFile(jarFile); return forJarFile(ImageReference.forJarFile(jarFile).inTaggedForm(), jarFile); } /** * Factory method to create a new {@link BuildRequest} from a JAR file. * @param name the name of the image that should be created * @param jarFile the source jar file * @return a new build request instance */ public static BuildRequest forJarFile(ImageReference name, File jarFile) { assertJarFile(jarFile); return new BuildRequest(name, (owner) -> TarArchive.fromZip(jarFile, owner)); } /** * Factory method to create a new {@link BuildRequest} with specific content. * @param name the name of the image that should be created * @param applicationContent function to provide the application content * @return a new build request instance */ public static BuildRequest of(ImageReference name, Function applicationContent) { return new BuildRequest(name, applicationContent); } private static void assertJarFile(File jarFile) { Assert.notNull(jarFile, "'jarFile' must not be null"); Assert.isTrue(jarFile.exists(), "'jarFile' must exist"); Assert.isTrue(jarFile.isFile(), "'jarFile' must be a file"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.util.List; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.DockerApi; 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.TotalProgressPullListener; import org.springframework.boot.buildpack.platform.docker.TotalProgressPushListener; import org.springframework.boot.buildpack.platform.docker.UpdateListener; import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException; 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.ImageArchive; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.io.IOBiConsumer; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Central API for running buildpack operations. * * @author Phillip Webb * @author Scott Frederick * @author Andrey Shlykov * @author Rafael Ceccone * @since 2.3.0 */ public class Builder { private final BuildLog log; private final DockerApi docker; private final BuilderDockerConfiguration dockerConfiguration; /** * Create a new builder instance. */ public Builder() { this(BuildLog.toSystemOut()); } /** * Create a new builder instance. * @param dockerConfiguration the docker configuration * @since 3.5.0 */ public Builder(BuilderDockerConfiguration dockerConfiguration) { this(BuildLog.toSystemOut(), dockerConfiguration); } /** * Create a new builder instance. * @param log a logger used to record output */ public Builder(BuildLog log) { this(log, new DockerApi(null, BuildLogAdapter.get(log)), null); } /** * Create a new builder instance. * @param log a logger used to record output * @param dockerConfiguration the docker configuration * @since 3.5.0 */ public Builder(BuildLog log, @Nullable BuilderDockerConfiguration dockerConfiguration) { this(log, new DockerApi((dockerConfiguration != null) ? dockerConfiguration.connection() : null, BuildLogAdapter.get(log)), dockerConfiguration); } Builder(BuildLog log, DockerApi docker, @Nullable BuilderDockerConfiguration dockerConfiguration) { Assert.notNull(log, "'log' must not be null"); this.log = log; this.docker = docker; this.dockerConfiguration = (dockerConfiguration != null) ? dockerConfiguration : new BuilderDockerConfiguration(); } public void build(BuildRequest request) throws DockerEngineException, IOException { Assert.notNull(request, "'request' must not be null"); this.log.start(request); validateBindings(request.getBindings()); PullPolicy pullPolicy = request.getPullPolicy(); ImagePlatform platform = request.getImagePlatform(); boolean specifiedPlatform = request.getImagePlatform() != null; DockerRegistryAuthentication registryAuthentication = this.dockerConfiguration.builderRegistryAuthentication(); ImageFetcher imageFetcher = new ImageFetcher(registryAuthentication, pullPolicy); Image builderImage = imageFetcher.fetchImage(ImageType.BUILDER, request.getBuilder(), platform); BuilderMetadata builderMetadata = BuilderMetadata.fromImage(builderImage); request = withRunImageIfNeeded(request, builderMetadata); Assert.state(request.getRunImage() != null, "'request.getRunImage()' must not be null"); platform = (platform != null) ? platform : ImagePlatform.from(builderImage); Image runImage = imageFetcher.fetchImage(ImageType.RUNNER, request.getRunImage(), platform); if (specifiedPlatform && runImage.getPrimaryDigest() != null) { request = request.withRunImage(request.getRunImage().withDigest(runImage.getPrimaryDigest())); runImage = imageFetcher.fetchImage(ImageType.RUNNER, request.getRunImage(), platform); } assertStackIdsMatch(runImage, builderImage); BuildOwner buildOwner = BuildOwner.fromEnv(builderImage.getConfig().getEnv()); BuildpackLayersMetadata buildpackLayersMetadata = BuildpackLayersMetadata.fromImage(builderImage); Buildpacks buildpacks = getBuildpacks(request, imageFetcher, platform, builderMetadata, buildpackLayersMetadata); EphemeralBuilder ephemeralBuilder = new EphemeralBuilder(buildOwner, builderImage, request.getName(), builderMetadata, request.getCreator(), request.getEnv(), buildpacks); executeLifecycle(request, ephemeralBuilder); tagImage(request.getName(), request.getTags()); if (request.isPublish()) { pushImages(request.getName(), request.getTags()); } } private void validateBindings(List bindings) { for (Binding binding : bindings) { if (binding.usesSensitiveContainerPath()) { this.log.sensitiveTargetBindingDetected(binding); } } } private BuildRequest withRunImageIfNeeded(BuildRequest request, BuilderMetadata metadata) { if (request.getRunImage() != null) { return request; } return request.withRunImage(getRunImageReference(metadata)); } private ImageReference getRunImageReference(BuilderMetadata metadata) { if (metadata.getRunImages() != null && !metadata.getRunImages().isEmpty()) { String runImageName = metadata.getRunImages().get(0).getImage(); return ImageReference.of(runImageName).inTaggedOrDigestForm(); } String runImageName = metadata.getStack().getRunImage().getImage(); Assert.state(StringUtils.hasText(runImageName), "Run image must be specified in the builder image metadata"); return ImageReference.of(runImageName).inTaggedOrDigestForm(); } private void assertStackIdsMatch(Image runImage, Image builderImage) { StackId runImageStackId = StackId.fromImage(runImage); StackId builderImageStackId = StackId.fromImage(builderImage); if (runImageStackId.hasId() && builderImageStackId.hasId()) { Assert.state(runImageStackId.equals(builderImageStackId), () -> "Run image stack '" + runImageStackId + "' does not match builder stack '" + builderImageStackId + "'"); } } private Buildpacks getBuildpacks(BuildRequest request, ImageFetcher imageFetcher, ImagePlatform platform, BuilderMetadata builderMetadata, BuildpackLayersMetadata buildpackLayersMetadata) { BuildpackResolverContext resolverContext = new BuilderResolverContext(imageFetcher, platform, builderMetadata, buildpackLayersMetadata); return BuildpackResolvers.resolveAll(resolverContext, request.getBuildpacks()); } private void executeLifecycle(BuildRequest request, EphemeralBuilder builder) throws IOException { try (Lifecycle lifecycle = new Lifecycle(this.log, this.docker, getDockerHost(), request, builder)) { executeLifecycle(builder, lifecycle); } } private void executeLifecycle(EphemeralBuilder builder, Lifecycle lifecycle) throws IOException { ImageArchive archive = builder.getArchive(lifecycle.getApplicationDirectory()); this.docker.image().load(archive, UpdateListener.none()); try { lifecycle.execute(); } finally { this.docker.image().remove(builder.getName(), true); } } private @Nullable ResolvedDockerHost getDockerHost() { boolean bindToBuilder = this.dockerConfiguration.bindHostToBuilder(); return (bindToBuilder) ? ResolvedDockerHost.from(this.dockerConfiguration.connection()) : null; } private void tagImage(ImageReference sourceReference, List tags) throws IOException { for (ImageReference tag : tags) { this.docker.image().tag(sourceReference, tag); this.log.taggedImage(tag); } } private void pushImages(ImageReference name, List tags) throws IOException { pushImage(name); for (ImageReference tag : tags) { pushImage(tag); } } private void pushImage(ImageReference reference) throws IOException { Consumer progressConsumer = this.log.pushingImage(reference); TotalProgressPushListener listener = new TotalProgressPushListener(progressConsumer); String authHeader = authHeader(this.dockerConfiguration.publishRegistryAuthentication(), reference); this.docker.image().push(reference, listener, authHeader); this.log.pushedImage(reference); } private static @Nullable String authHeader(@Nullable DockerRegistryAuthentication authentication, ImageReference reference) { return (authentication != null) ? authentication.getAuthHeader(reference) : null; } /** * Internal utility class used to fetch images. */ private class ImageFetcher { private final @Nullable DockerRegistryAuthentication registryAuthentication; private final PullPolicy pullPolicy; ImageFetcher(@Nullable DockerRegistryAuthentication registryAuthentication, PullPolicy pullPolicy) { this.registryAuthentication = registryAuthentication; this.pullPolicy = pullPolicy; } Image fetchImage(ImageType type, ImageReference reference, @Nullable ImagePlatform platform) throws IOException { Assert.notNull(type, "'type' must not be null"); Assert.notNull(reference, "'reference' must not be null"); if (this.pullPolicy == PullPolicy.ALWAYS) { return pullImageAndCheckForPlatformMismatch(type, reference, platform); } try { Image image = Builder.this.docker.image().inspect(reference, platform); return checkPlatformMismatch(image, reference, platform); } catch (DockerEngineException ex) { if (this.pullPolicy == PullPolicy.IF_NOT_PRESENT && ex.getStatusCode() == 404) { return pullImageAndCheckForPlatformMismatch(type, reference, platform); } throw ex; } } private Image pullImageAndCheckForPlatformMismatch(ImageType type, ImageReference reference, @Nullable ImagePlatform platform) throws IOException { try { Image image = pullImage(reference, type, platform); return checkPlatformMismatch(image, reference, platform); } catch (DockerEngineException ex) { // Try to throw our own exception for consistent log output. Matching // on the message is a little brittle, but it doesn't matter too much // if it fails as the original exception is still enough to stop the build if (platform != null && ex.getMessage() != null && ex.getMessage().contains("does not provide the specified platform")) { throwAsPlatformMismatchException(type, reference, platform, ex); } throw ex; } } private void throwAsPlatformMismatchException(ImageType type, ImageReference reference, ImagePlatform platform, @Nullable Throwable cause) throws IOException { try { Image image = pullImage(reference, type, null); throw new PlatformMismatchException(reference, platform, ImagePlatform.from(image), cause); } catch (DockerEngineException ex) { } } private Image pullImage(ImageReference reference, ImageType imageType, @Nullable ImagePlatform platform) throws IOException { TotalProgressPullListener listener = new TotalProgressPullListener( Builder.this.log.pullingImage(reference, platform, imageType)); String authHeader = authHeader(this.registryAuthentication, reference); Image image = Builder.this.docker.image().pull(reference, platform, listener, authHeader); Builder.this.log.pulledImage(image, imageType); return image; } private Image checkPlatformMismatch(Image image, ImageReference reference, @Nullable ImagePlatform requestedPlatform) { if (requestedPlatform != null) { ImagePlatform actualPlatform = ImagePlatform.from(image); if (!actualPlatform.equals(requestedPlatform)) { throw new PlatformMismatchException(reference, requestedPlatform, actualPlatform, null); } } return image; } } private static final class PlatformMismatchException extends RuntimeException { private PlatformMismatchException(ImageReference imageReference, ImagePlatform requestedPlatform, ImagePlatform actualPlatform, @Nullable Throwable cause) { super("Image platform mismatch detected. The configured platform '%s' is not supported by the image '%s'. Requested platform '%s' but got '%s'" .formatted(requestedPlatform, imageReference, requestedPlatform, actualPlatform), cause); } } /** * A {@link DockerLog} implementation that adapts to an {@link AbstractBuildLog}. */ static final class BuildLogAdapter implements DockerLog { private final AbstractBuildLog log; private BuildLogAdapter(AbstractBuildLog log) { this.log = log; } @Override public void log(String message) { this.log.log(message); } /** * Creates {@link DockerLog} instance based on the provided {@link BuildLog}. *

* If the provided {@link BuildLog} instance is an {@link AbstractBuildLog}, the * method returns a {@link BuildLogAdapter}, otherwise it returns a default * {@link DockerLog#toSystemOut()}. * @param log the {@link BuildLog} instance to delegate * @return a {@link DockerLog} instance for logging */ static DockerLog get(BuildLog log) { if (log instanceof AbstractBuildLog abstractBuildLog) { return new BuildLogAdapter(abstractBuildLog); } return DockerLog.toSystemOut(); } } /** * {@link BuildpackResolverContext} implementation for the {@link Builder}. */ private class BuilderResolverContext implements BuildpackResolverContext { private final ImageFetcher imageFetcher; private final ImagePlatform platform; private final BuilderMetadata builderMetadata; private final BuildpackLayersMetadata buildpackLayersMetadata; BuilderResolverContext(ImageFetcher imageFetcher, ImagePlatform platform, BuilderMetadata builderMetadata, BuildpackLayersMetadata buildpackLayersMetadata) { this.imageFetcher = imageFetcher; this.platform = platform; this.builderMetadata = builderMetadata; this.buildpackLayersMetadata = buildpackLayersMetadata; } @Override public List getBuildpackMetadata() { return this.builderMetadata.getBuildpacks(); } @Override public BuildpackLayersMetadata getBuildpackLayersMetadata() { return this.buildpackLayersMetadata; } @Override public Image fetchImage(ImageReference reference, ImageType imageType) throws IOException { return this.imageFetcher.fetchImage(imageType, reference, this.platform); } @Override public void exportImageLayers(ImageReference reference, IOBiConsumer exports) throws IOException { Builder.this.docker.image().exportLayers(reference, this.platform, exports); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderBuildpack.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.type.Layer; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.util.Assert; /** * A {@link Buildpack} that references a buildpack contained in the builder. * * The buildpack reference must contain a buildpack ID (for example, * {@code "example/buildpack"}) or a buildpack ID and version (for example, * {@code "example/buildpack@1.0.0"}). The reference can optionally contain a prefix * {@code urn:cnb:builder:} to unambiguously identify it as a builder buildpack reference. * If a version is not provided, the reference will match any version of a buildpack with * the same ID as the reference. * * @author Scott Frederick */ class BuilderBuildpack implements Buildpack { private static final String PREFIX = "urn:cnb:builder:"; private final BuildpackCoordinates coordinates; BuilderBuildpack(BuildpackMetadata buildpackMetadata) { this.coordinates = BuildpackCoordinates.fromBuildpackMetadata(buildpackMetadata); } @Override public BuildpackCoordinates getCoordinates() { return this.coordinates; } @Override public void apply(IOConsumer layers) throws IOException { } /** * A {@link BuildpackResolver} compatible method to resolve builder buildpacks. * @param context the resolver context * @param reference the buildpack reference * @return the resolved {@link Buildpack} or {@code null} */ static @Nullable Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) { boolean unambiguous = reference.hasPrefix(PREFIX); BuilderReference builderReference = BuilderReference .of(unambiguous ? getSubReference(reference) : reference.toString()); BuildpackMetadata buildpackMetadata = findBuildpackMetadata(context, builderReference); if (unambiguous) { Assert.state(buildpackMetadata != null, () -> "Buildpack '" + reference + "' not found in builder"); } return (buildpackMetadata != null) ? new BuilderBuildpack(buildpackMetadata) : null; } private static String getSubReference(BuildpackReference reference) { String result = reference.getSubReference(PREFIX); Assert.state(result != null, "'result' must not be null"); return result; } private static @Nullable BuildpackMetadata findBuildpackMetadata(BuildpackResolverContext context, BuilderReference builderReference) { for (BuildpackMetadata candidate : context.getBuildpackMetadata()) { if (builderReference.matches(candidate)) { return candidate; } } return null; } /** * A reference to a buildpack builder. */ static class BuilderReference { private final String id; private final @Nullable String version; BuilderReference(String id, @Nullable String version) { this.id = id; this.version = version; } @Override public String toString() { return (this.version != null) ? this.id + "@" + this.version : this.id; } boolean matches(BuildpackMetadata candidate) { return this.id.equals(candidate.getId()) && (this.version == null || this.version.equals(candidate.getVersion())); } static BuilderReference of(String value) { if (value.contains("@")) { String[] parts = value.split("@"); return new BuilderReference(parts[0], parts[1]); } return new BuilderReference(value, null); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderDockerConfiguration.java ================================================ /* * 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.buildpack.platform.build; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication; /** * {@link Builder} configuration options for Docker. * * @param connection the Docker host configuration * @param bindHostToBuilder if the host resolved from the connection should be bound to * the builder * @param builderRegistryAuthentication the builder {@link DockerRegistryAuthentication} * @param publishRegistryAuthentication the publish {@link DockerRegistryAuthentication} * @author Phillip Webb * @author Wei Jiang * @author Scott Frederick * @since 3.5.0 */ public record BuilderDockerConfiguration(@Nullable DockerConnectionConfiguration connection, boolean bindHostToBuilder, @Nullable DockerRegistryAuthentication builderRegistryAuthentication, @Nullable DockerRegistryAuthentication publishRegistryAuthentication) { public BuilderDockerConfiguration() { this(null, false, null, null); } public BuilderDockerConfiguration withContext(String context) { return withConnection(new DockerConnectionConfiguration.Context(context)); } public BuilderDockerConfiguration withHost(String address, boolean secure, @Nullable String certificatePath) { return withConnection(new DockerConnectionConfiguration.Host(address, secure, certificatePath)); } private BuilderDockerConfiguration withConnection(DockerConnectionConfiguration hostConfiguration) { return new BuilderDockerConfiguration(hostConfiguration, this.bindHostToBuilder, this.builderRegistryAuthentication, this.publishRegistryAuthentication); } public BuilderDockerConfiguration withBindHostToBuilder(boolean bindHostToBuilder) { return new BuilderDockerConfiguration(this.connection, bindHostToBuilder, this.builderRegistryAuthentication, this.publishRegistryAuthentication); } public BuilderDockerConfiguration withBuilderRegistryAuthentication( DockerRegistryAuthentication builderRegistryAuthentication) { return new BuilderDockerConfiguration(this.connection, this.bindHostToBuilder, builderRegistryAuthentication, this.publishRegistryAuthentication); } public BuilderDockerConfiguration withPublishRegistryAuthentication( DockerRegistryAuthentication publishRegistryAuthentication) { return new BuilderDockerConfiguration(this.connection, this.bindHostToBuilder, this.builderRegistryAuthentication, publishRegistryAuthentication); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderException.java ================================================ /* * 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.buildpack.platform.build; import org.jspecify.annotations.Nullable; import org.springframework.util.StringUtils; /** * Exception thrown to indicate a Builder error. * * @author Scott Frederick * @since 2.3.0 */ public class BuilderException extends RuntimeException { private final @Nullable String operation; private final int statusCode; BuilderException(@Nullable String operation, int statusCode) { super(buildMessage(operation, statusCode)); this.operation = operation; this.statusCode = statusCode; } /** * Return the Builder operation that failed. * @return the operation description */ public @Nullable String getOperation() { return this.operation; } /** * Return the status code returned from a Builder operation. * @return the statusCode the status code */ public int getStatusCode() { return this.statusCode; } private static String buildMessage(@Nullable String operation, int statusCode) { StringBuilder message = new StringBuilder("Builder"); if (StringUtils.hasLength(operation)) { message.append(" lifecycle '").append(operation).append("'"); } message.append(" failed with status code ").append(statusCode); return message.toString(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderMetadata.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; import tools.jackson.core.JacksonException; import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.ObjectNode; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Builder metadata information. * * @author Phillip Webb * @author Andy Wilkinson * @author Scott Frederick */ class BuilderMetadata extends MappedObject { private static final String LABEL_NAME = "io.buildpacks.builder.metadata"; private static final String[] EMPTY_MIRRORS = {}; private final Stack stack; private final List runImages; private final Lifecycle lifecycle; private final CreatedBy createdBy; private final List buildpacks; BuilderMetadata(JsonNode node) { super(node, MethodHandles.lookup()); this.stack = extractStack(); this.runImages = childrenAt("/images", RunImage::new); this.lifecycle = extractLifecycle(); this.createdBy = extractCreatedBy(); this.buildpacks = extractBuildpacks(getNode().at("/buildpacks")); } private CreatedBy extractCreatedBy() { CreatedBy result = valueAt("/createdBy", CreatedBy.class); Assert.state(result != null, "'result' must not be null"); return result; } private Lifecycle extractLifecycle() { Lifecycle result = valueAt("/lifecycle", Lifecycle.class); Assert.state(result != null, "'result' must not be null"); return result; } private Stack extractStack() { Stack result = valueAt("/stack", Stack.class); Assert.state(result != null, "'result' must not be null"); return result; } private List extractBuildpacks(JsonNode node) { if (node.isEmpty()) { return Collections.emptyList(); } List entries = new ArrayList<>(); node.forEach((child) -> entries.add(BuildpackMetadata.fromJson(child))); return entries; } /** * Return stack metadata. * @return the stack metadata */ Stack getStack() { return this.stack; } /** * Return run images metadata. * @return the run images metadata */ List getRunImages() { return this.runImages; } /** * Return lifecycle metadata. * @return the lifecycle metadata */ Lifecycle getLifecycle() { return this.lifecycle; } /** * Return information about who created the builder. * @return the created by metadata */ CreatedBy getCreatedBy() { return this.createdBy; } /** * Return the buildpacks that are bundled in the builder. * @return the buildpacks */ List getBuildpacks() { return this.buildpacks; } /** * Create an updated copy of this metadata. * @param update consumer to apply updates * @return an updated metadata instance */ BuilderMetadata copy(Consumer update) { return new Update(this).run(update); } /** * Attach this metadata to the given update callback. * @param update the update used to attach the metadata */ void attachTo(ImageConfig.Update update) { try { String json = SharedJsonMapper.get().writeValueAsString(getNode()); update.withLabel(LABEL_NAME, json); } catch (JacksonException ex) { throw new IllegalStateException(ex); } } /** * Factory method to extract {@link BuilderMetadata} from an image. * @param image the source image * @return the builder metadata * @throws IOException on IO error */ static BuilderMetadata fromImage(Image image) throws IOException { Assert.notNull(image, "'image' must not be null"); return fromImageConfig(image.getConfig()); } /** * Factory method to extract {@link BuilderMetadata} from image config. * @param imageConfig the image config * @return the builder metadata * @throws IOException on IO error */ static BuilderMetadata fromImageConfig(ImageConfig imageConfig) throws IOException { Assert.notNull(imageConfig, "'imageConfig' must not be null"); String json = imageConfig.getLabels().get(LABEL_NAME); Assert.state(json != null, () -> "No '" + LABEL_NAME + "' label found in image config labels '" + StringUtils.collectionToCommaDelimitedString(imageConfig.getLabels().keySet()) + "'"); return fromJson(json); } /** * Factory method create {@link BuilderMetadata} from some JSON. * @param json the source JSON * @return the builder metadata * @throws IOException on IO error */ static BuilderMetadata fromJson(String json) throws IOException { return new BuilderMetadata(SharedJsonMapper.get().readTree(json)); } /** * Stack metadata. */ interface Stack { /** * Return run image metadata. * @return the run image metadata */ RunImage getRunImage(); /** * Run image metadata. */ interface RunImage { /** * Return the builder image reference. * @return the image reference */ String getImage(); /** * Return stack mirrors. * @return the stack mirrors */ default String[] getMirrors() { return EMPTY_MIRRORS; } } } static class RunImage extends MappedObject { private final String image; private final List mirrors; /** * Create a new {@link MappedObject} instance. * @param node the source node */ RunImage(JsonNode node) { super(node, MethodHandles.lookup()); this.image = extractImage(); this.mirrors = childrenAt("/mirrors", JsonNode::asString); } private String extractImage() { String result = valueAt("/image", String.class); Assert.state(result != null, "'result' must not be null"); return result; } String getImage() { return this.image; } List getMirrors() { return this.mirrors; } } /** * Lifecycle metadata. */ interface Lifecycle { /** * Return the lifecycle version. * @return the lifecycle version */ String getVersion(); /** * Return the default API versions. * @return the API versions */ Api getApi(); /** * Return the supported API versions. * @return the API versions */ Apis getApis(); /** * Default API versions. */ interface Api { /** * Return the default buildpack API version. * @return the buildpack version */ String getBuildpack(); /** * Return the default platform API version. * @return the platform version */ String getPlatform(); } /** * Supported API versions. */ interface Apis { /** * Return the supported buildpack API versions. * @return the buildpack versions */ default String @Nullable [] getBuildpack() { return valueAt(this, "/buildpack/supported", String[].class); } /** * Return the supported platform API versions. * @return the platform versions */ default String @Nullable [] getPlatform() { return valueAt(this, "/platform/supported", String[].class); } } } /** * Created-by metadata. */ interface CreatedBy { /** * Return the name of the creator. * @return the creator name */ String getName(); /** * Return the version of the creator. * @return the creator version */ String getVersion(); } /** * Update class used to change data when creating a copy. */ static final class Update { private final ObjectNode copy; private Update(BuilderMetadata source) { this.copy = (ObjectNode) source.getNode().deepCopy(); } private BuilderMetadata run(Consumer update) { update.accept(this); return new BuilderMetadata(this.copy); } /** * Update the builder meta-data with a specific created by section. * @param name the name of the creator * @param version the version of the creator */ void withCreatedBy(String name, String version) { ObjectNode createdBy = (ObjectNode) this.copy.at("/createdBy"); if (createdBy == null) { createdBy = this.copy.putObject("createdBy"); } createdBy.put("name", name); createdBy.put("version", version); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Buildpack.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import org.springframework.boot.buildpack.platform.docker.type.Layer; import org.springframework.boot.buildpack.platform.io.IOConsumer; /** * A Buildpack that should be invoked by the builder during image building. * * @author Scott Frederick * @see BuildpackResolver */ interface Buildpack { /** * Return the coordinates of the builder. * @return the builder coordinates */ BuildpackCoordinates getCoordinates(); /** * Apply the necessary buildpack layers. * @param layers a consumer that should accept the layers * @throws IOException on IO error */ void apply(IOConsumer layers) throws IOException; } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackCoordinates.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import org.jspecify.annotations.Nullable; import org.tomlj.Toml; import org.tomlj.TomlParseResult; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * A set of buildpack coordinates that uniquely identifies a buildpack. * * @author Scott Frederick * @see Platform * Interface Specification */ final class BuildpackCoordinates { private final String id; private final @Nullable String version; private BuildpackCoordinates(String id, @Nullable String version) { Assert.hasText(id, "'id' must not be empty"); this.id = id; this.version = version; } String getId() { return this.id; } /** * Return the buildpack ID with all "/" replaced by "_". * @return the ID */ String getSanitizedId() { return this.id.replace("/", "_"); } @Nullable String getVersion() { return this.version; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } BuildpackCoordinates other = (BuildpackCoordinates) obj; return this.id.equals(other.id) && ObjectUtils.nullSafeEquals(this.version, other.version); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + this.id.hashCode(); result = prime * result + ObjectUtils.nullSafeHashCode(this.version); return result; } @Override public String toString() { return this.id + ((StringUtils.hasText(this.version)) ? "@" + this.version : ""); } /** * Create {@link BuildpackCoordinates} from a {@code buildpack.toml} * file. * @param inputStream an input stream containing {@code buildpack.toml} content * @param path the path to the buildpack containing the {@code buildpack.toml} file * @return a new {@link BuildpackCoordinates} instance * @throws IOException on IO error */ static BuildpackCoordinates fromToml(InputStream inputStream, Path path) throws IOException { return fromToml(Toml.parse(inputStream), path); } private static BuildpackCoordinates fromToml(TomlParseResult toml, Path path) { Assert.isTrue(!toml.isEmpty(), () -> "Buildpack descriptor 'buildpack.toml' is required in buildpack '" + path + "'"); String buildpackId = toml.getString("buildpack.id"); Assert.hasText(buildpackId, () -> "Buildpack descriptor must contain ID in buildpack '" + path + "'"); Assert.hasText(toml.getString("buildpack.version"), () -> "Buildpack descriptor must contain version in buildpack '" + path + "'"); Assert.isTrue(toml.contains("stacks") || toml.contains("order"), () -> "Buildpack descriptor must contain either 'stacks' or 'order' in buildpack '" + path + "'"); Assert.isTrue(!(toml.contains("stacks") && toml.contains("order")), () -> "Buildpack descriptor must not contain both 'stacks' and 'order' in buildpack '" + path + "'"); return new BuildpackCoordinates(buildpackId, toml.getString("buildpack.version")); } /** * Create {@link BuildpackCoordinates} by extracting values from * {@link BuildpackMetadata}. * @param buildpackMetadata the buildpack metadata * @return a new {@link BuildpackCoordinates} instance */ static BuildpackCoordinates fromBuildpackMetadata(BuildpackMetadata buildpackMetadata) { Assert.notNull(buildpackMetadata, "'buildpackMetadata' must not be null"); return new BuildpackCoordinates(buildpackMetadata.getId(), buildpackMetadata.getVersion()); } /** * Create {@link BuildpackCoordinates} from an ID and version. * @param id the buildpack ID * @param version the buildpack version * @return a new {@link BuildpackCoordinates} instance */ static BuildpackCoordinates of(String id, @Nullable String version) { return new BuildpackCoordinates(id, version); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackLayersMetadata.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.util.HashMap; import java.util.Map; import org.jspecify.annotations.Nullable; import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Buildpack layers metadata information. * * @author Scott Frederick */ final class BuildpackLayersMetadata extends MappedObject { private static final String LABEL_NAME = "io.buildpacks.buildpack.layers"; private final Buildpacks buildpacks; private BuildpackLayersMetadata(JsonNode node) { super(node, MethodHandles.lookup()); this.buildpacks = Buildpacks.fromJson(getNode()); } /** * Return the metadata details of a buildpack with the given ID and version. * @param id the buildpack ID * @param version the buildpack version * @return the buildpack details or {@code null} if a buildpack with the given ID and * version does not exist in the metadata */ @Nullable BuildpackLayerDetails getBuildpack(String id, @Nullable String version) { return this.buildpacks.getBuildpack(id, version); } /** * Create a {@link BuildpackLayersMetadata} from an image. * @param image the source image * @return the buildpack layers metadata * @throws IOException on IO error */ static BuildpackLayersMetadata fromImage(Image image) throws IOException { Assert.notNull(image, "'image' must not be null"); return fromImageConfig(image.getConfig()); } /** * Create a {@link BuildpackLayersMetadata} from image config. * @param imageConfig the source image config * @return the buildpack layers metadata * @throws IOException on IO error */ static BuildpackLayersMetadata fromImageConfig(ImageConfig imageConfig) throws IOException { Assert.notNull(imageConfig, "'imageConfig' must not be null"); String json = imageConfig.getLabels().get(LABEL_NAME); Assert.state(json != null, () -> "No '" + LABEL_NAME + "' label found in image config labels '" + StringUtils.collectionToCommaDelimitedString(imageConfig.getLabels().keySet()) + "'"); return fromJson(json); } /** * Create a {@link BuildpackLayersMetadata} from JSON. * @param json the source JSON * @return the buildpack layers metadata * @throws IOException on IO error */ static BuildpackLayersMetadata fromJson(String json) throws IOException { return fromJson(SharedJsonMapper.get().readTree(json)); } /** * Create a {@link BuildpackLayersMetadata} from JSON. * @param node the source JSON * @return the buildpack layers metadata */ static BuildpackLayersMetadata fromJson(JsonNode node) { return new BuildpackLayersMetadata(node); } private static final class Buildpacks { private final Map buildpacks = new HashMap<>(); private @Nullable BuildpackLayerDetails getBuildpack(String id, @Nullable String version) { if (this.buildpacks.containsKey(id)) { return this.buildpacks.get(id).getBuildpack(version); } return null; } private void addBuildpackVersions(String id, BuildpackVersions versions) { this.buildpacks.put(id, versions); } private static Buildpacks fromJson(JsonNode node) { Buildpacks buildpacks = new Buildpacks(); node.properties() .forEach((field) -> buildpacks.addBuildpackVersions(field.getKey(), BuildpackVersions.fromJson(field.getValue()))); return buildpacks; } } private static final class BuildpackVersions { private final Map versions = new HashMap<>(); private @Nullable BuildpackLayerDetails getBuildpack(@Nullable String version) { return this.versions.get(version); } private void addBuildpackVersion(String version, BuildpackLayerDetails details) { this.versions.put(version, details); } private static BuildpackVersions fromJson(JsonNode node) { BuildpackVersions versions = new BuildpackVersions(); node.properties() .forEach((field) -> versions.addBuildpackVersion(field.getKey(), BuildpackLayerDetails.fromJson(field.getValue()))); return versions; } } static final class BuildpackLayerDetails extends MappedObject { private final @Nullable String name; private final @Nullable String homepage; private final @Nullable String layerDiffId; private BuildpackLayerDetails(JsonNode node) { super(node, MethodHandles.lookup()); this.name = valueAt("/name", String.class); this.homepage = valueAt("/homepage", String.class); this.layerDiffId = valueAt("/layerDiffID", String.class); } /** * Return the buildpack name. * @return the name */ @Nullable String getName() { return this.name; } /** * Return the buildpack homepage address. * @return the homepage address */ @Nullable String getHomepage() { return this.homepage; } /** * Return the buildpack layer {@code diffID}. * @return the layer {@code diffID} */ @Nullable String getLayerDiffId() { return this.layerDiffId; } private static BuildpackLayerDetails fromJson(JsonNode node) { return new BuildpackLayerDetails(node); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackMetadata.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.lang.invoke.MethodHandles; import org.jspecify.annotations.Nullable; import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Buildpack metadata information. * * @author Scott Frederick */ final class BuildpackMetadata extends MappedObject { private static final String LABEL_NAME = "io.buildpacks.buildpackage.metadata"; private final String id; private final @Nullable String version; private final @Nullable String homepage; private BuildpackMetadata(JsonNode node) { super(node, MethodHandles.lookup()); this.id = extractId(); this.version = valueAt("/version", String.class); this.homepage = valueAt("/homepage", String.class); } private String extractId() { String result = valueAt("/id", String.class); Assert.state(result != null, "'result' must not be null"); return result; } /** * Return the buildpack ID. * @return the ID */ String getId() { return this.id; } /** * Return the buildpack version. * @return the version */ @Nullable String getVersion() { return this.version; } /** * Return the buildpack homepage address. * @return the homepage */ @Nullable String getHomepage() { return this.homepage; } /** * Factory method to extract {@link BuildpackMetadata} from an image. * @param image the source image * @return the builder metadata * @throws IOException on IO error */ static BuildpackMetadata fromImage(Image image) throws IOException { Assert.notNull(image, "'image' must not be null"); return fromImageConfig(image.getConfig()); } /** * Factory method to extract {@link BuildpackMetadata} from image config. * @param imageConfig the source image config * @return the builder metadata * @throws IOException on IO error */ static BuildpackMetadata fromImageConfig(ImageConfig imageConfig) throws IOException { Assert.notNull(imageConfig, "'imageConfig' must not be null"); String json = imageConfig.getLabels().get(LABEL_NAME); Assert.state(json != null, () -> "No '" + LABEL_NAME + "' label found in image config labels '" + StringUtils.collectionToCommaDelimitedString(imageConfig.getLabels().keySet()) + "'"); return fromJson(json); } /** * Factory method create {@link BuildpackMetadata} from JSON. * @param json the source JSON * @return the builder metadata * @throws IOException on IO error */ static BuildpackMetadata fromJson(String json) throws IOException { return fromJson(SharedJsonMapper.get().readTree(json)); } /** * Factory method create {@link BuildpackMetadata} from JSON. * @param node the source JSON * @return the builder metadata */ static BuildpackMetadata fromJson(JsonNode node) { return new BuildpackMetadata(node); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackReference.java ================================================ /* * 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.buildpack.platform.build; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** * An opaque reference to a {@link Buildpack}. * * @author Phillip Webb * @author Scott Frederick * @since 2.5.0 * @see BuildpackResolver */ public final class BuildpackReference { private final String value; private BuildpackReference(String value) { this.value = value; } boolean hasPrefix(String prefix) { return this.value.startsWith(prefix); } @Nullable String getSubReference(String prefix) { return this.value.startsWith(prefix) ? this.value.substring(prefix.length()) : null; } @Nullable Path asPath() { try { URL url = new URL(this.value); if (url.getProtocol().equals("file")) { return Paths.get(url.toURI()); } return null; } catch (MalformedURLException | URISyntaxException ex) { // not a URL, fall through to attempting to find a plain file path } try { return Paths.get(this.value); } catch (Exception ex) { return null; } } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } return this.value.equals(((BuildpackReference) obj).value); } @Override public int hashCode() { return this.value.hashCode(); } @Override public String toString() { return this.value; } /** * Create a new {@link BuildpackReference} from the given value. * @param value the value to use * @return a new {@link BuildpackReference} */ public static BuildpackReference of(String value) { Assert.hasText(value, "'value' must not be empty"); return new BuildpackReference(value); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackResolver.java ================================================ /* * 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.buildpack.platform.build; import org.jspecify.annotations.Nullable; /** * Strategy interface used to resolve a {@link BuildpackReference} to a {@link Buildpack}. * * @author Scott Frederick * @author Phillip Webb * @see BuildpackResolvers */ interface BuildpackResolver { /** * Attempt to resolve the given {@link BuildpackReference}. * @param context the resolver context * @param reference the reference to resolve * @return a resolved {@link Buildpack} instance or {@code null} */ @Nullable Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference); } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackResolverContext.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.util.List; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.io.IOBiConsumer; import org.springframework.boot.buildpack.platform.io.TarArchive; /** * Context passed to a {@link BuildpackResolver}. * * @author Scott Frederick * @author Phillip Webb */ interface BuildpackResolverContext { List getBuildpackMetadata(); BuildpackLayersMetadata getBuildpackLayersMetadata(); /** * Retrieve an image. * @param reference the image reference * @param type the type of image * @return the retrieved image * @throws IOException on IO error */ Image fetchImage(ImageReference reference, ImageType type) throws IOException; /** * Export the layers of an image. * @param reference the reference to export * @param exports a consumer to receive the layers (contents can only be accessed * during the callback) * @throws IOException on IO error */ void exportImageLayers(ImageReference reference, IOBiConsumer exports) throws IOException; } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackResolvers.java ================================================ /* * 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.buildpack.platform.build; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** * All {@link BuildpackResolver} instances that can be used to resolve * {@link BuildpackReference BuildpackReferences}. * * @author Scott Frederick * @author Phillip Webb */ final class BuildpackResolvers { private static final List resolvers = getResolvers(); private BuildpackResolvers() { } private static List getResolvers() { List resolvers = new ArrayList<>(); resolvers.add(BuilderBuildpack::resolve); resolvers.add(DirectoryBuildpack::resolve); resolvers.add(TarGzipBuildpack::resolve); resolvers.add(ImageBuildpack::resolve); return Collections.unmodifiableList(resolvers); } /** * Resolve a collection of {@link BuildpackReference BuildpackReferences} to a * {@link Buildpacks} instance. * @param context the resolver context * @param references the references to resolve * @return a {@link Buildpacks} instance */ static Buildpacks resolveAll(BuildpackResolverContext context, Collection references) { Assert.notNull(context, "'context' must not be null"); if (CollectionUtils.isEmpty(references)) { return Buildpacks.EMPTY; } List buildpacks = new ArrayList<>(references.size()); for (BuildpackReference reference : references) { buildpacks.add(resolve(context, reference)); } return Buildpacks.of(buildpacks); } private static Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) { Assert.notNull(reference, "'reference' must not be null"); for (BuildpackResolver resolver : resolvers) { Buildpack buildpack = resolver.resolve(context, reference); if (buildpack != null) { return buildpack; } } throw new IllegalArgumentException("Invalid buildpack reference '" + reference + "'"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Buildpacks.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.util.Collections; import java.util.List; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.type.Layer; import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.Layout; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** * A collection of {@link Buildpack} instances that can be used to apply buildpack layers. * * @author Scott Frederick * @author Phillip Webb */ final class Buildpacks { static final Buildpacks EMPTY = new Buildpacks(Collections.emptyList()); private final List buildpacks; private Buildpacks(List buildpacks) { this.buildpacks = buildpacks; } List getBuildpacks() { return this.buildpacks; } void apply(IOConsumer layers) throws IOException { if (!this.buildpacks.isEmpty()) { for (Buildpack buildpack : this.buildpacks) { buildpack.apply(layers); } layers.accept(Layer.of(this::addOrderLayerContent)); } } void addOrderLayerContent(Layout layout) throws IOException { layout.file("/cnb/order.toml", Owner.ROOT, Content.of(getOrderToml())); } private String getOrderToml() { StringBuilder builder = new StringBuilder(); builder.append("[[order]]\n\n"); for (Buildpack buildpack : this.buildpacks) { appendToOrderToml(builder, buildpack.getCoordinates()); } return builder.toString(); } private void appendToOrderToml(StringBuilder builder, BuildpackCoordinates coordinates) { builder.append(" [[order.group]]\n"); builder.append(" id = \"" + coordinates.getId() + "\"\n"); if (StringUtils.hasText(coordinates.getVersion())) { builder.append(" version = \"" + coordinates.getVersion() + "\"\n"); } builder.append("\n"); } static Buildpacks of(@Nullable List buildpacks) { return CollectionUtils.isEmpty(buildpacks) ? EMPTY : new Buildpacks(buildpacks); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Cache.java ================================================ /* * 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.buildpack.platform.build; import java.util.Objects; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** * Details of a cache for use by the CNB builder. * * @author Scott Frederick * @since 2.6.0 */ public class Cache { /** * The format of the cache. */ public enum Format { /** * A cache stored as a volume in the Docker daemon. */ VOLUME("volume"), /** * A cache stored as a bind mount. */ BIND("bind mount"); private final String description; Format(String description) { this.description = description; } public String getDescription() { return this.description; } } protected final Format format; Cache(Format format) { this.format = format; } /** * Return the details of the cache if it is a volume cache. * @return the cache, or {@code null} if it is not a volume cache */ public @Nullable Volume getVolume() { return (this.format.equals(Format.VOLUME)) ? (Volume) this : null; } /** * Return the details of the cache if it is a bind cache. * @return the cache, or {@code null} if it is not a bind cache */ public @Nullable Bind getBind() { return (this.format.equals(Format.BIND)) ? (Bind) this : null; } /** * Create a new {@code Cache} that uses a volume with the provided name. * @param name the cache volume name * @return a new cache instance */ public static Cache volume(String name) { Assert.notNull(name, "'name' must not be null"); return new Volume(VolumeName.of(name)); } /** * Create a new {@code Cache} that uses a volume with the provided name. * @param name the cache volume name * @return a new cache instance */ public static Cache volume(VolumeName name) { Assert.notNull(name, "'name' must not be null"); return new Volume(name); } /** * Create a new {@code Cache} that uses a bind mount with the provided source. * @param source the cache bind mount source * @return a new cache instance */ public static Cache bind(String source) { Assert.notNull(source, "'source' must not be null"); return new Bind(source); } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } Cache other = (Cache) obj; return Objects.equals(this.format, other.format); } @Override public int hashCode() { return ObjectUtils.nullSafeHashCode(this.format); } /** * Details of a cache stored in a Docker volume. */ public static class Volume extends Cache { private final VolumeName name; Volume(VolumeName name) { super(Format.VOLUME); this.name = name; } public String getName() { return this.name.toString(); } public VolumeName getVolumeName() { return this.name; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } if (!super.equals(obj)) { return false; } Volume other = (Volume) obj; return Objects.equals(this.name, other.name); } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + ObjectUtils.nullSafeHashCode(this.name); return result; } @Override public String toString() { return this.format.getDescription() + " '" + this.name + "'"; } } /** * Details of a cache stored in a bind mount. */ public static class Bind extends Cache { private final String source; Bind(String source) { super(Format.BIND); this.source = source; } public String getSource() { return this.source; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } if (!super.equals(obj)) { return false; } Bind other = (Bind) obj; return Objects.equals(this.source, other.source); } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + ObjectUtils.nullSafeHashCode(this.source); return result; } @Override public String toString() { return this.format.getDescription() + " '" + this.source + "'"; } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Creator.java ================================================ /* * 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.buildpack.platform.build; import org.springframework.util.Assert; /** * Identifying information about the tooling that created a builder. * * @author Scott Frederick * @since 2.3.0 */ public class Creator { private final String version; Creator(String version) { this.version = version; } /** * Return the name of the builder creator. * @return the name */ public String getName() { return "Spring Boot"; } /** * Return the version of the builder creator. * @return the version */ public String getVersion() { return this.version; } /** * Create a new {@code Creator} using the provided version. * @param version the creator version * @return a new creator instance */ public static Creator withVersion(String version) { Assert.notNull(version, "'version' must not be null"); return new Creator(version); } @Override public String toString() { return getName() + " version " + getVersion(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/DirectoryBuildpack.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.type.Layer; import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.FilePermissions; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.Layout; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.util.Assert; /** * A {@link Buildpack} that references a buildpack in a directory on the local file * system. * * The file system must contain a buildpack descriptor named {@code buildpack.toml} in the * root of the directory. The contents of the directory tree will be provided as a single * layer to be included in the builder image. * * @author Scott Frederick */ final class DirectoryBuildpack implements Buildpack { private final Path path; private final BuildpackCoordinates coordinates; private DirectoryBuildpack(Path path) { this.path = path; this.coordinates = findBuildpackCoordinates(path); } private BuildpackCoordinates findBuildpackCoordinates(Path path) { Path buildpackToml = path.resolve("buildpack.toml"); Assert.state(Files.exists(buildpackToml), () -> "Buildpack descriptor 'buildpack.toml' is required in buildpack '" + path + "'"); try { try (InputStream inputStream = Files.newInputStream(buildpackToml)) { return BuildpackCoordinates.fromToml(inputStream, path); } } catch (IOException ex) { throw new IllegalArgumentException("Error parsing descriptor for buildpack '" + path + "'", ex); } } @Override public BuildpackCoordinates getCoordinates() { return this.coordinates; } @Override public void apply(IOConsumer layers) throws IOException { layers.accept(Layer.of(this::addLayerContent)); } private void addLayerContent(Layout layout) throws IOException { String id = this.coordinates.getSanitizedId(); Path cnbPath = Paths.get("/cnb/buildpacks/", id, this.coordinates.getVersion()); writeBasePathEntries(layout, cnbPath); Files.walkFileTree(this.path, new LayoutFileVisitor(this.path, cnbPath, layout)); } private void writeBasePathEntries(Layout layout, Path basePath) throws IOException { int pathCount = basePath.getNameCount(); for (int pathIndex = 1; pathIndex < pathCount + 1; pathIndex++) { String name = "/" + basePath.subpath(0, pathIndex) + "/"; layout.directory(name, Owner.ROOT); } } /** * A {@link BuildpackResolver} compatible method to resolve directory buildpacks. * @param context the resolver context * @param reference the buildpack reference * @return the resolved {@link Buildpack} or {@code null} */ static @Nullable Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) { Path path = reference.asPath(); if (path != null && Files.exists(path) && Files.isDirectory(path)) { return new DirectoryBuildpack(path); } return null; } /** * {@link SimpleFileVisitor} to used to create the {@link Layout}. */ private static class LayoutFileVisitor extends SimpleFileVisitor { private final Path basePath; private final Path layerPath; private final Layout layout; LayoutFileVisitor(Path basePath, Path layerPath, Layout layout) { this.basePath = basePath; this.layerPath = layerPath; this.layout = layout; } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { if (!dir.equals(this.basePath)) { this.layout.directory(relocate(dir), Owner.ROOT, getMode(dir)); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { this.layout.file(relocate(file), Owner.ROOT, getMode(file), Content.of(file.toFile())); return FileVisitResult.CONTINUE; } private int getMode(Path path) throws IOException { try { return FilePermissions.umaskForPath(path); } catch (IllegalStateException ex) { throw new IllegalStateException( "Buildpack content in a directory is not supported on this operating system"); } } private String relocate(Path path) { Path node = path.subpath(this.basePath.getNameCount(), path.getNameCount()); return Paths.get(this.layerPath.toString(), node.toString()).toString(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/EphemeralBuilder.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.util.Map; import org.jspecify.annotations.Nullable; 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.Update; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.Layer; import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** * A short-lived builder that is created for each {@link Lifecycle} run. * * @author Phillip Webb * @author Scott Frederick */ class EphemeralBuilder { static final String BUILDER_FOR_LABEL_NAME = "org.springframework.boot.builderFor"; private ImageReference name; private final BuildOwner buildOwner; private final Creator creator; private final BuilderMetadata builderMetadata; private final Image builderImage; private final IOConsumer archiveUpdate; /** * Create a new {@link EphemeralBuilder} instance. * @param buildOwner the build owner * @param builderImage the base builder image * @param targetImage the image being built * @param builderMetadata the builder metadata * @param creator the builder creator * @param env the builder env * @param buildpacks an optional set of buildpacks to apply */ EphemeralBuilder(BuildOwner buildOwner, Image builderImage, ImageReference targetImage, BuilderMetadata builderMetadata, Creator creator, @Nullable Map env, @Nullable Buildpacks buildpacks) { this.name = ImageReference.random("pack.local/builder/").inTaggedForm(); this.buildOwner = buildOwner; this.creator = creator; this.builderMetadata = builderMetadata.copy(this::updateMetadata); this.builderImage = builderImage; this.archiveUpdate = (update) -> { update.withUpdatedConfig(this.builderMetadata::attachTo); update.withUpdatedConfig((config) -> config.withLabel(BUILDER_FOR_LABEL_NAME, targetImage.toString())); update.withTag(this.name); if (!CollectionUtils.isEmpty(env)) { update.withNewLayer(getEnvLayer(env)); } if (buildpacks != null) { buildpacks.apply(update::withNewLayer); } }; } private void updateMetadata(BuilderMetadata.Update update) { update.withCreatedBy(this.creator.getName(), this.creator.getVersion()); } private Layer getEnvLayer(Map env) throws IOException { return Layer.of((layout) -> { for (Map.Entry entry : env.entrySet()) { String name = "/platform/env/" + entry.getKey(); Content content = Content.of((entry.getValue() != null) ? entry.getValue() : ""); layout.file(name, Owner.ROOT, content); } }); } /** * Return the name of this archive as tagged in Docker. * @return the ephemeral builder name */ ImageReference getName() { return this.name; } /** * Return the build owner that should be used for written content. * @return the builder owner */ Owner getBuildOwner() { return this.buildOwner; } /** * Return the builder meta-data that was used to create this ephemeral builder. * @return the builder meta-data */ BuilderMetadata getBuilderMetadata() { return this.builderMetadata; } /** * Return the contents of ephemeral builder for passing to Docker. * @param applicationDirectory the application directory * @return the ephemeral builder archive * @throws IOException on IO error */ ImageArchive getArchive(@Nullable String applicationDirectory) throws IOException { return ImageArchive.from(this.builderImage, (update) -> { this.archiveUpdate.accept(update); if (StringUtils.hasLength(applicationDirectory)) { update.withNewLayer(applicationDirectoryLayer(applicationDirectory)); } }); } private Layer applicationDirectoryLayer(String applicationDirectory) throws IOException { return Layer.of((layout) -> layout.directory(applicationDirectory, this.buildOwner)); } @Override public String toString() { return this.name.toString(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageBuildpack.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.build.BuildpackLayersMetadata.BuildpackLayerDetails; import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.Layer; import org.springframework.boot.buildpack.platform.docker.type.LayerId; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; /** * A {@link Buildpack} that references a buildpack contained in an OCI image. * * The reference must be an OCI image reference. The reference can optionally contain a * prefix {@code docker://} to unambiguously identify it as an image buildpack reference. * * @author Scott Frederick * @author Phillip Webb */ final class ImageBuildpack implements Buildpack { private static final String PREFIX = "docker://"; private final BuildpackCoordinates coordinates; private final @Nullable ExportedLayers exportedLayers; private ImageBuildpack(BuildpackResolverContext context, ImageReference imageReference) { ImageReference reference = imageReference.inTaggedOrDigestForm(); try { Image image = context.fetchImage(reference, ImageType.BUILDPACK); BuildpackMetadata buildpackMetadata = BuildpackMetadata.fromImage(image); this.coordinates = BuildpackCoordinates.fromBuildpackMetadata(buildpackMetadata); this.exportedLayers = (!buildpackExistsInBuilder(context, image.getLayers())) ? new ExportedLayers(context, reference) : null; } catch (IOException | DockerEngineException ex) { throw new IllegalArgumentException("Error pulling buildpack image '" + reference + "'", ex); } } private boolean buildpackExistsInBuilder(BuildpackResolverContext context, List imageLayers) { BuildpackLayerDetails buildpackLayerDetails = context.getBuildpackLayersMetadata() .getBuildpack(this.coordinates.getId(), this.coordinates.getVersion()); String layerDiffId = (buildpackLayerDetails != null) ? buildpackLayerDetails.getLayerDiffId() : null; return (layerDiffId != null) && imageLayers.stream().map(LayerId::toString).anyMatch(layerDiffId::equals); } @Override public BuildpackCoordinates getCoordinates() { return this.coordinates; } @Override public void apply(IOConsumer layers) throws IOException { if (this.exportedLayers != null) { this.exportedLayers.apply(layers); } } /** * A {@link BuildpackResolver} compatible method to resolve image buildpacks. * @param context the resolver context * @param reference the buildpack reference * @return the resolved {@link Buildpack} or {@code null} */ static @Nullable Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) { boolean unambiguous = reference.hasPrefix(PREFIX); try { ImageReference imageReference; if (unambiguous) { String subReference = reference.getSubReference(PREFIX); Assert.state(subReference != null, "'subReference' must not be null"); imageReference = ImageReference.of(subReference); } else { imageReference = ImageReference.of(reference.toString()); } return new ImageBuildpack(context, imageReference); } catch (IllegalArgumentException ex) { if (unambiguous) { throw ex; } return null; } } private static class ExportedLayers { private final List layerFiles; ExportedLayers(BuildpackResolverContext context, ImageReference imageReference) throws IOException { List layerFiles = new ArrayList<>(); context.exportImageLayers(imageReference, (name, tarArchive) -> layerFiles.add(createLayerFile(tarArchive))); this.layerFiles = Collections.unmodifiableList(layerFiles); } private Path createLayerFile(TarArchive tarArchive) throws IOException { Path sourceTarFile = Files.createTempFile("create-builder-scratch-source-", null); try (OutputStream out = Files.newOutputStream(sourceTarFile)) { tarArchive.writeTo(out); } Path layerFile = Files.createTempFile("create-builder-scratch-", null); try (TarArchiveOutputStream out = new TarArchiveOutputStream(Files.newOutputStream(layerFile))) { try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(sourceTarFile))) { out.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); TarArchiveEntry entry = in.getNextEntry(); while (entry != null) { out.putArchiveEntry(entry); StreamUtils.copy(in, out); out.closeArchiveEntry(); entry = in.getNextEntry(); } out.finish(); } } return layerFile; } void apply(IOConsumer layers) throws IOException { for (Path path : this.layerFiles) { layers.accept(Layer.fromTarArchive((out) -> { InputStream in = Files.newInputStream(path); StreamUtils.copy(in, out); })); Files.delete(path); } } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageType.java ================================================ /* * 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.buildpack.platform.build; /** * Image types. * * @author Andrey Shlykov */ enum ImageType { /** * Builder image. */ BUILDER("builder image"), /** * Run image. */ RUNNER("run image"), /** * Buildpack image. */ BUILDPACK("buildpack image"); private final String description; ImageType(String description) { this.description = description; } String getDescription() { return this.description; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java ================================================ /* * 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.buildpack.platform.build; import java.io.Closeable; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.function.Consumer; import com.sun.jna.Platform; import org.jspecify.annotations.Nullable; 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.LogUpdateEvent; 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.ContainerConfig; 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.ContainerStatus; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.util.Assert; import org.springframework.util.FileSystemUtils; /** * A buildpack lifecycle used to run the build {@link Phase phases} needed to package an * application. * * @author Phillip Webb * @author Scott Frederick * @author Jeroen Meijer * @author Julian Liebig */ class Lifecycle implements Closeable { private static final LifecycleVersion LOGGING_MINIMUM_VERSION = LifecycleVersion.parse("0.0.5"); private static final String PLATFORM_API_VERSION_KEY = "CNB_PLATFORM_API"; private static final String SOURCE_DATE_EPOCH_KEY = "SOURCE_DATE_EPOCH"; private static final String DOMAIN_SOCKET_PATH = "/var/run/docker.sock"; private static final List DEFAULT_SECURITY_OPTIONS = List.of("label=disable"); private final BuildLog log; private final DockerApi docker; private final @Nullable ResolvedDockerHost dockerHost; private final BuildRequest request; private final EphemeralBuilder builder; private final LifecycleVersion lifecycleVersion; private final ApiVersion platformVersion; private final Cache layers; private final Cache application; private final Cache buildCache; private final Cache launchCache; private final String applicationDirectory; private final List securityOptions; private boolean executed; private boolean applicationVolumePopulated; /** * Create a new {@link Lifecycle} instance. * @param log build output log * @param docker the Docker API * @param dockerHost the Docker host information * @param request the request to process * @param builder the ephemeral builder used to run the phases */ Lifecycle(BuildLog log, DockerApi docker, @Nullable ResolvedDockerHost dockerHost, BuildRequest request, EphemeralBuilder builder) { this.log = log; this.docker = docker; this.dockerHost = dockerHost; this.request = request; this.builder = builder; this.lifecycleVersion = LifecycleVersion.parse(builder.getBuilderMetadata().getLifecycle().getVersion()); this.platformVersion = getPlatformVersion(builder.getBuilderMetadata().getLifecycle()); this.layers = getLayersBindingSource(request); this.application = getApplicationBindingSource(request); this.buildCache = getBuildCache(request); this.launchCache = getLaunchCache(request); this.applicationDirectory = getApplicationDirectory(request); this.securityOptions = getSecurityOptions(request); } String getApplicationDirectory() { return this.applicationDirectory; } private Cache getBuildCache(BuildRequest request) { if (request.getBuildCache() != null) { return request.getBuildCache(); } return createVolumeCache(request, "build"); } private Cache getLaunchCache(BuildRequest request) { if (request.getLaunchCache() != null) { return request.getLaunchCache(); } return createVolumeCache(request, "launch"); } private String getApplicationDirectory(BuildRequest request) { return (request.getApplicationDirectory() != null) ? request.getApplicationDirectory() : Directory.APPLICATION; } private List getSecurityOptions(BuildRequest request) { if (request.getSecurityOptions() != null) { return request.getSecurityOptions(); } return (Platform.isWindows()) ? Collections.emptyList() : DEFAULT_SECURITY_OPTIONS; } private ApiVersion getPlatformVersion(BuilderMetadata.Lifecycle lifecycle) { if (lifecycle.getApis().getPlatform() != null) { String[] supportedVersions = lifecycle.getApis().getPlatform(); return ApiVersions.SUPPORTED_PLATFORMS.findLatestSupported(supportedVersions); } String version = lifecycle.getApi().getPlatform(); return ApiVersions.SUPPORTED_PLATFORMS.findLatestSupported(version); } /** * Execute this lifecycle by running each phase in turn. * @throws IOException on IO error */ void execute() throws IOException { Assert.state(!this.executed, "Lifecycle has already been executed"); this.executed = true; this.log.executingLifecycle(this.request, this.lifecycleVersion, this.buildCache); if (this.request.isCleanCache()) { deleteCache(this.buildCache); } if (this.request.isTrustBuilder()) { run(createPhase()); } else { run(analyzePhase()); run(detectPhase()); if (!this.request.isCleanCache()) { run(restorePhase()); } else { this.log.skippingPhase("restorer", "because 'cleanCache' is enabled"); } run(buildPhase()); run(exportPhase()); } this.log.executedLifecycle(this.request); } private Phase createPhase() { Phase phase = new Phase("creator", isVerboseLogging()); phase.withApp(this.applicationDirectory, Binding.from(getCacheBindingSource(this.application), this.applicationDirectory)); phase.withPlatform(Directory.PLATFORM); ImageReference runImage = this.request.getRunImage(); Assert.state(runImage != null, "'runImage' must not be null"); phase.withRunImage(runImage); phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); phase.withBuildCache(Directory.CACHE, Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE)); phase.withLaunchCache(Directory.LAUNCH_CACHE, Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE)); configureDaemonAccess(phase); if (this.request.isCleanCache()) { phase.withSkipRestore(); } if (requiresProcessTypeDefault()) { phase.withProcessType("web"); } phase.withImageName(this.request.getName()); configureOptions(phase); configureCreatedDate(phase); return phase; } private Phase analyzePhase() { Phase phase = new Phase("analyzer", isVerboseLogging()); configureDaemonAccess(phase); phase.withLaunchCache(Directory.LAUNCH_CACHE, Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE)); phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); ImageReference runImage = this.request.getRunImage(); Assert.state(runImage != null, "'runImage' must not be null"); phase.withRunImage(runImage); phase.withImageName(this.request.getName()); configureOptions(phase); return phase; } private Phase detectPhase() { Phase phase = new Phase("detector", isVerboseLogging()); phase.withApp(this.applicationDirectory, Binding.from(getCacheBindingSource(this.application), this.applicationDirectory)); phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); phase.withPlatform(Directory.PLATFORM); configureOptions(phase); return phase; } private Phase restorePhase() { Phase phase = new Phase("restorer", isVerboseLogging()); configureDaemonAccess(phase); phase.withBuildCache(Directory.CACHE, Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE)); phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); configureOptions(phase); return phase; } private Phase buildPhase() { Phase phase = new Phase("builder", isVerboseLogging()); phase.withApp(this.applicationDirectory, Binding.from(getCacheBindingSource(this.application), this.applicationDirectory)); phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); phase.withPlatform(Directory.PLATFORM); configureOptions(phase); return phase; } private Phase exportPhase() { Phase phase = new Phase("exporter", isVerboseLogging()); configureDaemonAccess(phase); phase.withApp(this.applicationDirectory, Binding.from(getCacheBindingSource(this.application), this.applicationDirectory)); phase.withBuildCache(Directory.CACHE, Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE)); phase.withLaunchCache(Directory.LAUNCH_CACHE, Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE)); phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); if (requiresProcessTypeDefault()) { phase.withProcessType("web"); } phase.withImageName(this.request.getName()); configureOptions(phase); configureCreatedDate(phase); return phase; } private Cache getLayersBindingSource(BuildRequest request) { if (request.getBuildWorkspace() != null) { return getBuildWorkspaceBindingSource(request.getBuildWorkspace(), "layers"); } return createVolumeCache("pack-layers-"); } private Cache getApplicationBindingSource(BuildRequest request) { if (request.getBuildWorkspace() != null) { return getBuildWorkspaceBindingSource(request.getBuildWorkspace(), "app"); } return createVolumeCache("pack-app-"); } private Cache getBuildWorkspaceBindingSource(Cache buildWorkspace, String suffix) { if (buildWorkspace.getVolume() != null) { return Cache.volume(buildWorkspace.getVolume().getName() + "-" + suffix); } Bind bind = buildWorkspace.getBind(); Assert.state(bind != null, "'bind' must not be null"); return Cache.bind(bind.getSource() + "-" + suffix); } private String getCacheBindingSource(Cache cache) { if (cache.getVolume() != null) { return cache.getVolume().getName(); } Bind bind = cache.getBind(); Assert.state(bind != null, "'bind' must not be null"); return bind.getSource(); } private Cache createVolumeCache(String prefix) { return Cache.volume(createRandomVolumeName(prefix)); } private Cache createVolumeCache(BuildRequest request, String suffix) { return Cache.volume( VolumeName.basedOn(request.getName(), ImageReference::toLegacyString, "pack-cache-", "." + suffix, 6)); } protected VolumeName createRandomVolumeName(String prefix) { return VolumeName.random(prefix); } private void configureDaemonAccess(Phase phase) { phase.withDaemonAccess(); if (this.dockerHost != null) { if (this.dockerHost.isRemote()) { phase.withEnv("DOCKER_HOST", this.dockerHost.getAddress()); if (this.dockerHost.isSecure()) { phase.withEnv("DOCKER_TLS_VERIFY", "1"); if (this.dockerHost.getCertificatePath() != null) { phase.withEnv("DOCKER_CERT_PATH", this.dockerHost.getCertificatePath()); } } } else { phase.withBinding(Binding.from(this.dockerHost.getAddress(), DOMAIN_SOCKET_PATH)); } } else { phase.withBinding(Binding.from(DOMAIN_SOCKET_PATH, DOMAIN_SOCKET_PATH)); } if (this.securityOptions != null) { this.securityOptions.forEach(phase::withSecurityOption); } } private void configureCreatedDate(Phase phase) { if (this.request.getCreatedDate() != null) { phase.withEnv(SOURCE_DATE_EPOCH_KEY, Long.toString(this.request.getCreatedDate().getEpochSecond())); } } private void configureOptions(Phase phase) { if (this.request.getBindings() != null) { this.request.getBindings().forEach(phase::withBinding); } if (this.request.getNetwork() != null) { phase.withNetworkMode(this.request.getNetwork()); } phase.withEnv(PLATFORM_API_VERSION_KEY, this.platformVersion.toString()); } private boolean isVerboseLogging() { return this.request.isVerboseLogging() && this.lifecycleVersion.isEqualOrGreaterThan(LOGGING_MINIMUM_VERSION); } private boolean requiresProcessTypeDefault() { return this.platformVersion.supportsAny(ApiVersion.of(0, 4), ApiVersion.of(0, 5)); } private void run(Phase phase) throws IOException { Consumer logConsumer = this.log.runningPhase(this.request, phase.getName()); ContainerConfig containerConfig = ContainerConfig.of(this.builder.getName(), phase::apply); ContainerReference reference = createContainer(containerConfig, phase.requiresApp()); try { this.docker.container().start(reference); this.docker.container().logs(reference, logConsumer::accept); ContainerStatus status = this.docker.container().wait(reference); if (status.getStatusCode() != 0) { throw new BuilderException(phase.getName(), status.getStatusCode()); } } finally { this.docker.container().remove(reference, true); } } private ContainerReference createContainer(ContainerConfig config, boolean requiresAppUpload) throws IOException { if (!requiresAppUpload || this.applicationVolumePopulated) { return this.docker.container().create(config, this.request.getImagePlatform()); } try { if (this.application.getBind() != null) { Files.createDirectories(Path.of(this.application.getBind().getSource())); } TarArchive applicationContent = this.request.getApplicationContent(this.builder.getBuildOwner()); return this.docker.container() .create(config, this.request.getImagePlatform(), ContainerContent.of(applicationContent, this.applicationDirectory)); } finally { this.applicationVolumePopulated = true; } } @Override public void close() throws IOException { deleteCache(this.layers); deleteCache(this.application); } private void deleteCache(Cache cache) throws IOException { if (cache.getVolume() != null) { deleteVolume(cache.getVolume().getVolumeName()); } if (cache.getBind() != null) { deleteBind(cache.getBind()); } } private void deleteVolume(VolumeName name) throws IOException { this.docker.volume().delete(name, true); } private void deleteBind(Cache.Bind bind) { try { FileSystemUtils.deleteRecursively(Path.of(bind.getSource())); } catch (Exception ex) { this.log.failedCleaningWorkDir(bind, ex); } } /** * Common directories used by the various phases. */ private static final class Directory { /** * The directory used by buildpacks to write their layer contributions. A new * layer directory is created for each lifecycle execution. *

* Maps to the {@code } concept in the * buildpack * specification and the {@code -layers} argument to lifecycle phases. */ static final String LAYERS = "/layers"; /** * The directory containing the original contributed application. A new * application directory is created for each lifecycle execution. *

* Maps to the {@code } concept in the * buildpack * specification and the {@code -app} argument from the reference lifecycle * implementation. The reference lifecycle follows the Kubernetes/Docker * convention of using {@code '/workspace'}. *

* Note that application content is uploaded to the container with the first phase * that runs and saved in a volume that is passed to subsequent phases. The * directory is mutable and buildpacks may modify the content. */ static final String APPLICATION = "/workspace"; /** * The directory used by buildpacks to obtain environment variables and platform * specific concerns. The platform directory is read-only and is created/populated * by the {@link EphemeralBuilder}. *

* Maps to the {@code /env} and {@code /#} concepts in the * buildpack * specification and the {@code -platform} argument to lifecycle phases. */ static final String PLATFORM = "/platform"; /** * The directory used by buildpacks for caching. The volume name is based on the * image {@link BuildRequest#getName() name} being built, and is persistent across * invocations even if the application content has changed. *

* Maps to the {@code -path} argument to lifecycle phases. */ static final String CACHE = "/cache"; /** * The directory used by buildpacks for launch related caching. The volume name is * based on the image {@link BuildRequest#getName() name} being built, and is * persistent across invocations even if the application content has changed. *

* Maps to the {@code -launch-cache} argument to lifecycle phases. */ static final String LAUNCH_CACHE = "/launch-cache"; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/LifecycleVersion.java ================================================ /* * 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.buildpack.platform.build; import java.util.Comparator; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** * A lifecycle version number comprised of a major, minor and patch value. * * @author Phillip Webb */ class LifecycleVersion implements Comparable { private static final Comparator COMPARATOR = Comparator.comparingInt(LifecycleVersion::getMajor) .thenComparingInt(LifecycleVersion::getMinor) .thenComparing(LifecycleVersion::getPatch); private final int major; private final int minor; private final int patch; LifecycleVersion(int major, int minor, int patch) { this.major = major; this.minor = minor; this.patch = patch; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } LifecycleVersion other = (LifecycleVersion) obj; boolean result = true; result = result && this.major == other.major; result = result && this.minor == other.minor; result = result && this.patch == other.patch; return result; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + this.major; result = prime * result + this.minor; result = prime * result + this.patch; return result; } @Override public String toString() { return "v" + this.major + "." + this.minor + "." + this.patch; } /** * Return if this version is greater than or equal to the specified version. * @param other the version to compare * @return {@code true} if this version is greater than or equal to the specified * version */ boolean isEqualOrGreaterThan(LifecycleVersion other) { return compareTo(other) >= 0; } @Override public int compareTo(LifecycleVersion other) { return COMPARATOR.compare(this, other); } /** * Return the major version number. * @return the major version */ int getMajor() { return this.major; } /** * Return the minor version number. * @return the minor version */ int getMinor() { return this.minor; } /** * Return the patch version number. * @return the patch version */ int getPatch() { return this.patch; } /** * Factory method to parse a string into a {@link LifecycleVersion} instance. * @param value the value to parse. * @return the corresponding {@link LifecycleVersion} * @throws IllegalArgumentException if the value could not be parsed */ static LifecycleVersion parse(String value) { Assert.hasText(value, "'value' must not be empty"); String withoutPrefix = (value.startsWith("v") || value.startsWith("V")) ? value.substring(1) : value; String[] components = withoutPrefix.split("\\."); Assert.isTrue(components.length <= 3, () -> "'value' [%s] must be a valid version number".formatted(value)); int[] versions = new int[3]; for (int i = 0; i < components.length; i++) { try { versions[i] = Integer.parseInt(components[i]); } catch (NumberFormatException ex) { throw new IllegalArgumentException("'value' [" + value + "] must be a valid version number", ex); } } return new LifecycleVersion(versions[0], versions[1], versions[2]); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java ================================================ /* * 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.buildpack.platform.build; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.jspecify.annotations.Nullable; 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.ImageReference; import org.springframework.util.StringUtils; /** * An individual build phase executed as part of a {@link Lifecycle} run. * * @author Phillip Webb * @author Scott Frederick * @author Jeroen Meijer */ class Phase { private final String name; private boolean daemonAccess; private final List args = new ArrayList<>(); private final List bindings = new ArrayList<>(); private final Map env = new LinkedHashMap<>(); private final List securityOptions = new ArrayList<>(); private @Nullable String networkMode; private boolean requiresApp; /** * Create a new {@link Phase} instance. * @param name the name of the phase * @param verboseLogging if verbose logging is requested */ Phase(String name, boolean verboseLogging) { this.name = name; withLogLevelArg(verboseLogging); } void withApp(String path, Binding binding) { withArgs("-app", path); withBinding(binding); this.requiresApp = true; } void withBuildCache(String path, Binding binding) { withArgs("-cache-dir", path); withBinding(binding); } /** * Update this phase with Docker daemon access. */ void withDaemonAccess() { this.withArgs("-daemon"); this.daemonAccess = true; } void withImageName(ImageReference imageName) { withArgs(imageName); } void withLaunchCache(String path, Binding binding) { withArgs("-launch-cache", path); withBinding(binding); } void withLayers(String path, Binding binding) { withArgs("-layers", path); withBinding(binding); } void withPlatform(String path) { withArgs("-platform", path); } void withProcessType(String type) { withArgs("-process-type", type); } void withRunImage(ImageReference runImage) { withArgs("-run-image", runImage); } void withSkipRestore() { withArgs("-skip-restore"); } /** * Update this phase with a debug log level arguments if verbose logging has been * requested. * @param verboseLogging if verbose logging is requested */ private void withLogLevelArg(boolean verboseLogging) { if (verboseLogging) { this.args.add("-log-level"); this.args.add("debug"); } } /** * Update this phase with additional run arguments. * @param args the arguments to add */ void withArgs(Object... args) { Arrays.stream(args).map(Object::toString).forEach(this.args::add); } /** * Update this phase with an addition volume binding. * @param binding the binding */ void withBinding(Binding binding) { this.bindings.add(binding); } /** * Update this phase with an additional environment variable. * @param name the variable name * @param value the variable value */ void withEnv(String name, String value) { this.env.put(name, value); } /** * Update this phase with the network the build container will connect to. * @param networkMode the network */ void withNetworkMode(@Nullable String networkMode) { this.networkMode = networkMode; } /** * Update this phase with a security option. * @param option the security option */ void withSecurityOption(String option) { this.securityOptions.add(option); } /** * Return the name of the phase. * @return the phase name */ String getName() { return this.name; } boolean requiresApp() { return this.requiresApp; } @Override public String toString() { return this.name; } /** * Apply this phase settings to a {@link ContainerConfig} update. * @param update the update to apply the phase to */ void apply(ContainerConfig.Update update) { if (this.daemonAccess) { update.withUser("root"); } update.withCommand("/cnb/lifecycle/" + this.name, StringUtils.toStringArray(this.args)); update.withLabel("author", "spring-boot"); this.bindings.forEach(update::withBinding); this.env.forEach(update::withEnv); if (this.networkMode != null) { update.withNetworkMode(this.networkMode); } this.securityOptions.forEach(update::withSecurityOption); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/PrintStreamBuildLog.java ================================================ /* * 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.buildpack.platform.build; import java.io.PrintStream; import java.util.function.Consumer; import org.springframework.boot.buildpack.platform.docker.TotalProgressBar; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; /** * {@link BuildLog} implementation that prints output to a {@link PrintStream}. * * @author Phillip Webb * @see BuildLog#to(PrintStream) */ class PrintStreamBuildLog extends AbstractBuildLog { private final PrintStream out; PrintStreamBuildLog(PrintStream out) { this.out = out; } @Override protected void log(String message) { this.out.println(message); } @Override protected Consumer getProgressConsumer(String prefix) { return new TotalProgressBar(prefix, '.', false, this.out); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/PullPolicy.java ================================================ /* * 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.buildpack.platform.build; /** * Image pull policy. * * @author Andrey Shlykov * @since 2.4.0 */ public enum PullPolicy { /** * Always pull the image from the registry. */ ALWAYS, /** * Never pull the image from the registry. */ NEVER, /** * Pull the image from the registry only if it does not exist locally. */ IF_NOT_PRESENT } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/StackId.java ================================================ /* * 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.buildpack.platform.build; import java.util.Objects; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.util.Assert; /** * A Stack ID. * * @author Phillip Webb * @author Andy Wilkinson */ class StackId { private static final String LABEL_NAME = "io.buildpacks.stack.id"; private final @Nullable String value; StackId(@Nullable String value) { this.value = value; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } return Objects.equals(this.value, ((StackId) obj).value); } boolean hasId() { return this.value != null; } @Override public int hashCode() { return Objects.hashCode(this.value); } @Override public String toString() { return (this.value != null) ? this.value : ""; } /** * Factory method to create a {@link StackId} from an {@link Image}. * @param image the source image * @return the extracted stack ID */ static StackId fromImage(Image image) { Assert.notNull(image, "'image' must not be null"); return fromImageConfig(image.getConfig()); } /** * Factory method to create a {@link StackId} from an {@link ImageConfig}. * @param imageConfig the source image config * @return the extracted stack ID */ private static StackId fromImageConfig(ImageConfig imageConfig) { String value = imageConfig.getLabels().get(LABEL_NAME); return new StackId(value); } /** * Factory method to create a {@link StackId} with a given value. * @param value the stack ID value * @return a new stack ID instance */ static StackId of(String value) { Assert.hasText(value, "'value' must not be empty"); return new StackId(value); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/TarGzipBuildpack.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.type.Layer; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.util.StreamUtils; /** * A {@link Buildpack} that references a buildpack contained in a local gzipped tar * archive file. * * The archive must contain a buildpack descriptor named {@code buildpack.toml} at the * root of the archive. The contents of the archive will be provided as a single layer to * be included in the builder image. * * @author Scott Frederick */ final class TarGzipBuildpack implements Buildpack { private final Path path; private final BuildpackCoordinates coordinates; private TarGzipBuildpack(Path path) { this.path = path; this.coordinates = findBuildpackCoordinates(path); } private BuildpackCoordinates findBuildpackCoordinates(Path path) { try { try (TarArchiveInputStream tar = new TarArchiveInputStream( new GzipCompressorInputStream(Files.newInputStream(path)))) { ArchiveEntry entry = tar.getNextEntry(); while (entry != null) { if ("buildpack.toml".equals(entry.getName())) { return BuildpackCoordinates.fromToml(tar, path); } entry = tar.getNextEntry(); } throw new IllegalArgumentException( "Buildpack descriptor 'buildpack.toml' is required in buildpack '" + path + "'"); } } catch (IOException ex) { throw new RuntimeException("Error parsing descriptor for buildpack '" + path + "'", ex); } } @Override public BuildpackCoordinates getCoordinates() { return this.coordinates; } @Override public void apply(IOConsumer layers) throws IOException { layers.accept(Layer.fromTarArchive(this::copyAndRebaseEntries)); } private void copyAndRebaseEntries(OutputStream outputStream) throws IOException { String id = this.coordinates.getSanitizedId(); Path basePath = Paths.get("/cnb/buildpacks/", id, this.coordinates.getVersion()); try (TarArchiveInputStream tar = new TarArchiveInputStream( new GzipCompressorInputStream(Files.newInputStream(this.path))); TarArchiveOutputStream output = new TarArchiveOutputStream(outputStream)) { writeBasePathEntries(output, basePath); TarArchiveEntry entry = tar.getNextEntry(); while (entry != null) { entry.setName(basePath + "/" + entry.getName()); output.putArchiveEntry(entry); StreamUtils.copy(tar, output); output.closeArchiveEntry(); entry = tar.getNextEntry(); } output.finish(); } } private void writeBasePathEntries(TarArchiveOutputStream output, Path basePath) throws IOException { int pathCount = basePath.getNameCount(); for (int pathIndex = 1; pathIndex < pathCount + 1; pathIndex++) { String name = "/" + basePath.subpath(0, pathIndex) + "/"; TarArchiveEntry entry = new TarArchiveEntry(name); output.putArchiveEntry(entry); output.closeArchiveEntry(); } } /** * A {@link BuildpackResolver} compatible method to resolve tar-gzip buildpacks. * @param context the resolver context * @param reference the buildpack reference * @return the resolved {@link Buildpack} or {@code null} */ static @Nullable Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) { Path path = reference.asPath(); if (path != null && Files.exists(path) && Files.isRegularFile(path)) { return new TarGzipBuildpack(path); } return null; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/package-info.java ================================================ /* * 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. */ /** * Central API for performing a buildpack build. */ @NullMarked package org.springframework.boot.buildpack.platform.build; import org.jspecify.annotations.NullMarked; ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ApiVersion.java ================================================ /* * 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.buildpack.platform.docker; import java.util.Comparator; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** * API Version number comprised of a major and minor value. * * @author Phillip Webb * @author Scott Frederick * @since 4.0.0 */ public final class ApiVersion implements Comparable { private static final Pattern PATTERN = Pattern.compile("^v?(\\d+)\\.(\\d*)$"); private static final Comparator COMPARATOR = Comparator.comparing(ApiVersion::getMajor) .thenComparing(ApiVersion::getMinor); private final int major; private final int minor; private ApiVersion(int major, int minor) { this.major = major; this.minor = minor; } /** * Return the major version number. * @return the major version */ int getMajor() { return this.major; } /** * Return the minor version number. * @return the minor version */ int getMinor() { return this.minor; } /** * Returns if this API version supports the given version. A {@code 0.x} matches only * the same version number. A 1.x or higher release matches when the versions have the * same major version and a minor that is equal or greater. * @param other the version to check against * @return if the specified API version is supported */ public boolean supports(ApiVersion other) { if (equals(other)) { return true; } if (this.major == 0 || this.major != other.major) { return false; } return this.minor >= other.minor; } /** * Returns if this API version supports any of the given versions. * @param others the versions to check against * @return if any of the specified API versions are supported * @see #supports(ApiVersion) */ public boolean supportsAny(ApiVersion... others) { for (ApiVersion other : others) { if (supports(other)) { return true; } } return false; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } ApiVersion other = (ApiVersion) obj; return (this.major == other.major) && (this.minor == other.minor); } @Override public int hashCode() { return this.major * 31 + this.minor; } @Override public String toString() { return this.major + "." + this.minor; } /** * Factory method to parse a string into an {@link ApiVersion} instance. * @param value the value to parse. * @return the corresponding {@link ApiVersion} * @throws IllegalArgumentException if the value could not be parsed */ public static ApiVersion parse(String value) { Assert.hasText(value, "'value' must not be empty"); Matcher matcher = PATTERN.matcher(value); Assert.isTrue(matcher.matches(), () -> "'value' [%s] must contain a well formed version number".formatted(value)); try { int major = Integer.parseInt(matcher.group(1)); int minor = Integer.parseInt(matcher.group(2)); return new ApiVersion(major, minor); } catch (NumberFormatException ex) { throw new IllegalArgumentException("'value' must contain a well formed version number [" + value + "]", ex); } } public static ApiVersion of(int major, int minor) { return new ApiVersion(major, minor); } @Override public int compareTo(ApiVersion other) { return COMPARATOR.compare(this, other); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java ================================================ /* * 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.buildpack.platform.docker; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.net.URIBuilder; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.PushImageUpdateEvent.ErrorDetail; 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.Response; 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.ContainerReference; 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.ImageArchive; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.io.IOBiConsumer; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.json.JsonStream; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Provides access to the limited set of Docker APIs needed by pack. * * @author Phillip Webb * @author Scott Frederick * @author Rafael Ceccone * @author Moritz Halbritter * @since 2.3.0 */ public class DockerApi { private static final List FORCE_PARAMS = Collections.unmodifiableList(Arrays.asList("force", "1")); static final ApiVersion UNKNOWN_API_VERSION = ApiVersion.of(0, 0); static final ApiVersion PREFERRED_API_VERSION = ApiVersion.of(1, 50); static final String API_VERSION_HEADER_NAME = "API-Version"; private final HttpTransport http; private final JsonStream jsonStream; private final ImageApi image; private final ContainerApi container; private final VolumeApi volume; private final SystemApi system; private volatile @Nullable ApiVersion apiVersion; /** * Create a new {@link DockerApi} instance. */ public DockerApi() { this(HttpTransport.create((DockerConnectionConfiguration) null), DockerLog.toSystemOut()); } /** * Create a new {@link DockerApi} instance. * @param connectionConfiguration the connection configuration to use * @param log a logger used to record output * @since 3.5.0 */ public DockerApi(@Nullable DockerConnectionConfiguration connectionConfiguration, DockerLog log) { this(HttpTransport.create(connectionConfiguration), log); } /** * Create a new {@link DockerApi} instance backed by a specific {@link HttpTransport} * implementation. * @param http the http implementation * @param log a logger used to record output */ DockerApi(HttpTransport http, DockerLog log) { Assert.notNull(http, "'http' must not be null"); Assert.notNull(log, "'log' must not be null"); this.http = http; this.jsonStream = new JsonStream(SharedJsonMapper.get()); this.image = new ImageApi(); this.container = new ContainerApi(); this.volume = new VolumeApi(); this.system = new SystemApi(log); } private HttpTransport http() { return this.http; } private JsonStream jsonStream() { return this.jsonStream; } URI buildPlatformJsonUrl(Feature feature, @Nullable ImagePlatform platform, String path) { if (platform != null && getApiVersion().supports(feature.minimumVersion())) { return buildUrl(feature, path, "platform", platform.toJson()); } return buildUrl(path); } private URI buildUrl(String path, @Nullable Collection params) { return buildUrl(Feature.BASELINE, path, (params != null) ? params.toArray() : null); } private URI buildUrl(String path, Object... params) { return buildUrl(Feature.BASELINE, path, params); } URI buildUrl(Feature feature, String path, Object @Nullable ... params) { ApiVersion version = getApiVersion(); if (version.equals(UNKNOWN_API_VERSION) || (version.compareTo(PREFERRED_API_VERSION) >= 0 && version.compareTo(feature.minimumVersion()) >= 0)) { return buildVersionedUrl(PREFERRED_API_VERSION, path, params); } if (version.compareTo(feature.minimumVersion()) >= 0) { return buildVersionedUrl(version, path, params); } throw new IllegalStateException( "Docker API version must be at least %s to support this feature, but current API version is %s" .formatted(feature.minimumVersion(), version)); } private URI buildVersionedUrl(ApiVersion version, String path, Object @Nullable ... params) { try { URIBuilder builder = new URIBuilder("/v" + version + path); if (params != null) { int param = 0; while (param < params.length) { builder.addParameter(Objects.toString(params[param++]), Objects.toString(params[param++])); } } return builder.build(); } catch (URISyntaxException ex) { throw new IllegalStateException(ex); } } private ApiVersion getApiVersion() { ApiVersion apiVersion = this.apiVersion; if (apiVersion == null) { apiVersion = this.system.getApiVersion(); this.apiVersion = apiVersion; } return apiVersion; } /** * Return the Docker API for image operations. * @return the image API */ public ImageApi image() { return this.image; } /** * Return the Docker API for container operations. * @return the container API */ public ContainerApi container() { return this.container; } public VolumeApi volume() { return this.volume; } SystemApi system() { return this.system; } /** * Docker API for image operations. */ public class ImageApi { ImageApi() { } /** * Pull an image from a registry. * @param reference the image reference to pull * @param platform the platform (os/architecture/variant) of the image to pull * @param listener a pull listener to receive update events * @return the {@link ImageApi pulled image} instance * @throws IOException on IO error */ public Image pull(ImageReference reference, @Nullable ImagePlatform platform, UpdateListener listener) throws IOException { return pull(reference, platform, listener, null); } /** * Pull an image from a registry. * @param reference the image reference to pull * @param platform the platform (os/architecture/variant) of the image to pull * @param listener a pull listener to receive update events * @param registryAuth registry authentication credentials * @return the {@link ImageApi pulled image} instance * @throws IOException on IO error */ public Image pull(ImageReference reference, @Nullable ImagePlatform platform, UpdateListener listener, @Nullable String registryAuth) throws IOException { Assert.notNull(reference, "'reference' must not be null"); Assert.notNull(listener, "'listener' must not be null"); URI createUri = (platform != null) ? buildUrl(Feature.PLATFORM_IMAGE_PULL, "/images/create", "fromImage", reference, "platform", platform) : buildUrl("/images/create", "fromImage", reference); DigestCaptureUpdateListener digestCapture = new DigestCaptureUpdateListener(); listener.onStart(); try { try (Response response = http().post(createUri, registryAuth)) { jsonStream().get(response.getContent(), PullImageUpdateEvent.class, (event) -> { digestCapture.onUpdate(event); listener.onUpdate(event); }); } return inspect(reference, platform); } finally { listener.onFinish(); } } /** * Push an image to a registry. * @param reference the image reference to push * @param listener a push listener to receive update events * @param registryAuth registry authentication credentials * @throws IOException on IO error */ public void push(ImageReference reference, UpdateListener listener, @Nullable String registryAuth) throws IOException { Assert.notNull(reference, "'reference' must not be null"); Assert.notNull(listener, "'listener' must not be null"); URI pushUri = buildUrl("/images/" + reference + "/push"); ErrorCaptureUpdateListener errorListener = new ErrorCaptureUpdateListener(); listener.onStart(); try { try (Response response = http().post(pushUri, registryAuth)) { jsonStream().get(response.getContent(), PushImageUpdateEvent.class, (event) -> { errorListener.onUpdate(event); listener.onUpdate(event); }); } } finally { listener.onFinish(); } } /** * Load an {@link ImageArchive} into Docker. * @param archive the archive to load * @param listener a pull listener to receive update events * @throws IOException on IO error */ public void load(ImageArchive archive, UpdateListener listener) throws IOException { Assert.notNull(archive, "'archive' must not be null"); Assert.notNull(listener, "'listener' must not be null"); URI loadUri = buildUrl("/images/load"); LoadImageUpdateListener streamListener = new LoadImageUpdateListener(archive); listener.onStart(); try { try (Response response = http().post(loadUri, "application/x-tar", archive::writeTo)) { InputStream content = response.getContent(); if (content != null) { jsonStream().get(content, LoadImageUpdateEvent.class, (event) -> { streamListener.onUpdate(event); listener.onUpdate(event); }); } } streamListener.assertValidResponseReceived(); } finally { listener.onFinish(); } } /** * Export the layers of an image as {@link TarArchive TarArchives}. * @param reference the reference to export * @param exports a consumer to receive the layers (contents can only be accessed * during the callback) * @throws IOException on IO error */ public void exportLayers(ImageReference reference, IOBiConsumer exports) throws IOException { exportLayers(reference, null, exports); } /** * Export the layers of an image as {@link TarArchive TarArchives}. * @param reference the reference to export * @param platform the platform (os/architecture/variant) of the image to export. * Ignored on older versions of Docker. * @param exports a consumer to receive the layers (contents can only be accessed * during the callback) * @throws IOException on IO error * @since 3.4.12 */ public void exportLayers(ImageReference reference, @Nullable ImagePlatform platform, IOBiConsumer exports) throws IOException { Assert.notNull(reference, "'reference' must not be null"); Assert.notNull(exports, "'exports' must not be null"); URI uri = buildPlatformJsonUrl(Feature.PLATFORM_IMAGE_EXPORT, platform, "/images/" + reference + "/get"); try (Response response = http().get(uri)) { try (ExportedImageTar exportedImageTar = new ExportedImageTar(reference, response.getContent())) { exportedImageTar.exportLayers(exports); } } } /** * Remove a specific image. * @param reference the reference the remove * @param force if removal should be forced * @throws IOException on IO error */ public void remove(ImageReference reference, boolean force) throws IOException { Assert.notNull(reference, "'reference' must not be null"); Collection params = force ? FORCE_PARAMS : Collections.emptySet(); URI uri = buildUrl("/images/" + reference, params); http().delete(uri).close(); } /** * Inspect an image. * @param reference the image reference * @return the image from the local repository * @throws IOException on IO error */ public Image inspect(ImageReference reference) throws IOException { return inspect(reference, null); } /** * Inspect an image. * @param reference the image reference * @param platform the platform (os/architecture/variant) of the image to inspect. * Ignored on older versions of Docker. * @return the image from the local repository * @throws IOException on IO error * @since 3.4.12 */ public Image inspect(ImageReference reference, @Nullable ImagePlatform platform) throws IOException { // The Docker documentation is incomplete but platform parameters // are supported since 1.49 (see https://github.com/moby/moby/pull/49586) Assert.notNull(reference, "'reference' must not be null"); URI inspectUrl = buildPlatformJsonUrl(Feature.PLATFORM_IMAGE_INSPECT, platform, "/images/" + reference + "/json"); try (Response response = http().get(inspectUrl)) { return Image.of(response.getContent()); } } public void tag(ImageReference sourceReference, ImageReference targetReference) throws IOException { Assert.notNull(sourceReference, "'sourceReference' must not be null"); Assert.notNull(targetReference, "'targetReference' must not be null"); String tag = targetReference.getTag(); String path = "/images/" + sourceReference + "/tag"; URI uri = (tag != null) ? buildUrl(path, "repo", targetReference.inTaglessForm(), "tag", tag) : buildUrl(path, "repo", targetReference); http().post(uri).close(); } } /** * Docker API for container operations. */ public class ContainerApi { ContainerApi() { } /** * Create a new container a {@link ContainerConfig}. * @param config the container config * @param platform the platform (os/architecture/variant) of the image the * container should be created from * @param contents additional contents to include * @return a {@link ContainerReference} for the newly created container * @throws IOException on IO error */ public ContainerReference create(ContainerConfig config, @Nullable ImagePlatform platform, ContainerContent... contents) throws IOException { Assert.notNull(config, "'config' must not be null"); Assert.noNullElements(contents, "'contents' must not contain null elements"); ContainerReference containerReference = createContainer(config, platform); for (ContainerContent content : contents) { uploadContainerContent(containerReference, content); } return containerReference; } private ContainerReference createContainer(ContainerConfig config, @Nullable ImagePlatform platform) throws IOException { URI createUri = (platform != null) ? buildUrl(Feature.PLATFORM_CONTAINER_CREATE, "/containers/create", "platform", platform) : buildUrl("/containers/create"); try (Response response = http().post(createUri, "application/json", config::writeTo)) { return ContainerReference .of(SharedJsonMapper.get().readTree(response.getContent()).at("/Id").asString()); } } private void uploadContainerContent(ContainerReference reference, ContainerContent content) throws IOException { URI uri = buildUrl("/containers/" + reference + "/archive", "path", content.getDestinationPath()); http().put(uri, "application/x-tar", content.getArchive()::writeTo).close(); } /** * Start a specific container. * @param reference the container reference to start * @throws IOException on IO error */ public void start(ContainerReference reference) throws IOException { Assert.notNull(reference, "'reference' must not be null"); URI uri = buildUrl("/containers/" + reference + "/start"); http().post(uri).close(); } /** * Return and follow logs for a specific container. * @param reference the container reference * @param listener a listener to receive log update events * @throws IOException on IO error */ public void logs(ContainerReference reference, UpdateListener listener) throws IOException { Assert.notNull(reference, "'reference' must not be null"); Assert.notNull(listener, "'listener' must not be null"); Object[] params = { "stdout", "1", "stderr", "1", "follow", "1" }; URI uri = buildUrl("/containers/" + reference + "/logs", params); listener.onStart(); try { try (Response response = http().get(uri)) { LogUpdateEvent.readAll(response.getContent(), listener::onUpdate); } } finally { listener.onFinish(); } } /** * Wait for a container to stop and retrieve the status. * @param reference the container reference * @return a {@link ContainerStatus} indicating the exit status of the container * @throws IOException on IO error */ public ContainerStatus wait(ContainerReference reference) throws IOException { Assert.notNull(reference, "'reference' must not be null"); URI uri = buildUrl("/containers/" + reference + "/wait"); try (Response response = http().post(uri)) { return ContainerStatus.of(response.getContent()); } } /** * Remove a specific container. * @param reference the container to remove * @param force if removal should be forced * @throws IOException on IO error */ public void remove(ContainerReference reference, boolean force) throws IOException { Assert.notNull(reference, "'reference' must not be null"); Collection params = force ? FORCE_PARAMS : Collections.emptySet(); URI uri = buildUrl("/containers/" + reference, params); http().delete(uri).close(); } } /** * Docker API for volume operations. */ public class VolumeApi { VolumeApi() { } /** * Delete a volume. * @param name the name of the volume to delete * @param force if the deletion should be forced * @throws IOException on IO error */ public void delete(VolumeName name, boolean force) throws IOException { Assert.notNull(name, "'name' must not be null"); Collection params = force ? FORCE_PARAMS : Collections.emptySet(); URI uri = buildUrl("/volumes/" + name, params); http().delete(uri).close(); } } /** * Docker API for system operations. */ class SystemApi { private final DockerLog log; SystemApi(DockerLog log) { this.log = log; } /** * Get the API version supported by the Docker daemon. * @return the Docker daemon API version */ ApiVersion getApiVersion() { try { URI uri = new URIBuilder("/_ping").build(); try (Response response = http().head(uri)) { Header apiVersionHeader = response.getHeader(API_VERSION_HEADER_NAME); if (apiVersionHeader != null) { return ApiVersion.parse(apiVersionHeader.getValue()); } } catch (Exception ex) { this.log.log("Warning: Failed to determine Docker API version: " + ex.getMessage()); // fall through to return default value } return UNKNOWN_API_VERSION; } catch (URISyntaxException ex) { throw new IllegalStateException(ex); } } } /** * {@link UpdateListener} used to capture the image digest. */ private static final class DigestCaptureUpdateListener implements UpdateListener { private static final String PREFIX = "Digest:"; private @Nullable String digest; @Override public void onUpdate(ProgressUpdateEvent event) { String status = event.getStatus(); if (status != null && status.startsWith(PREFIX)) { String digest = status.substring(PREFIX.length()).trim(); Assert.state(this.digest == null || this.digest.equals(digest), "Different digests IDs provided"); this.digest = digest; } } } /** * {@link UpdateListener} for an image load response stream. */ private static final class LoadImageUpdateListener implements UpdateListener { private final ImageArchive archive; private @Nullable String stream; private LoadImageUpdateListener(ImageArchive archive) { this.archive = archive; } @Override public void onUpdate(LoadImageUpdateEvent event) { Assert.state(event.getErrorDetail() == null, () -> "Error response received when loading image" + image() + ": " + event.getErrorDetail()); this.stream = event.getStream(); } private String image() { ImageReference tag = this.archive.getTag(); return (tag != null) ? " \"" + tag + "\"" : ""; } private void assertValidResponseReceived() { Assert.state(StringUtils.hasText(this.stream), () -> "Invalid response received when loading image" + image()); } } /** * {@link UpdateListener} used to capture the details of an error in a response * stream. */ private static final class ErrorCaptureUpdateListener implements UpdateListener { @Override public void onUpdate(PushImageUpdateEvent event) { ErrorDetail errorDetail = event.getErrorDetail(); if (errorDetail != null) { throw new IllegalStateException( "Error response received when pushing image: " + errorDetail.getMessage()); } } } enum Feature { BASELINE(ApiVersion.of(1, 24)), PLATFORM_IMAGE_PULL(ApiVersion.of(1, 41)), PLATFORM_CONTAINER_CREATE(ApiVersion.of(1, 41)), PLATFORM_IMAGE_INSPECT(ApiVersion.of(1, 49)), PLATFORM_IMAGE_EXPORT(ApiVersion.of(1, 48)); private final ApiVersion minimumVersion; Feature(ApiVersion minimumVersion) { this.minimumVersion = minimumVersion; } ApiVersion minimumVersion() { return this.minimumVersion; } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerLog.java ================================================ /* * 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.buildpack.platform.docker; import java.io.PrintStream; /** * Callback interface used to provide {@link DockerApi} output logging. * * @author Dmytro Nosan * @since 3.5.0 * @see #toSystemOut() */ public interface DockerLog { /** * Logs a given message. * @param message the message to log */ void log(String message); /** * Factory method that returns a {@link DockerLog} that outputs to {@link System#out}. * @return {@link DockerLog} instance that logs to system out */ static DockerLog toSystemOut() { return to(System.out); } /** * Factory method that returns a {@link DockerLog} that outputs to a given * {@link PrintStream}. * @param out the print stream used to output the log * @return {@link DockerLog} instance that logs to the given print stream */ static DockerLog to(PrintStream out) { return out::println; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ExportedImageTar.java ================================================ /* * 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.buildpack.platform.docker; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.type.BlobReference; import org.springframework.boot.buildpack.platform.docker.type.ImageArchiveIndex; import org.springframework.boot.buildpack.platform.docker.type.ImageArchiveManifest; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.Manifest; import org.springframework.boot.buildpack.platform.docker.type.ManifestList; import org.springframework.boot.buildpack.platform.io.IOBiConsumer; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.io.TarArchive.Compression; import org.springframework.util.Assert; import org.springframework.util.function.ThrowingFunction; /** * Internal helper class used by the {@link DockerApi} to extract layers from an exported * image tar. * * @author Phillip Webb * @author Moritz Halbritter * @author Scott Frederick */ class ExportedImageTar implements Closeable { private final Path tarFile; private final LayerArchiveFactory layerArchiveFactory; ExportedImageTar(ImageReference reference, InputStream inputStream) throws IOException { this.tarFile = Files.createTempFile("docker-layers-", null); Files.copy(inputStream, this.tarFile, StandardCopyOption.REPLACE_EXISTING); this.layerArchiveFactory = LayerArchiveFactory.create(reference, this.tarFile); } void exportLayers(IOBiConsumer exports) throws IOException { try (TarArchiveInputStream tar = openTar(this.tarFile)) { TarArchiveEntry entry = tar.getNextEntry(); while (entry != null) { TarArchive layerArchive = this.layerArchiveFactory.getLayerArchive(tar, entry); if (layerArchive != null) { exports.accept(entry.getName(), layerArchive); } entry = tar.getNextEntry(); } } } private static TarArchiveInputStream openTar(Path path) throws IOException { return new TarArchiveInputStream(Files.newInputStream(path)); } @Override public void close() throws IOException { Files.delete(this.tarFile); } /** * Factory class used to create a {@link TarArchiveEntry} for layer. */ private abstract static class LayerArchiveFactory { /** * Create a new {@link TarArchive} if the given entry represents a layer. * @param tar the tar input stream * @param entry the candidate entry * @return a new {@link TarArchive} instance or {@code null} if this entry is not * a layer. */ abstract @Nullable TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry); /** * Create a new {@link LayerArchiveFactory} for the given tar file using either * the {@code index.json} or {@code manifest.json} to detect layers. * @param reference the image that was referenced * @param tarFile the source tar file * @return a new {@link LayerArchiveFactory} instance * @throws IOException on IO error */ static LayerArchiveFactory create(ImageReference reference, Path tarFile) throws IOException { try (TarArchiveInputStream tar = openTar(tarFile)) { ImageArchiveIndex index = null; ImageArchiveManifest manifest = null; TarArchiveEntry entry = tar.getNextEntry(); while (entry != null) { if ("index.json".equals(entry.getName())) { index = ImageArchiveIndex.of(tar); break; } if ("manifest.json".equals(entry.getName())) { manifest = ImageArchiveManifest.of(tar); } entry = tar.getNextEntry(); } Assert.state(index != null || manifest != null, () -> "Exported image '%s' does not contain 'index.json' or 'manifest.json'" .formatted(reference)); if (index != null) { return new IndexLayerArchiveFactory(tarFile, index); } Assert.state(manifest != null, "'manifest' must not be null"); return new ManifestLayerArchiveFactory(manifest); } } } /** * {@link LayerArchiveFactory} backed by the more recent {@code index.json} file. */ private static class IndexLayerArchiveFactory extends LayerArchiveFactory { private final Map layerMediaTypes; IndexLayerArchiveFactory(Path tarFile, ImageArchiveIndex index) throws IOException { this(tarFile, withNestedIndexes(tarFile, index)); } IndexLayerArchiveFactory(Path tarFile, List indexes) throws IOException { Set manifestDigests = getDigests(indexes, this::isManifest); Set manifestListDigests = getDigests(indexes, IndexLayerArchiveFactory::isManifestList); List manifestLists = getManifestLists(tarFile, manifestListDigests); List manifests = getManifests(tarFile, manifestDigests, manifestLists); this.layerMediaTypes = manifests.stream() .flatMap((manifest) -> manifest.getLayers().stream()) .collect(Collectors.toMap(IndexLayerArchiveFactory::getEntryName, BlobReference::getMediaType)); } private static List withNestedIndexes(Path tarFile, ImageArchiveIndex index) throws IOException { Set indexDigests = getDigests(Stream.of(index), IndexLayerArchiveFactory::isIndex); List indexes = new ArrayList<>(); indexes.add(index); indexes.addAll(getDigestMatches(tarFile, indexDigests, ImageArchiveIndex::of)); return indexes; } private static Set getDigests(List indexes, Predicate predicate) { return getDigests(indexes.stream(), predicate); } private static Set getDigests(Stream indexes, Predicate predicate) { return indexes.flatMap((index) -> index.getManifests().stream()) .filter(predicate) .map(BlobReference::getDigest) .collect(Collectors.toUnmodifiableSet()); } private static List getManifestLists(Path tarFile, Set digests) throws IOException { return getDigestMatches(tarFile, digests, ManifestList::of); } private List getManifests(Path tarFile, Set manifestDigests, List manifestLists) throws IOException { Set digests = new HashSet<>(manifestDigests); manifestLists.stream() .flatMap(ManifestList::streamManifests) .filter(this::isManifest) .map(BlobReference::getDigest) .forEach(digests::add); return getDigestMatches(tarFile, digests, Manifest::of); } private static List getDigestMatches(Path tarFile, Set digests, ThrowingFunction factory) throws IOException { if (digests.isEmpty()) { return Collections.emptyList(); } Set names = digests.stream() .map(IndexLayerArchiveFactory::getEntryName) .collect(Collectors.toUnmodifiableSet()); List result = new ArrayList<>(); try (TarArchiveInputStream tar = openTar(tarFile)) { TarArchiveEntry entry = tar.getNextEntry(); while (entry != null) { if (names.contains(entry.getName())) { result.add(factory.apply(tar)); } entry = tar.getNextEntry(); } } return Collections.unmodifiableList(result); } private boolean isManifest(BlobReference reference) { return isJsonWithPrefix(reference.getMediaType(), "application/vnd.oci.image.manifest.v") || isJsonWithPrefix(reference.getMediaType(), "application/vnd.docker.distribution.manifest.v"); } private static boolean isIndex(BlobReference reference) { return isJsonWithPrefix(reference.getMediaType(), "application/vnd.oci.image.index.v"); } private static boolean isManifestList(BlobReference reference) { return isJsonWithPrefix(reference.getMediaType(), "application/vnd.docker.distribution.manifest.list.v"); } private static boolean isJsonWithPrefix(String mediaType, String prefix) { return mediaType.startsWith(prefix) && mediaType.endsWith("+json"); } private static String getEntryName(BlobReference reference) { return getEntryName(reference.getDigest()); } private static String getEntryName(String digest) { return "blobs/" + digest.replace(':', '/'); } @Override @Nullable TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry) { String mediaType = this.layerMediaTypes.get(entry.getName()); if (mediaType == null) { return null; } return TarArchive.fromInputStream(tar, getCompression(mediaType)); } private Compression getCompression(String mediaType) { if (mediaType.endsWith(".tar.gzip") || mediaType.endsWith(".tar+gzip")) { return Compression.GZIP; } if (mediaType.endsWith(".tar.zstd") || mediaType.endsWith(".tar+zstd")) { return Compression.ZSTD; } return Compression.NONE; } } /** * {@link LayerArchiveFactory} backed by the legacy {@code manifest.json} file. */ private static class ManifestLayerArchiveFactory extends LayerArchiveFactory { private final Set layers; ManifestLayerArchiveFactory(ImageArchiveManifest manifest) { this.layers = manifest.getEntries() .stream() .flatMap((entry) -> entry.getLayers().stream()) .collect(Collectors.toUnmodifiableSet()); } @Override @Nullable TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry) { if (!this.layers.contains(entry.getName())) { return null; } return TarArchive.fromInputStream(tar, Compression.NONE); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ImagePlatform.java ================================================ /* * 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.buildpack.platform.docker; import java.util.Objects; import org.jspecify.annotations.Nullable; import tools.jackson.databind.node.ObjectNode; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * A platform specification for a Docker image. * * @author Scott Frederick * @since 4.0.0 */ public class ImagePlatform { private final String os; private final @Nullable String architecture; private final @Nullable String variant; ImagePlatform(String os, @Nullable String architecture, @Nullable String variant) { Assert.hasText(os, "'os' must not be empty"); this.os = os; this.architecture = architecture; this.variant = variant; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } ImagePlatform other = (ImagePlatform) obj; return Objects.equals(this.architecture, other.architecture) && Objects.equals(this.os, other.os) && Objects.equals(this.variant, other.variant); } @Override public int hashCode() { return Objects.hash(this.architecture, this.os, this.variant); } @Override public String toString() { StringBuilder builder = new StringBuilder(this.os); if (this.architecture != null) { builder.append("/").append(this.architecture); } if (this.variant != null) { builder.append("/").append(this.variant); } return builder.toString(); } /** * Create a new {@link ImagePlatform} from the given value in the form * {@code os[/architecture[/variant]]}. * @param value the value to parse * @return an {@link ImagePlatform} instance */ public static ImagePlatform of(String value) { Assert.hasText(value, "'value' must not be empty"); String[] split = value.split("/+"); return switch (split.length) { case 1 -> new ImagePlatform(split[0], null, null); case 2 -> new ImagePlatform(split[0], split[1], null); case 3 -> new ImagePlatform(split[0], split[1], split[2]); default -> throw new IllegalArgumentException( "'value' [" + value + "] must be in the form 'os[/architecture[/variant]]'"); }; } /** * Create a new {@link ImagePlatform} matching the platform information from the * provided {@link Image}. * @param image the image to get platform information from * @return an {@link ImagePlatform} instance */ public static ImagePlatform from(Image image) { return new ImagePlatform(image.getOs(), image.getArchitecture(), image.getVariant()); } /** * Return a JSON-encoded representation of this platform. * @return the JSON string */ public String toJson() { ObjectNode json = SharedJsonMapper.get().createObjectNode(); json.put("os", this.os); if (StringUtils.hasText(this.architecture)) { json.put("architecture", this.architecture); } if (StringUtils.hasText(this.variant)) { json.put("variant", this.variant); } return json.toString(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ImageProgressUpdateEvent.java ================================================ /* * 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.buildpack.platform.docker; import org.jspecify.annotations.Nullable; /** * A {@link ProgressUpdateEvent} fired for image events. * * @author Phillip Webb * @author Scott Frederick * @since 2.4.0 */ public class ImageProgressUpdateEvent extends ProgressUpdateEvent { private final @Nullable String id; protected ImageProgressUpdateEvent(@Nullable String id, String status, @Nullable ProgressDetail progressDetail, @Nullable String progress) { super(status, progressDetail, progress); this.id = id; } /** * Returns the ID of the image layer being updated if available. * @return the ID of the updated layer or {@code null} */ public @Nullable String getId() { return this.id; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/LoadImageUpdateEvent.java ================================================ /* * 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.buildpack.platform.docker; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.jspecify.annotations.Nullable; /** * A {@link ProgressUpdateEvent} fired as an image is loaded. * * @author Phillip Webb * @since 2.3.0 */ public class LoadImageUpdateEvent extends ProgressUpdateEvent { private final @Nullable String stream; private final @Nullable ErrorDetail errorDetail; @JsonCreator public LoadImageUpdateEvent(@Nullable String stream, String status, ProgressDetail progressDetail, String progress, @Nullable ErrorDetail errorDetail) { super(status, progressDetail, progress); this.stream = stream; this.errorDetail = errorDetail; } /** * Return the stream response or {@code null} if no response is available. * @return the stream response. */ public @Nullable String getStream() { return this.stream; } /** * Return the error detail or {@code null} if no error occurred. * @return the error detail, if any * @since 3.2.12 */ public @Nullable ErrorDetail getErrorDetail() { return this.errorDetail; } /** * Details of an error embedded in a response stream. * * @since 3.2.12 */ public static class ErrorDetail { private final String message; @JsonCreator public ErrorDetail(@JsonProperty("message") String message) { this.message = message; } /** * Returns the message field from the error detail. * @return the message */ public String getMessage() { return this.message; } @Override public String toString() { return this.message; } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/LogUpdateEvent.java ================================================ /* * 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.buildpack.platform.docker; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.function.Consumer; import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; /** * An update event used to provide log updates. * * @author Phillip Webb * @since 2.3.0 */ public class LogUpdateEvent extends UpdateEvent { private static final Pattern ANSI_PATTERN = Pattern.compile("\u001B\\[[;\\d]*m"); private static final Pattern TRAILING_NEW_LINE_PATTERN = Pattern.compile("\\n$"); private final StreamType streamType; private final byte[] payload; private final String string; LogUpdateEvent(StreamType streamType, byte[] payload) { this.streamType = streamType; this.payload = payload; String string = new String(payload, StandardCharsets.UTF_8); string = ANSI_PATTERN.matcher(string).replaceAll(""); string = TRAILING_NEW_LINE_PATTERN.matcher(string).replaceAll(""); this.string = string; } public void print() { switch (this.streamType) { case STD_OUT -> System.out.println(this); case STD_ERR -> System.err.println(this); } } public StreamType getStreamType() { return this.streamType; } public byte[] getPayload() { return this.payload; } @Override public String toString() { return this.string; } static void readAll(InputStream inputStream, Consumer consumer) throws IOException { try { LogUpdateEvent event; while ((event = LogUpdateEvent.read(inputStream)) != null) { consumer.accept(event); } } catch (IllegalStateException ex) { byte[] message = (ex.getMessage() == null) ? new byte[0] : ex.getMessage().getBytes(StandardCharsets.UTF_8); consumer.accept(new LogUpdateEvent(StreamType.STD_ERR, message)); StreamUtils.drain(inputStream); } finally { inputStream.close(); } } private static @Nullable LogUpdateEvent read(InputStream inputStream) throws IOException { byte[] header = read(inputStream, 8); if (header == null) { return null; } StreamType streamType = StreamType.forId(header[0]); long size = 0; for (int i = 0; i < 4; i++) { size = (size << 8) + (header[i + 4] & 0xff); } byte[] payload = read(inputStream, size); Assert.state(payload != null, "'payload' must not be null"); return new LogUpdateEvent(streamType, payload); } private static byte @Nullable [] read(InputStream inputStream, long size) throws IOException { byte[] data = new byte[(int) size]; int offset = 0; do { int amountRead = inputStream.read(data, offset, data.length - offset); if (amountRead == -1) { return null; } offset += amountRead; } while (offset < data.length); return data; } /** * Stream types supported by the event. */ public enum StreamType { /** * Input from {@code stdin}. */ STD_IN, /** * Output to {@code stdout}. */ STD_OUT, /** * Output to {@code stderr}. */ STD_ERR; static StreamType forId(byte id) { int upperBound = values().length; Assert.state(id > 0 && id < upperBound, () -> "Stream type is out of bounds. Must be >= 0 and < " + upperBound + ", but was " + id); return values()[id]; } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ProgressUpdateEvent.java ================================================ /* * 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.buildpack.platform.docker; import com.fasterxml.jackson.annotation.JsonCreator; import org.jspecify.annotations.Nullable; /** * An {@link UpdateEvent} that includes progress information. * * @author Phillip Webb * @author Wolfgang Kronberg * @since 2.3.0 */ public abstract class ProgressUpdateEvent extends UpdateEvent { private final @Nullable String status; private final @Nullable ProgressDetail progressDetail; private final @Nullable String progress; protected ProgressUpdateEvent(@Nullable String status, @Nullable ProgressDetail progressDetail, @Nullable String progress) { this.status = status; this.progressDetail = (ProgressDetail.isEmpty(progressDetail)) ? null : progressDetail; this.progress = progress; } /** * Return the status for the update. For example, "Extracting" or "Downloading". * @return the status of the update. */ public @Nullable String getStatus() { return this.status; } /** * Return progress details if available. * @return progress details or {@code null} */ public @Nullable ProgressDetail getProgressDetail() { return this.progressDetail; } /** * Return a text based progress bar if progress information is available. * @return the progress bar or {@code null} */ public @Nullable String getProgress() { return this.progress; } /** * Provide details about the progress of a task. */ public static class ProgressDetail { private final @Nullable Long current; private final @Nullable Long total; @JsonCreator public ProgressDetail(@Nullable Long current, @Nullable Long total) { this.current = current; this.total = total; } /** * Return the progress as a percentage. * @return the progress percentage * @since 3.3.7 */ public int asPercentage() { if (this.total == null || this.current == null) { return 0; } int percentage = (int) ((100.0 / this.total) * this.current); return (percentage < 0) ? 0 : Math.min(percentage, 100); } private static boolean isEmpty(@Nullable ProgressDetail progressDetail) { return progressDetail == null || progressDetail.current == null || progressDetail.total == null; } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/PullImageUpdateEvent.java ================================================ /* * 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.buildpack.platform.docker; import com.fasterxml.jackson.annotation.JsonCreator; /** * A {@link ProgressUpdateEvent} fired as an image is pulled. * * @author Phillip Webb * @author Scott Frederick * @since 2.3.0 */ public class PullImageUpdateEvent extends ImageProgressUpdateEvent { @JsonCreator public PullImageUpdateEvent(String id, String status, ProgressDetail progressDetail, String progress) { super(id, status, progressDetail, progress); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/PushImageUpdateEvent.java ================================================ /* * 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.buildpack.platform.docker; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.jspecify.annotations.Nullable; /** * A {@link ProgressUpdateEvent} fired as an image is pushed to a registry. * * @author Scott Frederick * @since 2.4.0 */ public class PushImageUpdateEvent extends ImageProgressUpdateEvent { private final @Nullable ErrorDetail errorDetail; @JsonCreator public PushImageUpdateEvent(String id, String status, ProgressDetail progressDetail, String progress, @Nullable ErrorDetail errorDetail) { super(id, status, progressDetail, progress); this.errorDetail = errorDetail; } /** * Returns the details of any error encountered during processing. * @return the error */ public @Nullable ErrorDetail getErrorDetail() { return this.errorDetail; } /** * Details of an error embedded in a response stream. */ public static class ErrorDetail { private final String message; @JsonCreator public ErrorDetail(@JsonProperty("message") String message) { this.message = message; } /** * Returns the message field from the error detail. * @return the message */ public String getMessage() { return this.message; } @Override public String toString() { return this.message; } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/TotalProgressBar.java ================================================ /* * 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.buildpack.platform.docker; import java.io.PrintStream; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; import org.springframework.util.StringUtils; /** * Utility to render a simple progress bar based on consumed {@link TotalProgressEvent} * objects. * * @author Phillip Webb * @since 2.3.0 */ public class TotalProgressBar implements Consumer { private final char progressChar; private final boolean bookend; private final PrintStream out; private int printed; /** * Create a new {@link TotalProgressBar} instance. * @param prefix the prefix to output */ public TotalProgressBar(String prefix) { this(prefix, System.out); } /** * Create a new {@link TotalProgressBar} instance. * @param prefix the prefix to output * @param out the output print stream to use */ public TotalProgressBar(String prefix, PrintStream out) { this(prefix, '#', true, out); } /** * Create a new {@link TotalProgressBar} instance. * @param prefix the prefix to output * @param progressChar the progress char to print * @param bookend if bookends should be printed * @param out the output print stream to use */ public TotalProgressBar(@Nullable String prefix, char progressChar, boolean bookend, PrintStream out) { this.progressChar = progressChar; this.bookend = bookend; if (StringUtils.hasLength(prefix)) { out.print(prefix); out.print(" "); } if (bookend) { out.print("[ "); } this.out = out; } @Override public void accept(TotalProgressEvent event) { int percent = event.getPercent() / 2; while (this.printed < percent) { this.out.print(this.progressChar); this.printed++; } if (event.getPercent() == 100) { this.out.println(this.bookend ? " ]" : ""); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/TotalProgressEvent.java ================================================ /* * 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.buildpack.platform.docker; import org.springframework.util.Assert; /** * Event published by the {@link TotalProgressPullListener} showing the total progress of * an operation. * * @author Phillip Webb * @since 2.3.0 */ public class TotalProgressEvent { private final int percent; /** * Create a new {@link TotalProgressEvent} with a specific percent value. * @param percent the progress as a percentage */ public TotalProgressEvent(int percent) { Assert.isTrue(percent >= 0 && percent <= 100, "'percent' must be in the range 0 to 100"); this.percent = percent; } /** * Return the total progress. * @return the total progress */ public int getPercent() { return this.percent; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/TotalProgressListener.java ================================================ /* * 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.buildpack.platform.docker; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import org.springframework.boot.buildpack.platform.docker.ProgressUpdateEvent.ProgressDetail; /** * {@link UpdateListener} that calculates the total progress of the entire image operation * and publishes {@link TotalProgressEvent}. * * @param the type of {@link ImageProgressUpdateEvent} * @author Phillip Webb * @author Scott Frederick * @since 2.4.0 */ public abstract class TotalProgressListener implements UpdateListener { private final Map layers = new ConcurrentHashMap<>(); private final Consumer consumer; private final String[] trackedStatusKeys; private boolean progressStarted; /** * Create a new {@link TotalProgressListener} that sends {@link TotalProgressEvent * events} to the given consumer. * @param consumer the consumer that receives {@link TotalProgressEvent progress * events} * @param trackedStatusKeys a list of status event keys to track the progress of */ protected TotalProgressListener(Consumer consumer, String[] trackedStatusKeys) { this.consumer = consumer; this.trackedStatusKeys = trackedStatusKeys; } @Override public void onStart() { } @Override public void onUpdate(E event) { if (event.getId() != null) { this.layers.computeIfAbsent(event.getId(), (value) -> new Layer(this.trackedStatusKeys)).update(event); } this.progressStarted = this.progressStarted || event.getProgress() != null; if (this.progressStarted) { publish(0); } } @Override public void onFinish() { this.layers.values().forEach(Layer::finish); publish(100); } private void publish(int fallback) { int count = 0; int total = 0; for (Layer layer : this.layers.values()) { count++; total += layer.getProgress(); } TotalProgressEvent event = new TotalProgressEvent( (count != 0) ? withinPercentageBounds(total / count) : fallback); this.consumer.accept(event); } private static int withinPercentageBounds(int value) { return (value < 0) ? 0 : Math.min(value, 100); } /** * Progress for an individual layer. */ private static class Layer { private final Map progressByStatus = new HashMap<>(); Layer(String[] trackedStatusKeys) { Arrays.stream(trackedStatusKeys).forEach((status) -> this.progressByStatus.put(status, 0)); } void update(ImageProgressUpdateEvent event) { String status = event.getStatus(); if (status == null) { return; } if (event.getProgressDetail() != null && this.progressByStatus.containsKey(status)) { int current = this.progressByStatus.get(status); this.progressByStatus.put(status, updateProgress(current, event.getProgressDetail())); } } private int updateProgress(int current, ProgressDetail detail) { return Math.max(detail.asPercentage(), current); } void finish() { this.progressByStatus.keySet().forEach((key) -> this.progressByStatus.put(key, 100)); } int getProgress() { return withinPercentageBounds((this.progressByStatus.values().stream().mapToInt(Integer::intValue).sum()) / this.progressByStatus.size()); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/TotalProgressPullListener.java ================================================ /* * 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.buildpack.platform.docker; import java.util.function.Consumer; /** * {@link UpdateListener} that calculates the total progress of the entire pull operation * and publishes {@link TotalProgressEvent}. * * @author Phillip Webb * @author Scott Frederick * @since 2.3.0 */ public class TotalProgressPullListener extends TotalProgressListener { private static final String[] TRACKED_STATUS_KEYS = { "Downloading", "Extracting" }; /** * Create a new {@link TotalProgressPullListener} that prints a progress bar to * {@link System#out}. * @param prefix the prefix to output */ public TotalProgressPullListener(String prefix) { this(new TotalProgressBar(prefix)); } /** * Create a new {@link TotalProgressPullListener} that sends {@link TotalProgressEvent * events} to the given consumer. * @param consumer the consumer that receives {@link TotalProgressEvent progress * events} */ public TotalProgressPullListener(Consumer consumer) { super(consumer, TRACKED_STATUS_KEYS); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/TotalProgressPushListener.java ================================================ /* * 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.buildpack.platform.docker; import java.util.function.Consumer; /** * {@link UpdateListener} that calculates the total progress of the entire push operation * and publishes {@link TotalProgressEvent}. * * @author Scott Frederick * @since 2.4.0 */ public class TotalProgressPushListener extends TotalProgressListener { private static final String[] TRACKED_STATUS_KEYS = { "Pushing" }; /** * Create a new {@link TotalProgressPushListener} that prints a progress bar to * {@link System#out}. * @param prefix the prefix to output */ public TotalProgressPushListener(String prefix) { this(new TotalProgressBar(prefix)); } /** * Create a new {@link TotalProgressPushListener} that sends {@link TotalProgressEvent * events} to the given consumer. * @param consumer the consumer that receives {@link TotalProgressEvent progress * events} */ public TotalProgressPushListener(Consumer consumer) { super(consumer, TRACKED_STATUS_KEYS); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/UpdateEvent.java ================================================ /* * 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.buildpack.platform.docker; /** * Base class for update events published by Docker. * * @author Phillip Webb * @since 2.3.0 * @see UpdateListener */ public abstract class UpdateEvent { } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/UpdateListener.java ================================================ /* * 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.buildpack.platform.docker; /** * Listener for update events published from the {@link DockerApi}. * * @param the update event type * @author Phillip Webb * @since 2.3.0 */ @FunctionalInterface public interface UpdateListener { /** * A no-op update listener. * @see #none() */ UpdateListener NONE = (event) -> { }; /** * Called when the operation starts. */ default void onStart() { } /** * Called when an update event is available. * @param event the update event */ void onUpdate(E event); /** * Called when the operation finishes (with or without error). */ default void onFinish() { } /** * A no-op update listener that does nothing. * @param the event type * @return a no-op update listener */ @SuppressWarnings("unchecked") static UpdateListener none() { return (UpdateListener) NONE; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/Credential.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.lang.invoke.MethodHandles; import org.jspecify.annotations.Nullable; import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.Assert; /** * A class that represents credentials for a server as returned from a * {@link CredentialHelper}. * * @author Dmytro Nosan */ class Credential extends MappedObject { /** * If the secret being stored is an identity token, the username should be set to * {@code }. */ private static final String TOKEN_USERNAME = ""; private final String username; private final String secret; private final @Nullable String serverUrl; Credential(JsonNode node) { super(node, MethodHandles.lookup()); this.username = extractUsername(); this.secret = extractSecret(); this.serverUrl = valueAt("/ServerURL", String.class); } private String extractSecret() { String result = valueAt("/Secret", String.class); Assert.state(result != null, "'result' must not be null"); return result; } private String extractUsername() { String result = valueAt("/Username", String.class); Assert.state(result != null, "'result' must not be null"); return result; } String getUsername() { return this.username; } String getSecret() { return this.secret; } @Nullable String getServerUrl() { return this.serverUrl; } boolean isIdentityToken() { return TOKEN_USERNAME.equals(this.username); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/CredentialHelper.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import com.sun.jna.Platform; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; /** * Invokes a Docker credential helper executable that can be used to get {@link Credential * credentials}. * * @author Dmytro Nosan * @author Phillip Webb */ class CredentialHelper { private static final String USR_LOCAL_BIN = "/usr/local/bin/"; private static final Set CREDENTIAL_NOT_FOUND_MESSAGES = Set.of("credentials not found in native keychain", "no credentials server URL", "no credentials username"); private final String executable; CredentialHelper(String executable) { this.executable = executable; } @Nullable Credential get(String serverUrl) throws IOException { ProcessBuilder processBuilder = processBuilder("get"); Process process = start(processBuilder); try (OutputStream request = process.getOutputStream()) { request.write(serverUrl.getBytes(StandardCharsets.UTF_8)); } try { int exitCode = process.waitFor(); try (InputStream response = process.getInputStream()) { if (exitCode == 0) { return new Credential(SharedJsonMapper.get().readTree(response)); } String errorMessage = new String(response.readAllBytes(), StandardCharsets.UTF_8); if (!isCredentialsNotFoundError(errorMessage)) { throw new IOException("%s' exited with code %d: %s".formatted(process, exitCode, errorMessage)); } return null; } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); return null; } } private ProcessBuilder processBuilder(String action) { ProcessBuilder processBuilder = new ProcessBuilder().redirectErrorStream(true); if (Platform.isWindows()) { processBuilder.command("cmd", "/c"); } processBuilder.command().addAll(Arrays.asList(this.executable, action)); return processBuilder; } private Process start(ProcessBuilder processBuilder) throws IOException { try { return processBuilder.start(); } catch (IOException ex) { if (!Platform.isMac()) { throw ex; } try { List command = new ArrayList<>(processBuilder.command()); command.set(0, USR_LOCAL_BIN + command.get(0)); return processBuilder.command(command).start(); } catch (Exception suppressed) { // Suppresses the exception and rethrows the original exception ex.addSuppressed(suppressed); throw ex; } } } private static boolean isCredentialsNotFoundError(String message) { return CREDENTIAL_NOT_FOUND_MESSAGES.contains(message.trim()); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.HexFormat; import java.util.Map; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import tools.jackson.core.JacksonException; import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.NullNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.buildpack.platform.system.Environment; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.function.SingletonSupplier; /** * Docker configuration stored in metadata files managed by the Docker CLI. * * @author Scott Frederick * @author Dmytro Nosan */ final class DockerConfigurationMetadata { private static final String DOCKER_CONFIG = "DOCKER_CONFIG"; private static final String DEFAULT_CONTEXT = "default"; private static final String CONFIG_DIR = ".docker"; private static final String CONTEXTS_DIR = "contexts"; private static final String META_DIR = "meta"; private static final String TLS_DIR = "tls"; private static final String DOCKER_ENDPOINT = "docker"; private static final String CONFIG_FILE_NAME = "config.json"; private static final String CONTEXT_FILE_NAME = "meta.json"; private static final Supplier systemEnvironmentConfigurationMetadata = SingletonSupplier .of(() -> DockerConfigurationMetadata.create(Environment.SYSTEM)); private final String configLocation; private final DockerConfig config; private final DockerContext context; private DockerConfigurationMetadata(String configLocation, DockerConfig config, DockerContext context) { this.configLocation = configLocation; this.config = config; this.context = context; } DockerConfig getConfiguration() { return this.config; } DockerContext getContext() { return this.context; } DockerContext forContext(@Nullable String context) { return createDockerContext(this.configLocation, context); } static DockerConfigurationMetadata from(Environment environment) { if (environment == Environment.SYSTEM) { return systemEnvironmentConfigurationMetadata.get(); } return create(environment); } private static DockerConfigurationMetadata create(Environment environment) { String configLocation = environment.get(DOCKER_CONFIG); configLocation = (configLocation != null) ? configLocation : getUserHomeConfigLocation(); DockerConfig dockerConfig = createDockerConfig(configLocation); DockerContext dockerContext = createDockerContext(configLocation, dockerConfig.getCurrentContext()); return new DockerConfigurationMetadata(configLocation, dockerConfig, dockerContext); } private static String getUserHomeConfigLocation() { return Path.of(System.getProperty("user.home"), CONFIG_DIR).toString(); } private static DockerConfig createDockerConfig(String configLocation) { Path path = Path.of(configLocation, CONFIG_FILE_NAME); if (!path.toFile().exists()) { return DockerConfig.empty(); } try { return DockerConfig.fromJson(readPathContent(path)); } catch (JacksonException ex) { throw new IllegalStateException("Error parsing Docker configuration file '" + path + "'", ex); } } private static DockerContext createDockerContext(String configLocation, @Nullable String currentContext) { if (currentContext == null || DEFAULT_CONTEXT.equals(currentContext)) { return DockerContext.empty(); } String hash = asHash(currentContext); Path metaPath = Path.of(configLocation, CONTEXTS_DIR, META_DIR, hash, CONTEXT_FILE_NAME); Path tlsPath = Path.of(configLocation, CONTEXTS_DIR, TLS_DIR, hash, DOCKER_ENDPOINT); if (!metaPath.toFile().exists()) { throw new IllegalArgumentException("Docker context '" + currentContext + "' does not exist"); } try { DockerContext context = DockerContext.fromJson(readPathContent(metaPath)); if (tlsPath.toFile().isDirectory()) { return context.withTlsPath(tlsPath.toString()); } return context; } catch (JacksonException ex) { throw new IllegalStateException("Error parsing Docker context metadata file '" + metaPath + "'", ex); } } private static String asHash(String currentContext) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(currentContext.getBytes(StandardCharsets.UTF_8)); return HexFormat.of().formatHex(hash); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException("SHA-256 is not available", ex); } } private static String readPathContent(Path path) { try { return Files.readString(path); } catch (IOException ex) { throw new IllegalStateException("Error reading Docker configuration file '" + path + "'", ex); } } static final class DockerConfig extends MappedObject { private final @Nullable String currentContext; private final @Nullable String credsStore; private final Map credHelpers; private final Map auths; private DockerConfig(JsonNode node) { super(node, MethodHandles.lookup()); this.currentContext = valueAt("/currentContext", String.class); this.credsStore = valueAt("/credsStore", String.class); this.credHelpers = mapAt("/credHelpers", JsonNode::stringValue); this.auths = mapAt("/auths", Auth::new); } @Nullable String getCurrentContext() { return this.currentContext; } @Nullable String getCredsStore() { return this.credsStore; } Map getCredHelpers() { return this.credHelpers; } Map getAuths() { return this.auths; } static DockerConfig fromJson(String json) { return new DockerConfig(SharedJsonMapper.get().readTree(json)); } static DockerConfig empty() { return new DockerConfig(NullNode.instance); } } static final class Auth extends MappedObject { private final @Nullable String username; private final @Nullable String password; private final @Nullable String email; Auth(JsonNode node) { super(node, MethodHandles.lookup()); String auth = valueAt("/auth", String.class); if (StringUtils.hasLength(auth)) { String[] parts = new String(Base64.getDecoder().decode(auth)).split(":", 2); Assert.state(parts.length == 2, "Malformed auth in docker configuration metadata"); this.username = parts[0]; this.password = trim(parts[1], Character.MIN_VALUE); } else { this.username = valueAt("/username", String.class); this.password = valueAt("/password", String.class); } this.email = valueAt("/email", String.class); } @Nullable String getUsername() { return this.username; } @Nullable String getPassword() { return this.password; } @Nullable String getEmail() { return this.email; } private static String trim(String source, char character) { source = StringUtils.trimLeadingCharacter(source, character); return StringUtils.trimTrailingCharacter(source, character); } } static final class DockerContext extends MappedObject { private final @Nullable String dockerHost; private final @Nullable Boolean skipTlsVerify; private final @Nullable String tlsPath; private DockerContext(JsonNode node, @Nullable String tlsPath) { super(node, MethodHandles.lookup()); this.dockerHost = valueAt("/Endpoints/" + DOCKER_ENDPOINT + "/Host", String.class); this.skipTlsVerify = valueAt("/Endpoints/" + DOCKER_ENDPOINT + "/SkipTLSVerify", Boolean.class); this.tlsPath = tlsPath; } @Nullable String getDockerHost() { return this.dockerHost; } Boolean isTlsVerify() { return this.skipTlsVerify != null && !this.skipTlsVerify; } @Nullable String getTlsPath() { return this.tlsPath; } DockerContext withTlsPath(String tlsPath) { return new DockerContext(this.getNode(), tlsPath); } static DockerContext fromJson(String json) { return new DockerContext(SharedJsonMapper.get().readTree(json), null); } static DockerContext empty() { return new DockerContext(NullNode.instance, null); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConnectionConfiguration.java ================================================ /* * 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.buildpack.platform.docker.configuration; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** * Configuration for how to connect to Docker. * * @author Phillip Webb * @since 3.5.0 */ public sealed interface DockerConnectionConfiguration { /** * Connect to specific host. * * @param address the host address * @param secure if connection is secure * @param certificatePath a path to the certificate used for secure connections */ record Host(String address, boolean secure, @Nullable String certificatePath) implements DockerConnectionConfiguration { public Host(String address) { this(address, false, null); } public Host { Assert.hasLength(address, "'address' must not be empty"); } } /** * Connect using a specific context reference. * * @param context a reference to the Docker context */ record Context(String context) implements DockerConnectionConfiguration { public Context { Assert.hasLength(context, "'context' must not be empty"); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java ================================================ /* * 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.buildpack.platform.docker.configuration; import org.jspecify.annotations.Nullable; /** * Docker host connection options. * * @author Scott Frederick * @since 2.4.0 */ public class DockerHost { private final @Nullable String address; private final boolean secure; private final @Nullable String certificatePath; public DockerHost(@Nullable String address) { this(address, false, null); } public DockerHost(@Nullable String address, boolean secure, @Nullable String certificatePath) { this.address = address; this.secure = secure; this.certificatePath = certificatePath; } public @Nullable String getAddress() { return this.address; } public boolean isSecure() { return this.secure; } public @Nullable String getCertificatePath() { return this.certificatePath; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryAuthentication.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.util.function.BiConsumer; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.util.Assert; /** * Docker registry authentication configuration. * * @author Scott Frederick * @since 2.4.0 */ @FunctionalInterface public interface DockerRegistryAuthentication { /** * An empty {@link #user(String, String, String, String)} authentication. * @since 3.5.0 */ DockerRegistryAuthentication EMPTY_USER = DockerRegistryAuthentication.user("", "", "", ""); /** * Returns the auth header that should be used for docker authentication for the given * image reference. * @param imageReference the image reference or {@code null} * @return the auth header * @since 3.5.0 */ default @Nullable String getAuthHeader(@Nullable ImageReference imageReference) { return getAuthHeader(); } /** * Returns the auth header that should be used for docker authentication. * @return the auth header */ @Nullable String getAuthHeader(); /** * Factory method to that returns a new {@link DockerRegistryAuthentication} instance * that uses a header generated by base64 encoding a JSON payload created from the * given parameters. * @param identityToken the identity token JSON field * @return a new {@link DockerRegistryAuthentication} instance * @since 3.5.0 */ static DockerRegistryAuthentication token(String identityToken) { return new DockerRegistryTokenAuthentication(identityToken); } /** * Factory method to that returns a new {@link DockerRegistryAuthentication} instance * that uses a header generated by base64 encoding a JSON payload created from the * given parameters. * @param username the username JSON field * @param password the password JSON field * @param serverAddress the server address JSON field * @param email the email JSON field * @return a new {@link DockerRegistryAuthentication} instance * @since 3.5.0 */ static DockerRegistryAuthentication user(String username, String password, @Nullable String serverAddress, @Nullable String email) { return new DockerRegistryUserAuthentication(username, password, serverAddress, email); } /** * Factory method that returns a new {@link DockerRegistryAuthentication} instance * that uses the standard docker JSON config (including support for credential * helpers) to generate auth headers. * @param fallback the fallback authentication to use if no suitable config is found, * may be {@code null} * @return a new {@link DockerRegistryAuthentication} instance * @since 3.5.0 * @see #configuration(DockerRegistryAuthentication, BiConsumer) */ static DockerRegistryAuthentication configuration(@Nullable DockerRegistryAuthentication fallback) { return configuration(fallback, (message, ex) -> System.out.println(message)); } /** * Factory method that returns a new {@link DockerRegistryAuthentication} instance * that uses the standard docker JSON config (including support for credential * helpers) to generate auth headers. * @param fallback the fallback authentication to use if no suitable config is found, * may be {@code null} * @param credentialHelperExceptionHandler callback that should handle credential * helper exceptions, never {@code null} * @return a new {@link DockerRegistryAuthentication} instance * @since 3.5.0 * @see #configuration(DockerRegistryAuthentication, BiConsumer) */ static DockerRegistryAuthentication configuration(@Nullable DockerRegistryAuthentication fallback, BiConsumer credentialHelperExceptionHandler) { Assert.notNull(credentialHelperExceptionHandler, () -> "'credentialHelperExceptionHandler' must not be null"); return new DockerRegistryConfigAuthentication(fallback, credentialHelperExceptionHandler); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigAuthentication.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.Auth; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerConfig; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.system.Environment; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * {@link DockerRegistryAuthentication} for * {@link DockerRegistryAuthentication#configuration(DockerRegistryAuthentication, BiConsumer)}. * * @author Dmytro Nosan * @author Phillip Webb */ class DockerRegistryConfigAuthentication implements DockerRegistryAuthentication { private static final String DEFAULT_DOMAIN = "docker.io"; private static final String INDEX_URL = "https://index.docker.io/v1/"; static Map credentialFromHelperCache = new ConcurrentHashMap<>(); private final @Nullable DockerRegistryAuthentication fallback; private final BiConsumer credentialHelperExceptionHandler; private final Function credentialHelperFactory; private final DockerConfig dockerConfig; DockerRegistryConfigAuthentication(@Nullable DockerRegistryAuthentication fallback, BiConsumer credentialHelperExceptionHandler) { this(fallback, credentialHelperExceptionHandler, Environment.SYSTEM, (helper) -> new CredentialHelper("docker-credential-" + helper)); } DockerRegistryConfigAuthentication(@Nullable DockerRegistryAuthentication fallback, BiConsumer credentialHelperExceptionHandler, Environment environment, Function credentialHelperFactory) { this.fallback = fallback; this.credentialHelperExceptionHandler = credentialHelperExceptionHandler; this.dockerConfig = DockerConfigurationMetadata.from(environment).getConfiguration(); this.credentialHelperFactory = credentialHelperFactory; } @Override public @Nullable String getAuthHeader() { return getAuthHeader(null); } @Override public @Nullable String getAuthHeader(@Nullable ImageReference imageReference) { String serverUrl = getServerUrl(imageReference); DockerRegistryAuthentication authentication = getAuthentication(serverUrl); return (authentication != null) ? authentication.getAuthHeader(imageReference) : null; } private @Nullable String getServerUrl(@Nullable ImageReference imageReference) { String domain = (imageReference != null) ? imageReference.getDomain() : null; return (!DEFAULT_DOMAIN.equals(domain)) ? domain : INDEX_URL; } private @Nullable DockerRegistryAuthentication getAuthentication(@Nullable String serverUrl) { Credential credentialsFromHelper = getCredentialsFromHelper(serverUrl); Map.Entry authConfigEntry = getAuthConfigEntry(serverUrl); Auth authConfig = (authConfigEntry != null) ? authConfigEntry.getValue() : null; if (credentialsFromHelper != null) { return getAuthentication(credentialsFromHelper, authConfig, serverUrl); } if (authConfig != null) { Assert.state(authConfigEntry != null, "'authConfigEntry' must not be null"); String username = authConfig.getUsername(); String password = authConfig.getPassword(); Assert.state(username != null, "'username' must not be null"); Assert.state(password != null, "'password' must not be null"); return DockerRegistryAuthentication.user(username, password, authConfigEntry.getKey(), authConfig.getEmail()); } return this.fallback; } private DockerRegistryAuthentication getAuthentication(Credential credentialsFromHelper, @Nullable Auth authConfig, @Nullable String serverUrl) { if (credentialsFromHelper.isIdentityToken()) { return DockerRegistryAuthentication.token(credentialsFromHelper.getSecret()); } String username = credentialsFromHelper.getUsername(); String password = credentialsFromHelper.getSecret(); String serverAddress = (StringUtils.hasLength(credentialsFromHelper.getServerUrl())) ? credentialsFromHelper.getServerUrl() : serverUrl; String email = (authConfig != null) ? authConfig.getEmail() : null; return DockerRegistryAuthentication.user(username, password, serverAddress, email); } private @Nullable Credential getCredentialsFromHelper(@Nullable String serverUrl) { return StringUtils.hasLength(serverUrl) ? credentialFromHelperCache.computeIfAbsent(serverUrl, this::computeCredentialsFromHelper) : null; } private @Nullable Credential computeCredentialsFromHelper(String serverUrl) { CredentialHelper credentialHelper = getCredentialHelper(serverUrl); if (credentialHelper != null) { try { return credentialHelper.get(serverUrl); } catch (Exception ex) { String message = "Error retrieving credentials for '%s' due to: %s".formatted(serverUrl, ex.getMessage()); this.credentialHelperExceptionHandler.accept(message, ex); } } return null; } private @Nullable CredentialHelper getCredentialHelper(String serverUrl) { String name = this.dockerConfig.getCredHelpers().getOrDefault(serverUrl, this.dockerConfig.getCredsStore()); return (StringUtils.hasLength(name)) ? this.credentialHelperFactory.apply(name) : null; } private Map.@Nullable Entry getAuthConfigEntry(@Nullable String serverUrl) { if (serverUrl == null) { return null; } for (Map.Entry candidate : this.dockerConfig.getAuths().entrySet()) { if (candidate.getKey().equals(serverUrl) || candidate.getKey().endsWith("://" + serverUrl)) { return candidate; } } return null; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryTokenAuthentication.java ================================================ /* * 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.buildpack.platform.docker.configuration; import com.fasterxml.jackson.annotation.JsonProperty; /** * {@link DockerRegistryAuthentication} for * {@link DockerRegistryAuthentication#user(String, String, String, String)}. * * @author Scott Frederick */ class DockerRegistryTokenAuthentication extends JsonEncodedDockerRegistryAuthentication { @JsonProperty("identitytoken") private final String token; DockerRegistryTokenAuthentication(String token) { this.token = token; createAuthHeader(); } String getToken() { return this.token; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryUserAuthentication.java ================================================ /* * 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.buildpack.platform.docker.configuration; import com.fasterxml.jackson.annotation.JsonProperty; import org.jspecify.annotations.Nullable; /** * {@link DockerRegistryAuthentication} for * {@link DockerRegistryAuthentication#token(String)}. * * @author Scott Frederick */ class DockerRegistryUserAuthentication extends JsonEncodedDockerRegistryAuthentication { @JsonProperty private final String username; @JsonProperty private final String password; @JsonProperty("serveraddress") private final @Nullable String url; @JsonProperty private final @Nullable String email; DockerRegistryUserAuthentication(String username, String password, @Nullable String url, @Nullable String email) { this.username = username; this.password = password; this.url = url; this.email = email; createAuthHeader(); } String getUsername() { return this.username; } String getPassword() { return this.password; } @Nullable String getUrl() { return this.url; } @Nullable String getEmail() { return this.email; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/JsonEncodedDockerRegistryAuthentication.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.util.Base64; import com.fasterxml.jackson.annotation.JsonIgnore; import org.jspecify.annotations.Nullable; import tools.jackson.core.JacksonException; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; /** * {@link DockerRegistryAuthentication} that uses a Base64 encoded auth header value based * on the JSON created from the instance. * * @author Scott Frederick */ class JsonEncodedDockerRegistryAuthentication implements DockerRegistryAuthentication { @JsonIgnore private @Nullable String authHeader; @Override public @Nullable String getAuthHeader() { return this.authHeader; } protected void createAuthHeader() { try { this.authHeader = Base64.getUrlEncoder().encodeToString(SharedJsonMapper.get().writeValueAsBytes(this)); } catch (JacksonException ex) { throw new IllegalStateException("Error creating Docker registry authentication header", ex); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.nio.file.Files; import java.nio.file.Paths; import com.sun.jna.Platform; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerContext; import org.springframework.boot.buildpack.platform.system.Environment; /** * Resolves a {@link DockerHost} from the environment, configuration, or using defaults. * * @author Scott Frederick * @since 2.7.0 */ public class ResolvedDockerHost extends DockerHost { private static final String UNIX_SOCKET_PREFIX = "unix://"; private static final String DOMAIN_SOCKET_PATH = "/var/run/docker.sock"; private static final String WINDOWS_NAMED_PIPE_PATH = "//./pipe/docker_engine"; private static final String DOCKER_HOST = "DOCKER_HOST"; private static final String DOCKER_TLS_VERIFY = "DOCKER_TLS_VERIFY"; private static final String DOCKER_CERT_PATH = "DOCKER_CERT_PATH"; private static final String DOCKER_CONTEXT = "DOCKER_CONTEXT"; ResolvedDockerHost(@Nullable String address) { super(address); } ResolvedDockerHost(@Nullable String address, boolean secure, @Nullable String certificatePath) { super(address, secure, certificatePath); } @Override public String getAddress() { String address = super.getAddress(); if (address == null) { return getDefaultAddress(); } if (address.startsWith(UNIX_SOCKET_PREFIX)) { return address.substring(UNIX_SOCKET_PREFIX.length()); } if (address.startsWith("tcp://")) { while (address.endsWith("/")) { address = address.substring(0, address.length() - 1); } } return address; } public boolean isRemote() { return getAddress().startsWith("http") || getAddress().startsWith("tcp"); } public boolean isLocalFileReference() { try { return Files.exists(Paths.get(getAddress())); } catch (Exception ex) { return false; } } /** * Create a new {@link ResolvedDockerHost} from the given host configuration. * @param connectionConfiguration the host configuration or {@code null} * @return the resolved docker host */ public static ResolvedDockerHost from(@Nullable DockerConnectionConfiguration connectionConfiguration) { return from(Environment.SYSTEM, connectionConfiguration); } static ResolvedDockerHost from(Environment environment, @Nullable DockerConnectionConfiguration connectionConfiguration) { DockerConfigurationMetadata environmentConfiguration = DockerConfigurationMetadata.from(environment); if (environment.get(DOCKER_CONTEXT) != null) { DockerContext context = environmentConfiguration.forContext(environment.get(DOCKER_CONTEXT)); return new ResolvedDockerHost(context.getDockerHost(), context.isTlsVerify(), context.getTlsPath()); } if (connectionConfiguration instanceof DockerConnectionConfiguration.Context contextConfiguration) { DockerContext context = environmentConfiguration.forContext(contextConfiguration.context()); return new ResolvedDockerHost(context.getDockerHost(), context.isTlsVerify(), context.getTlsPath()); } if (environment.get(DOCKER_HOST) != null) { return new ResolvedDockerHost(environment.get(DOCKER_HOST), isTrue(environment.get(DOCKER_TLS_VERIFY)), environment.get(DOCKER_CERT_PATH)); } if (connectionConfiguration instanceof DockerConnectionConfiguration.Host addressConfiguration) { return new ResolvedDockerHost(addressConfiguration.address(), addressConfiguration.secure(), addressConfiguration.certificatePath()); } if (environmentConfiguration.getContext().getDockerHost() != null) { DockerContext context = environmentConfiguration.getContext(); return new ResolvedDockerHost(context.getDockerHost(), context.isTlsVerify(), context.getTlsPath()); } return new ResolvedDockerHost(getDefaultAddress()); } private static String getDefaultAddress() { return Platform.isWindows() ? WINDOWS_NAMED_PIPE_PATH : DOMAIN_SOCKET_PATH; } private static boolean isTrue(@Nullable String value) { try { return (value != null) && (Integer.parseInt(value) == 1); } catch (NumberFormatException ex) { return false; } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/package-info.java ================================================ /* * 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. */ /** * Docker configuration options. */ @NullMarked package org.springframework.boot.buildpack.platform.docker.configuration; import org.jspecify.annotations.NullMarked; ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/package-info.java ================================================ /* * 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. */ /** * A limited Docker API providing the operations needed by pack. */ @NullMarked package org.springframework.boot.buildpack.platform.docker; import org.jspecify.annotations.NullMarked; ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ssl/KeyStoreFactory.java ================================================ /* * 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.buildpack.platform.docker.ssl; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.List; import org.jspecify.annotations.Nullable; /** * Utility methods for creating Java trust material from key and certificate files. * * @author Scott Frederick */ final class KeyStoreFactory { private static final char[] NO_PASSWORD = {}; private KeyStoreFactory() { } /** * Create a new {@link KeyStore} populated with the certificate stored at the * specified file path and an optional private key. * @param certPath the path to the certificate authority file * @param keyPath the path to the private file * @param alias the alias to use for KeyStore entries * @return the {@code KeyStore} */ static KeyStore create(Path certPath, @Nullable Path keyPath, String alias) { try { KeyStore keyStore = getKeyStore(); String certificateText = Files.readString(certPath); List certificates = PemCertificateParser.parse(certificateText); PrivateKey privateKey = getPrivateKey(keyPath); try { addCertificates(keyStore, certificates.toArray(X509Certificate[]::new), privateKey, alias); } catch (KeyStoreException ex) { throw new IllegalStateException("Error adding certificates to KeyStore: " + ex.getMessage(), ex); } return keyStore; } catch (GeneralSecurityException | IOException ex) { throw new IllegalStateException("Error creating KeyStore: " + ex.getMessage(), ex); } } private static KeyStore getKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); return keyStore; } private static @Nullable PrivateKey getPrivateKey(@Nullable Path path) throws IOException { if (path != null && Files.exists(path)) { String text = Files.readString(path); return PemPrivateKeyParser.parse(text); } return null; } private static void addCertificates(KeyStore keyStore, X509Certificate[] certificates, @Nullable PrivateKey privateKey, String alias) throws KeyStoreException { if (privateKey != null) { keyStore.setKeyEntry(alias, privateKey, NO_PASSWORD, certificates); } else { for (int index = 0; index < certificates.length; index++) { keyStore.setCertificateEntry(alias + "-" + index, certificates[index]); } } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ssl/PemCertificateParser.java ================================================ /* * 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.buildpack.platform.docker.ssl; import java.io.ByteArrayInputStream; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; import org.springframework.lang.Contract; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** * Parser for X.509 certificates in PEM format. * * @author Scott Frederick * @author Phillip Webb */ final class PemCertificateParser { private static final String HEADER = "-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+"; private static final String BASE64_TEXT = "([a-z0-9+/=\\r\\n]+)"; private static final String FOOTER = "-+END\\s+.*CERTIFICATE[^-]*-+"; private static final Pattern PATTERN = Pattern.compile(HEADER + BASE64_TEXT + FOOTER, Pattern.CASE_INSENSITIVE); private PemCertificateParser() { } /** * Parse certificates from the specified string. * @param text the text to parse * @return the parsed certificates */ @Contract("!null -> !null") static @Nullable List parse(@Nullable String text) { if (text == null) { return null; } CertificateFactory factory = getCertificateFactory(); List certs = new ArrayList<>(); readCertificates(text, factory, certs::add); Assert.state(!CollectionUtils.isEmpty(certs), "Missing certificates or unrecognized format"); return List.copyOf(certs); } private static CertificateFactory getCertificateFactory() { try { return CertificateFactory.getInstance("X.509"); } catch (CertificateException ex) { throw new IllegalStateException("Unable to get X.509 certificate factory", ex); } } private static void readCertificates(String text, CertificateFactory factory, Consumer consumer) { try { Matcher matcher = PATTERN.matcher(text); while (matcher.find()) { String encodedText = matcher.group(1); byte[] decodedBytes = decodeBase64(encodedText); ByteArrayInputStream inputStream = new ByteArrayInputStream(decodedBytes); while (inputStream.available() > 0) { consumer.accept((X509Certificate) factory.generateCertificate(inputStream)); } } } catch (CertificateException ex) { throw new IllegalStateException("Error reading certificate: " + ex.getMessage(), ex); } } private static byte[] decodeBase64(String content) { byte[] bytes = content.replace("\r", "").replace("\n", "").getBytes(); return Base64.getDecoder().decode(bytes); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ssl/PemPrivateKeyParser.java ================================================ /* * 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.buildpack.platform.docker.ssl; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.HexFormat; import java.util.List; import java.util.Map; import java.util.function.BiFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.crypto.Cipher; import javax.crypto.EncryptedPrivateKeyInfo; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.ssl.PemPrivateKeyParser.DerElement.TagType; import org.springframework.boot.buildpack.platform.docker.ssl.PemPrivateKeyParser.DerElement.ValueType; import org.springframework.util.Assert; /** * Parser for PKCS private key files in PEM format. * * @author Scott Frederick * @author Phillip Webb * @author Moritz Halbritter */ final class PemPrivateKeyParser { private static final String PKCS1_RSA_HEADER = "-+BEGIN\\s+RSA\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+"; private static final String PKCS1_RSA_FOOTER = "-+END\\s+RSA\\s+PRIVATE\\s+KEY[^-]*-+"; private static final String PKCS8_HEADER = "-+BEGIN\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+"; private static final String PKCS8_FOOTER = "-+END\\s+PRIVATE\\s+KEY[^-]*-+"; private static final String PKCS8_ENCRYPTED_HEADER = "-+BEGIN\\s+ENCRYPTED\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+"; private static final String PKCS8_ENCRYPTED_FOOTER = "-+END\\s+ENCRYPTED\\s+PRIVATE\\s+KEY[^-]*-+"; private static final String SEC1_EC_HEADER = "-+BEGIN\\s+EC\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+"; private static final String SEC1_EC_FOOTER = "-+END\\s+EC\\s+PRIVATE\\s+KEY[^-]*-+"; private static final String BASE64_TEXT = "([a-z0-9+/=\\r\\n]+)"; public static final int BASE64_TEXT_GROUP = 1; private static final EncodedOid RSA_ALGORITHM = EncodedOid.OID_1_2_840_113549_1_1_1; private static final EncodedOid ELLIPTIC_CURVE_ALGORITHM = EncodedOid.OID_1_2_840_10045_2_1; private static final EncodedOid ELLIPTIC_CURVE_384_BIT = EncodedOid.OID_1_3_132_0_34; private static final Map ALGORITHMS; static { Map algorithms = new HashMap<>(); algorithms.put(EncodedOid.OID_1_2_840_113549_1_1_1, "RSA"); algorithms.put(EncodedOid.OID_1_2_840_113549_1_1_10, "RSA"); algorithms.put(EncodedOid.OID_1_2_840_10040_4_1, "DSA"); algorithms.put(EncodedOid.OID_1_3_101_110, "XDH"); algorithms.put(EncodedOid.OID_1_3_101_111, "XDH"); algorithms.put(EncodedOid.OID_1_3_101_112, "EdDSA"); algorithms.put(EncodedOid.OID_1_3_101_113, "EdDSA"); algorithms.put(EncodedOid.OID_1_2_840_10045_2_1, "EC"); ALGORITHMS = Collections.unmodifiableMap(algorithms); } private static final List PEM_PARSERS; static { List parsers = new ArrayList<>(); parsers.add(new PemParser(PKCS1_RSA_HEADER, PKCS1_RSA_FOOTER, PemPrivateKeyParser::createKeySpecForPkcs1Rsa, "RSA")); parsers.add(new PemParser(SEC1_EC_HEADER, SEC1_EC_FOOTER, PemPrivateKeyParser::createKeySpecForSec1Ec, "EC")); parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, PemPrivateKeyParser::createKeySpecForPkcs8, "RSA", "RSASSA-PSS", "EC", "DSA", "EdDSA", "XDH")); parsers.add(new PemParser(PKCS8_ENCRYPTED_HEADER, PKCS8_ENCRYPTED_FOOTER, PemPrivateKeyParser::createKeySpecForPkcs8Encrypted, "RSA", "RSASSA-PSS", "EC", "DSA", "EdDSA", "XDH")); PEM_PARSERS = Collections.unmodifiableList(parsers); } private PemPrivateKeyParser() { } private static PKCS8EncodedKeySpec createKeySpecForPkcs1Rsa(byte[] bytes, @Nullable String password) { return createKeySpecForAlgorithm(bytes, RSA_ALGORITHM, null); } private static PKCS8EncodedKeySpec createKeySpecForSec1Ec(byte[] bytes, @Nullable String password) { DerElement ecPrivateKey = DerElement.of(bytes); Assert.state(ecPrivateKey != null, "Unable to find private key"); Assert.state(ecPrivateKey.isType(ValueType.ENCODED, TagType.SEQUENCE), "Key spec should be an ASN.1 encoded sequence"); DerElement version = DerElement.of(ecPrivateKey.getContents()); Assert.state(version != null && version.isType(ValueType.PRIMITIVE, TagType.INTEGER), "Key spec should start with version"); Assert.state(version.getContents().remaining() == 1 && version.getContents().get() == 1, "Key spec version must be 1"); DerElement privateKey = DerElement.of(ecPrivateKey.getContents()); Assert.state(privateKey != null && privateKey.isType(ValueType.PRIMITIVE, TagType.OCTET_STRING), "Key spec should contain private key"); DerElement parameters = DerElement.of(ecPrivateKey.getContents()); return createKeySpecForAlgorithm(bytes, ELLIPTIC_CURVE_ALGORITHM, getEcParameters(parameters)); } private static EncodedOid getEcParameters(@Nullable DerElement parameters) { if (parameters == null) { return ELLIPTIC_CURVE_384_BIT; } Assert.state(parameters.isType(ValueType.ENCODED), "Key spec should contain encoded parameters"); DerElement contents = DerElement.of(parameters.getContents()); Assert.state(contents != null && contents.isType(ValueType.PRIMITIVE, TagType.OBJECT_IDENTIFIER), "Key spec parameters should contain object identifier"); return EncodedOid.of(contents); } private static PKCS8EncodedKeySpec createKeySpecForAlgorithm(byte[] bytes, EncodedOid algorithm, @Nullable EncodedOid parameters) { try { DerEncoder encoder = new DerEncoder(); encoder.integer(0x00); // Version 0 DerEncoder algorithmIdentifier = new DerEncoder(); algorithmIdentifier.objectIdentifier(algorithm); algorithmIdentifier.objectIdentifier(parameters); encoder.sequence(algorithmIdentifier.toByteArray()); encoder.octetString(bytes); return new PKCS8EncodedKeySpec(encoder.toSequence()); } catch (IOException ex) { throw new IllegalStateException(ex); } } private static PKCS8EncodedKeySpec createKeySpecForPkcs8(byte[] bytes, @Nullable String password) { DerElement ecPrivateKey = DerElement.of(bytes); Assert.state(ecPrivateKey != null, "Unable to find private key"); Assert.state(ecPrivateKey.isType(ValueType.ENCODED, TagType.SEQUENCE), "Key spec should be an ASN.1 encoded sequence"); DerElement version = DerElement.of(ecPrivateKey.getContents()); Assert.state(version != null && version.isType(ValueType.PRIMITIVE, TagType.INTEGER), "Key spec should start with version"); DerElement sequence = DerElement.of(ecPrivateKey.getContents()); Assert.state(sequence != null && sequence.isType(ValueType.ENCODED, TagType.SEQUENCE), "Key spec should contain private key"); DerElement algorithmId = DerElement.of(sequence.getContents()); Assert.state(algorithmId != null && algorithmId.isType(ValueType.PRIMITIVE, TagType.OBJECT_IDENTIFIER), "Key spec container object identifier"); String algorithmName = ALGORITHMS.get(EncodedOid.of(algorithmId)); return (algorithmName != null) ? new PKCS8EncodedKeySpec(bytes, algorithmName) : new PKCS8EncodedKeySpec(bytes); } private static PKCS8EncodedKeySpec createKeySpecForPkcs8Encrypted(byte[] bytes, @Nullable String password) { return Pkcs8PrivateKeyDecryptor.decrypt(bytes, password); } /** * Parse a private key from the specified string. * @param text the text to parse * @return the parsed private key */ static @Nullable PrivateKey parse(String text) { return parse(text, null); } /** * Parse a private key from the specified string, using the provided password for * decryption if necessary. * @param text the text to parse * @param password the password used to decrypt an encrypted private key * @return the parsed private key */ static @Nullable PrivateKey parse(@Nullable String text, @Nullable String password) { if (text == null) { return null; } try { for (PemParser pemParser : PEM_PARSERS) { PrivateKey privateKey = pemParser.parse(text, password); if (privateKey != null) { return privateKey; } } } catch (Exception ex) { throw new IllegalStateException("Error loading private key file: " + ex.getMessage(), ex); } throw new IllegalStateException("Missing private key or unrecognized format"); } /** * Parser for a specific PEM format. */ private static class PemParser { private final Pattern pattern; private final BiFunction keySpecFactory; private final String[] algorithms; PemParser(String header, String footer, BiFunction keySpecFactory, String... algorithms) { this.pattern = Pattern.compile(header + BASE64_TEXT + footer, Pattern.CASE_INSENSITIVE); this.keySpecFactory = keySpecFactory; this.algorithms = algorithms; } @Nullable PrivateKey parse(String text, @Nullable String password) { Matcher matcher = this.pattern.matcher(text); return (!matcher.find()) ? null : parse(decodeBase64(matcher.group(BASE64_TEXT_GROUP)), password); } private static byte[] decodeBase64(String content) { byte[] contentBytes = content.replace("\r", "").replace("\n", "").getBytes(); return Base64.getDecoder().decode(contentBytes); } private @Nullable PrivateKey parse(byte[] bytes, @Nullable String password) { PKCS8EncodedKeySpec keySpec = this.keySpecFactory.apply(bytes, password); if (keySpec.getAlgorithm() != null) { try { KeyFactory keyFactory = KeyFactory.getInstance(keySpec.getAlgorithm()); return keyFactory.generatePrivate(keySpec); } catch (InvalidKeySpecException | NoSuchAlgorithmException ex) { // Ignore } } for (String algorithm : this.algorithms) { try { KeyFactory keyFactory = KeyFactory.getInstance(algorithm); return keyFactory.generatePrivate(keySpec); } catch (InvalidKeySpecException | NoSuchAlgorithmException ex) { // Ignore } } return null; } } /** * Simple ASN.1 DER encoder. */ static class DerEncoder { private final ByteArrayOutputStream stream = new ByteArrayOutputStream(); void objectIdentifier(@Nullable EncodedOid encodedOid) throws IOException { int code = (encodedOid != null) ? 0x06 : 0x05; codeLengthBytes(code, (encodedOid != null) ? encodedOid.toByteArray() : null); } void integer(int... encodedInteger) throws IOException { codeLengthBytes(0x02, bytes(encodedInteger)); } void octetString(byte[] bytes) throws IOException { codeLengthBytes(0x04, bytes); } void sequence(byte[] bytes) throws IOException { codeLengthBytes(0x30, bytes); } void codeLengthBytes(int code, byte @Nullable [] bytes) throws IOException { this.stream.write(code); int length = (bytes != null) ? bytes.length : 0; if (length <= 127) { this.stream.write(length & 0xFF); } else { ByteArrayOutputStream lengthStream = new ByteArrayOutputStream(); while (length != 0) { lengthStream.write(length & 0xFF); length = length >> 8; } byte[] lengthBytes = lengthStream.toByteArray(); this.stream.write(0x80 | lengthBytes.length); for (int i = lengthBytes.length - 1; i >= 0; i--) { this.stream.write(lengthBytes[i]); } } if (bytes != null) { this.stream.write(bytes); } } private static byte @Nullable [] bytes(int @Nullable ... elements) { if (elements == null) { return null; } byte[] result = new byte[elements.length]; for (int i = 0; i < elements.length; i++) { result[i] = (byte) elements[i]; } return result; } byte[] toSequence() throws IOException { DerEncoder sequenceEncoder = new DerEncoder(); sequenceEncoder.sequence(toByteArray()); return sequenceEncoder.toByteArray(); } byte[] toByteArray() { return this.stream.toByteArray(); } } /** * An ASN.1 DER encoded element. */ static final class DerElement { private final ValueType valueType; private final long tagType; private final ByteBuffer contents; private DerElement(ByteBuffer bytes) { byte b = bytes.get(); this.valueType = ((b & 0x20) == 0) ? ValueType.PRIMITIVE : ValueType.ENCODED; this.tagType = decodeTagType(b, bytes); int length = decodeLength(bytes); bytes.limit(bytes.position() + length); this.contents = bytes.slice(); bytes.limit(bytes.capacity()); bytes.position(bytes.position() + length); } private long decodeTagType(byte b, ByteBuffer bytes) { long tagType = (b & 0x1F); if (tagType != 0x1F) { return tagType; } tagType = 0; b = bytes.get(); while ((b & 0x80) != 0) { tagType <<= 7; tagType = tagType | (b & 0x7F); b = bytes.get(); } return tagType; } private int decodeLength(ByteBuffer bytes) { byte b = bytes.get(); if ((b & 0x80) == 0) { return b & 0x7F; } int numberOfLengthBytes = (b & 0x7F); Assert.state(numberOfLengthBytes != 0, "Infinite length encoding is not supported"); Assert.state(numberOfLengthBytes != 0x7F, "Reserved length encoding is not supported"); Assert.state(numberOfLengthBytes <= 4, "Length overflow"); int length = 0; for (int i = 0; i < numberOfLengthBytes; i++) { length <<= 8; length |= (bytes.get() & 0xFF); } return length; } boolean isType(ValueType valueType) { return this.valueType == valueType; } boolean isType(ValueType valueType, TagType tagType) { return this.valueType == valueType && this.tagType == tagType.getNumber(); } ByteBuffer getContents() { return this.contents; } static @Nullable DerElement of(byte[] bytes) { return of(ByteBuffer.wrap(bytes)); } static @Nullable DerElement of(ByteBuffer bytes) { return (bytes.remaining() > 0) ? new DerElement(bytes) : null; } enum ValueType { PRIMITIVE, ENCODED } enum TagType { INTEGER(0x02), OCTET_STRING(0x04), OBJECT_IDENTIFIER(0x06), SEQUENCE(0x10); private final int number; TagType(int number) { this.number = number; } int getNumber() { return this.number; } } } /** * Decryptor for PKCS8 encoded private keys. */ static class Pkcs8PrivateKeyDecryptor { public static final String PBES2_ALGORITHM = "PBES2"; static PKCS8EncodedKeySpec decrypt(byte[] bytes, @Nullable String password) { Assert.state(password != null, "Password is required for an encrypted private key"); try { EncryptedPrivateKeyInfo keyInfo = new EncryptedPrivateKeyInfo(bytes); AlgorithmParameters algorithmParameters = keyInfo.getAlgParameters(); String encryptionAlgorithm = getEncryptionAlgorithm(algorithmParameters, keyInfo.getAlgName()); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptionAlgorithm); SecretKey key = keyFactory.generateSecret(new PBEKeySpec(password.toCharArray())); Cipher cipher = Cipher.getInstance(encryptionAlgorithm); cipher.init(Cipher.DECRYPT_MODE, key, algorithmParameters); return keyInfo.getKeySpec(cipher); } catch (IOException | GeneralSecurityException ex) { throw new IllegalArgumentException("Error decrypting private key", ex); } } private static String getEncryptionAlgorithm(@Nullable AlgorithmParameters algParameters, String algName) { if (algParameters != null && PBES2_ALGORITHM.equals(algName)) { return algParameters.toString(); } return algName; } } /** * ANS.1 encoded object identifier. */ static final class EncodedOid { static final EncodedOid OID_1_2_840_10040_4_1 = EncodedOid.of("2a8648ce380401"); static final EncodedOid OID_1_2_840_113549_1_1_1 = EncodedOid.of("2A864886F70D010101"); static final EncodedOid OID_1_2_840_113549_1_1_10 = EncodedOid.of("2a864886f70d01010a"); static final EncodedOid OID_1_3_101_110 = EncodedOid.of("2b656e"); static final EncodedOid OID_1_3_101_111 = EncodedOid.of("2b656f"); static final EncodedOid OID_1_3_101_112 = EncodedOid.of("2b6570"); static final EncodedOid OID_1_3_101_113 = EncodedOid.of("2b6571"); static final EncodedOid OID_1_2_840_10045_2_1 = EncodedOid.of("2a8648ce3d0201"); static final EncodedOid OID_1_3_132_0_34 = EncodedOid.of("2b81040022"); private final byte[] value; private EncodedOid(byte[] value) { this.value = value; } byte[] toByteArray() { return this.value.clone(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } return Arrays.equals(this.value, ((EncodedOid) obj).value); } @Override public int hashCode() { return Arrays.hashCode(this.value); } static EncodedOid of(String hexString) { return of(HexFormat.of().parseHex(hexString)); } static EncodedOid of(DerElement derElement) { return of(derElement.getContents()); } static EncodedOid of(ByteBuffer byteBuffer) { return of(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), byteBuffer.remaining()); } static EncodedOid of(byte[] bytes) { return of(bytes, 0, bytes.length); } static EncodedOid of(byte[] bytes, int off, int len) { byte[] value = new byte[len]; System.arraycopy(bytes, off, value, 0, len); return new EncodedOid(value); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ssl/SslContextFactory.java ================================================ /* * 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.buildpack.platform.docker.ssl; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import org.springframework.util.Assert; /** * Builds an {@link SSLContext} for use with an HTTP connection. * * @author Scott Frederick * @author Phillip Webb * @since 2.3.0 */ public class SslContextFactory { private static final char[] NO_PASSWORD = {}; private static final String KEY_STORE_ALIAS = "spring-boot-docker"; public SslContextFactory() { } /** * Create an {@link SSLContext} from files in the specified directory. The directory * must contain files with the names 'key.pem', 'cert.pem', and 'ca.pem'. * @param directory the path to a directory containing certificate and key files * @return the {@code SSLContext} */ public SSLContext forDirectory(String directory) { try { Path keyPath = Paths.get(directory, "key.pem"); Path certPath = Paths.get(directory, "cert.pem"); Path caPath = Paths.get(directory, "ca.pem"); Path caKeyPath = Paths.get(directory, "ca-key.pem"); verifyCertificateFiles(keyPath, certPath, caPath); KeyManagerFactory keyManagerFactory = getKeyManagerFactory(keyPath, certPath); TrustManagerFactory trustManagerFactory = getTrustManagerFactory(caPath, caKeyPath); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); return sslContext; } catch (RuntimeException ex) { throw ex; } catch (Exception ex) { throw new RuntimeException(ex.getMessage(), ex); } } private KeyManagerFactory getKeyManagerFactory(Path keyPath, Path certPath) throws Exception { KeyStore store = KeyStoreFactory.create(certPath, keyPath, KEY_STORE_ALIAS); KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); factory.init(store, NO_PASSWORD); return factory; } private TrustManagerFactory getTrustManagerFactory(Path caPath, Path caKeyPath) throws NoSuchAlgorithmException, KeyStoreException { KeyStore store = KeyStoreFactory.create(caPath, caKeyPath, KEY_STORE_ALIAS); TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); factory.init(store); return factory; } private static void verifyCertificateFiles(Path... paths) { for (Path path : paths) { Assert.state(Files.exists(path) && Files.isRegularFile(path), "Certificate path must contain the files 'ca.pem', 'cert.pem', and 'key.pem' files"); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ssl/package-info.java ================================================ /* * 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. */ /** * Utilities and classes for managing SSL context and keys. */ @NullMarked package org.springframework.boot.buildpack.platform.docker.ssl; import org.jspecify.annotations.NullMarked; ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/DockerConnectionException.java ================================================ /* * 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.buildpack.platform.docker.transport; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Exception thrown when connection to the Docker daemon fails. * * @author Scott Frederick * @since 2.3.0 */ public class DockerConnectionException extends RuntimeException { private static final String JNA_EXCEPTION_CLASS_NAME = "com.sun.jna.LastErrorException"; public DockerConnectionException(String host, Exception cause) { super(buildMessage(host, cause), cause); } private static String buildMessage(String host, Exception cause) { Assert.notNull(host, "'host' must not be null"); Assert.notNull(cause, "'cause' must not be null"); StringBuilder message = new StringBuilder("Connection to the Docker daemon at '" + host + "' failed"); String causeMessage = getCauseMessage(cause); if (StringUtils.hasText(causeMessage)) { message.append(" with error \"").append(causeMessage).append("\""); } message.append("; ensure the Docker daemon is running and accessible"); return message.toString(); } private static @Nullable String getCauseMessage(Exception cause) { if (cause.getCause() != null && cause.getCause().getClass().getName().equals(JNA_EXCEPTION_CLASS_NAME)) { return cause.getCause().getMessage(); } return cause.getMessage(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/DockerEngineException.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.net.URI; import java.nio.charset.StandardCharsets; import org.apache.hc.core5.http.HttpStatus; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * Exception thrown when a call to the Docker API fails. * * @author Phillip Webb * @author Scott Frederick * @author Siva Sai Udayagiri * @since 2.3.0 */ public class DockerEngineException extends RuntimeException { private final int statusCode; private final @Nullable String reasonPhrase; private final @Nullable Errors errors; private final @Nullable Message responseMessage; public DockerEngineException(String host, URI uri, int statusCode, @Nullable String reasonPhrase, @Nullable Errors errors, @Nullable Message responseMessage, byte @Nullable [] content) { super(buildMessage(host, uri, statusCode, reasonPhrase, errors, responseMessage, content)); this.statusCode = statusCode; this.reasonPhrase = reasonPhrase; this.errors = errors; this.responseMessage = responseMessage; } /** * Return the status code returned by the Docker API. * @return the statusCode the status code */ public int getStatusCode() { return this.statusCode; } /** * Return the reason phrase returned by the Docker API. * @return the reasonPhrase */ public @Nullable String getReasonPhrase() { return this.reasonPhrase; } /** * Return the errors from the body of the Docker API response, or {@code null} if the * errors JSON could not be read. * @return the errors or {@code null} */ public @Nullable Errors getErrors() { return this.errors; } /** * Return the message from the body of the Docker API response, or {@code null} if the * message JSON could not be read. * @return the message or {@code null} */ public @Nullable Message getResponseMessage() { return this.responseMessage; } private static String buildMessage(String host, URI uri, int statusCode, @Nullable String reasonPhrase, @Nullable Errors errors, @Nullable Message responseMessage, byte @Nullable [] content) { Assert.notNull(host, "'host' must not be null"); Assert.notNull(uri, "'uri' must not be null"); StringBuilder message = new StringBuilder( "Docker API call to '" + host + uri + "' failed with status code " + statusCode); if (StringUtils.hasLength(reasonPhrase)) { message.append(" \"").append(reasonPhrase).append("\""); } if (responseMessage != null && StringUtils.hasLength(responseMessage.getMessage())) { message.append(" and message \"").append(responseMessage.getMessage()).append("\""); } else if (statusCode == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED && !ObjectUtils.isEmpty(content)) { String contentString = new String(content, StandardCharsets.UTF_8); message.append(" and content \"").append(contentString.trim()).append("\""); } if (errors != null && !errors.isEmpty()) { message.append(" ").append(errors); } return message.toString(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/Errors.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.jspecify.annotations.Nullable; /** * Errors returned from the Docker API. * * @author Phillip Webb * @since 2.3.0 */ public class Errors implements Iterable { private final List errors; @JsonCreator Errors(@JsonProperty("errors") @Nullable List errors) { this.errors = (errors != null) ? errors : Collections.emptyList(); } @Override public Iterator iterator() { return this.errors.iterator(); } /** * Returns a sequential {@code Stream} of the errors. * @return a stream of the errors */ public Stream stream() { return this.errors.stream(); } /** * Return if there are any contained errors. * @return if the errors are empty */ public boolean isEmpty() { return this.errors.isEmpty(); } @Override public String toString() { return this.errors.toString(); } /** * An individual Docker error. */ public static class Error { private final String code; private final String message; @JsonCreator Error(String code, String message) { this.code = code; this.message = message; } /** * Return the error code. * @return the error code */ public String getCode() { return this.code; } /** * Return the error message. * @return the error message */ public String getMessage() { return this.message; } @Override public String toString() { return this.code + ": " + this.message; } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.classic.methods.HttpDelete; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpHead; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.classic.methods.HttpPut; import org.apache.hc.client5.http.classic.methods.HttpUriRequest; import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; import org.jspecify.annotations.Nullable; import tools.jackson.core.JacksonException; import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Abstract base class for {@link HttpTransport} implementations backed by a * {@link HttpClient}. * * @author Phillip Webb * @author Mike Smithson * @author Scott Frederick * @author Moritz Halbritter */ abstract class HttpClientTransport implements HttpTransport { static final String REGISTRY_AUTH_HEADER = "X-Registry-Auth"; private final HttpClient client; private final HttpHost host; protected HttpClientTransport(HttpClient client, HttpHost host) { Assert.notNull(client, "'client' must not be null"); Assert.notNull(host, "'host' must not be null"); this.client = client; this.host = host; } /** * Perform an HTTP GET operation. * @param uri the destination URI * @return the operation response */ @Override public Response get(URI uri) { return execute(new HttpGet(uri)); } /** * Perform an HTTP POST operation. * @param uri the destination URI * @return the operation response */ @Override public Response post(URI uri) { return execute(new HttpPost(uri)); } /** * Perform an HTTP POST operation. * @param uri the destination URI * @param registryAuth registry authentication credentials * @return the operation response */ @Override public Response post(URI uri, @Nullable String registryAuth) { return execute(new HttpPost(uri), registryAuth); } /** * Perform an HTTP POST operation. * @param uri the destination URI * @param contentType the content type to write * @param writer a content writer * @return the operation response */ @Override public Response post(URI uri, String contentType, IOConsumer writer) { return execute(new HttpPost(uri), contentType, writer); } /** * Perform an HTTP PUT operation. * @param uri the destination URI * @param contentType the content type to write * @param writer a content writer * @return the operation response */ @Override public Response put(URI uri, String contentType, IOConsumer writer) { return execute(new HttpPut(uri), contentType, writer); } /** * Perform an HTTP DELETE operation. * @param uri the destination URI * @return the operation response */ @Override public Response delete(URI uri) { return execute(new HttpDelete(uri)); } /** * Perform an HTTP HEAD operation. * @param uri the destination URI * @return the operation response */ @Override public Response head(URI uri) { return execute(new HttpHead(uri)); } private Response execute(HttpUriRequestBase request, String contentType, IOConsumer writer) { request.setEntity(new WritableHttpEntity(contentType, writer)); return execute(request); } private Response execute(HttpUriRequestBase request, @Nullable String registryAuth) { if (StringUtils.hasText(registryAuth)) { request.setHeader(REGISTRY_AUTH_HEADER, registryAuth); } return execute(request); } private Response execute(HttpUriRequest request) { try { beforeExecute(request); ClassicHttpResponse response = this.client.executeOpen(this.host, request, null); int statusCode = response.getCode(); if (statusCode >= 400 && statusCode <= 500) { byte[] content = readContent(response); response.close(); Errors errors = (statusCode != 500) ? deserializeErrors(content) : null; Message message = deserializeMessage(content); throw new DockerEngineException(this.host.toHostString(), request.getUri(), statusCode, response.getReasonPhrase(), errors, message, content); } return new HttpClientResponse(response); } catch (IOException | URISyntaxException ex) { throw new DockerConnectionException(this.host.toHostString(), ex); } } protected void beforeExecute(HttpRequest request) { } private byte @Nullable [] readContent(ClassicHttpResponse response) throws IOException { HttpEntity entity = response.getEntity(); if (entity == null) { return null; } try (InputStream stream = entity.getContent()) { return (stream != null) ? stream.readAllBytes() : null; } } private @Nullable Errors deserializeErrors(byte @Nullable [] content) { if (content == null) { return null; } try { return SharedJsonMapper.get().readValue(content, Errors.class); } catch (JacksonException ex) { return null; } } private @Nullable Message deserializeMessage(byte @Nullable [] content) { if (content == null) { return null; } try { Message message = SharedJsonMapper.get().readValue(content, Message.class); return (message.getMessage() != null) ? message : null; } catch (JacksonException ex) { return null; } } HttpHost getHost() { return this.host; } /** * {@link HttpEntity} to send {@link Content} content. */ private static class WritableHttpEntity extends AbstractHttpEntity { private final IOConsumer writer; WritableHttpEntity(String contentType, IOConsumer writer) { super(contentType, "UTF-8"); this.writer = writer; } @Override public boolean isRepeatable() { return false; } @Override public long getContentLength() { if (this.getContentType() != null && this.getContentType().equals("application/json")) { return calculateStringContentLength(); } return -1; } @Override public InputStream getContent() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public void writeTo(OutputStream outputStream) throws IOException { this.writer.accept(outputStream); } @Override public boolean isStreaming() { return true; } private int calculateStringContentLength() { try { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); this.writer.accept(bytes); return bytes.toByteArray().length; } catch (IOException ex) { return -1; } } @Override public void close() throws IOException { } } /** * An HTTP operation response. */ private static class HttpClientResponse implements Response { private final ClassicHttpResponse response; HttpClientResponse(ClassicHttpResponse response) { this.response = response; } @Override public InputStream getContent() throws IOException { return this.response.getEntity().getContent(); } @Override public Header getHeader(String name) { return this.response.getFirstHeader(name); } @Override public void close() throws IOException { this.response.close(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import org.apache.hc.core5.http.Header; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.io.IOConsumer; /** * HTTP transport used for docker access. * * @author Phillip Webb * @author Scott Frederick * @since 2.3.0 */ public interface HttpTransport { /** * Perform an HTTP GET operation. * @param uri the destination URI (excluding any host/port) * @return the operation response * @throws IOException on IO error */ Response get(URI uri) throws IOException; /** * Perform an HTTP POST operation. * @param uri the destination URI (excluding any host/port) * @return the operation response * @throws IOException on IO error */ Response post(URI uri) throws IOException; /** * Perform an HTTP POST operation. * @param uri the destination URI (excluding any host/port) * @param registryAuth registry authentication credentials * @return the operation response * @throws IOException on IO error */ Response post(URI uri, @Nullable String registryAuth) throws IOException; /** * Perform an HTTP POST operation. * @param uri the destination URI (excluding any host/port) * @param contentType the content type to write * @param writer a content writer * @return the operation response * @throws IOException on IO error */ Response post(URI uri, String contentType, IOConsumer writer) throws IOException; /** * Perform an HTTP PUT operation. * @param uri the destination URI (excluding any host/port) * @param contentType the content type to write * @param writer a content writer * @return the operation response * @throws IOException on IO error */ Response put(URI uri, String contentType, IOConsumer writer) throws IOException; /** * Perform an HTTP DELETE operation. * @param uri the destination URI (excluding any host/port) * @return the operation response * @throws IOException on IO error */ Response delete(URI uri) throws IOException; /** * Perform an HTTP HEAD operation. * @param uri the destination URI (excluding any host/port) * @return the operation response * @throws IOException on IO error */ Response head(URI uri) throws IOException; /** * Create the most suitable {@link HttpTransport} based on the {@link DockerHost}. * @param connectionConfiguration the Docker host information * @return a {@link HttpTransport} instance */ static HttpTransport create(@Nullable DockerConnectionConfiguration connectionConfiguration) { ResolvedDockerHost host = ResolvedDockerHost.from(connectionConfiguration); HttpTransport remote = RemoteHttpClientTransport.createIfPossible(host); return (remote != null) ? remote : LocalHttpClientTransport.create(host); } /** * An HTTP operation response. */ interface Response extends Closeable { /** * Return the content of the response. * @return the response content * @throws IOException on IO error */ InputStream getContent() throws IOException; default @Nullable Header getHeader(String name) { throw new UnsupportedOperationException(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.io.IOException; import java.net.InetAddress; import java.net.Proxy; import java.net.Socket; import com.sun.jna.Platform; import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager; import org.apache.hc.client5.http.impl.io.DefaultHttpClientConnectionOperator; import org.apache.hc.client5.http.io.DetachedSocketFactory; import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.client5.http.ssl.TlsSocketStrategy; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.TimeValue; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket; import org.springframework.boot.buildpack.platform.socket.UnixDomainSocket; /** * {@link HttpClientTransport} that talks to local Docker. * * @author Phillip Webb * @author Scott Frederick * @author Moritz Halbritter */ final class LocalHttpClientTransport extends HttpClientTransport { private static final String DOCKER_SCHEME = "docker"; private static final int DEFAULT_DOCKER_PORT = 2376; private static final HttpHost LOCAL_DOCKER_HOST = new HttpHost(DOCKER_SCHEME, "localhost", DEFAULT_DOCKER_PORT); private LocalHttpClientTransport(HttpClient client, HttpHost host) { super(client, host); } @Override protected void beforeExecute(HttpRequest request) { request.setHeader("Host", LOCAL_DOCKER_HOST.toHostString()); } static LocalHttpClientTransport create(ResolvedDockerHost dockerHost) { HttpClientBuilder builder = HttpClients.custom() .setConnectionManager(new LocalConnectionManager(dockerHost)) .setRoutePlanner(new LocalRoutePlanner()); HttpHost host = new HttpHost(DOCKER_SCHEME, dockerHost.getAddress()); return new LocalHttpClientTransport(builder.build(), host); } /** * {@link HttpClientConnectionManager} for local Docker. */ private static class LocalConnectionManager extends BasicHttpClientConnectionManager { private static final ConnectionConfig CONNECTION_CONFIG = ConnectionConfig.copy(ConnectionConfig.DEFAULT) .setValidateAfterInactivity(TimeValue.NEG_ONE_MILLISECOND) .build(); private static final Lookup<@Nullable TlsSocketStrategy> NO_TLS_SOCKET = (name) -> null; LocalConnectionManager(ResolvedDockerHost dockerHost) { super(createHttpClientConnectionOperator(dockerHost), null); setConnectionConfig(CONNECTION_CONFIG); } private static DefaultHttpClientConnectionOperator createHttpClientConnectionOperator( ResolvedDockerHost dockerHost) { LocalDetachedSocketFactory detachedSocketFactory = new LocalDetachedSocketFactory(dockerHost); LocalDnsResolver dnsResolver = new LocalDnsResolver(); return new DefaultHttpClientConnectionOperator(detachedSocketFactory, null, dnsResolver, NO_TLS_SOCKET); } } /** * {@link DetachedSocketFactory} for local Docker. */ static class LocalDetachedSocketFactory implements DetachedSocketFactory { private static final String NPIPE_PREFIX = "npipe://"; private final ResolvedDockerHost dockerHost; LocalDetachedSocketFactory(ResolvedDockerHost dockerHost) { this.dockerHost = dockerHost; } @Override public Socket create(Proxy proxy) throws IOException { String address = this.dockerHost.getAddress(); if (address.startsWith(NPIPE_PREFIX)) { return NamedPipeSocket.get(address.substring(NPIPE_PREFIX.length())); } return (!Platform.isWindows()) ? UnixDomainSocket.get(address) : NamedPipeSocket.get(address); } } /** * {@link DnsResolver} that ensures only the loopback address is used. */ private static final class LocalDnsResolver implements DnsResolver { private static final InetAddress LOOPBACK = InetAddress.getLoopbackAddress(); @Override public InetAddress[] resolve(String host) { return new InetAddress[] { LOOPBACK }; } @Override public String resolveCanonicalHostname(String host) { return LOOPBACK.getCanonicalHostName(); } } /** * {@link HttpRoutePlanner} for local Docker. */ private static final class LocalRoutePlanner implements HttpRoutePlanner { @Override public HttpRoute determineRoute(HttpHost target, HttpContext context) { return new HttpRoute(LOCAL_DOCKER_HOST); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/Message.java ================================================ /* * 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.buildpack.platform.docker.transport; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.jspecify.annotations.Nullable; /** * A message returned from the Docker API. * * @author Scott Frederick * @since 2.3.1 */ public class Message { private final @Nullable String message; @JsonCreator Message(@JsonProperty("message") @Nullable String message) { this.message = message; } /** * Return the message contained in the response. * @return the message */ public @Nullable String getMessage() { return this.message; } @Override public String toString() { return (this.message == null) ? "" : this.message; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.net.URISyntaxException; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; import org.apache.hc.client5.http.ssl.TlsSocketStrategy; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.util.Timeout; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.ssl.SslContextFactory; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * {@link HttpClientTransport} that talks to a remote Docker. * * @author Scott Frederick * @author Phillip Webb */ final class RemoteHttpClientTransport extends HttpClientTransport { private static final Timeout SOCKET_TIMEOUT = Timeout.of(30, TimeUnit.MINUTES); private RemoteHttpClientTransport(HttpClient client, HttpHost host) { super(client, host); } static @Nullable RemoteHttpClientTransport createIfPossible(ResolvedDockerHost dockerHost) { return createIfPossible(dockerHost, new SslContextFactory()); } static @Nullable RemoteHttpClientTransport createIfPossible(ResolvedDockerHost dockerHost, SslContextFactory sslContextFactory) { if (!dockerHost.isRemote()) { return null; } try { return create(dockerHost, sslContextFactory, HttpHost.create(dockerHost.getAddress())); } catch (URISyntaxException ex) { return null; } } private static RemoteHttpClientTransport create(DockerHost host, SslContextFactory sslContextFactory, HttpHost tcpHost) { SocketConfig socketConfig = SocketConfig.copy(SocketConfig.DEFAULT).setSoTimeout(SOCKET_TIMEOUT).build(); PoolingHttpClientConnectionManagerBuilder connectionManagerBuilder = PoolingHttpClientConnectionManagerBuilder .create() .setDefaultSocketConfig(socketConfig); if (host.isSecure()) { connectionManagerBuilder.setTlsSocketStrategy(getTlsSocketStrategy(host, sslContextFactory)); } HttpClientBuilder builder = HttpClients.custom(); builder.setConnectionManager(connectionManagerBuilder.build()); String scheme = host.isSecure() ? "https" : "http"; HttpHost httpHost = new HttpHost(scheme, tcpHost.getHostName(), tcpHost.getPort()); return new RemoteHttpClientTransport(builder.build(), httpHost); } private static TlsSocketStrategy getTlsSocketStrategy(DockerHost host, SslContextFactory sslContextFactory) { String directory = host.getCertificatePath(); Assert.state(StringUtils.hasText(directory), "Docker host TLS verification requires trust material location to be specified with certificate path"); SSLContext sslContext = sslContextFactory.forDirectory(directory); return new DefaultClientTlsStrategy(sslContext); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/package-info.java ================================================ /* * 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. */ /** * Docker transport classes providing HTTP operations on a local or remote engine. */ @NullMarked package org.springframework.boot.buildpack.platform.docker.transport; import org.jspecify.annotations.NullMarked; ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Binding.java ================================================ /* * 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.buildpack.platform.docker.type; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; import org.springframework.util.Assert; /** * Volume bindings to apply when creating a container. * * @author Scott Frederick * @author Moritz Halbritter * @since 2.5.0 */ public final class Binding { /** * Sensitive container paths, which lead to problems if used in a binding. */ private static final Set SENSITIVE_CONTAINER_PATHS = Set.of("/cnb", "/layers", "/workspace", "c:\\cnb", "c:\\layers", "c:\\workspace"); private final String value; private Binding(String value) { this.value = value; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Binding binding)) { return false; } return Objects.equals(this.value, binding.value); } @Override public int hashCode() { return Objects.hash(this.value); } @Override public String toString() { return this.value; } /** * Whether the binding uses a sensitive container path. * @return whether the binding uses a sensitive container path * @since 3.4.0 */ public boolean usesSensitiveContainerPath() { return SENSITIVE_CONTAINER_PATHS.contains(getContainerDestinationPath()); } /** * Returns the container destination path. * @return the container destination path */ String getContainerDestinationPath() { List parts = getParts(); Assert.state(parts.size() >= 2, () -> "Expected 2 or more parts, but found %d".formatted(parts.size())); return parts.get(1); } private List getParts() { // Format is ::[] List parts = new ArrayList<>(); StringBuilder buffer = new StringBuilder(); for (int i = 0; i < this.value.length(); i++) { char ch = this.value.charAt(i); char nextChar = (i + 1 < this.value.length()) ? this.value.charAt(i + 1) : '\0'; if (ch == ':' && nextChar != '\\') { parts.add(buffer.toString()); buffer.setLength(0); } else { buffer.append(ch); } } parts.add(buffer.toString()); return parts; } /** * Create a {@link Binding} with the specified value containing a host source, * container destination, and options. * @param value the volume binding value * @return a new {@link Binding} instance */ public static Binding of(String value) { Assert.notNull(value, "'value' must not be null"); return new Binding(value); } /** * Create a {@link Binding} from the specified source and destination. * @param sourceVolume the volume binding host source * @param destination the volume binding container destination * @return a new {@link Binding} instance */ public static Binding from(VolumeName sourceVolume, String destination) { Assert.notNull(sourceVolume, "'sourceVolume' must not be null"); return from(sourceVolume.toString(), destination); } /** * Create a {@link Binding} from the specified source and destination. * @param source the volume binding host source * @param destination the volume binding container destination * @return a new {@link Binding} instance */ public static Binding from(String source, String destination) { Assert.notNull(source, "'source' must not be null"); Assert.notNull(destination, "'destination' must not be null"); return new Binding(source + ":" + destination); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/BlobReference.java ================================================ /* * 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.buildpack.platform.docker.type; import java.lang.invoke.MethodHandles; import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.Assert; /** * A reference to a blob by its digest. * * @author Phillip Webb * @since 3.2.6 */ public class BlobReference extends MappedObject { private final String digest; private final String mediaType; BlobReference(JsonNode node) { super(node, MethodHandles.lookup()); this.digest = extractDigest(); this.mediaType = extractMediaType(); } private String extractMediaType() { String result = valueAt("/mediaType", String.class); Assert.state(result != null, "'result' must not be null"); return result; } private String extractDigest() { String result = valueAt("/digest", String.class); Assert.state(result != null, "'result' must not be null"); return result; } /** * Return the digest of the blob. * @return the blob digest */ public String getDigest() { return this.digest; } /** * Return the media type of the blob. * @return the blob media type */ public String getMediaType() { return this.mediaType; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.ObjectNode; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; /** * Configuration used when creating a new container. * * @author Phillip Webb * @author Scott Frederick * @author Jeroen Meijer * @since 2.3.0 */ public class ContainerConfig { private final String json; ContainerConfig(@Nullable String user, ImageReference image, String command, List args, Map labels, List bindings, Map env, @Nullable String networkMode, List securityOptions) { Assert.notNull(image, "'image' must not be null"); Assert.hasText(command, "'command' must not be empty"); JsonMapper jsonMapper = SharedJsonMapper.get(); ObjectNode node = jsonMapper.createObjectNode(); if (StringUtils.hasText(user)) { node.put("User", user); } node.put("Image", image.toString()); ArrayNode commandNode = node.putArray("Cmd"); commandNode.add(command); args.forEach(commandNode::add); ArrayNode envNode = node.putArray("Env"); env.forEach((name, value) -> envNode.add(name + "=" + value)); ObjectNode labelsNode = node.putObject("Labels"); labels.forEach(labelsNode::put); ObjectNode hostConfigNode = node.putObject("HostConfig"); if (networkMode != null) { hostConfigNode.put("NetworkMode", networkMode); } ArrayNode bindsNode = hostConfigNode.putArray("Binds"); bindings.forEach((binding) -> bindsNode.add(binding.toString())); if (!CollectionUtils.isEmpty(securityOptions)) { ArrayNode securityOptsNode = hostConfigNode.putArray("SecurityOpt"); securityOptions.forEach(securityOptsNode::add); } this.json = jsonMapper.writeValueAsString(node); } /** * Write this container configuration to the specified {@link OutputStream}. * @param outputStream the output stream * @throws IOException on IO error */ public void writeTo(OutputStream outputStream) throws IOException { StreamUtils.copy(this.json, StandardCharsets.UTF_8, outputStream); } @Override public String toString() { return this.json; } /** * Factory method to create a {@link ContainerConfig} with specific settings. * @param imageReference the source image for the container config * @param update an update callback used to customize the config * @return a new {@link ContainerConfig} instance */ public static ContainerConfig of(ImageReference imageReference, Consumer update) { Assert.notNull(imageReference, "'imageReference' must not be null"); Assert.notNull(update, "'update' must not be null"); return new Update(imageReference).run(update); } /** * Update class used to change data when creating a container config. */ public static class Update { private final ImageReference image; private @Nullable String user; private @Nullable String command; private final List args = new ArrayList<>(); private final Map labels = new LinkedHashMap<>(); private final List bindings = new ArrayList<>(); private final Map env = new LinkedHashMap<>(); private @Nullable String networkMode; private final List securityOptions = new ArrayList<>(); Update(ImageReference image) { this.image = image; } private ContainerConfig run(Consumer update) { update.accept(this); Assert.state(this.command != null, "'command' must not be null"); return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.bindings, this.env, this.networkMode, this.securityOptions); } /** * Update the container config with a specific user. * @param user the user to set */ public void withUser(String user) { this.user = user; } /** * Update the container config with a specific command. * @param command the command to set * @param args additional arguments to add * @see #withArgs(String...) */ public void withCommand(String command, String... args) { this.command = command; withArgs(args); } /** * Update the container config with additional args. * @param args the arguments to add */ public void withArgs(String... args) { this.args.addAll(Arrays.asList(args)); } /** * Update the container config with an additional label. * @param name the label name * @param value the label value */ public void withLabel(String name, String value) { this.labels.put(name, value); } /** * Update the container config with an additional binding. * @param binding the binding */ public void withBinding(Binding binding) { this.bindings.add(binding); } /** * Update the container config with an additional environment variable. * @param name the variable name * @param value the variable value */ public void withEnv(String name, String value) { this.env.put(name, value); } /** * Update the container config with the network that the build container will * connect to. * @param networkMode the network */ public void withNetworkMode(@Nullable String networkMode) { this.networkMode = networkMode; } /** * Update the container config with a security option. * @param option the security option */ public void withSecurityOption(String option) { this.securityOptions.add(option); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerContent.java ================================================ /* * 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.buildpack.platform.docker.type; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.util.Assert; /** * Additional content that can be written to a created container. * * @author Phillip Webb * @since 2.3.0 */ public interface ContainerContent { /** * Return the actual content to be added. * @return the content */ TarArchive getArchive(); /** * Return the destination path where the content should be added. * @return the destination path */ String getDestinationPath(); /** * Factory method to create a new {@link ContainerContent} instance written to the * root of the container. * @param archive the archive to add * @return a new {@link ContainerContent} instance */ static ContainerContent of(TarArchive archive) { return of(archive, "/"); } /** * Factory method to create a new {@link ContainerContent} instance. * @param archive the archive to add * @param destinationPath the destination path within the container * @return a new {@link ContainerContent} instance */ static ContainerContent of(TarArchive archive, String destinationPath) { Assert.notNull(archive, "'archive' must not be null"); Assert.hasText(destinationPath, "'destinationPath' must not be empty"); return new ContainerContent() { @Override public TarArchive getArchive() { return archive; } @Override public String getDestinationPath() { return destinationPath; } }; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerReference.java ================================================ /* * 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.buildpack.platform.docker.type; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** * A reference to a Docker container. * * @author Phillip Webb * @since 2.3.0 */ public final class ContainerReference { private final String value; private ContainerReference(String value) { Assert.hasText(value, "'value' must not be empty"); this.value = value; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } ContainerReference other = (ContainerReference) obj; return this.value.equals(other.value); } @Override public int hashCode() { return this.value.hashCode(); } @Override public String toString() { return this.value; } /** * Factory method to create a {@link ContainerReference} with a specific value. * @param value the container reference value * @return a new container reference instance */ public static ContainerReference of(String value) { return new ContainerReference(value); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerStatus.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; import org.jspecify.annotations.Nullable; import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.NullNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.Assert; /** * Status details returned from {@code Docker container wait}. * * @author Scott Frederick * @since 2.3.0 */ public class ContainerStatus extends MappedObject { private final int statusCode; private final @Nullable String waitingErrorMessage; ContainerStatus(int statusCode, @Nullable String waitingErrorMessage) { super(NullNode.getInstance(), MethodHandles.lookup()); this.statusCode = statusCode; this.waitingErrorMessage = waitingErrorMessage; } ContainerStatus(JsonNode node) { super(node, MethodHandles.lookup()); this.statusCode = extractStatusCode(); this.waitingErrorMessage = valueAt("/Error/Message", String.class); } private Integer extractStatusCode() { Integer result = valueAt("/StatusCode", Integer.class); Assert.state(result != null, "'result' must not be null"); return result; } /** * Return the container exit status code. * @return the exit status code */ public int getStatusCode() { return this.statusCode; } /** * Return a message indicating an error waiting for a container to stop. * @return the waiting error message */ public @Nullable String getWaitingErrorMessage() { return this.waitingErrorMessage; } /** * Create a new {@link ContainerStatus} instance from the specified JSON content * stream. * @param content the JSON content stream * @return a new {@link ContainerStatus} instance * @throws IOException on IO error */ public static ContainerStatus of(InputStream content) throws IOException { return of(content, ContainerStatus::new); } /** * Create a new {@link ContainerStatus} instance with the specified values. * @param statusCode the status code * @param errorMessage the error message * @return a new {@link ContainerStatus} instance */ public static ContainerStatus of(int statusCode, @Nullable String errorMessage) { return new ContainerStatus(statusCode, errorMessage); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Image.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import org.jspecify.annotations.Nullable; import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** * Image details as returned from {@code Docker inspect}. * * @author Phillip Webb * @author Scott Frederick * @since 2.3.0 */ public class Image extends MappedObject { private final List digests; private final ImageConfig config; private final List layers; private final @Nullable String os; private final @Nullable String architecture; private final @Nullable String variant; private final @Nullable String created; private final @Nullable Descriptor descriptor; Image(JsonNode node) { super(node, MethodHandles.lookup()); this.digests = childrenAt("/RepoDigests", JsonNode::asString); this.config = new ImageConfig(getNode().at("/Config")); this.layers = extractLayers(valueAt("/RootFS/Layers", String[].class)); this.os = valueAt("/Os", String.class); this.architecture = valueAt("/Architecture", String.class); this.variant = valueAt("/Variant", String.class); this.created = valueAt("/Created", String.class); JsonNode descriptorNode = getNode().path("Descriptor"); this.descriptor = (descriptorNode.isMissingNode() || descriptorNode.isNull()) ? null : new Descriptor(descriptorNode); } private List extractLayers(String @Nullable [] layers) { if (layers == null) { return Collections.emptyList(); } return Arrays.stream(layers).map(LayerId::of).toList(); } /** * Return the digests of the image. * @return the image digests */ public List getDigests() { return this.digests; } /** * Return image config information. * @return the image config */ public ImageConfig getConfig() { return this.config; } /** * Return the layer IDs contained in the image. * @return the layer IDs. */ public List getLayers() { return this.layers; } /** * Return the OS of the image. * @return the image OS */ public String getOs() { return (StringUtils.hasText(this.os)) ? this.os : "linux"; } /** * Return the architecture of the image. * @return the image architecture */ public @Nullable String getArchitecture() { return this.architecture; } /** * Return the variant of the image. * @return the image variant */ public @Nullable String getVariant() { return this.variant; } /** * Return the created date of the image. * @return the image created date */ public @Nullable String getCreated() { return this.created; } /** * Return the descriptor for this image as reported by Docker Engine inspect. * @return the image descriptor or {@code null} */ public @Nullable Descriptor getDescriptor() { return this.descriptor; } /** * Return the primary digest of the image or {@code null}. Checks the * {@code Descriptor.digest} first, falling back to {@code RepoDigest}. * @return the primary digest or {@code null} * @since 3.4.12 */ public @Nullable String getPrimaryDigest() { if (this.descriptor != null && StringUtils.hasText(this.descriptor.getDigest())) { return this.descriptor.getDigest(); } if (!CollectionUtils.isEmpty(this.digests)) { try { String digest = this.digests.get(0); return (digest != null) ? ImageReference.of(digest).getDigest() : null; } catch (RuntimeException ex) { } } return null; } /** * Create a new {@link Image} instance from the specified JSON content. * @param content the JSON content * @return a new {@link Image} instance * @throws IOException on IO error */ public static Image of(InputStream content) throws IOException { return of(content, Image::new); } /** * Descriptor details as reported in the {@code Docker inspect} response. * * @since 3.4.12 */ public final class Descriptor extends MappedObject { private final String digest; Descriptor(JsonNode node) { super(node, MethodHandles.lookup()); this.digest = Objects.requireNonNull(valueAt("/digest", String.class)); } public String getDigest() { return this.digest; } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchive.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.IOException; import java.io.OutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; import tools.jackson.databind.JsonNode; import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.ObjectNode; import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.InspectedContent; import org.springframework.boot.buildpack.platform.io.Layout; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.util.Assert; /** * An image archive that can be loaded into Docker. * * @author Phillip Webb * @author Scott Frederick * @since 2.3.0 * @see #from(Image, IOConsumer) * @see Docker Image * Specification */ public class ImageArchive implements TarArchive { private static final Instant WINDOWS_EPOCH_PLUS_SECOND = OffsetDateTime.of(1980, 1, 1, 0, 0, 1, 0, ZoneOffset.UTC) .toInstant(); private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_ZONED_DATE_TIME .withZone(ZoneOffset.UTC); private static final String EMPTY_LAYER_NAME_PREFIX = "blank_"; private static final IOConsumer NO_UPDATES = (update) -> { }; private final JsonMapper jsonMapper; private final ImageConfig imageConfig; private final Instant createDate; private final @Nullable ImageReference tag; private final String os; private final @Nullable String architecture; private final @Nullable String variant; private final List existingLayers; private final List newLayers; ImageArchive(JsonMapper jsonMapper, ImageConfig imageConfig, Instant createDate, @Nullable ImageReference tag, String os, @Nullable String architecture, @Nullable String variant, List existingLayers, List newLayers) { this.jsonMapper = jsonMapper; this.imageConfig = imageConfig; this.createDate = createDate; this.tag = tag; this.os = os; this.architecture = architecture; this.variant = variant; this.existingLayers = existingLayers; this.newLayers = newLayers; } /** * Return the image config for the archive. * @return the image config */ public ImageConfig getImageConfig() { return this.imageConfig; } /** * Return the create date of the archive. * @return the create date */ public Instant getCreateDate() { return this.createDate; } /** * Return the tag of the archive. * @return the tag */ public @Nullable ImageReference getTag() { return this.tag; } @Override public void writeTo(OutputStream outputStream) throws IOException { TarArchive.of(this::write).writeTo(outputStream); } private void write(Layout writer) throws IOException { List writtenLayers = writeLayers(writer); String config = writeConfig(writer, writtenLayers); writeManifest(writer, config, writtenLayers); } private List writeLayers(Layout writer) throws IOException { for (int i = 0; i < this.existingLayers.size(); i++) { writeEmptyLayer(writer, EMPTY_LAYER_NAME_PREFIX + i); } List writtenLayers = new ArrayList<>(); for (Layer layer : this.newLayers) { writtenLayers.add(writeLayer(writer, layer)); } return Collections.unmodifiableList(writtenLayers); } private void writeEmptyLayer(Layout writer, String name) throws IOException { writer.file(name, Owner.ROOT, Content.of("")); } private LayerId writeLayer(Layout writer, Layer layer) throws IOException { LayerId id = layer.getId(); writer.file(id.getHash() + ".tar", Owner.ROOT, layer); return id; } private String writeConfig(Layout writer, List writtenLayers) throws IOException { try { ObjectNode config = createConfig(writtenLayers); String json = this.jsonMapper.writeValueAsString(config).replace("\r\n", "\n"); MessageDigest digest = MessageDigest.getInstance("SHA-256"); InspectedContent content = InspectedContent.of(Content.of(json), digest::update); String name = LayerId.ofSha256Digest(digest.digest()).getHash() + ".json"; writer.file(name, Owner.ROOT, content); return name; } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException(ex); } } private ObjectNode createConfig(List writtenLayers) { ObjectNode config = this.jsonMapper.createObjectNode(); config.set("Config", this.imageConfig.getNodeCopy()); config.set("Created", config.stringNode(getCreatedDate())); config.set("History", createHistory(writtenLayers)); config.set("Os", config.stringNode(this.os)); config.set("Architecture", config.stringNode(this.architecture)); config.set("Variant", config.stringNode(this.variant)); config.set("RootFS", createRootFs(writtenLayers)); return config; } private String getCreatedDate() { return DATE_FORMATTER.format(this.createDate); } private JsonNode createHistory(List writtenLayers) { ArrayNode history = this.jsonMapper.createArrayNode(); int size = this.existingLayers.size() + writtenLayers.size(); for (int i = 0; i < size; i++) { history.addObject(); } return history; } private JsonNode createRootFs(List writtenLayers) { ObjectNode rootFs = this.jsonMapper.createObjectNode(); ArrayNode diffIds = rootFs.putArray("diff_ids"); this.existingLayers.stream().map(Object::toString).forEach(diffIds::add); writtenLayers.stream().map(Object::toString).forEach(diffIds::add); return rootFs; } private void writeManifest(Layout writer, String config, List writtenLayers) throws IOException { ArrayNode manifest = createManifest(config, writtenLayers); String manifestJson = this.jsonMapper.writeValueAsString(manifest); writer.file("manifest.json", Owner.ROOT, Content.of(manifestJson)); } private ArrayNode createManifest(String config, List writtenLayers) { ArrayNode manifest = this.jsonMapper.createArrayNode(); ObjectNode entry = manifest.addObject(); entry.set("Config", entry.stringNode(config)); entry.set("Layers", getManifestLayers(writtenLayers)); if (this.tag != null) { entry.set("RepoTags", entry.arrayNode().add(this.tag.toString())); } return manifest; } private ArrayNode getManifestLayers(List writtenLayers) { ArrayNode layers = this.jsonMapper.createArrayNode(); for (int i = 0; i < this.existingLayers.size(); i++) { layers.add(EMPTY_LAYER_NAME_PREFIX + i); } writtenLayers.stream().map((id) -> id.getHash() + ".tar").forEach(layers::add); return layers; } /** * Create a new {@link ImageArchive} based on an existing {@link Image}. * @param image the image that this archive is based on * @return the new image archive. * @throws IOException on IO error */ public static ImageArchive from(Image image) throws IOException { return from(image, NO_UPDATES); } /** * Create a new {@link ImageArchive} based on an existing {@link Image}. * @param image the image that this archive is based on * @param update consumer to apply updates * @return the new image archive. * @throws IOException on IO error */ public static ImageArchive from(Image image, IOConsumer update) throws IOException { return new Update(image).applyTo(update); } /** * Update class used to change data when creating an image archive. */ public static final class Update { private final Image image; private ImageConfig config; private @Nullable Instant createDate; private @Nullable ImageReference tag; private final List newLayers = new ArrayList<>(); private Update(Image image) { this.image = image; this.config = image.getConfig(); } private ImageArchive applyTo(IOConsumer update) throws IOException { update.accept(this); Instant createDate = (this.createDate != null) ? this.createDate : WINDOWS_EPOCH_PLUS_SECOND; return new ImageArchive(SharedJsonMapper.get(), this.config, createDate, this.tag, this.image.getOs(), this.image.getArchitecture(), this.image.getVariant(), this.image.getLayers(), Collections.unmodifiableList(this.newLayers)); } /** * Apply updates to the {@link ImageConfig}. * @param update consumer to apply updates */ public void withUpdatedConfig(Consumer update) { this.config = this.config.copy(update); } /** * Add a new layer to the image archive. * @param layer the layer to add */ public void withNewLayer(Layer layer) { Assert.notNull(layer, "'layer' must not be null"); this.newLayers.add(layer); } /** * Set the create date for the image archive. * @param createDate the create date */ public void withCreateDate(Instant createDate) { Assert.notNull(createDate, "'createDate' must not be null"); this.createDate = createDate; } /** * Set the tag for the image archive. * @param tag the tag */ public void withTag(ImageReference tag) { Assert.notNull(tag, "'tag' must not be null"); this.tag = tag.inTaggedForm(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndex.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.util.List; import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.Assert; /** * Image archive index information as provided by {@code index.json}. * * @author Phillip Webb * @since 3.2.6 * @see OCI Image Index * Specification */ public class ImageArchiveIndex extends MappedObject { private final Integer schemaVersion; private final List manifests; protected ImageArchiveIndex(JsonNode node) { super(node, MethodHandles.lookup()); this.schemaVersion = extractSchemaVersion(); this.manifests = childrenAt("/manifests", BlobReference::new); } private Integer extractSchemaVersion() { Integer result = valueAt("/schemaVersion", Integer.class); Assert.state(result != null, "'result' must not be null"); return result; } public Integer getSchemaVersion() { return this.schemaVersion; } public List getManifests() { return this.manifests; } /** * Create an {@link ImageArchiveIndex} from the provided JSON input stream. * @param content the JSON input stream * @return a new {@link ImageArchiveIndex} instance * @throws IOException on IO error */ public static ImageArchiveIndex of(InputStream content) throws IOException { return of(content, ImageArchiveIndex::new); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifest.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.util.Collections; import java.util.List; import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; /** * Image archive manifest information as provided by {@code manifest.json}. * * @author Scott Frederick * @since 2.7.10 */ public class ImageArchiveManifest extends MappedObject { private final List entries; protected ImageArchiveManifest(JsonNode node) { super(node, MethodHandles.lookup()); this.entries = childrenAt(null, ManifestEntry::new); } /** * Return the entries contained in the manifest. * @return the manifest entries */ public List getEntries() { return this.entries; } /** * Create an {@link ImageArchiveManifest} from the provided JSON input stream. * @param content the JSON input stream * @return a new {@link ImageArchiveManifest} instance * @throws IOException on IO error */ public static ImageArchiveManifest of(InputStream content) throws IOException { return of(content, ImageArchiveManifest::new); } public static class ManifestEntry extends MappedObject { private final List layers; protected ManifestEntry(JsonNode node) { super(node, MethodHandles.lookup()); this.layers = extractLayers(); } /** * Return the collection of layer IDs from a section of the manifest. * @return a collection of layer IDs */ public List getLayers() { return this.layers; } @SuppressWarnings("unchecked") private List extractLayers() { List layers = valueAt("/Layers", List.class); if (layers == null) { return Collections.emptyList(); } return layers; } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageConfig.java ================================================ /* * 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.buildpack.platform.docker.type; import java.lang.invoke.MethodHandles; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.ObjectNode; import org.springframework.boot.buildpack.platform.json.MappedObject; /** * Image configuration information. * * @author Phillip Webb * @author Andy Wilkinson * @since 2.3.0 */ public class ImageConfig extends MappedObject { private final Map labels; private final Map configEnv; ImageConfig(JsonNode node) { super(node, MethodHandles.lookup()); this.labels = extractLabels(); this.configEnv = parseConfigEnv(); } @SuppressWarnings("unchecked") private Map extractLabels() { Map labels = valueAt("/Labels", Map.class); if (labels == null) { return Collections.emptyMap(); } return labels; } private Map parseConfigEnv() { String[] entries = valueAt("/Env", String[].class); if (entries == null) { return Collections.emptyMap(); } Map env = new LinkedHashMap<>(); for (String entry : entries) { int i = entry.indexOf('='); String name = (i != -1) ? entry.substring(0, i) : entry; String value = (i != -1) ? entry.substring(i + 1) : null; env.put(name, value); } return Collections.unmodifiableMap(env); } JsonNode getNodeCopy() { return super.getNode().deepCopy(); } /** * Return the image labels. If the image has no labels, an empty {@code Map} is * returned. * @return the image labels, never {@code null} */ public Map getLabels() { return this.labels; } /** * Return the image environment variables. If the image has no environment variables, * an empty {@code Map} is returned. * @return the env, never {@code null} */ public Map getEnv() { return this.configEnv; } /** * Create an updated copy of this image config. * @param update consumer to apply updates * @return an updated image config */ public ImageConfig copy(Consumer update) { return new Update(this).run(update); } /** * Update class used to change data when creating a copy. */ public static final class Update { private final ObjectNode copy; private Update(ImageConfig source) { this.copy = (ObjectNode) source.getNode().deepCopy(); } private ImageConfig run(Consumer update) { update.accept(this); return new ImageConfig(this.copy); } /** * Update the image config with an additional label. * @param label the label name * @param value the label value */ public void withLabel(String label, String value) { JsonNode labels = this.copy.at("/Labels"); if (labels.isMissingNode()) { labels = this.copy.putObject("Labels"); } ((ObjectNode) labels).put(label, value); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageName.java ================================================ /* * 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.buildpack.platform.docker.type; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** * A Docker image name of the form {@literal "docker.io/library/ubuntu"}. * * @author Phillip Webb * @author Scott Frederick * @since 2.3.0 * @see ImageReference * @see #of(String) */ public class ImageName { private static final String DEFAULT_DOMAIN = "docker.io"; private static final String OFFICIAL_REPOSITORY_NAME = "library"; private static final String LEGACY_DOMAIN = "index.docker.io"; private final String domain; private final String name; private final String string; ImageName(@Nullable String domain, String path) { Assert.hasText(path, "'path' must not be empty"); this.domain = getDomainOrDefault(domain); this.name = getNameWithDefaultPath(this.domain, path); this.string = this.domain + "/" + this.name; } /** * Return the domain for this image name. * @return the domain */ public String getDomain() { return this.domain; } /** * Return the name of this image. * @return the image name */ public String getName() { return this.name; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } ImageName other = (ImageName) obj; boolean result = true; result = result && this.domain.equals(other.domain); result = result && this.name.equals(other.name); return result; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + this.domain.hashCode(); result = prime * result + this.name.hashCode(); return result; } @Override public String toString() { return this.string; } public String toLegacyString() { if (DEFAULT_DOMAIN.equals(this.domain)) { return LEGACY_DOMAIN + "/" + this.name; } return this.string; } private String getDomainOrDefault(@Nullable String domain) { if (domain == null || LEGACY_DOMAIN.equals(domain)) { return DEFAULT_DOMAIN; } return domain; } private String getNameWithDefaultPath(String domain, String name) { if (DEFAULT_DOMAIN.equals(domain) && !name.contains("/")) { return OFFICIAL_REPOSITORY_NAME + "/" + name; } return name; } /** * Create a new {@link ImageName} from the given value. The following value forms can * be used: *

    *
  • {@code name} (maps to {@code docker.io/library/name})
  • *
  • {@code domain/name}
  • *
  • {@code domain:port/name}
  • *
* @param value the value to parse * @return an {@link ImageName} instance */ public static ImageName of(String value) { Assert.hasText(value, "'value' must not be empty"); String domain = parseDomain(value); String path = (domain != null) ? value.substring(domain.length() + 1) : value; Assert.isTrue(Regex.PATH.matcher(path).matches(), () -> "'value' [" + value + "] must be a parsable name in the form '[domainHost:port/][path/]name' (" + "with 'path' and 'name' containing only [a-z0-9][.][_][-])"); return new ImageName(domain, path); } static @Nullable String parseDomain(String value) { int firstSlash = value.indexOf('/'); String candidate = (firstSlash != -1) ? value.substring(0, firstSlash) : null; if (candidate != null && Regex.DOMAIN.matcher(candidate).matches()) { return candidate; } return null; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.File; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** * A reference to a Docker image of the form {@code "imagename[:tag|@digest]"}. * * @author Phillip Webb * @author Scott Frederick * @author Moritz Halbritter * @since 2.3.0 * @see ImageName */ public final class ImageReference { private static final Pattern JAR_VERSION_PATTERN = Pattern.compile("^(.*)(-\\d+)$"); private static final String LATEST = "latest"; private final ImageName name; private final @Nullable String tag; private final @Nullable String digest; private final String string; private ImageReference(ImageName name, @Nullable String tag, @Nullable String digest) { Assert.notNull(name, "'name' must not be null"); this.name = name; this.tag = tag; this.digest = digest; this.string = buildString(name.toString(), tag, digest); } /** * Return the domain for this image name. * @return the domain * @see ImageName#getDomain() */ public String getDomain() { return this.name.getDomain(); } /** * Return the name of this image. * @return the image name * @see ImageName#getName() */ public String getName() { return this.name.getName(); } /** * Return the tag from the reference or {@code null}. * @return the referenced tag */ public @Nullable String getTag() { return this.tag; } /** * Return the digest from the reference or {@code null}. * @return the referenced digest */ public @Nullable String getDigest() { return this.digest; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } ImageReference other = (ImageReference) obj; boolean result = true; result = result && this.name.equals(other.name); result = result && ObjectUtils.nullSafeEquals(this.tag, other.tag); result = result && ObjectUtils.nullSafeEquals(this.digest, other.digest); return result; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + this.name.hashCode(); result = prime * result + ObjectUtils.nullSafeHashCode(this.tag); result = prime * result + ObjectUtils.nullSafeHashCode(this.digest); return result; } @Override public String toString() { return this.string; } public String toLegacyString() { return buildString(this.name.toLegacyString(), this.tag, this.digest); } private String buildString(String name, @Nullable String tag, @Nullable String digest) { StringBuilder string = new StringBuilder(name); if (tag != null) { string.append(":").append(tag); } if (digest != null) { string.append("@").append(digest); } return string.toString(); } /** * Create a new {@link ImageReference} with an updated digest. * @param digest the new digest * @return an updated image reference */ public ImageReference withDigest(String digest) { return new ImageReference(this.name, null, digest); } /** * Return an {@link ImageReference} in the form {@code "imagename:tag"}. If the tag * has not been defined then {@code latest} is used. * @return the image reference in tagged form * @throws IllegalStateException if the image reference contains a digest */ public ImageReference inTaggedForm() { Assert.state(this.digest == null, () -> "Image reference '" + this + "' cannot contain a digest"); return new ImageReference(this.name, (this.tag != null) ? this.tag : LATEST, null); } /** * Return an {@link ImageReference} without the tag. * @return the image reference in tagless form * @since 2.7.12 */ public ImageReference inTaglessForm() { if (this.tag == null) { return this; } return new ImageReference(this.name, null, this.digest); } /** * Return an {@link ImageReference} containing either a tag or a digest. If neither * the digest nor the tag has been defined then tag {@code latest} is used. * @return the image reference in tagged or digest form */ public ImageReference inTaggedOrDigestForm() { if (this.digest != null) { return this; } return inTaggedForm(); } /** * Create a new {@link ImageReference} instance deduced from a source JAR file that * follows common Java naming conventions. * @param jarFile the source jar file * @return an {@link ImageName} for the jar file. */ public static ImageReference forJarFile(File jarFile) { Assert.notNull(jarFile, "'jarFile' must not be null"); String filename = jarFile.getName(); Assert.isTrue(filename.toLowerCase(Locale.ROOT).endsWith(".jar"), () -> "'jarFile' must end with '.jar' [" + jarFile + "]"); filename = filename.substring(0, filename.length() - 4); int firstDot = filename.indexOf('.'); if (firstDot == -1) { return of(filename); } String name = filename.substring(0, firstDot); String version = filename.substring(firstDot + 1); Matcher matcher = JAR_VERSION_PATTERN.matcher(name); if (matcher.matches()) { name = matcher.group(1); version = matcher.group(2).substring(1) + "." + version; } return of(ImageName.of(name), version); } /** * Generate an image name with a random suffix. * @param prefix the name prefix * @return a random image reference */ public static ImageReference random(String prefix) { return ImageReference.random(prefix, 10); } /** * Generate an image name with a random suffix. * @param prefix the name prefix * @param randomLength the number of chars in the random part of the name * @return a random image reference */ public static ImageReference random(String prefix, int randomLength) { return of(RandomString.generate(prefix, randomLength)); } /** * Create a new {@link ImageReference} from the given value. The following value forms * can be used: *
    *
  • {@code name} (maps to {@code docker.io/library/name})
  • *
  • {@code domain/name}
  • *
  • {@code domain:port/name}
  • *
  • {@code domain:port/name:tag}
  • *
  • {@code domain:port/name@digest}
  • *
* @param value the value to parse * @return an {@link ImageName} instance */ public static ImageReference of(String value) { Assert.hasText(value, "'value' must not be null"); String domain = ImageName.parseDomain(value); String path = (domain != null) ? value.substring(domain.length() + 1) : value; String digest = null; int digestSplit = path.indexOf("@"); if (digestSplit != -1) { String remainder = path.substring(digestSplit + 1); Matcher matcher = Regex.DIGEST.matcher(remainder); if (matcher.find()) { digest = remainder.substring(0, matcher.end()); remainder = remainder.substring(matcher.end()); path = path.substring(0, digestSplit) + remainder; } } String tag = null; int tagSplit = path.lastIndexOf(":"); if (tagSplit != -1) { String remainder = path.substring(tagSplit + 1); Matcher matcher = Regex.TAG.matcher(remainder); if (matcher.find()) { tag = remainder.substring(0, matcher.end()); remainder = remainder.substring(matcher.end()); path = path.substring(0, tagSplit) + remainder; } } Assert.isTrue(isLowerCase(path) && matchesPathRegex(path), () -> "'value' [" + value + "] must be an image reference in the form " + "'[domainHost:port/][path/]name[:tag][@digest]' " + "(with 'path' and 'name' containing only [a-z0-9][.][_][-])"); ImageName name = new ImageName(domain, path); return new ImageReference(name, tag, digest); } private static boolean isLowerCase(String path) { return path.toLowerCase(Locale.ENGLISH).equals(path); } private static boolean matchesPathRegex(String path) { return Regex.PATH.matcher(path).matches(); } /** * Create a new {@link ImageReference} from the given {@link ImageName}. * @param name the image name * @return a new image reference */ public static ImageReference of(ImageName name) { return new ImageReference(name, null, null); } /** * Create a new {@link ImageReference} from the given {@link ImageName} and tag. * @param name the image name * @param tag the referenced tag * @return a new image reference */ public static ImageReference of(ImageName name, String tag) { return new ImageReference(name, tag, null); } /** * Create a new {@link ImageReference} from the given {@link ImageName}, tag and * digest. * @param name the image name * @param tag the referenced tag * @param digest the referenced digest * @return a new image reference */ public static ImageReference of(ImageName name, String tag, String digest) { return new ImageReference(name, tag, digest); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Layer.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.IOException; import java.io.OutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.InspectedContent; import org.springframework.boot.buildpack.platform.io.Layout; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.util.Assert; /** * A layer that can be written to an {@link ImageArchive}. * * @author Phillip Webb * @since 2.3.0 */ public class Layer implements Content { private final Content content; private final LayerId id; Layer(TarArchive tarArchive) throws NoSuchAlgorithmException, IOException { MessageDigest digest = MessageDigest.getInstance("SHA-256"); this.content = InspectedContent.of(tarArchive::writeTo, digest::update); this.id = LayerId.ofSha256Digest(digest.digest()); } /** * Return the ID of the layer. * @return the layer ID */ public LayerId getId() { return this.id; } @Override public int size() { return this.content.size(); } @Override public void writeTo(OutputStream outputStream) throws IOException { this.content.writeTo(outputStream); } /** * Factory method to create a new {@link Layer} with a specific {@link Layout}. * @param layout the layer layout * @return a new layer instance * @throws IOException on IO error */ public static Layer of(IOConsumer layout) throws IOException { Assert.notNull(layout, "'layout' must not be null"); return fromTarArchive(TarArchive.of(layout)); } /** * Factory method to create a new {@link Layer} from a {@link TarArchive}. * @param tarArchive the contents of the layer * @return a new layer instance * @throws IOException on error */ public static Layer fromTarArchive(TarArchive tarArchive) throws IOException { Assert.notNull(tarArchive, "'tarArchive' must not be null"); try { return new Layer(tarArchive); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException(ex); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/LayerId.java ================================================ /* * 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.buildpack.platform.docker.type; import java.math.BigInteger; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** * A layer ID as used inside a Docker image of the form {@code algorithm: hash}. * * @author Phillip Webb * @since 2.3.0 */ public final class LayerId { private final String value; private final String algorithm; private final String hash; private LayerId(String value, String algorithm, String hash) { this.value = value; this.algorithm = algorithm; this.hash = hash; } /** * Return the algorithm of layer. * @return the algorithm */ public String getAlgorithm() { return this.algorithm; } /** * Return the hash of the layer. * @return the layer hash */ public String getHash() { return this.hash; } @Override public boolean equals(@Nullable Object obj) { if (obj == this) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } return this.value.equals(((LayerId) obj).value); } @Override public int hashCode() { return this.value.hashCode(); } @Override public String toString() { return this.value; } /** * Create a new {@link LayerId} with the specified value. * @param value the layer ID value of the form {@code algorithm: hash} * @return a new layer ID instance */ public static LayerId of(String value) { Assert.hasText(value, "'value' must not be empty"); int i = value.indexOf(':'); Assert.isTrue(i >= 0, () -> "'value' [%s] must contain a valid layer ID".formatted(value)); return new LayerId(value, value.substring(0, i), value.substring(i + 1)); } /** * Create a new {@link LayerId} from a SHA-256 digest. * @param digest the digest * @return a new layer ID instance */ public static LayerId ofSha256Digest(byte[] digest) { Assert.notNull(digest, "'digest' must not be null"); Assert.isTrue(digest.length == 32, "'digest' must be exactly 32 bytes"); String algorithm = "sha256"; String hash = String.format("%064x", new BigInteger(1, digest)); return new LayerId(algorithm + ":" + hash, algorithm, hash); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Manifest.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.util.List; import org.jspecify.annotations.Nullable; import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.Assert; /** * A manifest as defined in {@code application/vnd.docker.distribution.manifest} or * {@code application/vnd.oci.image.manifest} files. * * @author Phillip Webb * @since 3.2.6 * @see OCI * Image Manifest Specification */ public class Manifest extends MappedObject { private final Integer schemaVersion; private final @Nullable String mediaType; private final List layers; protected Manifest(JsonNode node) { super(node, MethodHandles.lookup()); this.schemaVersion = extractSchemaVersion(); this.mediaType = valueAt("/mediaType", String.class); this.layers = childrenAt("/layers", BlobReference::new); } private Integer extractSchemaVersion() { Integer result = valueAt("/schemaVersion", Integer.class); Assert.state(result != null, "'result' must not be null"); return result; } public Integer getSchemaVersion() { return this.schemaVersion; } public @Nullable String getMediaType() { return this.mediaType; } public List getLayers() { return this.layers; } /** * Create an {@link Manifest} from the provided JSON input stream. * @param content the JSON input stream * @return a new {@link Manifest} instance * @throws IOException on IO error */ public static Manifest of(InputStream content) throws IOException { return of(content, Manifest::new); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ManifestList.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.util.List; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.Assert; /** * A distribution manifest list as defined in * {@code application/vnd.docker.distribution.manifest.list} files. * * @author Phillip Webb * @since 3.2.6 * @see OCI * Image Manifest Specification */ public class ManifestList extends MappedObject { private final Integer schemaVersion; private final @Nullable String mediaType; private final List manifests; protected ManifestList(JsonNode node) { super(node, MethodHandles.lookup()); this.schemaVersion = extractSchemaVersion(); this.mediaType = valueAt("/mediaType", String.class); this.manifests = childrenAt("/manifests", BlobReference::new); } private Integer extractSchemaVersion() { Integer result = valueAt("/schemaVersion", Integer.class); Assert.state(result != null, "'result' must not be null"); return result; } public Integer getSchemaVersion() { return this.schemaVersion; } public @Nullable String getMediaType() { return this.mediaType; } public Stream streamManifests() { return getManifests().stream(); } public List getManifests() { return this.manifests; } /** * Create an {@link ManifestList} from the provided JSON input stream. * @param content the JSON input stream * @return a new {@link ManifestList} instance * @throws IOException on IO error */ public static ManifestList of(InputStream content) throws IOException { return of(content, ManifestList::new); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/RandomString.java ================================================ /* * 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.buildpack.platform.docker.type; import java.util.Random; import java.util.stream.IntStream; import org.springframework.util.Assert; /** * Utility class used to generate random strings. * * @author Phillip Webb */ final class RandomString { private static final Random random = new Random(); private RandomString() { } static String generate(String prefix, int randomLength) { Assert.notNull(prefix, "'prefix' must not be null"); return prefix + generateRandom(randomLength); } static CharSequence generateRandom(int length) { IntStream chars = random.ints('a', 'z' + 1).limit(length); return chars.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Regex.java ================================================ /* * 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.buildpack.platform.docker.type; import java.util.regex.Pattern; /** * Regular Expressions for image names and references based on those found in the Docker * codebase. * * @author Scott Frederick * @author Phillip Webb * @see Docker * grammar reference * @see Docker grammar * implementation * @see How * are Docker image names parsed? */ final class Regex implements CharSequence { static final Pattern DOMAIN; static { Regex component = Regex.oneOf("[a-zA-Z0-9]", "[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]"); Regex dotComponent = Regex.group("[.]", component); Regex colonPort = Regex.of("[:][0-9]+"); Regex dottedDomain = Regex.group(component, dotComponent.oneOrMoreTimes()); Regex dottedDomainAndPort = Regex.group(component, dotComponent.oneOrMoreTimes(), colonPort); Regex nameAndPort = Regex.group(component, colonPort); DOMAIN = Regex.oneOf(dottedDomain, nameAndPort, dottedDomainAndPort, "localhost").compile(); } private static final Regex PATH_COMPONENT; static { Regex segment = Regex.of("[a-z0-9]+"); Regex separator = Regex.group("[._-]{1,2}"); Regex separatedSegment = Regex.group(separator, segment).oneOrMoreTimes(); PATH_COMPONENT = Regex.of(segment, Regex.group(separatedSegment).zeroOrOnce()); } static final Pattern PATH; static { Regex component = PATH_COMPONENT; Regex slashComponent = Regex.group("[/]", component); Regex slashComponents = Regex.group(slashComponent.oneOrMoreTimes()); PATH = Regex.of(component, slashComponents.zeroOrOnce()).compile(); } static final Pattern TAG = Regex.of("^[\\w][\\w.-]{0,127}").compile(); static final Pattern DIGEST = Regex.of("^[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[A-Fa-f0-9]]{32,}") .compile(); private final String value; private Regex(CharSequence value) { this.value = value.toString(); } private Regex oneOrMoreTimes() { return new Regex(this.value + "+"); } private Regex zeroOrOnce() { return new Regex(this.value + "?"); } Pattern compile() { return Pattern.compile("^" + this.value + "$"); } @Override public int length() { return this.value.length(); } @Override public char charAt(int index) { return this.value.charAt(index); } @Override public CharSequence subSequence(int start, int end) { return this.value.subSequence(start, end); } @Override public String toString() { return this.value; } private static Regex of(CharSequence... expressions) { return new Regex(String.join("", expressions)); } private static Regex oneOf(CharSequence... expressions) { return new Regex("(?:" + String.join("|", expressions) + ")"); } private static Regex group(CharSequence... expressions) { return new Regex("(?:" + String.join("", expressions) + ")"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/VolumeName.java ================================================ /* * 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.buildpack.platform.docker.type; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HexFormat; import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** * A Docker volume name. * * @author Phillip Webb * @since 2.3.0 */ public final class VolumeName { private final String value; private VolumeName(String value) { this.value = value; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } return this.value.equals(((VolumeName) obj).value); } @Override public int hashCode() { return this.value.hashCode(); } @Override public String toString() { return this.value; } /** * Factory method to create a new {@link VolumeName} with a random name. * @param prefix the prefix to use with the random name * @return a randomly named volume */ public static VolumeName random(String prefix) { return random(prefix, 10); } /** * Factory method to create a new {@link VolumeName} with a random name. * @param prefix the prefix to use with the random name * @param randomLength the number of chars in the random part of the name * @return a randomly named volume reference */ public static VolumeName random(String prefix, int randomLength) { return of(RandomString.generate(prefix, randomLength)); } /** * Factory method to create a new {@link VolumeName} based on an object. The resulting * name will be based off a SHA-256 digest of the given object's {@code toString()} * method. * @param the source object type * @param source the source object * @param prefix the prefix to use with the volume name * @param suffix the suffix to use with the volume name * @param digestLength the number of chars in the digest part of the name * @return a name based off the image reference */ public static VolumeName basedOn(S source, String prefix, String suffix, int digestLength) { return basedOn(source, Object::toString, prefix, suffix, digestLength); } /** * Factory method to create a new {@link VolumeName} based on an object. The resulting * name will be based off a SHA-256 digest of the given object's name. * @param the source object type * @param source the source object * @param nameExtractor a method to extract the name of the object * @param prefix the prefix to use with the volume name * @param suffix the suffix to use with the volume name * @param digestLength the number of chars in the digest part of the name * @return a name based off the image reference */ public static VolumeName basedOn(S source, Function nameExtractor, String prefix, String suffix, int digestLength) { Assert.notNull(source, "'source' must not be null"); Assert.notNull(nameExtractor, "'nameExtractor' must not be null"); Assert.notNull(prefix, "'prefix' must not be null"); Assert.notNull(suffix, "'suffix' must not be null"); return of(prefix + getDigest(nameExtractor.apply(source), digestLength) + suffix); } private static String getDigest(String name, int length) { try { MessageDigest digest = MessageDigest.getInstance("sha-256"); return asHexString(digest.digest(name.getBytes(StandardCharsets.UTF_8)), length); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException(ex); } } private static String asHexString(byte[] digest, int digestLength) { Assert.isTrue(digestLength <= digest.length, () -> "'digestLength' must be less than or equal to " + digest.length); return HexFormat.of().formatHex(digest, 0, digestLength); } /** * Factory method to create a {@link VolumeName} with a specific value. * @param value the volume reference value * @return a new {@link VolumeName} instance */ public static VolumeName of(String value) { Assert.notNull(value, "'value' must not be null"); return new VolumeName(value); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/package-info.java ================================================ /* * 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. */ /** * Docker types. */ @NullMarked package org.springframework.boot.buildpack.platform.docker.type; import org.jspecify.annotations.NullMarked; ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/Content.java ================================================ /* * 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.buildpack.platform.io; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import org.springframework.util.Assert; import org.springframework.util.FileCopyUtils; /** * Content with a known size that can be written to an {@link OutputStream}. * * @author Phillip Webb * @since 2.3.0 */ public interface Content { /** * The size of the content in bytes. * @return the content size */ int size(); /** * Write the content to the given output stream. * @param outputStream the output stream to write to * @throws IOException on IO error */ void writeTo(OutputStream outputStream) throws IOException; /** * Create a new {@link Content} from the given UTF-8 string. * @param string the string to write * @return a new {@link Content} instance */ static Content of(String string) { Assert.notNull(string, "'string' must not be null"); return of(string.getBytes(StandardCharsets.UTF_8)); } /** * Create a new {@link Content} from the given input stream. * @param bytes the bytes to write * @return a new {@link Content} instance */ static Content of(byte[] bytes) { Assert.notNull(bytes, "'bytes' must not be null"); return of(bytes.length, () -> new ByteArrayInputStream(bytes)); } /** * Create a new {@link Content} from the given file. * @param file the file to write * @return a new {@link Content} instance */ static Content of(File file) { Assert.notNull(file, "'file' must not be null"); return of((int) file.length(), () -> new FileInputStream(file)); } /** * Create a new {@link Content} from the given input stream. The stream will be closed * after it has been written. * @param size the size of the supplied input stream * @param supplier the input stream supplier * @return a new {@link Content} instance */ static Content of(int size, IOSupplier supplier) { Assert.isTrue(size >= 0, "'size' must not be negative"); Assert.notNull(supplier, "'supplier' must not be null"); return new Content() { @Override public int size() { return size; } @Override public void writeTo(OutputStream outputStream) throws IOException { FileCopyUtils.copy(supplier.get(), outputStream); } }; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/DefaultOwner.java ================================================ /* * 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.buildpack.platform.io; /** * Default {@link Owner} implementation. * * @author Phillip Webb * @see Owner#of(long, long) */ class DefaultOwner implements Owner { private final long uid; private final long gid; DefaultOwner(long uid, long gid) { this.uid = uid; this.gid = gid; } @Override public long getUid() { return this.uid; } @Override public long getGid() { return this.gid; } @Override public String toString() { return this.uid + "/" + this.gid; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/FilePermissions.java ================================================ /* * 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.buildpack.platform.io; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFilePermission; import java.util.Collection; import org.springframework.util.Assert; /** * Utilities for dealing with file permissions and attributes. * * @author Scott Frederick * @since 2.5.0 */ public final class FilePermissions { private FilePermissions() { } /** * Return the integer representation of the file permissions for a path, where the * integer value conforms to the * umask octal notation. * @param path the file path * @return the integer representation * @throws IOException if path permissions cannot be read */ public static int umaskForPath(Path path) throws IOException { Assert.notNull(path, "'path' must not be null"); PosixFileAttributeView attributeView = Files.getFileAttributeView(path, PosixFileAttributeView.class); Assert.state(attributeView != null, "Unsupported file type for retrieving Posix attributes"); return posixPermissionsToUmask(attributeView.readAttributes().permissions()); } /** * Return the integer representation of a set of Posix file permissions, where the * integer value conforms to the * umask octal notation. * @param permissions the set of {@code PosixFilePermission}s * @return the integer representation */ public static int posixPermissionsToUmask(Collection permissions) { Assert.notNull(permissions, "'permissions' must not be null"); int owner = permissionToUmask(permissions, PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_READ); int group = permissionToUmask(permissions, PosixFilePermission.GROUP_EXECUTE, PosixFilePermission.GROUP_WRITE, PosixFilePermission.GROUP_READ); int other = permissionToUmask(permissions, PosixFilePermission.OTHERS_EXECUTE, PosixFilePermission.OTHERS_WRITE, PosixFilePermission.OTHERS_READ); return Integer.parseInt("" + owner + group + other, 8); } private static int permissionToUmask(Collection permissions, PosixFilePermission execute, PosixFilePermission write, PosixFilePermission read) { int value = 0; if (permissions.contains(execute)) { value += 1; } if (permissions.contains(write)) { value += 2; } if (permissions.contains(read)) { value += 4; } return value; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/IOBiConsumer.java ================================================ /* * 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.buildpack.platform.io; import java.io.IOException; /** * BiConsumer that can safely throw {@link IOException IO exceptions}. * * @param the first consumed type * @param the second consumed type * @author Phillip Webb * @since 2.3.0 */ @FunctionalInterface public interface IOBiConsumer { /** * Performs this operation on the given argument. * @param t the first instance to consume * @param u the second instance to consumer * @throws IOException on IO error */ void accept(T t, U u) throws IOException; } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/IOConsumer.java ================================================ /* * 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.buildpack.platform.io; import java.io.IOException; /** * Consumer that can safely throw {@link IOException IO exceptions}. * * @param the consumed type * @author Phillip Webb * @since 2.3.0 */ @FunctionalInterface public interface IOConsumer { /** * Performs this operation on the given argument. * @param t the instance to consume * @throws IOException on IO error */ void accept(T t) throws IOException; } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/IOSupplier.java ================================================ /* * 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.buildpack.platform.io; import java.io.IOException; /** * Supplier that can safely throw {@link IOException IO exceptions}. * * @param the supplied type * @author Phillip Webb * @since 2.3.0 */ @FunctionalInterface public interface IOSupplier { /** * Gets the supplied value. * @return the supplied value * @throws IOException on IO error */ T get() throws IOException; } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/InspectedContent.java ================================================ /* * 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.buildpack.platform.io; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.FileCopyUtils; import org.springframework.util.StreamUtils; /** * {@link Content} that is reads and inspects a source of data only once but allows it to * be consumed multiple times. * * @author Phillip Webb * @since 2.3.0 */ public class InspectedContent implements Content { static final int MEMORY_LIMIT = 4 * 1024 + 3; private final int size; private final Object content; InspectedContent(int size, Object content) { this.size = size; this.content = content; } @Override public int size() { return this.size; } @Override public void writeTo(OutputStream outputStream) throws IOException { if (this.content instanceof byte[] bytes) { FileCopyUtils.copy(bytes, outputStream); } else if (this.content instanceof File file) { InputStream inputStream = new FileInputStream(file); FileCopyUtils.copy(inputStream, outputStream); } else { throw new IllegalStateException("Unknown content type"); } } /** * Factory method to create an {@link InspectedContent} instance from a source input * stream. * @param inputStream the content input stream * @param inspectors any inspectors to apply * @return a new inspected content instance * @throws IOException on IO error */ public static InspectedContent of(InputStream inputStream, Inspector... inspectors) throws IOException { Assert.notNull(inputStream, "'inputStream' must not be null"); return of((outputStream) -> FileCopyUtils.copy(inputStream, outputStream), inspectors); } /** * Factory method to create an {@link InspectedContent} instance from source content. * @param content the content * @param inspectors any inspectors to apply * @return a new inspected content instance * @throws IOException on IO error */ public static InspectedContent of(Content content, Inspector... inspectors) throws IOException { Assert.notNull(content, "'content' must not be null"); return of(content::writeTo, inspectors); } /** * Factory method to create an {@link InspectedContent} instance from a source write * method. * @param writer a consumer representing the write method * @param inspectors any inspectors to apply * @return a new inspected content instance * @throws IOException on IO error */ public static InspectedContent of(IOConsumer writer, Inspector... inspectors) throws IOException { Assert.notNull(writer, "'writer' must not be null"); InspectingOutputStream outputStream = new InspectingOutputStream(inspectors); try (outputStream) { writer.accept(outputStream); } return new InspectedContent(outputStream.getSize(), outputStream.getContent()); } /** * Interface that can be used to inspect content as it is initially read. */ public interface Inspector { /** * Update inspected information based on the provided bytes. * @param input the array of bytes. * @param offset the offset to start from in the array of bytes. * @param len the number of bytes to use, starting at {@code offset}. * @throws IOException on IO error */ void update(byte[] input, int offset, int len) throws IOException; } /** * Internal {@link OutputStream} used to capture the content either as bytes, or to a * File if the content is too large. */ private static final class InspectingOutputStream extends OutputStream { private final Inspector[] inspectors; private int size; private OutputStream delegate; private @Nullable File tempFile; private final byte[] singleByteBuffer = new byte[1]; private InspectingOutputStream(Inspector[] inspectors) { this.inspectors = inspectors; this.delegate = new ByteArrayOutputStream(); } @Override public void write(int b) throws IOException { this.singleByteBuffer[0] = (byte) (b & 0xFF); write(this.singleByteBuffer); } @Override public void write(byte[] b, int off, int len) throws IOException { int size = len - off; if (this.tempFile == null && (this.size + size) > MEMORY_LIMIT) { convertToTempFile(); } this.delegate.write(b, off, len); for (Inspector inspector : this.inspectors) { inspector.update(b, off, len); } this.size += size; } private void convertToTempFile() throws IOException { this.tempFile = File.createTempFile("buildpack", ".tmp"); byte[] bytes = ((ByteArrayOutputStream) this.delegate).toByteArray(); this.delegate = new FileOutputStream(this.tempFile); StreamUtils.copy(bytes, this.delegate); } private Object getContent() { return (this.tempFile != null) ? this.tempFile : ((ByteArrayOutputStream) this.delegate).toByteArray(); } private int getSize() { return this.size; } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/Layout.java ================================================ /* * 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.buildpack.platform.io; import java.io.IOException; /** * Interface that can be used to write a file/directory layout. * * @author Phillip Webb * @author Scott Frederick * @since 2.3.0 */ public interface Layout { /** * Add a directory to the content. * @param name the full name of the directory to add * @param owner the owner of the directory * @throws IOException on IO error */ default void directory(String name, Owner owner) throws IOException { directory(name, owner, 0755); } /** * Add a directory to the content. * @param name the full name of the directory to add * @param owner the owner of the directory * @param mode the permissions for the file * @throws IOException on IO error */ void directory(String name, Owner owner, int mode) throws IOException; /** * Write a file to the content. * @param name the full name of the file to add * @param owner the owner of the file * @param content the content to add * @throws IOException on IO error */ default void file(String name, Owner owner, Content content) throws IOException { file(name, owner, 0644, content); } /** * Write a file to the content. * @param name the full name of the file to add * @param owner the owner of the file * @param mode the permissions for the file * @param content the content to add * @throws IOException on IO error */ void file(String name, Owner owner, int mode, Content content) throws IOException; } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/Owner.java ================================================ /* * 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.buildpack.platform.io; /** * A user and group ID that can be used to indicate file ownership. * * @author Phillip Webb * @since 2.3.0 */ public interface Owner { /** * Owner for root ownership. */ Owner ROOT = Owner.of(0, 0); /** * Return the user identifier (UID) of the owner. * @return the user identifier */ long getUid(); /** * Return the group identifier (GID) of the owner. * @return the group identifier */ long getGid(); /** * Factory method to create a new {@link Owner} with specified user/group identifier. * @param uid the user identifier * @param gid the group identifier * @return a new {@link Owner} instance */ static Owner of(long uid, long gid) { return new DefaultOwner(uid, gid); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/TarArchive.java ================================================ /* * 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.buildpack.platform.io; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.zip.GZIPInputStream; import org.springframework.util.StreamUtils; import org.springframework.util.function.ThrowingFunction; /** * A TAR archive that can be written to an output stream. * * @author Phillip Webb * @since 2.3.0 */ @FunctionalInterface public interface TarArchive { /** * {@link Instant} that can be used to normalize TAR files so all entries have the * same modification time. */ Instant NORMALIZED_TIME = OffsetDateTime.of(1980, 1, 1, 0, 0, 1, 0, ZoneOffset.UTC).toInstant(); /** * Write the TAR archive to the given output stream. * @param outputStream the output stream to write to * @throws IOException on IO error */ void writeTo(OutputStream outputStream) throws IOException; /** * Return the compression being used with the tar archive. * @return the used compression * @since 3.2.6 */ default Compression getCompression() { return Compression.NONE; } /** * Factory method to create a new {@link TarArchive} instance with a specific layout. * @param layout the TAR layout * @return a new {@link TarArchive} instance */ static TarArchive of(IOConsumer layout) { return (outputStream) -> { TarLayoutWriter writer = new TarLayoutWriter(outputStream); layout.accept(writer); writer.finish(); }; } /** * Factory method to adapt a ZIP file to {@link TarArchive}. * @param zip the source zip file * @param owner the owner of the entries in the TAR * @return a new {@link TarArchive} instance */ static TarArchive fromZip(File zip, Owner owner) { return new ZipFileTarArchive(zip, owner); } /** * Factory method to adapt a ZIP file to {@link TarArchive}. Assumes that * {@link #writeTo(OutputStream)} will only be called once. * @param inputStream the source input stream * @param compression the compression used * @return a new {@link TarArchive} instance * @since 3.2.6 */ static TarArchive fromInputStream(InputStream inputStream, Compression compression) { return new TarArchive() { @Override public void writeTo(OutputStream outputStream) throws IOException { StreamUtils.copy(compression.uncompress(inputStream), outputStream); } @Override public Compression getCompression() { return compression; } }; } /** * Compression type applied to the archive. * * @since 3.2.6 */ enum Compression { /** * The tar file is not compressed. */ NONE((inputStream) -> inputStream), /** * The tar file is compressed using gzip. */ GZIP(GZIPInputStream::new), /** * The tar file is compressed using zstd. */ ZSTD("zstd compression is not supported"); private final ThrowingFunction uncompressor; Compression(String uncompressError) { this((inputStream) -> { throw new IllegalStateException(uncompressError); }); } Compression(ThrowingFunction wrapper) { this.uncompressor = wrapper; } InputStream uncompress(InputStream inputStream) { return this.uncompressor.apply(inputStream); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/TarLayoutWriter.java ================================================ /* * 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.buildpack.platform.io; import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.archivers.tar.TarConstants; import org.springframework.util.StreamUtils; /** * {@link Layout} for writing TAR archive content directly to an {@link OutputStream}. * * @author Phillip Webb * @author Scott Frederick */ class TarLayoutWriter implements Layout, Closeable { static final long NORMALIZED_MOD_TIME = TarArchive.NORMALIZED_TIME.toEpochMilli(); private final TarArchiveOutputStream outputStream; TarLayoutWriter(OutputStream outputStream) { this.outputStream = new TarArchiveOutputStream(outputStream); this.outputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); } @Override public void directory(String name, Owner owner, int mode) throws IOException { this.outputStream.putArchiveEntry(createDirectoryEntry(name, owner, mode)); this.outputStream.closeArchiveEntry(); } @Override public void file(String name, Owner owner, int mode, Content content) throws IOException { this.outputStream.putArchiveEntry(createFileEntry(name, owner, mode, content.size())); content.writeTo(StreamUtils.nonClosing(this.outputStream)); this.outputStream.closeArchiveEntry(); } private TarArchiveEntry createDirectoryEntry(String name, Owner owner, int mode) { return createEntry(name, owner, TarConstants.LF_DIR, mode, 0); } private TarArchiveEntry createFileEntry(String name, Owner owner, int mode, int size) { return createEntry(name, owner, TarConstants.LF_NORMAL, mode, size); } private TarArchiveEntry createEntry(String name, Owner owner, byte linkFlag, int mode, int size) { TarArchiveEntry entry = new TarArchiveEntry(name, linkFlag, true); entry.setUserId(owner.getUid()); entry.setGroupId(owner.getGid()); entry.setMode(mode); entry.setModTime(NORMALIZED_MOD_TIME); entry.setSize(size); return entry; } void finish() throws IOException { this.outputStream.finish(); } @Override public void close() throws IOException { this.outputStream.close(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/ZipFileTarArchive.java ================================================ /* * 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.buildpack.platform.io; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Enumeration; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.archivers.tar.TarConstants; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; /** * Adapter class to convert a ZIP file to a {@link TarArchive}. * * @author Phillip Webb * @author Scott Frederick * @since 2.3.0 */ public class ZipFileTarArchive implements TarArchive { static final long NORMALIZED_MOD_TIME = TarArchive.NORMALIZED_TIME.toEpochMilli(); private final File zip; private final Owner owner; /** * Creates an archive from the contents of the given {@code zip}. Each entry in the * archive will be owned by the given {@code owner}. * @param zip the zip to use as a source * @param owner the owner of the tar entries */ public ZipFileTarArchive(File zip, Owner owner) { Assert.notNull(zip, "'zip' must not be null"); Assert.notNull(owner, "'owner' must not be null"); assertArchiveHasEntries(zip); this.zip = zip; this.owner = owner; } @Override public void writeTo(OutputStream outputStream) throws IOException { TarArchiveOutputStream tar = new TarArchiveOutputStream(outputStream); tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); try (ZipFile zipFile = ZipFile.builder().setFile(this.zip).get()) { Enumeration entries = zipFile.getEntries(); while (entries.hasMoreElements()) { ZipArchiveEntry zipEntry = entries.nextElement(); copy(zipEntry, zipFile.getInputStream(zipEntry), tar); } } tar.finish(); } private void assertArchiveHasEntries(File file) { try (ZipFile zipFile = ZipFile.builder().setFile(file).get()) { Assert.state(zipFile.getEntries().hasMoreElements(), () -> "Archive file '" + file + "' is not valid"); } catch (IOException ex) { throw new IllegalStateException("File '" + file + "' is not readable", ex); } } private void copy(ZipArchiveEntry zipEntry, InputStream zip, TarArchiveOutputStream tar) throws IOException { TarArchiveEntry tarEntry = convert(zipEntry); tar.putArchiveEntry(tarEntry); if (tarEntry.isFile()) { StreamUtils.copyRange(zip, tar, 0, tarEntry.getSize()); } tar.closeArchiveEntry(); } private TarArchiveEntry convert(ZipArchiveEntry zipEntry) { byte linkFlag = (zipEntry.isDirectory()) ? TarConstants.LF_DIR : TarConstants.LF_NORMAL; TarArchiveEntry tarEntry = new TarArchiveEntry(zipEntry.getName(), linkFlag, true); tarEntry.setUserId(this.owner.getUid()); tarEntry.setGroupId(this.owner.getGid()); tarEntry.setModTime(NORMALIZED_MOD_TIME); tarEntry.setMode(zipEntry.getUnixMode()); if (!zipEntry.isDirectory()) { tarEntry.setSize(zipEntry.getSize()); } return tarEntry; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/package-info.java ================================================ /* * 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. */ /** * IO classes and utilities. */ @NullMarked package org.springframework.boot.buildpack.platform.io; import org.jspecify.annotations.NullMarked; ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/JsonStream.java ================================================ /* * 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.buildpack.platform.json; import java.io.IOException; import java.io.InputStream; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; import tools.jackson.core.JsonParser; import tools.jackson.core.JsonToken; import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.node.ObjectNode; /** * Utility class that allows JSON to be parsed and processed as it's received. * * @author Phillip Webb * @since 2.3.0 */ public class JsonStream { private final JsonMapper jsonMapper; /** * Create a new {@link JsonStream} backed by the given JSON mapper. * @param jsonMapper the object mapper to use * @since 4.0.0 */ public JsonStream(JsonMapper jsonMapper) { this.jsonMapper = jsonMapper; } /** * Stream {@link ObjectNode object nodes} from the content as they become available. * @param content the source content * @param consumer the {@link ObjectNode} consumer * @throws IOException on IO error */ public void get(InputStream content, Consumer consumer) throws IOException { get(content, ObjectNode.class, consumer); } /** * Stream objects from the content as they become available. * @param the object type * @param content the source content * @param type the object type * @param consumer the {@link ObjectNode} consumer * @throws IOException on IO error */ public void get(InputStream content, Class type, Consumer consumer) throws IOException { try (JsonParser parser = this.jsonMapper.createParser(content)) { while (!parser.isClosed()) { JsonToken token = parser.nextToken(); if (token != null && token != JsonToken.END_OBJECT) { T node = read(parser, type); if (node != null) { consumer.accept(node); } } } } } @SuppressWarnings("unchecked") private @Nullable T read(JsonParser parser, Class type) { if (ObjectNode.class.isAssignableFrom(type)) { ObjectNode node = (ObjectNode) this.jsonMapper.readTree(parser); if (node == null || node.isMissingNode() || node.isEmpty()) { return null; } return (T) node; } return this.jsonMapper.readValue(parser, type); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/MappedObject.java ================================================ /* * 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.buildpack.platform.json; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import org.jspecify.annotations.Nullable; import tools.jackson.core.JacksonException; import tools.jackson.databind.JsonNode; import tools.jackson.databind.json.JsonMapper; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; /** * Base class for mapped JSON objects. * * @author Phillip Webb * @author Dmytro Nosan * @since 2.3.0 */ public class MappedObject { private final JsonNode node; private final Lookup lookup; /** * Create a new {@link MappedObject} instance. * @param node the source node * @param lookup method handle lookup */ protected MappedObject(JsonNode node, Lookup lookup) { this.node = node; this.lookup = lookup; } /** * Return the source node of the mapped object. * @return the source node */ protected final JsonNode getNode() { return this.node; } /** * Get the value at the given JSON path expression as a specific type. * @param the data type * @param expression the JSON path expression * @param type the desired type. May be a simple JSON type or an interface * @return the value */ protected @Nullable T valueAt(String expression, Class type) { return valueAt(this, this.node, this.lookup, expression, type); } /** * Get a {@link Map} at the given JSON path expression with a value mapped from a * related {@link JsonNode}. * @param the value type * @param expression the JSON path expression * @param valueMapper function to map the value from the {@link JsonNode} * @return the map * @since 3.5.0 */ protected Map mapAt(String expression, Function valueMapper) { Map map = new LinkedHashMap<>(); getNode().at(expression) .properties() .forEach((entry) -> map.put(entry.getKey(), valueMapper.apply(entry.getValue()))); return Collections.unmodifiableMap(map); } /** * Get children at the given JSON path expression by constructing them using the given * factory. * @param the child type * @param expression the JSON path expression * @param factory factory used to create the child * @return a list of children * @since 3.2.6 */ protected List childrenAt(@Nullable String expression, Function factory) { JsonNode node = (expression != null) ? this.node.at(expression) : this.node; if (node.isEmpty()) { return Collections.emptyList(); } List children = new ArrayList<>(); node.values().forEach((childNode) -> children.add(factory.apply(childNode))); return Collections.unmodifiableList(children); } @SuppressWarnings("unchecked") protected static T getRoot(Object proxy) { MappedInvocationHandler handler = (MappedInvocationHandler) Proxy.getInvocationHandler(proxy); return (T) handler.root; } protected static @Nullable T valueAt(Object proxy, String expression, Class type) { MappedInvocationHandler handler = (MappedInvocationHandler) Proxy.getInvocationHandler(proxy); return valueAt(handler.root, handler.node, handler.lookup, expression, type); } @SuppressWarnings("unchecked") private static @Nullable T valueAt(MappedObject root, JsonNode node, Lookup lookup, String expression, Class type) { JsonNode result = node.at(expression); if (result.isMissingNode() && expression.startsWith("/") && expression.length() > 1 && Character.isLowerCase(expression.charAt(1))) { StringBuilder alternative = new StringBuilder(expression); alternative.setCharAt(1, Character.toUpperCase(alternative.charAt(1))); result = node.at(alternative.toString()); } if (type.isInterface() && !type.getName().startsWith("java")) { return (T) Proxy.newProxyInstance(MappedObject.class.getClassLoader(), new Class[] { type }, new MappedInvocationHandler(root, result, lookup)); } if (result.isMissingNode()) { return null; } try { return SharedJsonMapper.get().treeToValue(result, type); } catch (JacksonException ex) { throw new IllegalStateException(ex); } } /** * Factory method to create a new {@link MappedObject} instance. * @param the mapped object type * @param content the JSON content for the object * @param factory a factory to create the mapped object from a {@link JsonNode} * @return the mapped object * @throws IOException on IO error */ protected static T of(String content, Function factory) throws IOException { return of(content, JsonMapper::readTree, factory); } /** * Factory method to create a new {@link MappedObject} instance. * @param the mapped object type * @param content the JSON content for the object * @param factory a factory to create the mapped object from a {@link JsonNode} * @return the mapped object * @throws IOException on IO error */ protected static T of(InputStream content, Function factory) throws IOException { return of(StreamUtils.nonClosing(content), JsonMapper::readTree, factory); } /** * Factory method to create a new {@link MappedObject} instance. * @param the mapped object type * @param the content type * @param content the JSON content for the object * @param reader the content reader * @param factory a factory to create the mapped object from a {@link JsonNode} * @return the mapped object * @throws IOException on IO error */ protected static T of(C content, ContentReader reader, Function factory) throws IOException { JsonMapper jsonMapper = SharedJsonMapper.get(); JsonNode node = reader.read(jsonMapper, content); return factory.apply(node); } /** * Strategy used to read JSON content. * * @param the content type */ @FunctionalInterface protected interface ContentReader { /** * Read JSON content as a {@link JsonNode}. * @param jsonMapper the source JSON mapper * @param content the content to read * @return a {@link JsonNode} * @throws IOException on IO error */ JsonNode read(JsonMapper jsonMapper, C content) throws IOException; } /** * {@link InvocationHandler} used to support * {@link MappedObject#valueAt(String, Class) valueAt} with {@code interface} types. */ private static class MappedInvocationHandler implements InvocationHandler { private static final String GET = "get"; private static final String IS = "is"; private final MappedObject root; private final JsonNode node; private final Lookup lookup; MappedInvocationHandler(MappedObject root, JsonNode node, Lookup lookup) { this.root = root; this.node = node; this.lookup = lookup; } @Override public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Class declaringClass = method.getDeclaringClass(); if (method.isDefault()) { Lookup lookup = this.lookup.in(declaringClass); MethodHandle methodHandle = lookup.unreflectSpecial(method, declaringClass).bindTo(proxy); return methodHandle.invokeWithArguments(); } if (declaringClass == Object.class) { method.invoke(proxy, args); } Assert.state(args == null || args.length == 0, () -> "Unsupported method " + method); String name = getName(method.getName()); Class type = method.getReturnType(); return valueForProperty(name, type); } private String getName(String name) { StringBuilder result = new StringBuilder(name); if (name.startsWith(GET)) { result = new StringBuilder(name.substring(GET.length())); } if (name.startsWith(IS)) { result = new StringBuilder(name.substring(IS.length())); } Assert.state(result.length() >= 0, "Missing name"); result.setCharAt(0, Character.toLowerCase(result.charAt(0))); return result.toString(); } private @Nullable Object valueForProperty(String name, Class type) { return valueAt(this.root, this.node, this.lookup, "/" + name, type); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/SharedJsonMapper.java ================================================ /* * 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.buildpack.platform.json; import tools.jackson.core.json.JsonWriteFeature; import tools.jackson.databind.DeserializationFeature; import tools.jackson.databind.PropertyNamingStrategies; import tools.jackson.databind.SerializationFeature; import tools.jackson.databind.json.JsonMapper; /** * Provides access to a shared pre-configured {@link JsonMapper}. * * @author Phillip Webb * @since 4.0.0 */ public final class SharedJsonMapper { private static final JsonMapper INSTANCE; static { INSTANCE = JsonMapper.builder() .enable(SerializationFeature.INDENT_OUTPUT) .disable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS) .disable(JsonWriteFeature.ESCAPE_FORWARD_SLASHES) .propertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE) .build(); } private SharedJsonMapper() { } public static JsonMapper get() { return INSTANCE; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/package-info.java ================================================ /* * 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. */ /** * Utilities and classes for JSON processing. */ @NullMarked package org.springframework.boot.buildpack.platform.json; import org.jspecify.annotations.NullMarked; ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/AbstractSocket.java ================================================ /* * 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.buildpack.platform.socket; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.net.SocketAddress; import org.jspecify.annotations.Nullable; /** * Abstract base class for custom socket implementation. * * @author Phillip Webb */ class AbstractSocket extends Socket { @Override public void connect(SocketAddress endpoint) throws IOException { } @Override public void connect(SocketAddress endpoint, int timeout) throws IOException { } @Override public boolean isConnected() { return true; } @Override public boolean isBound() { return true; } @Override public void shutdownInput() throws IOException { throw new UnsupportedSocketOperationException(); } @Override public void shutdownOutput() throws IOException { throw new UnsupportedSocketOperationException(); } @Override public @Nullable InetAddress getInetAddress() { return null; } @Override public @Nullable InetAddress getLocalAddress() { return null; } @Override public @Nullable SocketAddress getLocalSocketAddress() { return null; } @Override public @Nullable SocketAddress getRemoteSocketAddress() { return null; } private static class UnsupportedSocketOperationException extends UnsupportedOperationException { UnsupportedSocketOperationException() { super("Unsupported socket operation"); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/FileDescriptor.java ================================================ /* * 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.buildpack.platform.socket; import java.io.Closeable; import java.io.IOException; import java.util.function.IntConsumer; /** * Provides access to the underlying file system representation of an open file. * * @author Phillip Webb * @see #acquire() */ class FileDescriptor { private final Handle openHandle; private final Handle closedHandler; private final IntConsumer closer; private Status status = Status.OPEN; private int referenceCount; FileDescriptor(int handle, IntConsumer closer) { this.openHandle = new Handle(handle); this.closedHandler = new Handle(-1); this.closer = closer; } /** * Acquire an instance of the actual {@link Handle}. The caller must * {@link Handle#close() close} the resulting handle when done. * @return the handle */ synchronized Handle acquire() { this.referenceCount++; return (this.status != Status.OPEN) ? this.closedHandler : this.openHandle; } private synchronized void release() { this.referenceCount--; if (this.referenceCount == 0 && this.status == Status.CLOSE_PENDING) { this.closer.accept(this.openHandle.value); this.status = Status.CLOSED; } } /** * Close the underlying file when all handles have been released. */ synchronized void close() { if (this.status == Status.OPEN) { if (this.referenceCount == 0) { this.closer.accept(this.openHandle.value); this.status = Status.CLOSED; } else { this.status = Status.CLOSE_PENDING; } } } /** * The status of the file descriptor. */ private enum Status { OPEN, CLOSE_PENDING, CLOSED } /** * Provides access to the actual file descriptor handle. */ final class Handle implements Closeable { private final int value; private Handle(int value) { this.value = value; } boolean isClosed() { return this.value == -1; } int intValue() { return this.value; } @Override public void close() throws IOException { if (!isClosed()) { release(); } } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/NamedPipeSocket.java ================================================ /* * 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.buildpack.platform.socket; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousByteChannel; import java.nio.channels.AsynchronousCloseException; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.Channels; import java.nio.channels.CompletionHandler; import java.nio.file.FileSystemException; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import com.sun.jna.Platform; import com.sun.jna.platform.win32.Kernel32; /** * A {@link Socket} implementation for named pipes. * * @author Phillip Webb * @author Scott Frederick * @since 2.3.0 */ public class NamedPipeSocket extends Socket { private static final int WAIT_INTERVAL = 100; private static final long TIMEOUT = TimeUnit.MILLISECONDS.toNanos(1000); private final AsynchronousFileByteChannel channel; NamedPipeSocket(String path) throws IOException { this.channel = open(path); } private AsynchronousFileByteChannel open(String path) throws IOException { Consumer awaiter = Platform.isWindows() ? new WindowsAwaiter() : new SleepAwaiter(); long startTime = System.nanoTime(); while (true) { try { return new AsynchronousFileByteChannel(AsynchronousFileChannel.open(Paths.get(path), StandardOpenOption.READ, StandardOpenOption.WRITE)); } catch (FileSystemException ex) { if (System.nanoTime() - startTime >= TIMEOUT) { throw ex; } awaiter.accept(path); } } } @Override public void connect(SocketAddress endpoint) throws IOException { // No-op } @Override public void connect(SocketAddress endpoint, int timeout) throws IOException { // No-op } @Override public InputStream getInputStream() { return Channels.newInputStream(this.channel); } @Override public OutputStream getOutputStream() { return Channels.newOutputStream(this.channel); } @Override public void close() throws IOException { if (this.channel != null) { this.channel.close(); } } /** * Return a new {@link NamedPipeSocket} for the given path. * @param path the path to the domain socket * @return a {@link NamedPipeSocket} instance * @throws IOException if the socket cannot be opened */ public static NamedPipeSocket get(String path) throws IOException { return new NamedPipeSocket(path); } /** * Adapt an {@code AsynchronousByteChannel} to an {@code AsynchronousFileChannel}. */ private static class AsynchronousFileByteChannel implements AsynchronousByteChannel { private final AsynchronousFileChannel fileChannel; AsynchronousFileByteChannel(AsynchronousFileChannel fileChannel) { this.fileChannel = fileChannel; } @Override public void read(ByteBuffer dst, A attachment, CompletionHandler handler) { this.fileChannel.read(dst, 0, attachment, new CompletionHandler<>() { @Override public void completed(Integer read, A attachment) { handler.completed((read > 0) ? read : -1, attachment); } @Override public void failed(Throwable exc, A attachment) { if (exc instanceof AsynchronousCloseException) { handler.completed(-1, attachment); return; } handler.failed(exc, attachment); } }); } @Override public Future read(ByteBuffer dst) { CompletableFutureHandler future = new CompletableFutureHandler(); this.fileChannel.read(dst, 0, null, future); return future; } @Override public void write(ByteBuffer src, A attachment, CompletionHandler handler) { this.fileChannel.write(src, 0, attachment, handler); } @Override public Future write(ByteBuffer src) { return this.fileChannel.write(src, 0); } @Override public void close() throws IOException { this.fileChannel.close(); } @Override public boolean isOpen() { return this.fileChannel.isOpen(); } private static final class CompletableFutureHandler extends CompletableFuture implements CompletionHandler { @Override public void completed(Integer read, Object attachment) { complete((read > 0) ? read : -1); } @Override public void failed(Throwable exc, Object attachment) { if (exc instanceof AsynchronousCloseException) { complete(-1); return; } completeExceptionally(exc); } } } /** * Waits for the name pipe file using a simple sleep. */ private static final class SleepAwaiter implements Consumer { @Override public void accept(String path) { try { Thread.sleep(WAIT_INTERVAL); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } /** * Waits for the name pipe file using Windows specific logic. */ private static final class WindowsAwaiter implements Consumer { @Override public void accept(String path) { Kernel32.INSTANCE.WaitNamedPipe(path, WAIT_INTERVAL); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/UnixDomainSocket.java ================================================ /* * 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.buildpack.platform.socket; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.net.UnixDomainSocketAddress; import java.nio.channels.Channels; import java.nio.channels.SocketChannel; /** * A {@link Socket} implementation for Unix domain sockets. * * @author Scott Frederick * @since 3.4.0 */ public final class UnixDomainSocket extends AbstractSocket { /** * Create a new {@link Socket} for the given path. * @param path the path to the domain socket * @return a {@link Socket} instance * @throws IOException if the socket cannot be opened */ public static Socket get(String path) throws IOException { return new UnixDomainSocket(path); } private final SocketAddress socketAddress; private final SocketChannel socketChannel; private UnixDomainSocket(String path) throws IOException { this.socketAddress = UnixDomainSocketAddress.of(path); this.socketChannel = SocketChannel.open(this.socketAddress); } @Override public InputStream getInputStream() throws IOException { if (isClosed()) { throw new SocketException("Socket is closed"); } if (!isConnected()) { throw new SocketException("Socket is not connected"); } if (isInputShutdown()) { throw new SocketException("Socket input is shutdown"); } return Channels.newInputStream(this.socketChannel); } @Override public OutputStream getOutputStream() throws IOException { if (isClosed()) { throw new SocketException("Socket is closed"); } if (!isConnected()) { throw new SocketException("Socket is not connected"); } if (isOutputShutdown()) { throw new SocketException("Socket output is shutdown"); } return Channels.newOutputStream(this.socketChannel); } @Override public SocketAddress getLocalSocketAddress() { return this.socketAddress; } @Override public SocketAddress getRemoteSocketAddress() { return this.socketAddress; } @Override public void close() throws IOException { super.close(); this.socketChannel.close(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/package-info.java ================================================ /* * 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. */ /** * Low-level {@link java.net.Socket} implementations required for local Docker access. */ @NullMarked package org.springframework.boot.buildpack.platform.socket; import org.jspecify.annotations.NullMarked; ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/system/Environment.java ================================================ /* * 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.buildpack.platform.system; import org.jspecify.annotations.Nullable; /** * Provides access to environment variable values. * * @author Scott Frederick * @author Phillip Webb * @since 2.3.0 */ @FunctionalInterface public interface Environment { /** * Standard {@link Environment} implementation backed by * {@link System#getenv(String)}. */ Environment SYSTEM = System::getenv; /** * Gets the value of the specified environment variable. * @param name the name of the environment variable * @return the string value of the variable, or {@code null} if the variable is not * defined in the environment */ @Nullable String get(String name); } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/system/package-info.java ================================================ /* * 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. */ /** * System abstractions. */ @NullMarked package org.springframework.boot.buildpack.platform.system; import org.jspecify.annotations.NullMarked; ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/ApiVersionsTests.java ================================================ /* * 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.buildpack.platform.build; import java.util.stream.IntStream; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.ApiVersion; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link ApiVersions}. * * @author Scott Frederick */ class ApiVersionsTests { @Test void findsLatestWhenOneMatchesMajor() { ApiVersion version = ApiVersions.parse("1.1", "2.2").findLatestSupported("1.0"); assertThat(version).isEqualTo(ApiVersion.parse("1.1")); } @Test void findsLatestWhenOneMatchesWithReleaseVersions() { ApiVersion version = ApiVersions.parse("1.1", "1.2").findLatestSupported("1.1"); assertThat(version).isEqualTo(ApiVersion.parse("1.2")); } @Test void findsLatestWhenOneMatchesWithPreReleaseVersions() { ApiVersion version = ApiVersions.parse("0.2", "0.3").findLatestSupported("0.2"); assertThat(version).isEqualTo(ApiVersion.parse("0.2")); } @Test void findsLatestWhenMultipleMatchesWithReleaseVersions() { ApiVersion version = ApiVersions.parse("1.1", "1.2").findLatestSupported("1.1", "1.2"); assertThat(version).isEqualTo(ApiVersion.parse("1.2")); } @Test void findsLatestWhenMultipleMatchesWithPreReleaseVersions() { ApiVersion version = ApiVersions.parse("0.2", "0.3").findLatestSupported("0.2", "0.3"); assertThat(version).isEqualTo(ApiVersion.parse("0.3")); } @Test void findLatestWhenNoneSupportedThrowsException() { assertThatIllegalStateException() .isThrownBy(() -> ApiVersions.parse("1.1", "1.2").findLatestSupported("1.3", "1.4")) .withMessage("Detected platform API versions '1.3,1.4' are not included in supported versions '1.1,1.2'"); } @Test void createFromRange() { ApiVersions versions = ApiVersions.of(1, IntStream.rangeClosed(2, 7)); assertThat(versions).hasToString("1.2,1.3,1.4,1.5,1.6,1.7"); } @Test void toStringReturnsString() { assertThat(ApiVersions.parse("1.1", "2.2", "3.3")).hasToString("1.1,2.2,3.3"); } @Test void equalsAndHashCode() { ApiVersions v12a = ApiVersions.parse("1.2", "2.3"); ApiVersions v12b = ApiVersions.parse("1.2", "2.3"); ApiVersions v13 = ApiVersions.parse("1.3", "2.4"); assertThat(v12a).hasSameHashCodeAs(v12b); assertThat(v12a).isEqualTo(v12a).isEqualTo(v12b).isNotEqualTo(v13); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildLogTests.java ================================================ /* * 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.buildpack.platform.build; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link BuildLog}. * * @author Phillip Webb */ class BuildLogTests { @Test void toSystemOutPrintsToSystemOut() { BuildLog log = BuildLog.toSystemOut(); assertThat(log).isInstanceOf(PrintStreamBuildLog.class); assertThat(log).extracting("out").isSameAs(System.out); } @Test void toPrintsToOutput() { BuildLog log = BuildLog.to(System.err); assertThat(log).isInstanceOf(PrintStreamBuildLog.class); assertThat(log).extracting("out").isSameAs(System.err); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildOwnerTests.java ================================================ /* * 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.buildpack.platform.build; import java.util.LinkedHashMap; import java.util.Map; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link BuildOwner}. * * @author Phillip Webb * @author Andy Wilkinson */ class BuildOwnerTests { @Test void fromEnvReturnsOwner() { Map env = new LinkedHashMap<>(); env.put("CNB_USER_ID", "123"); env.put("CNB_GROUP_ID", "456"); BuildOwner owner = BuildOwner.fromEnv(env); assertThat(owner.getUid()).isEqualTo(123); assertThat(owner.getGid()).isEqualTo(456); assertThat(owner).hasToString("123/456"); } @Test @SuppressWarnings("NullAway") // Test null check void fromEnvWhenEnvIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> BuildOwner.fromEnv(null)) .withMessage("'env' must not be null"); } @Test void fromEnvWhenUserPropertyIsMissingThrowsException() { Map env = new LinkedHashMap<>(); env.put("CNB_GROUP_ID", "456"); assertThatIllegalStateException().isThrownBy(() -> BuildOwner.fromEnv(env)) .withMessage("Missing 'CNB_USER_ID' value from the builder environment '" + env + "'"); } @Test void fromEnvWhenGroupPropertyIsMissingThrowsException() { Map env = new LinkedHashMap<>(); env.put("CNB_USER_ID", "123"); assertThatIllegalStateException().isThrownBy(() -> BuildOwner.fromEnv(env)) .withMessage("Missing 'CNB_GROUP_ID' value from the builder environment '" + env + "'"); } @Test void fromEnvWhenUserPropertyIsMalformedThrowsException() { Map env = new LinkedHashMap<>(); env.put("CNB_USER_ID", "nope"); env.put("CNB_GROUP_ID", "456"); assertThatIllegalStateException().isThrownBy(() -> BuildOwner.fromEnv(env)) .withMessage("Malformed 'CNB_USER_ID' value 'nope' in the builder environment '" + env + "'"); } @Test void fromEnvWhenGroupPropertyIsMalformedThrowsException() { Map env = new LinkedHashMap<>(); env.put("CNB_USER_ID", "123"); env.put("CNB_GROUP_ID", "nope"); assertThatIllegalStateException().isThrownBy(() -> BuildOwner.fromEnv(env)) .withMessage("Malformed 'CNB_GROUP_ID' value 'nope' in the builder environment '" + env + "'"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Stream; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; 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.ImageName; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.entry; /** * Tests for {@link BuildRequest}. * * @author Phillip Webb * @author Scott Frederick * @author Jeroen Meijer * @author Rafael Ceccone */ class BuildRequestTests { private static final ZoneId UTC = ZoneId.of("UTC"); @TempDir @SuppressWarnings("NullAway.Init") File tempDir; @Test void forJarFileReturnsRequest() throws IOException { File jarFile = new File(this.tempDir, "my-app-0.0.1.jar"); writeTestJarFile(jarFile); BuildRequest request = BuildRequest.forJarFile(jarFile); assertThat(request.getName()).hasToString("docker.io/library/my-app:0.0.1"); assertThat(request.getBuilder()).hasToString("docker.io/" + BuildRequest.DEFAULT_BUILDER_IMAGE_REF); assertThat(request.getApplicationContent(Owner.ROOT)).satisfies(this::hasExpectedJarContent); assertThat(request.getEnv()).isEmpty(); } @Test void forJarFileWithNameReturnsRequest() throws IOException { File jarFile = new File(this.tempDir, "my-app-0.0.1.jar"); writeTestJarFile(jarFile); BuildRequest request = BuildRequest.forJarFile(ImageReference.of("test-app"), jarFile); assertThat(request.getName()).hasToString("docker.io/library/test-app:latest"); assertThat(request.getBuilder()).hasToString("docker.io/" + BuildRequest.DEFAULT_BUILDER_IMAGE_REF); assertThat(request.getApplicationContent(Owner.ROOT)).satisfies(this::hasExpectedJarContent); assertThat(request.getEnv()).isEmpty(); } @Test @SuppressWarnings("NullAway") // Test null check void forJarFileWhenJarFileIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> BuildRequest.forJarFile(null)) .withMessage("'jarFile' must not be null"); } @Test void forJarFileWhenJarFileIsMissingThrowsException() { assertThatIllegalArgumentException() .isThrownBy(() -> BuildRequest.forJarFile(new File(this.tempDir, "missing.jar"))) .withMessage("'jarFile' must exist"); } @Test void forJarFileWhenJarFileIsDirectoryThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> BuildRequest.forJarFile(this.tempDir)) .withMessage("'jarFile' must be a file"); } @Test void withBuilderUpdatesBuilder() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) .withBuilder(ImageReference.of("spring/builder")); assertThat(request.getBuilder()).hasToString("docker.io/spring/builder:latest"); assertThat(request.isTrustBuilder()).isFalse(); } @Test void withBuilderWhenHasDigestUpdatesBuilder() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) .withBuilder(ImageReference .of("spring/builder@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")); assertThat(request.getBuilder()).hasToString( "docker.io/spring/builder@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); assertThat(request.isTrustBuilder()).isFalse(); } @Test void withoutBuilderTrustsDefaultBuilder() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); assertThat(request.isTrustBuilder()).isTrue(); } @Test void withoutBuilderTrustsDefaultBuilderWithDifferentTag() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) .withBuilder(ImageReference.of(ImageName.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME), "other")); assertThat(request.isTrustBuilder()).isTrue(); } @Test void withoutBuilderTrustsDefaultBuilderWithDigest() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) .withBuilder(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF) .withDigest("sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")); assertThat(request.isTrustBuilder()).isTrue(); } @ParameterizedTest @MethodSource("trustedBuilders") void withKnownTrustedBuilderTrustsBuilder(ImageReference builder) throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")).withBuilder(builder); assertThat(request.isTrustBuilder()).isTrue(); } static Stream trustedBuilders() { return BuildRequest.KNOWN_TRUSTED_BUILDERS.stream(); } @Test void withoutTrustBuilderAndDefaultBuilderUpdatesTrustsBuilder() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")).withTrustBuilder(false); assertThat(request.isTrustBuilder()).isFalse(); } @Test void withTrustBuilderAndBuilderUpdatesTrustBuilder() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) .withBuilder(ImageReference.of("spring/builder")) .withTrustBuilder(true); assertThat(request.isTrustBuilder()).isTrue(); } @Test void withRunImageUpdatesRunImage() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) .withRunImage(ImageReference.of("example.com/custom/run-image:latest")); assertThat(request.getRunImage()).hasToString("example.com/custom/run-image:latest"); } @Test void withRunImageWhenHasDigestUpdatesRunImage() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) .withRunImage(ImageReference .of("example.com/custom/run-image@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")); assertThat(request.getRunImage()).hasToString( "example.com/custom/run-image@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); } @Test void withCreatorUpdatesCreator() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withCreator = request.withCreator(Creator.withVersion("1.0.0")); assertThat(request.getCreator().getName()).isEqualTo("Spring Boot"); assertThat(request.getCreator().getVersion()).isEmpty(); assertThat(withCreator.getCreator().getName()).isEqualTo("Spring Boot"); assertThat(withCreator.getCreator().getVersion()).isEqualTo("1.0.0"); } @Test void withEnvAddsEnvEntry() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withEnv = request.withEnv("spring", "boot"); assertThat(request.getEnv()).isEmpty(); assertThat(withEnv.getEnv()).containsExactly(entry("spring", "boot")); } @Test void withEnvMapAddsEnvEntries() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); Map env = new LinkedHashMap<>(); env.put("spring", "boot"); env.put("test", "test"); BuildRequest withEnv = request.withEnv(env); assertThat(request.getEnv()).isEmpty(); assertThat(withEnv.getEnv()).containsExactly(entry("spring", "boot"), entry("test", "test")); } @Test @SuppressWarnings("NullAway") // Test null check void withEnvWhenKeyIsNullThrowsException() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); assertThatIllegalArgumentException().isThrownBy(() -> request.withEnv(null, "test")) .withMessage("'name' must not be empty"); } @Test @SuppressWarnings("NullAway") // Test null check void withEnvWhenValueIsNullThrowsException() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); assertThatIllegalArgumentException().isThrownBy(() -> request.withEnv("test", null)) .withMessage("'value' must not be empty"); } @Test void withBuildpacksAddsBuildpacks() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildpackReference buildpackReference1 = BuildpackReference.of("example/buildpack1"); BuildpackReference buildpackReference2 = BuildpackReference.of("example/buildpack2"); BuildRequest withBuildpacks = request.withBuildpacks(buildpackReference1, buildpackReference2); assertThat(request.getBuildpacks()).isEmpty(); assertThat(withBuildpacks.getBuildpacks()).containsExactly(buildpackReference1, buildpackReference2); } @Test @SuppressWarnings("NullAway") // Test null check void withBuildpacksWhenBuildpacksIsNullThrowsException() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); assertThatIllegalArgumentException().isThrownBy(() -> request.withBuildpacks((List) null)) .withMessage("'buildpacks' must not be null"); } @Test void withBindingsAddsBindings() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withBindings = request.withBindings(Binding.of("/host/path:/container/path:ro"), Binding.of("volume-name:/container/path:rw")); assertThat(request.getBindings()).isEmpty(); assertThat(withBindings.getBindings()).containsExactly(Binding.of("/host/path:/container/path:ro"), Binding.of("volume-name:/container/path:rw")); } @Test @SuppressWarnings("NullAway") // Test null check void withBindingsWhenBindingsIsNullThrowsException() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); assertThatIllegalArgumentException().isThrownBy(() -> request.withBindings((List) null)) .withMessage("'bindings' must not be null"); } @Test void withNetworkUpdatesNetwork() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")).withNetwork("test"); assertThat(request.getNetwork()).isEqualTo("test"); } @Test void withTagsAddsTags() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withTags = request.withTags(ImageReference.of("docker.io/library/my-app:latest"), ImageReference.of("example.com/custom/my-app:0.0.1"), ImageReference.of("example.com/custom/my-app:latest")); assertThat(request.getTags()).isEmpty(); assertThat(withTags.getTags()).containsExactly(ImageReference.of("docker.io/library/my-app:latest"), ImageReference.of("example.com/custom/my-app:0.0.1"), ImageReference.of("example.com/custom/my-app:latest")); } @Test @SuppressWarnings("NullAway") // Test null check void withTagsWhenTagsIsNullThrowsException() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); assertThatIllegalArgumentException().isThrownBy(() -> request.withTags((List) null)) .withMessage("'tags' must not be null"); } @Test void withBuildWorkspaceVolumeAddsWorkspace() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withWorkspace = request.withBuildWorkspace(Cache.volume("build-workspace")); assertThat(request.getBuildWorkspace()).isNull(); assertThat(withWorkspace.getBuildWorkspace()).isEqualTo(Cache.volume("build-workspace")); } @Test void withBuildWorkspaceBindAddsWorkspace() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withWorkspace = request.withBuildWorkspace(Cache.bind("/tmp/build-workspace")); assertThat(request.getBuildWorkspace()).isNull(); assertThat(withWorkspace.getBuildWorkspace()).isEqualTo(Cache.bind("/tmp/build-workspace")); } @Test void withBuildVolumeCacheAddsCache() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withCache = request.withBuildCache(Cache.volume("build-volume")); assertThat(request.getBuildCache()).isNull(); assertThat(withCache.getBuildCache()).isEqualTo(Cache.volume("build-volume")); } @Test void withBuildBindCacheAddsCache() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withCache = request.withBuildCache(Cache.bind("/tmp/build-cache")); assertThat(request.getBuildCache()).isNull(); assertThat(withCache.getBuildCache()).isEqualTo(Cache.bind("/tmp/build-cache")); } @Test @SuppressWarnings("NullAway") // Test null check void withBuildVolumeCacheWhenCacheIsNullThrowsException() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); assertThatIllegalArgumentException().isThrownBy(() -> request.withBuildCache(null)) .withMessage("'buildCache' must not be null"); } @Test void withLaunchVolumeCacheAddsCache() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withCache = request.withLaunchCache(Cache.volume("launch-volume")); assertThat(request.getLaunchCache()).isNull(); assertThat(withCache.getLaunchCache()).isEqualTo(Cache.volume("launch-volume")); } @Test void withLaunchBindCacheAddsCache() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withCache = request.withLaunchCache(Cache.bind("/tmp/launch-cache")); assertThat(request.getLaunchCache()).isNull(); assertThat(withCache.getLaunchCache()).isEqualTo(Cache.bind("/tmp/launch-cache")); } @Test @SuppressWarnings("NullAway") // Test null check void withLaunchVolumeCacheWhenCacheIsNullThrowsException() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); assertThatIllegalArgumentException().isThrownBy(() -> request.withLaunchCache(null)) .withMessage("'launchCache' must not be null"); } @Test void withCreatedDateSetsCreatedDate() throws Exception { Instant createDate = Instant.now(); BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withCreatedDate = request.withCreatedDate(createDate.toString()); assertThat(withCreatedDate.getCreatedDate()).isEqualTo(createDate); } @Test void withCreatedDateNowSetsCreatedDate() throws Exception { OffsetDateTime now = OffsetDateTime.now(UTC); BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withCreatedDate = request.withCreatedDate("now"); OffsetDateTime createdDate = OffsetDateTime.ofInstant(withCreatedDate.getCreatedDate(), UTC); assertThat(createdDate.getYear()).isEqualTo(now.getYear()); assertThat(createdDate.getMonth()).isEqualTo(now.getMonth()); assertThat(createdDate.getDayOfMonth()).isEqualTo(now.getDayOfMonth()); withCreatedDate = request.withCreatedDate("NOW"); createdDate = OffsetDateTime.ofInstant(withCreatedDate.getCreatedDate(), UTC); assertThat(createdDate.getYear()).isEqualTo(now.getYear()); assertThat(createdDate.getMonth()).isEqualTo(now.getMonth()); assertThat(createdDate.getDayOfMonth()).isEqualTo(now.getDayOfMonth()); } @Test void withCreatedDateAndInvalidDateThrowsException() throws Exception { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); assertThatIllegalArgumentException().isThrownBy(() -> request.withCreatedDate("not a date")) .withMessageContaining("'not a date'"); } @Test void withApplicationDirectorySetsApplicationDirectory() throws Exception { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withAppDir = request.withApplicationDirectory("/application"); assertThat(withAppDir.getApplicationDirectory()).isEqualTo("/application"); } @Test void withSecurityOptionsSetsSecurityOptions() throws Exception { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withAppDir = request.withSecurityOptions(List.of("label=user:USER", "label=role:ROLE")); assertThat(withAppDir.getSecurityOptions()).containsExactly("label=user:USER", "label=role:ROLE"); } @Test void withPlatformSetsPlatform() throws Exception { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); BuildRequest withAppDir = request.withImagePlatform("linux/arm64"); assertThat(withAppDir.getImagePlatform()).isEqualTo(ImagePlatform.of("linux/arm64")); } private void hasExpectedJarContent(TarArchive archive) { try { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); archive.writeTo(outputStream); try (TarArchiveInputStream tar = new TarArchiveInputStream( new ByteArrayInputStream(outputStream.toByteArray()))) { assertThat(tar.getNextEntry().getName()).isEqualTo("spring/"); assertThat(tar.getNextEntry().getName()).isEqualTo("spring/boot"); assertThat(tar.getNextEntry()).isNull(); } } catch (IOException ex) { throw new IllegalStateException(ex); } } private File writeTestJarFile(String name) throws IOException { File file = new File(this.tempDir, name); writeTestJarFile(file); return file; } private void writeTestJarFile(File file) throws IOException { try (ZipArchiveOutputStream zip = new ZipArchiveOutputStream(file)) { ZipArchiveEntry dirEntry = new ZipArchiveEntry("spring/"); zip.putArchiveEntry(dirEntry); zip.closeArchiveEntry(); ZipArchiveEntry fileEntry = new ZipArchiveEntry("spring/boot"); zip.putArchiveEntry(fileEntry); zip.write("test".getBytes(StandardCharsets.UTF_8)); zip.closeArchiveEntry(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderBuildpackTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.type.Layer; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link BuilderBuildpack}. * * @author Scott Frederick */ class BuilderBuildpackTests extends AbstractJsonTests { private BuildpackResolverContext resolverContext; @BeforeEach void setUp() throws Exception { BuilderMetadata metadata = BuilderMetadata.fromJson(getContentAsString("builder-metadata.json")); this.resolverContext = mock(BuildpackResolverContext.class); given(this.resolverContext.getBuildpackMetadata()).willReturn(metadata.getBuildpacks()); } @Test void resolveWhenFullyQualifiedBuildpackWithVersionResolves() throws Exception { BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:paketo-buildpacks/spring-boot@3.5.0"); Buildpack buildpack = BuilderBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNotNull(); assertThat(buildpack.getCoordinates()) .isEqualTo(BuildpackCoordinates.of("paketo-buildpacks/spring-boot", "3.5.0")); assertThatNoLayersAreAdded(buildpack); } @Test void resolveWhenFullyQualifiedBuildpackWithoutVersionResolves() throws Exception { BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:paketo-buildpacks/spring-boot"); Buildpack buildpack = BuilderBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNotNull(); assertThat(buildpack.getCoordinates()) .isEqualTo(BuildpackCoordinates.of("paketo-buildpacks/spring-boot", "3.5.0")); assertThatNoLayersAreAdded(buildpack); } @Test void resolveWhenUnqualifiedBuildpackWithVersionResolves() throws Exception { BuildpackReference reference = BuildpackReference.of("paketo-buildpacks/spring-boot@3.5.0"); Buildpack buildpack = BuilderBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNotNull(); assertThat(buildpack.getCoordinates()) .isEqualTo(BuildpackCoordinates.of("paketo-buildpacks/spring-boot", "3.5.0")); assertThatNoLayersAreAdded(buildpack); } @Test void resolveWhenUnqualifiedBuildpackWithoutVersionResolves() throws Exception { BuildpackReference reference = BuildpackReference.of("paketo-buildpacks/spring-boot"); Buildpack buildpack = BuilderBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNotNull(); assertThat(buildpack.getCoordinates()) .isEqualTo(BuildpackCoordinates.of("paketo-buildpacks/spring-boot", "3.5.0")); assertThatNoLayersAreAdded(buildpack); } @Test void resolveWhenFullyQualifiedBuildpackWithVersionNotInBuilderThrowsException() { BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:example/buildpack1@1.2.3"); assertThatIllegalStateException().isThrownBy(() -> BuilderBuildpack.resolve(this.resolverContext, reference)) .withMessageContaining("'urn:cnb:builder:example/buildpack1@1.2.3'") .withMessageContaining("not found in builder"); } @Test void resolveWhenFullyQualifiedBuildpackWithoutVersionNotInBuilderThrowsException() { BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:example/buildpack1"); assertThatIllegalStateException().isThrownBy(() -> BuilderBuildpack.resolve(this.resolverContext, reference)) .withMessageContaining("'urn:cnb:builder:example/buildpack1'") .withMessageContaining("not found in builder"); } @Test void resolveWhenUnqualifiedBuildpackNotInBuilderReturnsNull() { BuildpackReference reference = BuildpackReference.of("example/buildpack1@1.2.3"); Buildpack buildpack = BuilderBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNull(); } private void assertThatNoLayersAreAdded(Buildpack buildpack) throws IOException { List layers = new ArrayList<>(); buildpack.apply(layers::add); assertThat(layers).isEmpty(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderExceptionTests.java ================================================ /* * 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.buildpack.platform.build; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link BuilderException}. * * @author Scott Frederick */ class BuilderExceptionTests { @Test void create() { BuilderException exception = new BuilderException("detector", 1); assertThat(exception.getOperation()).isEqualTo("detector"); assertThat(exception.getStatusCode()).isOne(); assertThat(exception.getMessage()).isEqualTo("Builder lifecycle 'detector' failed with status code 1"); } @Test void createWhenOperationIsNull() { BuilderException exception = new BuilderException(null, 1); assertThat(exception.getOperation()).isNull(); assertThat(exception.getStatusCode()).isOne(); assertThat(exception.getMessage()).isEqualTo("Builder failed with status code 1"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderMetadataTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.util.Collections; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.build.BuilderMetadata.RunImage; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.tuple; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link BuilderMetadata}. * * @author Phillip Webb * @author Scott Frederick * @author Andy Wilkinson */ class BuilderMetadataTests extends AbstractJsonTests { @Test void fromImageLoadsMetadata() throws IOException { Image image = Image.of(getContent("image.json")); BuilderMetadata metadata = BuilderMetadata.fromImage(image); assertThat(metadata.getStack().getRunImage().getImage()).isEqualTo("cloudfoundry/run:base-cnb"); assertThat(metadata.getStack().getRunImage().getMirrors()).isEmpty(); assertThat(metadata.getRunImages()).isEmpty(); assertThat(metadata.getLifecycle().getVersion()).isEqualTo("0.7.2"); assertThat(metadata.getLifecycle().getApi().getBuildpack()).isEqualTo("0.2"); assertThat(metadata.getLifecycle().getApi().getPlatform()).isEqualTo("0.3"); assertThat(metadata.getCreatedBy().getName()).isEqualTo("Pack CLI"); assertThat(metadata.getCreatedBy().getVersion()) .isEqualTo("v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)"); assertThat(metadata.getBuildpacks()).extracting(BuildpackMetadata::getId, BuildpackMetadata::getVersion) .contains(tuple("paketo-buildpacks/java", "4.10.0")) .contains(tuple("paketo-buildpacks/spring-boot", "3.5.0")) .contains(tuple("paketo-buildpacks/executable-jar", "3.1.3")) .contains(tuple("paketo-buildpacks/graalvm", "4.1.0")) .contains(tuple("paketo-buildpacks/java-native-image", "4.7.0")) .contains(tuple("paketo-buildpacks/spring-boot-native-image", "2.0.1")) .contains(tuple("paketo-buildpacks/bellsoft-liberica", "6.2.0")); } @Test void fromImageWithoutStackLoadsMetadata() throws IOException { Image image = Image.of(getContent("image-with-empty-stack.json")); BuilderMetadata metadata = BuilderMetadata.fromImage(image); assertThat(metadata.getRunImages()).extracting(RunImage::getImage, RunImage::getMirrors) .contains(tuple("cloudfoundry/run:base-cnb", Collections.emptyList())); assertThat(metadata.getLifecycle().getVersion()).isEqualTo("0.7.2"); assertThat(metadata.getLifecycle().getApi().getBuildpack()).isEqualTo("0.2"); assertThat(metadata.getLifecycle().getApi().getPlatform()).isEqualTo("0.3"); assertThat(metadata.getCreatedBy().getName()).isEqualTo("Pack CLI"); assertThat(metadata.getCreatedBy().getVersion()) .isEqualTo("v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)"); assertThat(metadata.getBuildpacks()).extracting(BuildpackMetadata::getId, BuildpackMetadata::getVersion) .contains(tuple("paketo-buildpacks/java", "4.10.0")) .contains(tuple("paketo-buildpacks/spring-boot", "3.5.0")) .contains(tuple("paketo-buildpacks/executable-jar", "3.1.3")) .contains(tuple("paketo-buildpacks/graalvm", "4.1.0")) .contains(tuple("paketo-buildpacks/java-native-image", "4.7.0")) .contains(tuple("paketo-buildpacks/spring-boot-native-image", "2.0.1")) .contains(tuple("paketo-buildpacks/bellsoft-liberica", "6.2.0")); } @Test @SuppressWarnings("NullAway") // Test null check void fromImageWhenImageIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> BuilderMetadata.fromImage(null)) .withMessage("'image' must not be null"); } @Test void fromImageWhenImageConfigIsNullThrowsException() { Image image = mock(Image.class); assertThatIllegalArgumentException().isThrownBy(() -> BuilderMetadata.fromImage(image)) .withMessage("'imageConfig' must not be null"); } @Test void fromImageConfigWhenLabelIsMissingThrowsException() { Image image = mock(Image.class); ImageConfig imageConfig = mock(ImageConfig.class); given(image.getConfig()).willReturn(imageConfig); given(imageConfig.getLabels()).willReturn(Collections.singletonMap("alpha", "a")); assertThatIllegalStateException().isThrownBy(() -> BuilderMetadata.fromImage(image)) .withMessage("No 'io.buildpacks.builder.metadata' label found in image config labels 'alpha'"); } @Test void fromJsonLoadsMetadataWithoutSupportedApis() throws IOException { BuilderMetadata metadata = BuilderMetadata.fromJson(getContentAsString("builder-metadata.json")); assertThat(metadata.getStack().getRunImage().getImage()).isEqualTo("cloudfoundry/run:base-cnb"); assertThat(metadata.getStack().getRunImage().getMirrors()).isEmpty(); assertThat(metadata.getLifecycle().getVersion()).isEqualTo("0.7.2"); assertThat(metadata.getLifecycle().getApi().getBuildpack()).isEqualTo("0.2"); assertThat(metadata.getLifecycle().getApi().getPlatform()).isEqualTo("0.8"); assertThat(metadata.getLifecycle().getApis().getBuildpack()).isNull(); assertThat(metadata.getLifecycle().getApis().getPlatform()).isNull(); } @Test void fromJsonLoadsMetadataWithSupportedApis() throws IOException { BuilderMetadata metadata = BuilderMetadata.fromJson(getContentAsString("builder-metadata-supported-apis.json")); assertThat(metadata.getLifecycle().getVersion()).isEqualTo("0.7.2"); assertThat(metadata.getLifecycle().getApi().getBuildpack()).isEqualTo("0.2"); assertThat(metadata.getLifecycle().getApi().getPlatform()).isEqualTo("0.8"); assertThat(metadata.getLifecycle().getApis().getBuildpack()).containsExactly("0.1", "0.2", "0.3"); assertThat(metadata.getLifecycle().getApis().getPlatform()).containsExactly("0.3", "0.4", "0.5", "0.6", "0.7", "0.8"); } @Test void copyWithUpdatedCreatedByReturnsNewMetadata() throws IOException { Image image = Image.of(getContent("image.json")); BuilderMetadata metadata = BuilderMetadata.fromImage(image); BuilderMetadata copy = metadata.copy((update) -> update.withCreatedBy("test123", "test456")); assertThat(copy).isNotSameAs(metadata); assertThat(copy.getCreatedBy().getName()).isEqualTo("test123"); assertThat(copy.getCreatedBy().getVersion()).isEqualTo("test456"); } @Test void attachToUpdatesMetadata() throws IOException { Image image = Image.of(getContent("image.json")); ImageConfig imageConfig = image.getConfig(); BuilderMetadata metadata = BuilderMetadata.fromImage(image); ImageConfig imageConfigCopy = imageConfig.copy(metadata::attachTo); String label = imageConfigCopy.getLabels().get("io.buildpacks.builder.metadata"); assertThat(label).isNotNull(); BuilderMetadata metadataCopy = BuilderMetadata.fromJson(label); assertThat(metadataCopy.getStack().getRunImage().getImage()) .isEqualTo(metadata.getStack().getRunImage().getImage()); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.net.URI; import java.util.Objects; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.stubbing.Answer; import org.springframework.boot.buildpack.platform.build.Builder.BuildLogAdapter; 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.ImageApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; 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.configuration.DockerRegistryAuthentication; import org.springframework.boot.buildpack.platform.docker.transport.TestDockerEngineException; import org.springframework.boot.buildpack.platform.docker.type.Binding; 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.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageArchive; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.io.TarArchive; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; /** * Tests for {@link Builder}. * * @author Phillip Webb * @author Scott Frederick * @author Rafael Ceccone */ class BuilderTests { private static final ImageReference PAKETO_BUILDPACKS_BUILDER = ImageReference .of("docker.io/paketobuildpacks/builder"); private static final ImageReference LATEST_PAKETO_BUILDPACKS_BUILDER = PAKETO_BUILDPACKS_BUILDER.inTaggedForm(); private static final ImageReference DEFAULT_BUILDER = ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF); private static final ImageReference BASE_CNB = ImageReference.of("docker.io/cloudfoundry/run:base-cnb"); private static final ImageReference PLATFORM_CNB = ImageReference .of("docker.io/cloudfoundry/run@sha256:fb5ecb90a42b2067a859aab23fc1f5e9d9c2589d07ba285608879e7baa415aad"); @Test @SuppressWarnings("NullAway") // Test null check void createWhenLogIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> new Builder((BuildLog) null)) .withMessage("'log' must not be null"); } @Test void createWithDockerConfiguration() { assertThatNoException().isThrownBy(() -> new Builder(BuildLog.toSystemOut())); } @Test void createDockerApiWithLogDockerLogDelegate() { Builder builder = new Builder(BuildLog.toSystemOut()); assertThat(builder).extracting("docker") .extracting("system") .extracting("log") .isInstanceOf(BuildLogAdapter.class); } @Test void createDockerApiWithLogDockerSystemOutDelegate() { Builder builder = new Builder(mock(BuildLog.class)); assertThat(builder).extracting("docker") .extracting("system") .extracting("log") .isInstanceOf(DockerLog.toSystemOut().getClass()); } @Test @SuppressWarnings("NullAway") // Test null check void buildWhenRequestIsNullThrowsException() { Builder builder = new Builder(); assertThatIllegalArgumentException().isThrownBy(() -> builder.build(null)) .withMessage("'request' must not be null"); } @Test void buildInvokesBuilder() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest(); builder.build(request); assertThat(out.toString()).contains("Running creator"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull()); then(docker.image()).should().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull()); then(docker.image()).should().load(archive.capture(), any()); ImageReference tag = archive.getValue().getTag(); assertThat(tag).isNotNull(); then(docker.image()).should().remove(tag, true); then(docker.image()).shouldHaveNoMoreInteractions(); } @Test void buildInvokesBuilderAndPublishesImage() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); DockerRegistryAuthentication builderToken = DockerRegistryAuthentication.token("builder token"); DockerRegistryAuthentication publishToken = DockerRegistryAuthentication.token("publish token"); BuilderDockerConfiguration dockerConfiguration = new BuilderDockerConfiguration() .withBuilderRegistryAuthentication(builderToken) .withPublishRegistryAuthentication(publishToken); given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), regAuthEq(builderToken))) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), regAuthEq(builderToken))) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration); BuildRequest request = getTestRequest().withPublish(true); builder.build(request); assertThat(out.toString()).contains("Running creator"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().pull(eq(DEFAULT_BUILDER), isNull(), any(), regAuthEq(builderToken)); then(docker.image()).should() .pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), regAuthEq(builderToken)); then(docker.image()).should().push(eq(request.getName()), any(), regAuthEq(publishToken)); then(docker.image()).should().load(archive.capture(), any()); ImageReference tag = archive.getValue().getTag(); assertThat(tag).isNotNull(); then(docker.image()).should().remove(tag, true); then(docker.image()).shouldHaveNoMoreInteractions(); } @Test void buildInvokesBuilderWithDefaultImageTags() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image-with-no-run-image-tag.json"); Image runImage = loadImage("run-image.json"); given(docker.image().pull(eq(LATEST_PAKETO_BUILDPACKS_BUILDER), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image() .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:latest")), eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest().withBuilder(PAKETO_BUILDPACKS_BUILDER); builder.build(request); assertThat(out.toString()).contains("Running creator"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().load(archive.capture(), any()); ImageReference tag = archive.getValue().getTag(); assertThat(tag).isNotNull(); then(docker.image()).should().remove(tag, true); } @Test void buildInvokesBuilderWithRunImageInDigestForm() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image-with-run-image-digest.json"); Image runImage = loadImage("run-image.json"); given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image() .pull(eq(ImageReference .of("docker.io/cloudfoundry/run@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")), eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest(); builder.build(request); assertThat(out.toString()).contains("Running creator"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().load(archive.capture(), any()); ImageReference tag = archive.getValue().getTag(); assertThat(tag).isNotNull(); then(docker.image()).should().remove(tag, true); } @Test void buildInvokesBuilderWithNoStack() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image-with-empty-stack.json"); Image runImage = loadImage("run-image.json"); given(docker.image().pull(eq(LATEST_PAKETO_BUILDPACKS_BUILDER), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest().withBuilder(PAKETO_BUILDPACKS_BUILDER); builder.build(request); assertThat(out.toString()).contains("Running creator"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().load(archive.capture(), any()); ImageReference tag = archive.getValue().getTag(); assertThat(tag).isNotNull(); then(docker.image()).should().remove(tag, true); } @Test void buildInvokesBuilderWithRunImageFromRequest() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image() .pull(eq(ImageReference.of("example.com/custom/run:latest")), eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest().withRunImage(ImageReference.of("example.com/custom/run:latest")); builder.build(request); assertThat(out.toString()).contains("Running creator"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().load(archive.capture(), any()); ImageReference tag = archive.getValue().getTag(); assertThat(tag).isNotNull(); then(docker.image()).should().remove(tag, true); } @Test void buildInvokesBuilderWithNeverPullPolicy() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); given(docker.image().inspect(eq(DEFAULT_BUILDER), any())).willReturn(builderImage); given(docker.image().inspect(eq(BASE_CNB), any())).willReturn(runImage); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest().withPullPolicy(PullPolicy.NEVER); builder.build(request); assertThat(out.toString()).contains("Running creator"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().load(archive.capture(), any()); ImageReference tag = archive.getValue().getTag(); assertThat(tag).isNotNull(); then(docker.image()).should().remove(tag, true); then(docker.image()).should(never()).pull(any(), any(), any()); then(docker.image()).should(times(2)).inspect(any(), any()); } @Test void buildInvokesBuilderWithAlwaysPullPolicy() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); given(docker.image().inspect(eq(DEFAULT_BUILDER))).willReturn(builderImage); given(docker.image().inspect(eq(BASE_CNB))).willReturn(runImage); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest().withPullPolicy(PullPolicy.ALWAYS); builder.build(request); assertThat(out.toString()).contains("Running creator"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().load(archive.capture(), any()); ImageReference tag = archive.getValue().getTag(); assertThat(tag).isNotNull(); then(docker.image()).should().remove(tag, true); then(docker.image()).should(times(2)).pull(any(), any(), any(), isNull()); then(docker.image()).should(never()).inspect(any()); } @Test void buildInvokesBuilderWithIfNotPresentPullPolicy() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); given(docker.image().inspect(eq(DEFAULT_BUILDER), any())) .willThrow(new TestDockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null, null, null)) .willReturn(builderImage); given(docker.image().inspect(eq(BASE_CNB), any())) .willThrow(new TestDockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null, null, null)) .willReturn(runImage); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest().withPullPolicy(PullPolicy.IF_NOT_PRESENT); builder.build(request); assertThat(out.toString()).contains("Running creator"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().load(archive.capture(), any()); ImageReference tag = archive.getValue().getTag(); assertThat(tag).isNotNull(); then(docker.image()).should().remove(tag, true); then(docker.image()).should(times(2)).inspect(any(), any()); then(docker.image()).should(times(2)).pull(any(), any(), any(), isNull()); } @Test void buildInvokesBuilderWithTags() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest().withTags(ImageReference.of("my-application:1.2.3")); builder.build(request); assertThat(out.toString()).contains("Running creator"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); assertThat(out.toString()).contains("Successfully created image tag 'docker.io/library/my-application:1.2.3'"); then(docker.image()).should().tag(eq(request.getName()), eq(ImageReference.of("my-application:1.2.3"))); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().load(archive.capture(), any()); ImageReference tag = archive.getValue().getTag(); assertThat(tag).isNotNull(); then(docker.image()).should().remove(tag, true); } @Test void buildInvokesBuilderWithTagsAndPublishesImageAndTags() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); DockerRegistryAuthentication builderToken = DockerRegistryAuthentication.token("builder token"); DockerRegistryAuthentication publishToken = DockerRegistryAuthentication.token("publish token"); BuilderDockerConfiguration dockerConfiguration = new BuilderDockerConfiguration() .withBuilderRegistryAuthentication(builderToken) .withPublishRegistryAuthentication(publishToken); ImageReference defaultBuilderImageReference = DEFAULT_BUILDER; given(docker.image().pull(eq(defaultBuilderImageReference), isNull(), any(), regAuthEq(builderToken))) .willAnswer(withPulledImage(builderImage)); ImageReference baseImageReference = BASE_CNB; given(docker.image() .pull(eq(baseImageReference), eq(ImagePlatform.from(builderImage)), any(), regAuthEq(builderToken))) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration); ImageReference builtImageReference = ImageReference.of("my-application:1.2.3"); BuildRequest request = getTestRequest().withPublish(true).withTags(builtImageReference); builder.build(request); assertThat(out.toString()).contains("Running creator"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); assertThat(out.toString()).contains("Successfully created image tag 'docker.io/library/my-application:1.2.3'"); then(docker.image()).should().pull(eq(defaultBuilderImageReference), isNull(), any(), regAuthEq(builderToken)); then(docker.image()).should() .pull(eq(baseImageReference), eq(ImagePlatform.from(builderImage)), any(), regAuthEq(builderToken)); then(docker.image()).should().push(eq(request.getName()), any(), regAuthEq(publishToken)); then(docker.image()).should().tag(eq(request.getName()), eq(builtImageReference)); then(docker.image()).should().push(eq(builtImageReference), any(), regAuthEq(publishToken)); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().load(archive.capture(), any()); ImageReference tag = archive.getValue().getTag(); assertThat(tag).isNotNull(); then(docker.image()).should().remove(tag, true); then(docker.image()).shouldHaveNoMoreInteractions(); } @Test void buildInvokesBuilderWithPlatform() throws Exception { TestPrintStream out = new TestPrintStream(); ImagePlatform platform = ImagePlatform.of("linux/arm64/v1"); DockerApi docker = mockDockerApi(platform); Image builderImage = loadImage("image-with-platform.json"); Image runImage = loadImage("run-image-with-platform.json"); given(docker.image().pull(eq(DEFAULT_BUILDER), eq(platform), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(BASE_CNB), eq(platform), any(), isNull())).willAnswer(withPulledImage(runImage)); given(docker.image().pull(eq(PLATFORM_CNB), eq(platform), any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest().withImagePlatform("linux/arm64/v1"); builder.build(request); assertThat(out.toString()).contains("Running creator"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().pull(eq(DEFAULT_BUILDER), eq(platform), any(), isNull()); then(docker.image()).should().pull(eq(BASE_CNB), eq(platform), any(), isNull()); then(docker.image()).should().pull(eq(PLATFORM_CNB), eq(platform), any(), isNull()); then(docker.image()).should().load(archive.capture(), any()); ImageReference tag = archive.getValue().getTag(); assertThat(tag).isNotNull(); then(docker.image()).should().remove(tag, true); then(docker.image()).shouldHaveNoMoreInteractions(); } @Test void buildWhenStackIdDoesNotMatchThrowsException() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image-with-bad-stack.json"); given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest(); assertThatIllegalStateException().isThrownBy(() -> builder.build(request)) .withMessage( "Run image stack 'org.cloudfoundry.stacks.cfwindowsfs3' does not match builder stack 'io.buildpacks.stacks.bionic'"); } @Test void buildWhenBuilderReturnsErrorThrowsException() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApiLifecycleError(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest(); assertThatExceptionOfType(BuilderException.class).isThrownBy(() -> builder.build(request)) .withMessage("Builder lifecycle 'creator' failed with status code 9"); } @Test void buildWhenRequestedBuildpackNotInBuilderThrowsException() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApiLifecycleError(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); given(docker.image().pull(eq(DEFAULT_BUILDER), any(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(BASE_CNB), any(), any(), isNull())).willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:example/buildpack@1.2.3"); BuildRequest request = getTestRequest().withBuildpacks(reference); assertThatIllegalStateException().isThrownBy(() -> builder.build(request)) .withMessageContaining("'urn:cnb:builder:example/buildpack@1.2.3'") .withMessageContaining("not found in builder"); } @Test void logsWarningIfBindingWithSensitiveTargetIsDetected() throws IOException { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); Image runImage = loadImage("run-image.json"); given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest().withBindings(Binding.from("/host", "/cnb")); builder.build(request); assertThat(out.toString()).contains( "Warning: Binding '/host:/cnb' uses a container path which is used by buildpacks while building. Binding to it can cause problems!"); } private DockerApi mockDockerApi() throws IOException { return mockDockerApi(null); } private DockerApi mockDockerApi(@Nullable ImagePlatform platform) throws IOException { ContainerApi containerApi = mock(ContainerApi.class); ContainerReference reference = ContainerReference.of("container-ref"); given(containerApi.create(any(), eq(platform), any())).willReturn(reference); given(containerApi.wait(eq(reference))).willReturn(ContainerStatus.of(0, null)); ImageApi imageApi = mock(ImageApi.class); VolumeApi volumeApi = mock(VolumeApi.class); DockerApi docker = mock(DockerApi.class); given(docker.image()).willReturn(imageApi); given(docker.container()).willReturn(containerApi); given(docker.volume()).willReturn(volumeApi); return docker; } private DockerApi mockDockerApiLifecycleError() throws IOException { ContainerApi containerApi = mock(ContainerApi.class); ContainerReference reference = ContainerReference.of("container-ref"); given(containerApi.create(any(), isNull(), any())).willReturn(reference); given(containerApi.wait(eq(reference))).willReturn(ContainerStatus.of(9, null)); ImageApi imageApi = mock(ImageApi.class); VolumeApi volumeApi = mock(VolumeApi.class); DockerApi docker = mock(DockerApi.class); given(docker.image()).willReturn(imageApi); given(docker.container()).willReturn(containerApi); given(docker.volume()).willReturn(volumeApi); return docker; } private BuildRequest getTestRequest() { TarArchive content = mock(TarArchive.class); ImageReference name = ImageReference.of("my-application"); return BuildRequest.of(name, (owner) -> content).withTrustBuilder(true); } private Image loadImage(String name) throws IOException { return Image.of(getClass().getResourceAsStream(name)); } private Answer withPulledImage(Image image) { return (invocation) -> { TotalProgressPullListener listener = invocation.getArgument(2, TotalProgressPullListener.class); listener.onStart(); listener.onFinish(); return image; }; } private static String regAuthEq(DockerRegistryAuthentication authentication) { return argThat((arg) -> Objects.equals(authentication.getAuthHeader(), arg)); } static class TestPrintStream extends PrintStream { TestPrintStream() { super(new ByteArrayOutputStream()); } @Override public String toString() { return this.out.toString(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildpackCoordinatesTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link BuildpackCoordinates}. * * @author Scott Frederick * @author Phillip Webb */ class BuildpackCoordinatesTests extends AbstractJsonTests { private final Path archive = Paths.get("/buildpack/path"); @Test void fromToml() throws IOException { BuildpackCoordinates coordinates = BuildpackCoordinates .fromToml(createTomlStream("example/buildpack1", "0.0.1", true, false), this.archive); assertThat(coordinates.getId()).isEqualTo("example/buildpack1"); assertThat(coordinates.getVersion()).isEqualTo("0.0.1"); } @Test void fromTomlWhenMissingDescriptorThrowsException() { ByteArrayInputStream coordinates = new ByteArrayInputStream("".getBytes()); assertThatIllegalArgumentException().isThrownBy(() -> BuildpackCoordinates.fromToml(coordinates, this.archive)) .withMessageContaining("Buildpack descriptor 'buildpack.toml' is required") .withMessageContaining(this.archive.toString()); } @Test void fromTomlWhenMissingIDThrowsException() throws IOException { try (InputStream coordinates = createTomlStream(null, null, true, false)) { assertThatIllegalArgumentException() .isThrownBy(() -> BuildpackCoordinates.fromToml(coordinates, this.archive)) .withMessageContaining("Buildpack descriptor must contain ID") .withMessageContaining(this.archive.toString()); } } @Test void fromTomlWhenMissingVersionThrowsException() throws IOException { try (InputStream coordinates = createTomlStream("example/buildpack1", null, true, false)) { assertThatIllegalArgumentException() .isThrownBy(() -> BuildpackCoordinates.fromToml(coordinates, this.archive)) .withMessageContaining("Buildpack descriptor must contain version") .withMessageContaining(this.archive.toString()); } } @Test void fromTomlWhenMissingStacksAndOrderThrowsException() throws IOException { try (InputStream coordinates = createTomlStream("example/buildpack1", "0.0.1", false, false)) { assertThatIllegalArgumentException() .isThrownBy(() -> BuildpackCoordinates.fromToml(coordinates, this.archive)) .withMessageContaining("Buildpack descriptor must contain either 'stacks' or 'order'") .withMessageContaining(this.archive.toString()); } } @Test void fromTomlWhenContainsBothStacksAndOrderThrowsException() throws IOException { try (InputStream coordinates = createTomlStream("example/buildpack1", "0.0.1", true, true)) { assertThatIllegalArgumentException() .isThrownBy(() -> BuildpackCoordinates.fromToml(coordinates, this.archive)) .withMessageContaining("Buildpack descriptor must not contain both 'stacks' and 'order'") .withMessageContaining(this.archive.toString()); } } @Test @SuppressWarnings("NullAway") // Test null check void fromBuildpackMetadataWhenMetadataIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> BuildpackCoordinates.fromBuildpackMetadata(null)) .withMessage("'buildpackMetadata' must not be null"); } @Test void fromBuildpackMetadataReturnsCoordinates() throws Exception { BuildpackMetadata metadata = BuildpackMetadata.fromJson(getContentAsString("buildpack-metadata.json")); BuildpackCoordinates coordinates = BuildpackCoordinates.fromBuildpackMetadata(metadata); assertThat(coordinates.getId()).isEqualTo("example/hello-universe"); assertThat(coordinates.getVersion()).isEqualTo("0.0.1"); } @Test @SuppressWarnings("NullAway") // Test null check void ofWhenIdIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> BuildpackCoordinates.of(null, null)) .withMessage("'id' must not be empty"); } @Test void ofReturnsCoordinates() { BuildpackCoordinates coordinates = BuildpackCoordinates.of("id", "1"); assertThat(coordinates).hasToString("id@1"); } @Test void getIdReturnsId() { BuildpackCoordinates coordinates = BuildpackCoordinates.of("id", "1"); assertThat(coordinates.getId()).isEqualTo("id"); } @Test void getVersionReturnsVersion() { BuildpackCoordinates coordinates = BuildpackCoordinates.of("id", "1"); assertThat(coordinates.getVersion()).isEqualTo("1"); } @Test void getVersionWhenVersionIsNullReturnsNull() { BuildpackCoordinates coordinates = BuildpackCoordinates.of("id", null); assertThat(coordinates.getVersion()).isNull(); } @Test void toStringReturnsNiceString() { BuildpackCoordinates coordinates = BuildpackCoordinates.of("id", "1"); assertThat(coordinates).hasToString("id@1"); } @Test void equalsAndHashCode() { BuildpackCoordinates c1a = BuildpackCoordinates.of("id", "1"); BuildpackCoordinates c1b = BuildpackCoordinates.of("id", "1"); BuildpackCoordinates c2 = BuildpackCoordinates.of("id", "2"); assertThat(c1a).isEqualTo(c1a).isEqualTo(c1b).isNotEqualTo(c2); assertThat(c1a).hasSameHashCodeAs(c1b); } private InputStream createTomlStream(@Nullable String id, @Nullable String version, boolean includeStacks, boolean includeOrder) { StringBuilder builder = new StringBuilder(); builder.append("[buildpack]\n"); if (id != null) { builder.append("id = \"").append(id).append("\"\n"); } if (version != null) { builder.append("version = \"").append(version).append("\"\n"); } builder.append("name = \"Example buildpack\"\n"); builder.append("homepage = \"https://github.com/example/example-buildpack\"\n"); if (includeStacks) { builder.append("[[stacks]]\n"); builder.append("id = \"io.buildpacks.stacks.bionic\"\n"); } if (includeOrder) { builder.append("[[order]]\n"); builder.append("group = [ { id = \"example/buildpack2\", version=\"0.0.2\" } ]\n"); } return new ByteArrayInputStream(builder.toString().getBytes(StandardCharsets.UTF_8)); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildpackLayersMetadataTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.util.Collections; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link BuildpackLayersMetadata}. * * @author Scott Frederick */ class BuildpackLayersMetadataTests extends AbstractJsonTests { @Test void fromImageLoadsMetadata() throws IOException { Image image = Image.of(getContent("buildpack-image.json")); BuildpackLayersMetadata metadata = BuildpackLayersMetadata.fromImage(image); assertThat(metadata.getBuildpack("example/hello-moon", "0.0.3")).extracting("homepage", "layerDiffId") .containsExactly("https://github.com/example/tree/main/buildpacks/hello-moon", "sha256:4bfdc8714aee68da6662c43bc28d3b41202c88e915641c356523dabe729814c2"); assertThat(metadata.getBuildpack("example/hello-world", "0.0.2")).extracting("homepage", "layerDiffId") .containsExactly("https://github.com/example/tree/main/buildpacks/hello-world", "sha256:f752fe099c846e501bdc991d1a22f98c055ddc62f01cfc0495fff2c69f8eb940"); assertThat(metadata.getBuildpack("example/hello-world", "version-does-not-exist")).isNull(); assertThat(metadata.getBuildpack("id-does-not-exist", "9.9.9")).isNull(); } @Test @SuppressWarnings("NullAway") // Test null check void fromImageWhenImageIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> BuildpackLayersMetadata.fromImage(null)) .withMessage("'image' must not be null"); } @Test void fromImageWhenImageConfigIsNullThrowsException() { Image image = mock(Image.class); assertThatIllegalArgumentException().isThrownBy(() -> BuildpackLayersMetadata.fromImage(image)) .withMessage("'imageConfig' must not be null"); } @Test void fromImageConfigWhenLabelIsMissingThrowsException() { Image image = mock(Image.class); ImageConfig imageConfig = mock(ImageConfig.class); given(image.getConfig()).willReturn(imageConfig); given(imageConfig.getLabels()).willReturn(Collections.singletonMap("alpha", "a")); assertThatIllegalStateException().isThrownBy(() -> BuildpackLayersMetadata.fromImage(image)) .withMessage("No 'io.buildpacks.buildpack.layers' label found in image config labels 'alpha'"); } @Test void fromJsonLoadsMetadata() throws IOException { BuildpackLayersMetadata metadata = BuildpackLayersMetadata .fromJson(getContentAsString("buildpack-layers-metadata.json")); assertThat(metadata.getBuildpack("example/hello-moon", "0.0.3")).extracting("name", "homepage", "layerDiffId") .containsExactly("Example hello-moon buildpack", "https://github.com/example/tree/main/buildpacks/hello-moon", "sha256:4bfdc8714aee68da6662c43bc28d3b41202c88e915641c356523dabe729814c2"); assertThat(metadata.getBuildpack("example/hello-world", "0.0.1")).extracting("name", "homepage", "layerDiffId") .containsExactly("Example hello-world buildpack", "https://github.com/example/tree/main/buildpacks/hello-world", "sha256:1c90e0b80d92555a0523c9ee6500845328fc39ba9dca9d30a877ff759ffbff28"); assertThat(metadata.getBuildpack("example/hello-world", "0.0.2")).extracting("name", "homepage", "layerDiffId") .containsExactly("Example hello-world buildpack", "https://github.com/example/tree/main/buildpacks/hello-world", "sha256:f752fe099c846e501bdc991d1a22f98c055ddc62f01cfc0495fff2c69f8eb940"); assertThat(metadata.getBuildpack("example/hello-world", "version-does-not-exist")).isNull(); assertThat(metadata.getBuildpack("id-does-not-exist", "9.9.9")).isNull(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildpackMetadataTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import java.util.Collections; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link BuildpackMetadata}. * * @author Scott Frederick */ class BuildpackMetadataTests extends AbstractJsonTests { @Test void fromImageLoadsMetadata() throws IOException { Image image = Image.of(getContent("buildpack-image.json")); BuildpackMetadata metadata = BuildpackMetadata.fromImage(image); assertThat(metadata.getId()).isEqualTo("example/hello-universe"); assertThat(metadata.getVersion()).isEqualTo("0.0.1"); } @Test @SuppressWarnings("NullAway") // Test null check void fromImageWhenImageIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> BuildpackMetadata.fromImage(null)) .withMessage("'image' must not be null"); } @Test void fromImageWhenImageConfigIsNullThrowsException() { Image image = mock(Image.class); assertThatIllegalArgumentException().isThrownBy(() -> BuildpackMetadata.fromImage(image)) .withMessage("'imageConfig' must not be null"); } @Test void fromImageConfigWhenLabelIsMissingThrowsException() { Image image = mock(Image.class); ImageConfig imageConfig = mock(ImageConfig.class); given(image.getConfig()).willReturn(imageConfig); given(imageConfig.getLabels()).willReturn(Collections.singletonMap("alpha", "a")); assertThatIllegalStateException().isThrownBy(() -> BuildpackMetadata.fromImage(image)) .withMessage("No 'io.buildpacks.buildpackage.metadata' label found in image config labels 'alpha'"); } @Test void fromJsonLoadsMetadata() throws IOException { BuildpackMetadata metadata = BuildpackMetadata.fromJson(getContentAsString("buildpack-metadata.json")); assertThat(metadata.getId()).isEqualTo("example/hello-universe"); assertThat(metadata.getVersion()).isEqualTo("0.0.1"); assertThat(metadata.getHomepage()).isEqualTo("https://github.com/example/tree/main/buildpacks/hello-universe"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildpackReferenceTests.java ================================================ /* * 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.buildpack.platform.build; import java.nio.file.Paths; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link BuildpackReference}. * * @author Phillip Webb */ class BuildpackReferenceTests { @Test void ofWhenValueIsEmptyThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> BuildpackReference.of("")) .withMessage("'value' must not be empty"); } @Test void ofCreatesInstance() { BuildpackReference reference = BuildpackReference.of("test"); assertThat(reference).isNotNull(); } @Test void toStringReturnsValue() { BuildpackReference reference = BuildpackReference.of("test"); assertThat(reference).hasToString("test"); } @Test void equalsAndHashCode() { BuildpackReference a = BuildpackReference.of("test1"); BuildpackReference b = BuildpackReference.of("test1"); BuildpackReference c = BuildpackReference.of("test2"); assertThat(a).isEqualTo(a).isEqualTo(b).isNotEqualTo(c); assertThat(a).hasSameHashCodeAs(b); } @Test void hasPrefixWhenPrefixMatchReturnsTrue() { BuildpackReference reference = BuildpackReference.of("test"); assertThat(reference.hasPrefix("te")).isTrue(); } @Test void hasPrefixWhenPrefixMismatchReturnsFalse() { BuildpackReference reference = BuildpackReference.of("test"); assertThat(reference.hasPrefix("st")).isFalse(); } @Test void getSubReferenceWhenPrefixMatchReturnsSubReference() { BuildpackReference reference = BuildpackReference.of("test"); assertThat(reference.getSubReference("te")).isEqualTo("st"); } @Test void getSubReferenceWhenPrefixMismatchReturnsNull() { BuildpackReference reference = BuildpackReference.of("test"); assertThat(reference.getSubReference("st")).isNull(); } @Test void asPathWhenFileUrlReturnsPath() { BuildpackReference reference = BuildpackReference.of("file:///test.dat"); assertThat(reference.asPath()).isEqualTo(Paths.get("/test.dat")); } @Test void asPathWhenPathReturnsPath() { BuildpackReference reference = BuildpackReference.of("/test.dat"); assertThat(reference.asPath()).isEqualTo(Paths.get("/test.dat")); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildpackResolversTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link BuildpackResolvers}. * * @author Scott Frederick */ class BuildpackResolversTests extends AbstractJsonTests { private BuildpackResolverContext resolverContext; @BeforeEach void setup() throws Exception { BuilderMetadata metadata = BuilderMetadata.fromJson(getContentAsString("builder-metadata.json")); this.resolverContext = mock(BuildpackResolverContext.class); given(this.resolverContext.getBuildpackMetadata()).willReturn(metadata.getBuildpacks()); } @Test void resolveAllWithBuilderBuildpackReferenceReturnsExpectedBuildpack() { BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:paketo-buildpacks/spring-boot@3.5.0"); Buildpacks buildpacks = BuildpackResolvers.resolveAll(this.resolverContext, Collections.singleton(reference)); assertThat(buildpacks.getBuildpacks()).hasSize(1); assertThat(buildpacks.getBuildpacks().get(0)).isInstanceOf(BuilderBuildpack.class); } @Test void resolveAllWithDirectoryBuildpackReferenceReturnsExpectedBuildpack(@TempDir Path temp) throws IOException { FileCopyUtils.copy(getClass().getResourceAsStream("buildpack.toml"), Files.newOutputStream(temp.resolve("buildpack.toml"))); BuildpackReference reference = BuildpackReference.of(temp.toAbsolutePath().toString()); Buildpacks buildpacks = BuildpackResolvers.resolveAll(this.resolverContext, Collections.singleton(reference)); assertThat(buildpacks.getBuildpacks()).hasSize(1); assertThat(buildpacks.getBuildpacks().get(0)).isInstanceOf(DirectoryBuildpack.class); } @Test void resolveAllWithTarGzipBuildpackReferenceReturnsExpectedBuildpack(@TempDir File temp) throws Exception { TestTarGzip testTarGzip = new TestTarGzip(temp); Path archive = testTarGzip.createArchive(); BuildpackReference reference = BuildpackReference.of(archive.toString()); Buildpacks buildpacks = BuildpackResolvers.resolveAll(this.resolverContext, Collections.singleton(reference)); assertThat(buildpacks.getBuildpacks()).hasSize(1); assertThat(buildpacks.getBuildpacks().get(0)).isInstanceOf(TarGzipBuildpack.class); } @Test void resolveAllWithImageBuildpackReferenceReturnsExpectedBuildpack() throws IOException { Image image = Image.of(getContent("buildpack-image.json")); BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class); given(resolverContext.getBuildpackLayersMetadata()).willReturn(BuildpackLayersMetadata.fromJson("{}")); given(resolverContext.fetchImage(any(), any())).willReturn(image); BuildpackReference reference = BuildpackReference.of("docker://example/buildpack1:latest"); Buildpacks buildpacks = BuildpackResolvers.resolveAll(resolverContext, Collections.singleton(reference)); assertThat(buildpacks.getBuildpacks()).hasSize(1); assertThat(buildpacks.getBuildpacks().get(0)).isInstanceOf(ImageBuildpack.class); } @Test void resolveAllWithInvalidLocatorThrowsException() { BuildpackReference reference = BuildpackReference.of("unknown-buildpack@0.0.1"); assertThatIllegalArgumentException() .isThrownBy(() -> BuildpackResolvers.resolveAll(this.resolverContext, Collections.singleton(reference))) .withMessageContaining("Invalid buildpack reference") .withMessageContaining("'unknown-buildpack@0.0.1'"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildpacksTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.type.Layer; import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link Buildpacks}. * * @author Scott Frederick * @author Phillip Webb */ class BuildpacksTests { @Test void ofWhenBuildpacksIsNullReturnsEmpty() { Buildpacks buildpacks = Buildpacks.of(null); assertThat(buildpacks).isSameAs(Buildpacks.EMPTY); assertThat(buildpacks.getBuildpacks()).isEmpty(); } @Test void ofReturnsBuildpacks() { List buildpackList = new ArrayList<>(); buildpackList.add(new TestBuildpack("example/buildpack1", "0.0.1")); buildpackList.add(new TestBuildpack("example/buildpack2", "0.0.2")); Buildpacks buildpacks = Buildpacks.of(buildpackList); assertThat(buildpacks.getBuildpacks()).isEqualTo(buildpackList); } @Test void applyWritesLayersAndOrderLayer() throws Exception { List buildpackList = new ArrayList<>(); buildpackList.add(new TestBuildpack("example/buildpack1", "0.0.1")); buildpackList.add(new TestBuildpack("example/buildpack2", "0.0.2")); buildpackList.add(new TestBuildpack("example/buildpack3", null)); Buildpacks buildpacks = Buildpacks.of(buildpackList); List layers = new ArrayList<>(); buildpacks.apply(layers::add); assertThat(layers).hasSize(4); assertThatLayerContentIsCorrect(layers.get(0), "example_buildpack1/0.0.1"); assertThatLayerContentIsCorrect(layers.get(1), "example_buildpack2/0.0.2"); assertThatLayerContentIsCorrect(layers.get(2), "example_buildpack3/null"); assertThatOrderLayerContentIsCorrect(layers.get(3)); } private void assertThatLayerContentIsCorrect(Layer layer, String path) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); layer.writeTo(out); try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(out.toByteArray()))) { assertThat(tar.getNextEntry().getName()).isEqualTo("/cnb/buildpacks/" + path + "/buildpack.toml"); assertThat(tar.getNextEntry()).isNull(); } } private void assertThatOrderLayerContentIsCorrect(Layer layer) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); layer.writeTo(out); try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(out.toByteArray()))) { assertThat(tar.getNextEntry().getName()).isEqualTo("/cnb/order.toml"); byte[] content = StreamUtils.copyToByteArray(tar); String toml = new String(content, StandardCharsets.UTF_8); assertThat(toml).isEqualTo(getExpectedToml()); } } private String getExpectedToml() { StringBuilder toml = new StringBuilder(); toml.append("[[order]]\n"); toml.append("\n"); toml.append(" [[order.group]]\n"); toml.append(" id = \"example/buildpack1\"\n"); toml.append(" version = \"0.0.1\"\n"); toml.append("\n"); toml.append(" [[order.group]]\n"); toml.append(" id = \"example/buildpack2\"\n"); toml.append(" version = \"0.0.2\"\n"); toml.append("\n"); toml.append(" [[order.group]]\n"); toml.append(" id = \"example/buildpack3\"\n"); toml.append("\n"); return toml.toString(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/DirectoryBuildpackTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermissions; import java.util.ArrayList; import java.util.List; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.tuple; import static org.mockito.Mockito.mock; /** * Tests for {@link DirectoryBuildpack}. * * @author Scott Frederick */ @DisabledOnOs(OS.WINDOWS) class DirectoryBuildpackTests { @TempDir @SuppressWarnings("NullAway.Init") File temp; private File buildpackDir; private BuildpackResolverContext resolverContext; @BeforeEach void setUp() { this.buildpackDir = new File(this.temp, "buildpack"); this.buildpackDir.mkdirs(); this.resolverContext = mock(BuildpackResolverContext.class); } @Test void resolveWhenPath() throws Exception { writeBuildpackDescriptor(); writeScripts(); BuildpackReference reference = BuildpackReference.of(this.buildpackDir.toString()); Buildpack buildpack = DirectoryBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNotNull(); assertThat(buildpack.getCoordinates()).hasToString("example/buildpack1@0.0.1"); assertHasExpectedLayers(buildpack); } @Test void resolveWhenFileUrl() throws Exception { writeBuildpackDescriptor(); writeScripts(); BuildpackReference reference = BuildpackReference.of("file://" + this.buildpackDir.toString()); Buildpack buildpack = DirectoryBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNotNull(); assertThat(buildpack.getCoordinates()).hasToString("example/buildpack1@0.0.1"); assertHasExpectedLayers(buildpack); } @Test void resolveWhenDirectoryWithoutBuildpackTomlThrowsException() throws Exception { Files.createDirectories(this.buildpackDir.toPath()); BuildpackReference reference = BuildpackReference.of(this.buildpackDir.toString()); assertThatIllegalStateException().isThrownBy(() -> DirectoryBuildpack.resolve(this.resolverContext, reference)) .withMessageContaining("Buildpack descriptor 'buildpack.toml' is required") .withMessageContaining(this.buildpackDir.getAbsolutePath()); } @Test void resolveWhenFileReturnsNull() throws Exception { Path file = Files.createFile(Paths.get(this.buildpackDir.toString(), "test")); BuildpackReference reference = BuildpackReference.of(file.toString()); Buildpack buildpack = DirectoryBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNull(); } @Test void resolveWhenDirectoryDoesNotExistReturnsNull() { BuildpackReference reference = BuildpackReference.of("/test/a/missing/buildpack"); Buildpack buildpack = DirectoryBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNull(); } @Test void locateDirectoryAsUrlThatDoesNotExistThrowsException() { BuildpackReference reference = BuildpackReference.of("file:///test/a/missing/buildpack"); Buildpack buildpack = DirectoryBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNull(); } private void assertHasExpectedLayers(Buildpack buildpack) throws IOException { List layers = new ArrayList<>(); buildpack.apply((layer) -> { ByteArrayOutputStream out = new ByteArrayOutputStream(); layer.writeTo(out); layers.add(out); }); assertThat(layers).hasSize(1); byte[] content = layers.get(0).toByteArray(); try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(content))) { List entries = new ArrayList<>(); TarArchiveEntry entry = tar.getNextEntry(); while (entry != null) { entries.add(entry); entry = tar.getNextEntry(); } assertThat(entries).extracting("name", "mode") .containsExactlyInAnyOrder(tuple("/cnb/", 0755), tuple("/cnb/buildpacks/", 0755), tuple("/cnb/buildpacks/example_buildpack1/", 0755), tuple("/cnb/buildpacks/example_buildpack1/0.0.1/", 0755), tuple("/cnb/buildpacks/example_buildpack1/0.0.1/buildpack.toml", 0644), tuple("/cnb/buildpacks/example_buildpack1/0.0.1/bin/", 0755), tuple("/cnb/buildpacks/example_buildpack1/0.0.1/bin/detect", 0744), tuple("/cnb/buildpacks/example_buildpack1/0.0.1/bin/build", 0744)); } } private void writeBuildpackDescriptor() throws IOException { Path descriptor = Files.createFile(Paths.get(this.buildpackDir.getAbsolutePath(), "buildpack.toml"), PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-r--r--"))); try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(descriptor))) { writer.println("[buildpack]"); writer.println("id = \"example/buildpack1\""); writer.println("version = \"0.0.1\""); writer.println("name = \"Example buildpack\""); writer.println("homepage = \"https://github.com/example/example-buildpack\""); writer.println("[[stacks]]"); writer.println("id = \"io.buildpacks.stacks.bionic\""); } } private void writeScripts() throws IOException { Path binDirectory = Files.createDirectory(Paths.get(this.buildpackDir.getAbsolutePath(), "bin"), PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-xr-x"))); binDirectory.toFile().mkdirs(); Path detect = Files.createFile(Paths.get(binDirectory.toString(), "detect"), PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr--r--"))); try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(detect))) { writer.println("#!/usr/bin/env bash"); writer.println("echo \"---> detect\""); } Path build = Files.createFile(Paths.get(binDirectory.toString(), "build"), PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr--r--"))); try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(build))) { writer.println("#!/usr/bin/env bash"); writer.println("echo \"---> build\""); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/EphemeralBuilderTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; 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.ImageConfig; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import org.springframework.util.FileCopyUtils; import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; /** * Tests for {@link EphemeralBuilder}. * * @author Phillip Webb * @author Scott Frederick */ class EphemeralBuilderTests extends AbstractJsonTests { private static final int EXISTING_IMAGE_LAYER_COUNT = 43; @TempDir @SuppressWarnings("NullAway.Init") File temp; private final BuildOwner owner = BuildOwner.of(123, 456); private Image image; private ImageReference targetImage; private BuilderMetadata metadata; private Map env; private @Nullable Buildpacks buildpacks; private final Creator creator = Creator.withVersion("dev"); @BeforeEach void setup() throws Exception { this.image = Image.of(getContent("image.json")); this.targetImage = ImageReference.of("my-image:latest"); this.metadata = BuilderMetadata.fromImage(this.image); this.env = new HashMap<>(); this.env.put("spring", "boot"); this.env.put("empty", null); } @Test void getNameHasRandomName() { EphemeralBuilder b1 = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata, this.creator, this.env, this.buildpacks); EphemeralBuilder b2 = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata, this.creator, this.env, this.buildpacks); assertThat(b1.getName().toString()).startsWith("pack.local/builder/").endsWith(":latest"); assertThat(b1.getName().toString()).isNotEqualTo(b2.getName().toString()); } @Test void getArchiveHasCreatedByConfig() throws Exception { EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata, this.creator, this.env, this.buildpacks); ImageConfig config = builder.getArchive(null).getImageConfig(); BuilderMetadata ephemeralMetadata = BuilderMetadata.fromImageConfig(config); assertThat(ephemeralMetadata.getCreatedBy().getName()).isEqualTo("Spring Boot"); assertThat(ephemeralMetadata.getCreatedBy().getVersion()).isEqualTo("dev"); } @Test void getArchiveHasTag() throws Exception { EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata, this.creator, this.env, this.buildpacks); ImageReference tag = builder.getArchive(null).getTag(); assertThat(tag).isNotNull(); assertThat(tag.toString()).startsWith("pack.local/builder/").endsWith(":latest"); } @Test void getArchiveHasFixedCreatedDate() throws Exception { EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata, this.creator, this.env, this.buildpacks); Instant createInstant = builder.getArchive(null).getCreateDate(); OffsetDateTime createDateTime = OffsetDateTime.ofInstant(createInstant, ZoneId.of("UTC")); assertThat(createDateTime.getYear()).isEqualTo(1980); assertThat(createDateTime.getMonthValue()).isOne(); assertThat(createDateTime.getDayOfMonth()).isOne(); assertThat(createDateTime.getHour()).isZero(); assertThat(createDateTime.getMinute()).isZero(); assertThat(createDateTime.getSecond()).isOne(); } @Test void getArchiveContainsEnvLayer() throws Exception { EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata, this.creator, this.env, this.buildpacks); File directory = unpack(getLayer(builder.getArchive(null), EXISTING_IMAGE_LAYER_COUNT), "env"); assertThat(new File(directory, "platform/env/spring")).usingCharset(StandardCharsets.UTF_8).hasContent("boot"); assertThat(new File(directory, "platform/env/empty")).usingCharset(StandardCharsets.UTF_8).hasContent(""); } @Test void getArchiveHasBuilderForLabel() throws Exception { EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata, this.creator, this.env, this.buildpacks); ImageConfig config = builder.getArchive(null).getImageConfig(); assertThat(config.getLabels()) .contains(entry(EphemeralBuilder.BUILDER_FOR_LABEL_NAME, this.targetImage.toString())); } @Test void getArchiveContainsBuildpackLayers() throws Exception { List buildpackList = new ArrayList<>(); buildpackList.add(new TestBuildpack("example/buildpack1", "0.0.1")); buildpackList.add(new TestBuildpack("example/buildpack2", "0.0.2")); buildpackList.add(new TestBuildpack("example/buildpack3", "0.0.3")); this.buildpacks = Buildpacks.of(buildpackList); EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata, this.creator, null, this.buildpacks); assertBuildpackLayerContent(builder, EXISTING_IMAGE_LAYER_COUNT, "/cnb/buildpacks/example_buildpack1/0.0.1/buildpack.toml"); assertBuildpackLayerContent(builder, EXISTING_IMAGE_LAYER_COUNT + 1, "/cnb/buildpacks/example_buildpack2/0.0.2/buildpack.toml"); assertBuildpackLayerContent(builder, EXISTING_IMAGE_LAYER_COUNT + 2, "/cnb/buildpacks/example_buildpack3/0.0.3/buildpack.toml"); File orderDirectory = unpack(getLayer(builder.getArchive(null), EXISTING_IMAGE_LAYER_COUNT + 3), "order"); assertThat(new File(orderDirectory, "cnb/order.toml")).usingCharset(StandardCharsets.UTF_8) .hasContent(content("order.toml")); } @Test void getArchiveHasApplicationDirectoryLayer() throws Exception { EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata, this.creator, this.env, this.buildpacks); File directory = unpack(getLayer(builder.getArchive("/myapp"), EXISTING_IMAGE_LAYER_COUNT + 1), "appdir"); assertThat(new File(directory, "myapp")).isDirectory(); } private void assertBuildpackLayerContent(EphemeralBuilder builder, int index, String s) throws Exception { File buildpackDirectory = unpack(getLayer(builder.getArchive(null), index), "buildpack"); assertThat(new File(buildpackDirectory, s)).usingCharset(StandardCharsets.UTF_8).hasContent("[test]"); } private TarArchiveInputStream getLayer(ImageArchive archive, int index) throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); archive.writeTo(outputStream); TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(outputStream.toByteArray())); for (int i = 0; i <= index; i++) { tar.getNextEntry(); } return new TarArchiveInputStream(tar); } private File unpack(TarArchiveInputStream archive, String name) throws Exception { File directory = new File(this.temp, name); directory.mkdirs(); ArchiveEntry entry = archive.getNextEntry(); while (entry != null) { File file = new File(directory, entry.getName()); if (entry.isDirectory()) { file.mkdirs(); } else { file.getParentFile().mkdirs(); try (OutputStream out = new FileOutputStream(file)) { StreamUtils.copy(archive, out); } } entry = archive.getNextEntry(); } return directory; } private String content(String fileName) throws IOException { InputStream in = getClass().getResourceAsStream(fileName); return FileCopyUtils.copyToString(new InputStreamReader(in, StandardCharsets.UTF_8)); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/ImageBuildpackTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import java.util.Random; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.invocation.InvocationOnMock; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.io.IOBiConsumer; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.io.TarArchive.Compression; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.fail; import static org.assertj.core.api.Assertions.tuple; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; /** * Tests for {@link ImageBuildpack}. * * @author Scott Frederick * @author Phillip Webb */ class ImageBuildpackTests extends AbstractJsonTests { private String longFilePath; @BeforeEach void setUp() { StringBuilder path = new StringBuilder(); new Random().ints('a', 'z' + 1).limit(100).forEach((i) -> path.append((char) i)); this.longFilePath = path.toString(); } @Test void resolveWhenFullyQualifiedReferenceReturnsBuildpack() throws Exception { Image image = Image.of(getContent("buildpack-image.json")); ImageReference imageReference = ImageReference.of("example/buildpack1:1.0.0"); BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class); given(resolverContext.getBuildpackLayersMetadata()).willReturn(BuildpackLayersMetadata.fromJson("{}")); given(resolverContext.fetchImage(eq(imageReference), eq(ImageType.BUILDPACK))).willReturn(image); willAnswer(this::withMockLayers).given(resolverContext).exportImageLayers(eq(imageReference), any()); BuildpackReference reference = BuildpackReference.of("docker://example/buildpack1:1.0.0"); Buildpack buildpack = ImageBuildpack.resolve(resolverContext, reference); assertThat(buildpack).isNotNull(); assertThat(buildpack.getCoordinates()).hasToString("example/hello-universe@0.0.1"); assertAppliesExpectedLayers(buildpack); } @Test void resolveWhenUnqualifiedReferenceReturnsBuildpack() throws Exception { Image image = Image.of(getContent("buildpack-image.json")); ImageReference imageReference = ImageReference.of("example/buildpack1:1.0.0"); BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class); given(resolverContext.getBuildpackLayersMetadata()).willReturn(BuildpackLayersMetadata.fromJson("{}")); given(resolverContext.fetchImage(eq(imageReference), eq(ImageType.BUILDPACK))).willReturn(image); willAnswer(this::withMockLayers).given(resolverContext).exportImageLayers(eq(imageReference), any()); BuildpackReference reference = BuildpackReference.of("example/buildpack1:1.0.0"); Buildpack buildpack = ImageBuildpack.resolve(resolverContext, reference); assertThat(buildpack).isNotNull(); assertThat(buildpack.getCoordinates()).hasToString("example/hello-universe@0.0.1"); assertAppliesExpectedLayers(buildpack); } @Test void resolveReferenceWithoutTagUsesLatestTag() throws Exception { Image image = Image.of(getContent("buildpack-image.json")); ImageReference imageReference = ImageReference.of("example/buildpack1:latest"); BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class); given(resolverContext.getBuildpackLayersMetadata()).willReturn(BuildpackLayersMetadata.fromJson("{}")); given(resolverContext.fetchImage(eq(imageReference), eq(ImageType.BUILDPACK))).willReturn(image); willAnswer(this::withMockLayers).given(resolverContext).exportImageLayers(eq(imageReference), any()); BuildpackReference reference = BuildpackReference.of("example/buildpack1"); Buildpack buildpack = ImageBuildpack.resolve(resolverContext, reference); assertThat(buildpack).isNotNull(); assertThat(buildpack.getCoordinates()).hasToString("example/hello-universe@0.0.1"); assertAppliesExpectedLayers(buildpack); } @Test void resolveReferenceWithDigestUsesDigest() throws Exception { Image image = Image.of(getContent("buildpack-image.json")); String digest = "sha256:4acb6bfd6c4f0cabaf7f3690e444afe51f1c7de54d51da7e63fac709c56f1c30"; ImageReference imageReference = ImageReference.of("example/buildpack1@" + digest); BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class); given(resolverContext.getBuildpackLayersMetadata()).willReturn(BuildpackLayersMetadata.fromJson("{}")); given(resolverContext.fetchImage(eq(imageReference), eq(ImageType.BUILDPACK))).willReturn(image); willAnswer(this::withMockLayers).given(resolverContext).exportImageLayers(eq(imageReference), any()); BuildpackReference reference = BuildpackReference.of("example/buildpack1@" + digest); Buildpack buildpack = ImageBuildpack.resolve(resolverContext, reference); assertThat(buildpack).isNotNull(); assertThat(buildpack.getCoordinates()).hasToString("example/hello-universe@0.0.1"); assertAppliesExpectedLayers(buildpack); } @Test void resolveWhenBuildpackExistsInBuilderSkipsLayers() throws Exception { Image image = Image.of(getContent("buildpack-image.json")); ImageReference imageReference = ImageReference.of("example/buildpack1:1.0.0"); BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class); given(resolverContext.getBuildpackLayersMetadata()) .willReturn(BuildpackLayersMetadata.fromJson(getContentAsString("buildpack-layers-metadata.json"))); given(resolverContext.fetchImage(eq(imageReference), eq(ImageType.BUILDPACK))).willReturn(image); willAnswer(this::withMockLayers).given(resolverContext).exportImageLayers(eq(imageReference), any()); BuildpackReference reference = BuildpackReference.of("docker://example/buildpack1:1.0.0"); Buildpack buildpack = ImageBuildpack.resolve(resolverContext, reference); assertThat(buildpack).isNotNull(); assertThat(buildpack.getCoordinates()).hasToString("example/hello-universe@0.0.1"); assertAppliesNoLayers(buildpack); } @Test void resolveWhenWhenImageNotPulledThrowsException() throws Exception { BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class); given(resolverContext.fetchImage(any(), any())).willThrow(IOException.class); BuildpackReference reference = BuildpackReference.of("docker://example/buildpack1"); assertThatIllegalArgumentException().isThrownBy(() -> ImageBuildpack.resolve(resolverContext, reference)) .withMessageContaining("Error pulling buildpack image") .withMessageContaining("example/buildpack1:latest"); } @Test void resolveWhenMissingMetadataLabelThrowsException() throws Exception { Image image = Image.of(getContent("image.json")); BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class); given(resolverContext.fetchImage(any(), any())).willReturn(image); BuildpackReference reference = BuildpackReference.of("docker://example/buildpack1:latest"); assertThatIllegalStateException().isThrownBy(() -> ImageBuildpack.resolve(resolverContext, reference)) .withMessageContaining("No 'io.buildpacks.buildpackage.metadata' label found"); } @Test void resolveWhenFullyQualifiedReferenceWithInvalidImageReferenceThrowsException() { BuildpackReference reference = BuildpackReference.of("docker://buildpack@0.0.1"); BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class); assertThatIllegalArgumentException().isThrownBy(() -> ImageBuildpack.resolve(resolverContext, reference)) .withMessageContaining("'value' [buildpack@0.0.1] must be an image reference"); } @Test void resolveWhenUnqualifiedReferenceWithInvalidImageReferenceReturnsNull() { BuildpackReference reference = BuildpackReference.of("buildpack@0.0.1"); BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class); Buildpack buildpack = ImageBuildpack.resolve(resolverContext, reference); assertThat(buildpack).isNull(); } private @Nullable Object withMockLayers(InvocationOnMock invocation) { try { IOBiConsumer consumer = invocation.getArgument(1); File tarFile = File.createTempFile("create-builder-test-", null); try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream(new FileOutputStream(tarFile))) { tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); writeTarEntry(tarOut, "/cnb/"); writeTarEntry(tarOut, "/cnb/buildpacks/"); writeTarEntry(tarOut, "/cnb/buildpacks/example_buildpack/"); writeTarEntry(tarOut, "/cnb/buildpacks/example_buildpack/0.0.1/"); writeTarEntry(tarOut, "/cnb/buildpacks/example_buildpack/0.0.1/buildpack.toml"); writeTarEntry(tarOut, "/cnb/buildpacks/example_buildpack/0.0.1/" + this.longFilePath); tarOut.finish(); } try (FileInputStream tarFileStream = new FileInputStream(tarFile)) { consumer.accept("test", TarArchive.fromInputStream(tarFileStream, Compression.NONE)); } Files.delete(tarFile.toPath()); } catch (IOException ex) { fail("Error writing mock layers", ex); } return null; } private void writeTarEntry(TarArchiveOutputStream tarOut, String name) throws IOException { TarArchiveEntry entry = new TarArchiveEntry(name); tarOut.putArchiveEntry(entry); tarOut.closeArchiveEntry(); } private void assertAppliesExpectedLayers(Buildpack buildpack) throws IOException { List layers = new ArrayList<>(); buildpack.apply((layer) -> { ByteArrayOutputStream out = new ByteArrayOutputStream(); layer.writeTo(out); layers.add(out); }); assertThat(layers).hasSize(1); byte[] content = layers.get(0).toByteArray(); List entries = new ArrayList<>(); try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(content))) { TarArchiveEntry entry = tar.getNextEntry(); while (entry != null) { entries.add(entry); entry = tar.getNextEntry(); } } assertThat(entries).extracting("name", "mode") .containsExactlyInAnyOrder(tuple("cnb/", TarArchiveEntry.DEFAULT_DIR_MODE), tuple("cnb/buildpacks/", TarArchiveEntry.DEFAULT_DIR_MODE), tuple("cnb/buildpacks/example_buildpack/", TarArchiveEntry.DEFAULT_DIR_MODE), tuple("cnb/buildpacks/example_buildpack/0.0.1/", TarArchiveEntry.DEFAULT_DIR_MODE), tuple("cnb/buildpacks/example_buildpack/0.0.1/buildpack.toml", TarArchiveEntry.DEFAULT_FILE_MODE), tuple("cnb/buildpacks/example_buildpack/0.0.1/" + this.longFilePath, TarArchiveEntry.DEFAULT_FILE_MODE)); } private void assertAppliesNoLayers(Buildpack buildpack) throws IOException { List layers = new ArrayList<>(); buildpack.apply((layer) -> { ByteArrayOutputStream out = new ByteArrayOutputStream(); layer.writeTo(out); layers.add(out); }); assertThat(layers).isEmpty(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.sun.jna.Platform; import org.json.JSONException; import org.json.JSONObject; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.mockito.stubbing.Answer; import org.skyscreamer.jsonassert.JSONAssert; import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.ArrayNode; 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.ImageApi; 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.ResolvedDockerHost; 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.ContainerContent; 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.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.testsupport.junit.BooleanValueSource; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; /** * Tests for {@link Lifecycle}. * * @author Phillip Webb * @author Scott Frederick * @author Jeroen Meijer */ class LifecycleTests { private TestPrintStream out; private DockerApi docker; private final Map configs = new LinkedHashMap<>(); private final Map content = new LinkedHashMap<>(); @BeforeEach void setup() { this.out = new TestPrintStream(); this.docker = mockDockerApi(); } @ParameterizedTest @BooleanValueSource void executeExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); createLifecycle(trustBuilder).execute(); if (trustBuilder) { assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json")); } else { assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json")); assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json")); assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json")); } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @Test void executeWithBindingsExecutesPhases() throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(true).withBindings(Binding.of("/host/src/path:/container/dest/path:ro"), Binding.of("volume-name:/container/volume/path:rw")); createLifecycle(request).execute(); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-bindings.json")); assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @Test void executeExecutesPhasesWithPlatformApi03() throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); createLifecycle(true, "builder-metadata-platform-api-0.3.json").execute(); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-platform-api-0.3.json")); assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @ParameterizedTest @BooleanValueSource void executeOnlyUploadsContentOnce(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); createLifecycle(trustBuilder).execute(); assertThat(this.content).hasSize(1); } @ParameterizedTest @BooleanValueSource void executeWhenAlreadyRunThrowsException(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); Lifecycle lifecycle = createLifecycle(trustBuilder); lifecycle.execute(); assertThatIllegalStateException().isThrownBy(lifecycle::execute) .withMessage("Lifecycle has already been executed"); } @ParameterizedTest @BooleanValueSource void executeWhenBuilderReturnsErrorThrowsException(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(9, null)); assertThatExceptionOfType(BuilderException.class).isThrownBy(() -> createLifecycle(trustBuilder).execute()) .withMessage( "Builder lifecycle '" + ((trustBuilder) ? "creator" : "analyzer") + "' failed with status code 9"); } @ParameterizedTest @BooleanValueSource void executeWhenCleanCacheClearsCache(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(trustBuilder).withCleanCache(true); createLifecycle(request).execute(); if (trustBuilder) { assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-clean-cache.json")); } else { assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json")); assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json")); assertThat(this.out.toString()).contains("Skipping restorer because 'cleanCache' is enabled"); } VolumeName name = VolumeName.of("pack-cache-b35197ac41ea.build"); then(this.docker.volume()).should().delete(name, true); } @Test void executeWhenPlatformApiNotSupportedThrowsException() throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); assertThatIllegalStateException() .isThrownBy(() -> createLifecycle(true, "builder-metadata-unsupported-api.json").execute()) .withMessageContaining("Detected platform API versions '0.2' are not included in supported versions"); } @Test void executeWhenMultiplePlatformApisNotSupportedThrowsException() throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); assertThatIllegalStateException() .isThrownBy(() -> createLifecycle(true, "builder-metadata-unsupported-apis.json").execute()) .withMessageContaining("Detected platform API versions '0.1,0.2' are not included in supported versions"); } @ParameterizedTest @BooleanValueSource void executeWhenMultiplePlatformApisSupportedExecutesPhase(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); createLifecycle(trustBuilder, "builder-metadata-supported-apis.json").execute(); if (trustBuilder) { assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json")); } else { assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json")); assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json")); assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json")); } } @Test void closeClearsVolumes() throws Exception { createLifecycle(true).close(); then(this.docker.volume()).should().delete(VolumeName.of("pack-layers-aaaaaaaaaa"), true); then(this.docker.volume()).should().delete(VolumeName.of("pack-app-aaaaaaaaaa"), true); } @Test void executeWithNetworkExecutesPhases() throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(true).withNetwork("test"); createLifecycle(request).execute(); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-network.json")); assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @ParameterizedTest @BooleanValueSource void executeWithCacheVolumeNamesExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(trustBuilder).withBuildWorkspace(Cache.volume("work-volume")) .withBuildCache(Cache.volume("build-volume")) .withLaunchCache(Cache.volume("launch-volume")); createLifecycle(request).execute(); if (trustBuilder) { assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-cache-volumes.json")); } else { assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-cache-volumes.json")); assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector-cache-volumes.json")); assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-cache-volumes.json")); assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder-cache-volumes.json")); assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-cache-volumes.json")); } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @ParameterizedTest @BooleanValueSource void executeWithCacheBindMountsExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(trustBuilder).withBuildWorkspace(Cache.bind("/tmp/work")) .withBuildCache(Cache.bind("/tmp/build-cache")) .withLaunchCache(Cache.bind("/tmp/launch-cache")); createLifecycle(request).execute(); if (trustBuilder) { assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-cache-bind-mounts.json")); } else { assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-cache-bind-mounts.json")); assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector-cache-bind-mounts.json")); assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-cache-bind-mounts.json")); assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder-cache-bind-mounts.json")); assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-cache-bind-mounts.json")); } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @ParameterizedTest @BooleanValueSource void executeWithCreatedDateExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(trustBuilder).withCreatedDate("2020-07-01T12:34:56Z"); createLifecycle(request).execute(); if (trustBuilder) { assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-created-date.json")); } else { assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json")); assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json")); assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-created-date.json")); } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @ParameterizedTest @BooleanValueSource void executeWithApplicationDirectoryExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(trustBuilder).withApplicationDirectory("/application"); createLifecycle(request).execute(); if (trustBuilder) { assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-app-dir.json")); } else { assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json")); assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector-app-dir.json")); assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json")); assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder-app-dir.json")); assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-app-dir.json")); } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @ParameterizedTest @BooleanValueSource void executeWithSecurityOptionsExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(trustBuilder) .withSecurityOptions(List.of("label=user:USER", "label=role:ROLE")); createLifecycle(request).execute(); if (trustBuilder) { assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-security-opts.json", true)); } else { assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-security-opts.json", true)); assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-security-opts.json", true)); assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-security-opts.json", true)); } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @ParameterizedTest @BooleanValueSource void executeWithDockerHostAndRemoteAddressExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(trustBuilder); createLifecycle(request, ResolvedDockerHost.from(new DockerConnectionConfiguration.Host("tcp://192.168.1.2:2376"))) .execute(); if (trustBuilder) { assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-remote.json")); } else { assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-inherit-remote.json")); assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-inherit-remote.json")); assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-inherit-remote.json")); } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @ParameterizedTest @BooleanValueSource void executeWithDockerHostAndLocalAddressExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(trustBuilder); createLifecycle(request, ResolvedDockerHost.from(new DockerConnectionConfiguration.Host("/var/alt.sock"))) .execute(); if (trustBuilder) { assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-local.json")); } else { assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-inherit-local.json")); assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-inherit-local.json")); assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-inherit-local.json")); } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @ParameterizedTest @BooleanValueSource void executeWithImagePlatformExecutesPhases(boolean trustBuilder) throws Exception { given(this.docker.container().create(any(), eq(ImagePlatform.of("linux/arm64")))) .willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), eq(ImagePlatform.of("linux/arm64")), any())) .willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(trustBuilder).withImagePlatform("linux/arm64"); createLifecycle(request).execute(); if (trustBuilder) { assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json")); } else { assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json")); assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json")); assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json")); assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json")); assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json")); } assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } private DockerApi mockDockerApi() { DockerApi docker = mock(DockerApi.class); ImageApi imageApi = mock(ImageApi.class); ContainerApi containerApi = mock(ContainerApi.class); VolumeApi volumeApi = mock(VolumeApi.class); given(docker.image()).willReturn(imageApi); given(docker.container()).willReturn(containerApi); given(docker.volume()).willReturn(volumeApi); return docker; } private BuildRequest getTestRequest(boolean trustBuilder) { TarArchive content = mock(TarArchive.class); ImageReference name = ImageReference.of("my-application"); return BuildRequest.of(name, (owner) -> content) .withRunImage(ImageReference.of("cloudfoundry/run")) .withTrustBuilder(trustBuilder); } private Lifecycle createLifecycle(boolean trustBuilder) throws IOException { return createLifecycle(getTestRequest(trustBuilder)); } private Lifecycle createLifecycle(BuildRequest request) throws IOException { EphemeralBuilder builder = mockEphemeralBuilder(); return createLifecycle(request, builder); } private Lifecycle createLifecycle(boolean trustBuilder, String builderMetadata) throws IOException { EphemeralBuilder builder = mockEphemeralBuilder(builderMetadata); return createLifecycle(getTestRequest(trustBuilder), builder); } private Lifecycle createLifecycle(BuildRequest request, ResolvedDockerHost dockerHost) throws IOException { EphemeralBuilder builder = mockEphemeralBuilder(); return new TestLifecycle(BuildLog.to(this.out), this.docker, dockerHost, request, builder); } private Lifecycle createLifecycle(BuildRequest request, EphemeralBuilder ephemeralBuilder) { return new TestLifecycle(BuildLog.to(this.out), this.docker, null, request, ephemeralBuilder); } private EphemeralBuilder mockEphemeralBuilder() throws IOException { return mockEphemeralBuilder("builder-metadata.json"); } private EphemeralBuilder mockEphemeralBuilder(String builderMetadata) throws IOException { EphemeralBuilder builder = mock(EphemeralBuilder.class); byte[] metadataContent = FileCopyUtils.copyToByteArray(getClass().getResourceAsStream(builderMetadata)); BuilderMetadata metadata = BuilderMetadata.fromJson(new String(metadataContent, StandardCharsets.UTF_8)); given(builder.getName()).willReturn(ImageReference.of("pack.local/ephemeral-builder")); given(builder.getBuilderMetadata()).willReturn(metadata); return builder; } private Answer answerWithGeneratedContainerId() { return (invocation) -> { ContainerConfig config = invocation.getArgument(0, ContainerConfig.class); ArrayNode command = getCommand(config); String name = command.get(0).asString().substring(1).replace('/', '-'); this.configs.put(name, config); if (invocation.getArguments().length > 2) { this.content.put(name, invocation.getArgument(2, ContainerContent.class)); } return ContainerReference.of(name); }; } private ArrayNode getCommand(ContainerConfig config) { JsonNode node = SharedJsonMapper.get().readTree(config.toString()); return (ArrayNode) node.at("/Cmd"); } private void assertPhaseWasRun(String name, IOConsumer configConsumer) throws IOException { ContainerReference containerReference = ContainerReference.of("cnb-lifecycle-" + name); then(this.docker.container()).should().start(containerReference); then(this.docker.container()).should().logs(eq(containerReference), any()); then(this.docker.container()).should().remove(containerReference, true); ContainerConfig containerConfig = this.configs.get(containerReference.toString()); assertThat(containerConfig).isNotNull(); configConsumer.accept(containerConfig); } private IOConsumer withExpectedConfig(String name) { return withExpectedConfig(name, false); } private IOConsumer withExpectedConfig(String name, boolean expectSecurityOptAlways) { return (config) -> { try { InputStream in = getClass().getResourceAsStream(name); String jsonString = FileCopyUtils.copyToString(new InputStreamReader(in, StandardCharsets.UTF_8)); JSONObject json = new JSONObject(jsonString); if (!expectSecurityOptAlways && Platform.isWindows()) { JSONObject hostConfig = json.getJSONObject("HostConfig"); hostConfig.remove("SecurityOpt"); } JSONAssert.assertEquals(config.toString(), json, true); } catch (JSONException ex) { throw new IOException(ex); } }; } static class TestLifecycle extends Lifecycle { TestLifecycle(BuildLog log, DockerApi docker, @Nullable ResolvedDockerHost dockerHost, BuildRequest request, EphemeralBuilder builder) { super(log, docker, dockerHost, request, builder); } @Override protected VolumeName createRandomVolumeName(String prefix) { return VolumeName.of(prefix + "aaaaaaaaaa"); } } static class TestPrintStream extends PrintStream { TestPrintStream() { super(new ByteArrayOutputStream()); } @Override public String toString() { return this.out.toString(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleVersionTests.java ================================================ /* * 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.buildpack.platform.build; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link LifecycleVersion}. * * @author Phillip Webb */ class LifecycleVersionTests { @Test @SuppressWarnings("NullAway") // Test null check void parseWhenValueIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> LifecycleVersion.parse(null)) .withMessage("'value' must not be empty"); } @Test void parseWhenTooLongThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> LifecycleVersion.parse("v1.2.3.4")) .withMessage("'value' [v1.2.3.4] must be a valid version number"); } @Test void parseWhenNonNumericThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> LifecycleVersion.parse("v1.2.3a")) .withMessage("'value' [v1.2.3a] must be a valid version number"); } @Test void compareTo() { LifecycleVersion v4 = LifecycleVersion.parse("0.0.4"); assertThat(LifecycleVersion.parse("0.0.3")).isLessThan(v4); assertThat(LifecycleVersion.parse("0.0.4")).isEqualByComparingTo(v4); assertThat(LifecycleVersion.parse("0.0.5")).isGreaterThan(v4); } @Test void isEqualOrGreaterThan() { LifecycleVersion v4 = LifecycleVersion.parse("0.0.4"); assertThat(LifecycleVersion.parse("0.0.3").isEqualOrGreaterThan(v4)).isFalse(); assertThat(LifecycleVersion.parse("0.0.4").isEqualOrGreaterThan(v4)).isTrue(); assertThat(LifecycleVersion.parse("0.0.5").isEqualOrGreaterThan(v4)).isTrue(); } @Test void parseReturnsVersion() { assertThat(LifecycleVersion.parse("1.2.3")).hasToString("v1.2.3"); assertThat(LifecycleVersion.parse("1.2")).hasToString("v1.2.0"); assertThat(LifecycleVersion.parse("1")).hasToString("v1.0.0"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java ================================================ /* * 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.buildpack.platform.build; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig.Update; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; /** * Tests for {@link Phase}. * * @author Phillip Webb * @author Scott Frederick * @author Jeroen Meijer */ class PhaseTests { private static final String[] NO_ARGS = {}; @Test void getNameReturnsName() { Phase phase = new Phase("test", false); assertThat(phase.getName()).isEqualTo("test"); } @Test void toStringReturnsName() { Phase phase = new Phase("test", false); assertThat(phase).hasToString("test"); } @Test void applyUpdatesConfiguration() { Phase phase = new Phase("test", false); Update update = mock(Update.class); phase.apply(update); then(update).should().withCommand("/cnb/lifecycle/test", NO_ARGS); then(update).should().withLabel("author", "spring-boot"); then(update).shouldHaveNoMoreInteractions(); } @Test void applyWhenWithDaemonAccessUpdatesConfigurationWithRootUser() { Phase phase = new Phase("test", false); phase.withDaemonAccess(); Update update = mock(Update.class); phase.apply(update); then(update).should().withUser("root"); then(update).should().withCommand("/cnb/lifecycle/test", "-daemon"); then(update).should().withLabel("author", "spring-boot"); then(update).shouldHaveNoMoreInteractions(); } @Test void applyWhenWithLogLevelArgAndVerboseLoggingUpdatesConfigurationWithLogLevel() { Phase phase = new Phase("test", true); Update update = mock(Update.class); phase.apply(update); then(update).should().withCommand("/cnb/lifecycle/test", "-log-level", "debug"); then(update).should().withLabel("author", "spring-boot"); then(update).shouldHaveNoMoreInteractions(); } @Test void applyWhenWithLogLevelArgAndNonVerboseLoggingDoesNotUpdateLogLevel() { Phase phase = new Phase("test", false); Update update = mock(Update.class); phase.apply(update); then(update).should().withCommand("/cnb/lifecycle/test"); then(update).should().withLabel("author", "spring-boot"); then(update).shouldHaveNoMoreInteractions(); } @Test void applyWhenWithArgsUpdatesConfigurationWithArguments() { Phase phase = new Phase("test", false); phase.withArgs("a", "b", "c"); Update update = mock(Update.class); phase.apply(update); then(update).should().withCommand("/cnb/lifecycle/test", "a", "b", "c"); then(update).should().withLabel("author", "spring-boot"); then(update).shouldHaveNoMoreInteractions(); } @Test void applyWhenWithBindsUpdatesConfigurationWithBinds() { Phase phase = new Phase("test", false); VolumeName volumeName = VolumeName.of("test"); phase.withBinding(Binding.from(volumeName, "/test")); Update update = mock(Update.class); phase.apply(update); then(update).should().withCommand("/cnb/lifecycle/test"); then(update).should().withLabel("author", "spring-boot"); then(update).should().withBinding(Binding.from(volumeName, "/test")); then(update).shouldHaveNoMoreInteractions(); } @Test void applyWhenWithEnvUpdatesConfigurationWithEnv() { Phase phase = new Phase("test", false); phase.withEnv("name1", "value1"); phase.withEnv("name2", "value2"); Update update = mock(Update.class); phase.apply(update); then(update).should().withCommand("/cnb/lifecycle/test"); then(update).should().withLabel("author", "spring-boot"); then(update).should().withEnv("name1", "value1"); then(update).should().withEnv("name2", "value2"); then(update).shouldHaveNoMoreInteractions(); } @Test void applyWhenWithNetworkModeUpdatesConfigurationWithNetworkMode() { Phase phase = new Phase("test", false); phase.withNetworkMode("test"); Update update = mock(Update.class); phase.apply(update); then(update).should().withCommand("/cnb/lifecycle/test"); then(update).should().withNetworkMode("test"); then(update).should().withLabel("author", "spring-boot"); then(update).shouldHaveNoMoreInteractions(); } @Test void applyWhenWithSecurityOptionsUpdatesConfigurationWithSecurityOptions() { Phase phase = new Phase("test", false); phase.withSecurityOption("option1=value1"); phase.withSecurityOption("option2=value2"); Update update = mock(Update.class); phase.apply(update); then(update).should().withCommand("/cnb/lifecycle/test"); then(update).should().withLabel("author", "spring-boot"); then(update).should().withSecurityOption("option1=value1"); then(update).should().withSecurityOption("option2=value2"); then(update).shouldHaveNoMoreInteractions(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PrintStreamBuildLogTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.ByteArrayOutputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.function.Consumer; 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.TotalProgressEvent; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link PrintStreamBuildLog}. * * @author Phillip Webb * @author Rafael Ceccone */ class PrintStreamBuildLogTests { @Test void printsExpectedOutput() throws Exception { TestPrintStream out = new TestPrintStream(); PrintStreamBuildLog log = new PrintStreamBuildLog(out); BuildRequest request = mock(BuildRequest.class); ImageReference name = ImageReference.of("my-app:latest"); ImageReference builderImageReference = ImageReference.of("cnb/builder"); ImagePlatform platform = ImagePlatform.of("linux/arm64/v1"); Image builderImage = mock(Image.class); given(builderImage.getDigests()).willReturn(Collections.singletonList("00000001")); ImageReference runImageReference = ImageReference.of("cnb/runner"); Image runImage = mock(Image.class); given(runImage.getDigests()).willReturn(Collections.singletonList("00000002")); given(request.getName()).willReturn(name); ImageReference tag = ImageReference.of("my-app:1.0"); given(request.getTags()).willReturn(Collections.singletonList(tag)); log.start(request); Consumer pullBuildImageConsumer = log.pullingImage(builderImageReference, null, ImageType.BUILDER); pullBuildImageConsumer.accept(new TotalProgressEvent(100)); log.pulledImage(builderImage, ImageType.BUILDER); Consumer pullRunImageConsumer = log.pullingImage(runImageReference, platform, ImageType.RUNNER); pullRunImageConsumer.accept(new TotalProgressEvent(100)); log.pulledImage(runImage, ImageType.RUNNER); log.executingLifecycle(request, LifecycleVersion.parse("0.5"), Cache.volume(VolumeName.of("pack-abc.cache"))); Consumer phase1Consumer = log.runningPhase(request, "alphabet"); phase1Consumer.accept(mockLogEvent("one")); phase1Consumer.accept(mockLogEvent("two")); phase1Consumer.accept(mockLogEvent("three")); Consumer phase2Consumer = log.runningPhase(request, "basket"); phase2Consumer.accept(mockLogEvent("spring")); phase2Consumer.accept(mockLogEvent("boot")); log.executedLifecycle(request); log.taggedImage(tag); String expected = FileCopyUtils.copyToString(new InputStreamReader( getClass().getResourceAsStream("print-stream-build-log.txt"), StandardCharsets.UTF_8)); assertThat(out.toString()).isEqualToIgnoringNewLines(expected); } private LogUpdateEvent mockLogEvent(String string) { LogUpdateEvent event = mock(LogUpdateEvent.class); given(event.toString()).willReturn(string); return event; } static class TestPrintStream extends PrintStream { TestPrintStream() { super(new ByteArrayOutputStream()); } @Override public String toString() { return this.out.toString(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/StackIdTests.java ================================================ /* * 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.buildpack.platform.build; import java.util.Collections; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link StackId}. * * @author Phillip Webb */ class StackIdTests { @Test @SuppressWarnings("NullAway") // Test null check void fromImageWhenImageIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> StackId.fromImage(null)) .withMessage("'image' must not be null"); } @Test void fromImageWhenLabelIsMissingHasNoId() { Image image = mock(Image.class); ImageConfig imageConfig = mock(ImageConfig.class); given(image.getConfig()).willReturn(imageConfig); StackId stackId = StackId.fromImage(image); assertThat(stackId.hasId()).isFalse(); } @Test void fromImageCreatesStackId() { Image image = mock(Image.class); ImageConfig imageConfig = mock(ImageConfig.class); given(image.getConfig()).willReturn(imageConfig); given(imageConfig.getLabels()).willReturn(Collections.singletonMap("io.buildpacks.stack.id", "test")); StackId stackId = StackId.fromImage(image); assertThat(stackId).hasToString("test"); assertThat(stackId.hasId()).isTrue(); } @Test void ofCreatesStackId() { StackId stackId = StackId.of("test"); assertThat(stackId).hasToString("test"); } @Test void equalsAndHashCode() { StackId s1 = StackId.of("a"); StackId s2 = StackId.of("a"); StackId s3 = StackId.of("b"); assertThat(s1).hasSameHashCodeAs(s2); assertThat(s1).isEqualTo(s1).isEqualTo(s2).isNotEqualTo(s3); } @Test void toStringReturnsValue() { StackId stackId = StackId.of("test"); assertThat(stackId).hasToString("test"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/TarGzipBuildpackTests.java ================================================ /* * 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.buildpack.platform.build; import java.io.File; import java.nio.file.Path; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; /** * Tests for {@link TarGzipBuildpack}. * * @author Scott Frederick */ class TarGzipBuildpackTests { private File buildpackDir; private TestTarGzip testTarGzip; private BuildpackResolverContext resolverContext; @BeforeEach void setUp(@TempDir File temp) { this.buildpackDir = new File(temp, "buildpack"); this.buildpackDir.mkdirs(); this.testTarGzip = new TestTarGzip(this.buildpackDir); this.resolverContext = mock(BuildpackResolverContext.class); } @Test void resolveWhenFilePathReturnsBuildpack() throws Exception { Path compressedArchive = this.testTarGzip.createArchive(); BuildpackReference reference = BuildpackReference.of(compressedArchive.toString()); Buildpack buildpack = TarGzipBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNotNull(); assertThat(buildpack.getCoordinates()).hasToString("example/buildpack1@0.0.1"); this.testTarGzip.assertHasExpectedLayers(buildpack); } @Test void resolveWhenFileUrlReturnsBuildpack() throws Exception { Path compressedArchive = this.testTarGzip.createArchive(); BuildpackReference reference = BuildpackReference.of(compressedArchive.toUri().toString()); Buildpack buildpack = TarGzipBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).as("Buildpack %s resolved from reference %s", buildpack, reference).isNotNull(); assertThat(buildpack.getCoordinates()).hasToString("example/buildpack1@0.0.1"); this.testTarGzip.assertHasExpectedLayers(buildpack); } @Test void resolveWhenArchiveWithoutDescriptorThrowsException() throws Exception { Path compressedArchive = this.testTarGzip.createEmptyArchive(); BuildpackReference reference = BuildpackReference.of(compressedArchive.toString()); assertThatIllegalArgumentException().isThrownBy(() -> TarGzipBuildpack.resolve(this.resolverContext, reference)) .withMessageContaining("Buildpack descriptor 'buildpack.toml' is required") .withMessageContaining(compressedArchive.toString()); } @Test void resolveWhenArchiveWithDirectoryReturnsNull() { BuildpackReference reference = BuildpackReference.of(this.buildpackDir.getAbsolutePath()); Buildpack buildpack = TarGzipBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNull(); } @Test void resolveWhenArchiveThatDoesNotExistReturnsNull() { BuildpackReference reference = BuildpackReference.of("/test/i/am/missing/buildpack.tar"); Buildpack buildpack = TarGzipBuildpack.resolve(this.resolverContext, reference); assertThat(buildpack).isNull(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/TestBuildpack.java ================================================ /* * 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.buildpack.platform.build; import java.io.IOException; import org.jspecify.annotations.Nullable; import org.springframework.boot.buildpack.platform.docker.type.Layer; import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.Layout; import org.springframework.boot.buildpack.platform.io.Owner; /** * A test {@link Buildpack}. * * @author Scott Frederick * @author Phillip Webb */ class TestBuildpack implements Buildpack { private final BuildpackCoordinates coordinates; TestBuildpack(String id, @Nullable String version) { this.coordinates = BuildpackCoordinates.of(id, version); } @Override public BuildpackCoordinates getCoordinates() { return this.coordinates; } @Override public void apply(IOConsumer layers) throws IOException { layers.accept(Layer.of(this::getContent)); } private void getContent(Layout layout) throws IOException { String id = this.coordinates.getSanitizedId(); String dir = "/cnb/buildpacks/" + id + "/" + this.coordinates.getVersion(); layout.file(dir + "/buildpack.toml", Owner.ROOT, Content.of("[test]")); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/TestTarGzip.java ================================================ /* * 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.buildpack.platform.build; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; import org.springframework.util.FileCopyUtils; import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Utility to create test tgz files. * * @author Scott Frederick */ class TestTarGzip { private final File buildpackDir; TestTarGzip(File buildpackDir) { this.buildpackDir = buildpackDir; } Path createArchive() throws Exception { return createArchive(true); } Path createEmptyArchive() throws Exception { return createArchive(false); } private Path createArchive(boolean addContent) throws Exception { Path path = Paths.get(this.buildpackDir.getAbsolutePath(), "buildpack.tar"); Path archive = Files.createFile(path); if (addContent) { writeBuildpackContentToArchive(archive); } return compressBuildpackArchive(archive); } private Path compressBuildpackArchive(Path archive) throws Exception { Path tgzPath = Paths.get(this.buildpackDir.getAbsolutePath(), "buildpack.tgz"); FileCopyUtils.copy(Files.newInputStream(archive), new GzipCompressorOutputStream(Files.newOutputStream(tgzPath))); return tgzPath; } private void writeBuildpackContentToArchive(Path archive) throws Exception { StringBuilder buildpackToml = new StringBuilder(); buildpackToml.append("[buildpack]\n"); buildpackToml.append("id = \"example/buildpack1\"\n"); buildpackToml.append("version = \"0.0.1\"\n"); buildpackToml.append("name = \"Example buildpack\"\n"); buildpackToml.append("homepage = \"https://github.com/example/example-buildpack\"\n"); buildpackToml.append("[[stacks]]\n"); buildpackToml.append("id = \"io.buildpacks.stacks.bionic\"\n"); String detectScript = """ #!/usr/bin/env bash echo "---> detect" """; String buildScript = """ #!/usr/bin/env bash echo "---> build" """; try (TarArchiveOutputStream tar = new TarArchiveOutputStream(Files.newOutputStream(archive))) { writeEntry(tar, "buildpack.toml", buildpackToml.toString()); writeEntry(tar, "bin/"); writeEntry(tar, "bin/detect", detectScript); writeEntry(tar, "bin/build", buildScript); tar.finish(); } } private void writeEntry(TarArchiveOutputStream tar, String entryName) throws IOException { TarArchiveEntry entry = new TarArchiveEntry(entryName); tar.putArchiveEntry(entry); tar.closeArchiveEntry(); } private void writeEntry(TarArchiveOutputStream tar, String entryName, String content) throws IOException { TarArchiveEntry entry = new TarArchiveEntry(entryName); entry.setSize(content.length()); tar.putArchiveEntry(entry); StreamUtils.copy(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), tar); tar.closeArchiveEntry(); } void assertHasExpectedLayers(Buildpack buildpack) throws IOException { List layers = new ArrayList<>(); buildpack.apply((layer) -> { ByteArrayOutputStream out = new ByteArrayOutputStream(); layer.writeTo(out); layers.add(out); }); assertThat(layers).hasSize(1); byte[] content = layers.get(0).toByteArray(); try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(content))) { assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/"); assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/"); assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/example_buildpack1/"); assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/"); assertThat(tar.getNextEntry().getName()) .isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/buildpack.toml"); assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/bin/"); assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/bin/detect"); assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/bin/build"); assertThat(tar.getNextEntry()).isNull(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ApiVersionTests.java ================================================ /* * 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.buildpack.platform.docker; import java.util.Arrays; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link ApiVersion}. * * @author Phillip Webb * @author Scott Frederick */ class ApiVersionTests { @Test @SuppressWarnings("NullAway") // Test null check void parseWhenVersionIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> ApiVersion.parse(null)) .withMessage("'value' must not be empty"); } @Test void parseWhenVersionIsEmptyThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> ApiVersion.parse("")) .withMessage("'value' must not be empty"); } @Test void parseWhenVersionDoesNotMatchPatternThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> ApiVersion.parse("bad")) .withMessage("'value' [bad] must contain a well formed version number"); } @Test void parseReturnsVersion() { ApiVersion version = ApiVersion.parse("1.2"); assertThat(version.getMajor()).isOne(); assertThat(version.getMinor()).isEqualTo(2); } @Test void supportsWhenSame() { assertThat(supports("0.0", "0.0")).isTrue(); assertThat(supports("0.1", "0.1")).isTrue(); assertThat(supports("1.0", "1.0")).isTrue(); assertThat(supports("1.1", "1.1")).isTrue(); } @Test void supportsWhenDifferentMajor() { assertThat(supports("0.0", "1.0")).isFalse(); assertThat(supports("1.0", "0.0")).isFalse(); assertThat(supports("1.0", "2.0")).isFalse(); assertThat(supports("2.0", "1.0")).isFalse(); assertThat(supports("1.1", "2.1")).isFalse(); assertThat(supports("2.1", "1.1")).isFalse(); } @Test void supportsWhenDifferentMinor() { assertThat(supports("1.2", "1.1")).isTrue(); assertThat(supports("1.2", "1.3")).isFalse(); } @Test void supportsWhenMajorZeroAndDifferentMinor() { assertThat(supports("0.2", "0.1")).isFalse(); assertThat(supports("0.2", "0.3")).isFalse(); } @Test void supportsAnyWhenOneMatches() { assertThat(supportsAny("0.2", "0.1", "0.2")).isTrue(); } @Test void supportsAnyWhenNoneMatch() { assertThat(supportsAny("0.2", "0.3", "0.4")).isFalse(); } @Test void toStringReturnsString() { assertThat(ApiVersion.parse("1.2")).hasToString("1.2"); } @Test void equalsAndHashCode() { ApiVersion v12a = ApiVersion.parse("1.2"); ApiVersion v12b = ApiVersion.parse("1.2"); ApiVersion v13 = ApiVersion.parse("1.3"); assertThat(v12a).hasSameHashCodeAs(v12b); assertThat(v12a).isEqualTo(v12a).isEqualTo(v12b).isNotEqualTo(v13); } @Test void compareTo() { assertThat(ApiVersion.of(0, 0).compareTo(ApiVersion.of(0, 0))).isZero(); assertThat(ApiVersion.of(0, 1).compareTo(ApiVersion.of(0, 1))).isZero(); assertThat(ApiVersion.of(1, 0).compareTo(ApiVersion.of(1, 0))).isZero(); assertThat(ApiVersion.of(0, 0).compareTo(ApiVersion.of(0, 1))).isLessThan(0); assertThat(ApiVersion.of(0, 1).compareTo(ApiVersion.of(0, 0))).isGreaterThan(0); assertThat(ApiVersion.of(1, 0).compareTo(ApiVersion.of(0, 1))).isGreaterThan(0); assertThat(ApiVersion.of(0, 1).compareTo(ApiVersion.of(1, 0))).isLessThan(0); } private boolean supports(String v1, String v2) { return ApiVersion.parse(v1).supports(ApiVersion.parse(v2)); } private boolean supportsAny(String v1, String... others) { return ApiVersion.parse(v1) .supportsAny(Arrays.stream(others).map(ApiVersion::parse).toArray(ApiVersion[]::new)); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java ================================================ /* * 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.buildpack.platform.docker; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Arrays; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.message.BasicHeader; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.Feature; import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi; 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.transport.HttpTransport; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response; 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.ContainerReference; 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.ImageArchive; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.testsupport.system.CapturedOutput; import org.springframework.boot.testsupport.system.OutputCaptureExtension; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; /** * Tests for {@link DockerApi}. * * @author Phillip Webb * @author Scott Frederick * @author Rafael Ceccone * @author Moritz Halbritter */ @ExtendWith({ MockitoExtension.class, OutputCaptureExtension.class }) class DockerApiTests { private static final String API_URL = "/v" + DockerApi.PREFERRED_API_VERSION; public static final String PING_URL = "/_ping"; private static final String IMAGES_URL = API_URL + "/images"; private static final String CONTAINERS_URL = API_URL + "/containers"; private static final String VOLUMES_URL = API_URL + "/volumes"; private static final ImagePlatform LINUX_ARM64_PLATFORM = ImagePlatform.of("linux/arm64/v1"); private static final String ENCODED_LINUX_ARM64_PLATFORM_JSON = URLEncoder.encode(LINUX_ARM64_PLATFORM.toJson(), StandardCharsets.UTF_8); @Mock @SuppressWarnings("NullAway.Init") private HttpTransport http; private DockerApi dockerApi; @BeforeEach void setup() { this.dockerApi = new DockerApi(this.http, DockerLog.toSystemOut()); } private HttpTransport http() { return this.http; } private Response emptyResponse() { return responseOf(null); } private Response responseOf(@Nullable String name) { return new Response() { @Override public void close() { } @Override public InputStream getContent() { if (name == null) { return new ByteArrayInputStream(new byte[0]); } return getClass().getResourceAsStream(name); } }; } private Response responseWithHeaders(Header... headers) { return new Response() { @Override public InputStream getContent() { return new ByteArrayInputStream(new byte[0]); } @Override public @Nullable Header getHeader(String name) { return Arrays.stream(headers) .filter((header) -> header.getName().equals(name)) .findFirst() .orElse(null); } @Override public void close() { } }; } @Test void createDockerApi() { DockerApi api = new DockerApi(); assertThat(api).isNotNull(); } @Test void buildUrlWhenUnknownVersionUsesPreferredVersion() throws Exception { setVersion("0.0"); assertThat(this.dockerApi.buildUrl(Feature.BASELINE, "/test")) .isEqualTo(URI.create("/v" + DockerApi.PREFERRED_API_VERSION + "/test")); } @Test void buildUrlWhenVersionIsGreaterThanPreferredUsesPreferred() throws Exception { setVersion("1000.0"); assertThat(this.dockerApi.buildUrl(Feature.BASELINE, "/test")) .isEqualTo(URI.create("/v" + DockerApi.PREFERRED_API_VERSION + "/test")); } @Test void buildUrlWhenVersionIsEqualToPreferredUsesPreferred() throws Exception { setVersion(DockerApi.PREFERRED_API_VERSION.toString()); assertThat(this.dockerApi.buildUrl(Feature.BASELINE, "/test")) .isEqualTo(URI.create("/v" + DockerApi.PREFERRED_API_VERSION + "/test")); } @Test void buildUrlWhenVersionIsLessThanPreferredAndGreaterThanMinimumUsesVersionVersion() throws Exception { setVersion("1.48"); assertThat(this.dockerApi.buildUrl(Feature.BASELINE, "/test")).isEqualTo(URI.create("/v1.48/test")); } @Test void buildUrlWhenVersionIsLessThanPreferredAndEqualToMinimumUsesVersionVersion() throws Exception { setVersion(Feature.BASELINE.minimumVersion().toString()); assertThat(this.dockerApi.buildUrl(Feature.BASELINE, "/test")).isEqualTo(URI.create("/v1.24/test")); } @Test void buildUrlWhenVersionIsLessThanMinimumThrowsException() throws Exception { setVersion("1.23"); assertThatIllegalStateException().isThrownBy(() -> this.dockerApi.buildUrl(Feature.BASELINE, "/test")) .withMessage("Docker API version must be at least 1.24 " + "to support this feature, but current API version is 1.23"); } private void setVersion(String version) throws IOException, URISyntaxException { given(http().head(eq(new URI(PING_URL)))) .willReturn(responseWithHeaders(new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, version))); } @Nested class ImageDockerApiTests { private ImageApi api; @Mock @SuppressWarnings("NullAway.Init") private UpdateListener pullListener; @Mock @SuppressWarnings("NullAway.Init") private UpdateListener pushListener; @Mock @SuppressWarnings("NullAway.Init") private UpdateListener loadListener; @Captor @SuppressWarnings("NullAway.Init") private ArgumentCaptor> writer; @BeforeEach void setup() { this.api = DockerApiTests.this.dockerApi.image(); } @Test @SuppressWarnings("NullAway") // Test null check void pullWhenReferenceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.pull(null, null, this.pullListener)) .withMessage("'reference' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void pullWhenListenerIsNullThrowsException() { assertThatIllegalArgumentException() .isThrownBy(() -> this.api.pull(ImageReference.of("ubuntu"), null, null)) .withMessage("'listener' must not be null"); } @Test void pullPullsImageAndProducesEvents() throws Exception { ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base"); URI createUri = new URI(IMAGES_URL + "/create?fromImage=docker.io%2Fpaketobuildpacks%2Fbuilder%3Abase"); URI imageUri = new URI(IMAGES_URL + "/docker.io/paketobuildpacks/builder:base/json"); given(http().post(eq(createUri), isNull())).willReturn(responseOf("pull-stream.json")); given(http().get(imageUri)).willReturn(responseOf("type/image.json")); Image image = this.api.pull(reference, null, this.pullListener); assertThat(image.getLayers()).hasSize(46); InOrder ordered = inOrder(this.pullListener); ordered.verify(this.pullListener).onStart(); ordered.verify(this.pullListener, times(595)).onUpdate(any()); ordered.verify(this.pullListener).onFinish(); } @Test void pullWithRegistryAuthPullsImageAndProducesEvents() throws Exception { ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base"); URI createUri = new URI(IMAGES_URL + "/create?fromImage=docker.io%2Fpaketobuildpacks%2Fbuilder%3Abase"); URI imageUri = new URI(IMAGES_URL + "/docker.io/paketobuildpacks/builder:base/json"); given(http().post(eq(createUri), eq("auth token"))).willReturn(responseOf("pull-stream.json")); given(http().get(imageUri)).willReturn(responseOf("type/image.json")); Image image = this.api.pull(reference, null, this.pullListener, "auth token"); assertThat(image.getLayers()).hasSize(46); InOrder ordered = inOrder(this.pullListener); ordered.verify(this.pullListener).onStart(); ordered.verify(this.pullListener, times(595)).onUpdate(any()); ordered.verify(this.pullListener).onFinish(); } @Test void pullWithPlatformPullsImageAndProducesEvents() throws Exception { ImageReference reference = ImageReference.of("gcr.io/paketo-buildpacks/builder:base"); URI createUri = new URI( "/v1.49/images/create?fromImage=gcr.io%2Fpaketo-buildpacks%2Fbuilder%3Abase&platform=linux%2Farm64%2Fv1"); URI imageUri = new URI("/v1.49/images/gcr.io/paketo-buildpacks/builder:base/json?platform=" + ENCODED_LINUX_ARM64_PLATFORM_JSON); setVersion("1.49"); given(http().post(eq(createUri), isNull())).willReturn(responseOf("pull-stream.json")); given(http().get(imageUri)).willReturn(responseOf("type/image.json")); Image image = this.api.pull(reference, LINUX_ARM64_PLATFORM, this.pullListener); assertThat(image.getLayers()).hasSize(46); InOrder ordered = inOrder(this.pullListener); ordered.verify(this.pullListener).onStart(); ordered.verify(this.pullListener, times(595)).onUpdate(any()); ordered.verify(this.pullListener).onFinish(); } @Test void pullWithPlatformAndInsufficientApiVersionThrowsException() throws Exception { ImageReference reference = ImageReference.of("gcr.io/paketo-buildpacks/builder:base"); ImagePlatform platform = ImagePlatform.of("linux/arm64/v1"); setVersion("1.24"); assertThatIllegalStateException().isThrownBy(() -> this.api.pull(reference, platform, this.pullListener)) .withMessageContaining("must be at least 1.41") .withMessageContaining("current API version is 1.24"); } @Test @SuppressWarnings("NullAway") // Test null check void pushWhenReferenceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.push(null, this.pushListener, null)) .withMessage("'reference' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void pushWhenListenerIsNullThrowsException() { assertThatIllegalArgumentException() .isThrownBy(() -> this.api.push(ImageReference.of("ubuntu"), null, null)) .withMessage("'listener' must not be null"); } @Test void pushPushesImageAndProducesEvents() throws Exception { ImageReference reference = ImageReference.of("localhost:5000/ubuntu"); URI pushUri = new URI(IMAGES_URL + "/localhost:5000/ubuntu/push"); given(http().post(pushUri, "auth token")).willReturn(responseOf("push-stream.json")); this.api.push(reference, this.pushListener, "auth token"); InOrder ordered = inOrder(this.pushListener); ordered.verify(this.pushListener).onStart(); ordered.verify(this.pushListener, times(44)).onUpdate(any()); ordered.verify(this.pushListener).onFinish(); } @Test void pushWithErrorInStreamThrowsException() throws Exception { ImageReference reference = ImageReference.of("localhost:5000/ubuntu"); URI pushUri = new URI(IMAGES_URL + "/localhost:5000/ubuntu/push"); given(http().post(pushUri, "auth token")).willReturn(responseOf("push-stream-with-error.json")); assertThatIllegalStateException() .isThrownBy(() -> this.api.push(reference, this.pushListener, "auth token")) .withMessageContaining("test message"); } @Test @SuppressWarnings("NullAway") // Test null check void loadWhenArchiveIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.load(null, UpdateListener.none())) .withMessage("'archive' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void loadWhenListenerIsNullThrowsException() { ImageArchive archive = mock(ImageArchive.class); assertThatIllegalArgumentException().isThrownBy(() -> this.api.load(archive, null)) .withMessage("'listener' must not be null"); } @Test // gh-23130 void loadWithEmptyResponseThrowsException() throws Exception { Image image = Image.of(getClass().getResourceAsStream("type/image.json")); ImageArchive archive = ImageArchive.from(image); URI loadUri = new URI(IMAGES_URL + "/load"); given(http().post(eq(loadUri), eq("application/x-tar"), any())).willReturn(emptyResponse()); assertThatIllegalStateException().isThrownBy(() -> this.api.load(archive, this.loadListener)) .withMessageContaining("Invalid response received"); } @Test // gh-31243 void loadWithErrorResponseThrowsException() throws Exception { Image image = Image.of(getClass().getResourceAsStream("type/image.json")); ImageArchive archive = ImageArchive.from(image); URI loadUri = new URI(IMAGES_URL + "/load"); given(http().post(eq(loadUri), eq("application/x-tar"), any())).willReturn(responseOf("load-error.json")); assertThatIllegalStateException().isThrownBy(() -> this.api.load(archive, this.loadListener)) .withMessageContaining("Error response received"); } @Test void loadLoadsImage() throws Exception { Image image = Image.of(getClass().getResourceAsStream("type/image.json")); ImageArchive archive = ImageArchive.from(image); URI loadUri = new URI(IMAGES_URL + "/load"); given(http().post(eq(loadUri), eq("application/x-tar"), any())).willReturn(responseOf("load-stream.json")); this.api.load(archive, this.loadListener); InOrder ordered = inOrder(this.loadListener); ordered.verify(this.loadListener).onStart(); ordered.verify(this.loadListener).onUpdate(any()); ordered.verify(this.loadListener).onFinish(); then(http()).should().post(any(), any(), this.writer.capture()); ByteArrayOutputStream out = new ByteArrayOutputStream(); this.writer.getValue().accept(out); assertThat(out.toByteArray()).hasSizeGreaterThan(21000); } @Test @SuppressWarnings("NullAway") // Test null check void removeWhenReferenceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.remove(null, true)) .withMessage("'reference' must not be null"); } @Test void removeRemovesContainer() throws Exception { ImageReference reference = ImageReference .of("ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); URI removeUri = new URI(IMAGES_URL + "/docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); given(http().delete(removeUri)).willReturn(emptyResponse()); this.api.remove(reference, false); then(http()).should().delete(removeUri); } @Test void removeWhenForceIsTrueRemovesContainer() throws Exception { ImageReference reference = ImageReference .of("ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); URI removeUri = new URI(IMAGES_URL + "/docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d?force=1"); given(http().delete(removeUri)).willReturn(emptyResponse()); this.api.remove(reference, true); then(http()).should().delete(removeUri); } @Test @SuppressWarnings("NullAway") // Test null check void inspectWhenReferenceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.inspect(null)) .withMessage("'reference' must not be null"); } @Test void inspectInspectImage() throws Exception { ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base"); URI imageUri = new URI(IMAGES_URL + "/docker.io/paketobuildpacks/builder:base/json"); given(http().get(imageUri)).willReturn(responseOf("type/image.json")); Image image = this.api.inspect(reference); assertThat(image.getArchitecture()).isEqualTo("amd64"); assertThat(image.getLayers()).hasSize(46); } @Test void inspectWithPlatformWhenSupportedVersionInspectImage() throws Exception { ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base"); URI imageUri = new URI("/v1.49/images/docker.io/paketobuildpacks/builder:base/json?platform=" + ENCODED_LINUX_ARM64_PLATFORM_JSON); setVersion("1.49"); given(http().get(imageUri)).willReturn(responseOf("type/image-platform.json")); Image image = this.api.inspect(reference, LINUX_ARM64_PLATFORM); assertThat(image.getArchitecture()).isEqualTo("arm64"); assertThat(image.getLayers()).hasSize(2); } @Test void inspectWithPlatformWhenOldVersionInspectImage() throws Exception { ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base"); URI imageUri = new URI("/v1.48/images/docker.io/paketobuildpacks/builder:base/json"); setVersion("1.48"); given(http().get(imageUri)).willReturn(responseOf("type/image.json")); Image image = this.api.inspect(reference, LINUX_ARM64_PLATFORM); assertThat(image.getArchitecture()).isEqualTo("amd64"); assertThat(image.getLayers()).hasSize(46); } @Test void exportLayersExportsLayerTars() throws Exception { ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base"); URI exportUri = new URI(IMAGES_URL + "/docker.io/paketobuildpacks/builder:base/get"); given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export.tar")); MultiValueMap contents = new LinkedMultiValueMap<>(); this.api.exportLayers(reference, (name, archive) -> { ByteArrayOutputStream out = new ByteArrayOutputStream(); archive.writeTo(out); try (TarArchiveInputStream in = new TarArchiveInputStream( new ByteArrayInputStream(out.toByteArray()))) { TarArchiveEntry entry = in.getNextEntry(); while (entry != null) { contents.add(name, entry.getName()); entry = in.getNextEntry(); } } }); assertThat(contents).hasSize(3) .containsKeys("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar", "74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar", "a69532b5b92bb891fbd9fa1a6b3af9087ea7050255f59ba61a796f8555ecd783/layer.tar"); assertThat(contents.get("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar")) .containsExactly("/cnb/order.toml"); assertThat(contents.get("74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar")) .containsExactly("/cnb/stack.toml"); } @Test void exportLayersExportsLayerTarsWithPlatformWhenSupportedVersion() throws Exception { setVersion("1.48"); ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base"); URI exportUri = new URI("/v1.48/images/docker.io/paketobuildpacks/builder:base/get?platform=" + ENCODED_LINUX_ARM64_PLATFORM_JSON); given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export.tar")); MultiValueMap contents = new LinkedMultiValueMap<>(); this.api.exportLayers(reference, LINUX_ARM64_PLATFORM, (name, archive) -> { ByteArrayOutputStream out = new ByteArrayOutputStream(); archive.writeTo(out); try (TarArchiveInputStream in = new TarArchiveInputStream( new ByteArrayInputStream(out.toByteArray()))) { TarArchiveEntry entry = in.getNextEntry(); while (entry != null) { contents.add(name, entry.getName()); entry = in.getNextEntry(); } } }); assertThat(contents).hasSize(3) .containsKeys("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar", "74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar", "a69532b5b92bb891fbd9fa1a6b3af9087ea7050255f59ba61a796f8555ecd783/layer.tar"); assertThat(contents.get("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar")) .containsExactly("/cnb/order.toml"); assertThat(contents.get("74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar")) .containsExactly("/cnb/stack.toml"); } @Test void exportLayersExportsLayerTarsWithPlatformWhenOldVersionInspectImage() throws Exception { setVersion("1.47"); ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base"); URI exportUri = new URI("/v1.47/images/docker.io/paketobuildpacks/builder:base/get"); given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export.tar")); MultiValueMap contents = new LinkedMultiValueMap<>(); this.api.exportLayers(reference, LINUX_ARM64_PLATFORM, (name, archive) -> { ByteArrayOutputStream out = new ByteArrayOutputStream(); archive.writeTo(out); try (TarArchiveInputStream in = new TarArchiveInputStream( new ByteArrayInputStream(out.toByteArray()))) { TarArchiveEntry entry = in.getNextEntry(); while (entry != null) { contents.add(name, entry.getName()); entry = in.getNextEntry(); } } }); assertThat(contents).hasSize(3) .containsKeys("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar", "74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar", "a69532b5b92bb891fbd9fa1a6b3af9087ea7050255f59ba61a796f8555ecd783/layer.tar"); assertThat(contents.get("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar")) .containsExactly("/cnb/order.toml"); assertThat(contents.get("74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar")) .containsExactly("/cnb/stack.toml"); } @Test void exportLayersWithSymlinksExportsLayerTars() throws Exception { ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base"); URI exportUri = new URI(IMAGES_URL + "/docker.io/paketobuildpacks/builder:base/get"); given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export-symlinks.tar")); MultiValueMap contents = new LinkedMultiValueMap<>(); this.api.exportLayers(reference, (name, archive) -> { ByteArrayOutputStream out = new ByteArrayOutputStream(); archive.writeTo(out); try (TarArchiveInputStream in = new TarArchiveInputStream( new ByteArrayInputStream(out.toByteArray()))) { TarArchiveEntry entry = in.getNextEntry(); while (entry != null) { contents.add(name, entry.getName()); entry = in.getNextEntry(); } } }); assertThat(contents).hasSize(3) .containsKeys("6aa3691a73805f608e5fce69fb6bc89aec8362f58a6b4be2682515e9cfa3cc1a.tar", "762e198f655bc2580ef3e56b538810fd2b9981bd707f8a44c70344b58f9aee68.tar", "d3cc975ad97fdfbb73d9daf157e7f658d6117249fd9c237e3856ad173c87e1d2.tar"); assertThat(contents.get("d3cc975ad97fdfbb73d9daf157e7f658d6117249fd9c237e3856ad173c87e1d2.tar")) .containsExactly("/cnb/order.toml"); assertThat(contents.get("762e198f655bc2580ef3e56b538810fd2b9981bd707f8a44c70344b58f9aee68.tar")) .containsExactly("/cnb/stack.toml"); } @Test @SuppressWarnings("NullAway") // Test null check void tagWhenReferenceIsNullThrowsException() { ImageReference tag = ImageReference.of("localhost:5000/ubuntu"); assertThatIllegalArgumentException().isThrownBy(() -> this.api.tag(null, tag)) .withMessage("'sourceReference' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void tagWhenTargetIsNullThrowsException() { ImageReference reference = ImageReference.of("localhost:5000/ubuntu"); assertThatIllegalArgumentException().isThrownBy(() -> this.api.tag(reference, null)) .withMessage("'targetReference' must not be null"); } @Test void tagTagsImage() throws Exception { ImageReference sourceReference = ImageReference.of("localhost:5000/ubuntu"); ImageReference targetReference = ImageReference.of("localhost:5000/ubuntu:tagged"); URI tagURI = new URI(IMAGES_URL + "/localhost:5000/ubuntu/tag?repo=localhost%3A5000%2Fubuntu&tag=tagged"); given(http().post(tagURI)).willReturn(emptyResponse()); this.api.tag(sourceReference, targetReference); then(http()).should().post(tagURI); } @Test void tagRenamesImage() throws Exception { ImageReference sourceReference = ImageReference.of("localhost:5000/ubuntu"); ImageReference targetReference = ImageReference.of("localhost:5000/ubuntu-2"); URI tagURI = new URI(IMAGES_URL + "/localhost:5000/ubuntu/tag?repo=localhost%3A5000%2Fubuntu-2"); given(http().post(tagURI)).willReturn(emptyResponse()); this.api.tag(sourceReference, targetReference); then(http()).should().post(tagURI); } } @Nested class ContainerDockerApiTests { private ContainerApi api; @Captor @SuppressWarnings("NullAway.Init") private ArgumentCaptor> writer; @Mock @SuppressWarnings("NullAway.Init") private UpdateListener logListener; @BeforeEach void setup() { this.api = DockerApiTests.this.dockerApi.container(); } @Test @SuppressWarnings("NullAway") // Test null check void createWhenConfigIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.create(null, null)) .withMessage("'config' must not be null"); } @Test void createCreatesContainer() throws Exception { ImageReference imageReference = ImageReference.of("ubuntu:bionic"); ContainerConfig config = ContainerConfig.of(imageReference, (update) -> update.withCommand("/bin/bash")); URI createUri = new URI(CONTAINERS_URL + "/create"); given(http().post(eq(createUri), eq("application/json"), any())) .willReturn(responseOf("create-container-response.json")); ContainerReference containerReference = this.api.create(config, null); assertThat(containerReference).hasToString("e90e34656806"); then(http()).should().post(any(), any(), this.writer.capture()); ByteArrayOutputStream out = new ByteArrayOutputStream(); this.writer.getValue().accept(out); assertThat(out.toByteArray()).hasSize(config.toString().length()); } @Test void createWhenHasContentContainerWithContent() throws Exception { ImageReference imageReference = ImageReference.of("ubuntu:bionic"); ContainerConfig config = ContainerConfig.of(imageReference, (update) -> update.withCommand("/bin/bash")); TarArchive archive = TarArchive.of((layout) -> { layout.directory("/test", Owner.ROOT); layout.file("/test/file", Owner.ROOT, Content.of("test")); }); ContainerContent content = ContainerContent.of(archive); URI createUri = new URI(CONTAINERS_URL + "/create"); given(http().post(eq(createUri), eq("application/json"), any())) .willReturn(responseOf("create-container-response.json")); URI uploadUri = new URI(CONTAINERS_URL + "/e90e34656806/archive?path=%2F"); given(http().put(eq(uploadUri), eq("application/x-tar"), any())).willReturn(emptyResponse()); ContainerReference containerReference = this.api.create(config, null, content); assertThat(containerReference).hasToString("e90e34656806"); then(http()).should().post(any(), any(), this.writer.capture()); ByteArrayOutputStream out = new ByteArrayOutputStream(); this.writer.getValue().accept(out); assertThat(out.toByteArray()).hasSize(config.toString().length()); then(http()).should().put(any(), any(), this.writer.capture()); this.writer.getValue().accept(out); assertThat(out.toByteArray()).hasSizeGreaterThan(2000); } @Test void createWithPlatformCreatesContainer() throws Exception { ImageReference imageReference = ImageReference.of("ubuntu:bionic"); ContainerConfig config = ContainerConfig.of(imageReference, (update) -> update.withCommand("/bin/bash")); ImagePlatform platform = ImagePlatform.of("linux/arm64/v1"); setVersion("1.41"); URI createUri = new URI("/v1.41/containers/create?platform=linux%2Farm64%2Fv1"); given(http().post(eq(createUri), eq("application/json"), any())) .willReturn(responseOf("create-container-response.json")); ContainerReference containerReference = this.api.create(config, platform); assertThat(containerReference).hasToString("e90e34656806"); then(http()).should().post(any(), any(), this.writer.capture()); ByteArrayOutputStream out = new ByteArrayOutputStream(); this.writer.getValue().accept(out); assertThat(out.toByteArray()).hasSize(config.toString().length()); } @Test void createWithPlatformAndUnknownApiVersionAttemptsCreate() throws Exception { createWithPlatform(null); } private void createWithPlatform(@Nullable String apiVersion) throws IOException, URISyntaxException { ImageReference imageReference = ImageReference.of("ubuntu:bionic"); ContainerConfig config = ContainerConfig.of(imageReference, (update) -> update.withCommand("/bin/bash")); ImagePlatform platform = ImagePlatform.of("linux/arm64/v1"); URI createUri = new URI(CONTAINERS_URL + "/create?platform=linux%2Farm64%2Fv1"); given(http().post(eq(createUri), eq("application/json"), any())) .willReturn(responseOf("create-container-response.json")); ContainerReference containerReference = this.api.create(config, platform); assertThat(containerReference).hasToString("e90e34656806"); then(http()).should().post(any(), any(), this.writer.capture()); ByteArrayOutputStream out = new ByteArrayOutputStream(); this.writer.getValue().accept(out); assertThat(out.toByteArray()).hasSize(config.toString().length()); } @Test void createWithPlatformAndKnownInsufficientApiVersionThrowsException() throws Exception { ImageReference imageReference = ImageReference.of("ubuntu:bionic"); ContainerConfig config = ContainerConfig.of(imageReference, (update) -> update.withCommand("/bin/bash")); ImagePlatform platform = ImagePlatform.of("linux/arm64/v1"); setVersion("1.24"); assertThatIllegalStateException().isThrownBy(() -> this.api.create(config, platform)) .withMessageContaining("must be at least 1.41") .withMessageContaining("current API version is 1.24"); } @Test @SuppressWarnings("NullAway") // Test null check void startWhenReferenceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.start(null)) .withMessage("'reference' must not be null"); } @Test void startStartsContainer() throws Exception { ContainerReference reference = ContainerReference.of("e90e34656806"); URI startContainerUri = new URI(CONTAINERS_URL + "/e90e34656806/start"); given(http().post(startContainerUri)).willReturn(emptyResponse()); this.api.start(reference); then(http()).should().post(startContainerUri); } @Test @SuppressWarnings("NullAway") // Test null check void logsWhenReferenceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.logs(null, UpdateListener.none())) .withMessage("'reference' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void logsWhenListenerIsNullThrowsException() { assertThatIllegalArgumentException() .isThrownBy(() -> this.api.logs(ContainerReference.of("e90e34656806"), null)) .withMessage("'listener' must not be null"); } @Test void logsProducesEvents() throws Exception { ContainerReference reference = ContainerReference.of("e90e34656806"); URI logsUri = new URI(CONTAINERS_URL + "/e90e34656806/logs?stdout=1&stderr=1&follow=1"); given(http().get(logsUri)).willReturn(responseOf("log-update-event.stream")); this.api.logs(reference, this.logListener); InOrder ordered = inOrder(this.logListener); ordered.verify(this.logListener).onStart(); ordered.verify(this.logListener, times(7)).onUpdate(any()); ordered.verify(this.logListener).onFinish(); } @Test @SuppressWarnings("NullAway") // Test null check void waitWhenReferenceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.wait(null)) .withMessage("'reference' must not be null"); } @Test void waitReturnsStatus() throws Exception { ContainerReference reference = ContainerReference.of("e90e34656806"); URI waitUri = new URI(CONTAINERS_URL + "/e90e34656806/wait"); given(http().post(waitUri)).willReturn(responseOf("container-wait-response.json")); ContainerStatus status = this.api.wait(reference); assertThat(status.getStatusCode()).isOne(); } @Test @SuppressWarnings("NullAway") // Test null check void removeWhenReferenceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.remove(null, true)) .withMessage("'reference' must not be null"); } @Test void removeRemovesContainer() throws Exception { ContainerReference reference = ContainerReference.of("e90e34656806"); URI removeUri = new URI(CONTAINERS_URL + "/e90e34656806"); given(http().delete(removeUri)).willReturn(emptyResponse()); this.api.remove(reference, false); then(http()).should().delete(removeUri); } @Test void removeWhenForceIsTrueRemovesContainer() throws Exception { ContainerReference reference = ContainerReference.of("e90e34656806"); URI removeUri = new URI(CONTAINERS_URL + "/e90e34656806?force=1"); given(http().delete(removeUri)).willReturn(emptyResponse()); this.api.remove(reference, true); then(http()).should().delete(removeUri); } } @Nested class VolumeDockerApiTests { private VolumeApi api; @BeforeEach void setup() { this.api = DockerApiTests.this.dockerApi.volume(); } @Test @SuppressWarnings("NullAway") // Test null check void deleteWhenNameIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.delete(null, false)) .withMessage("'name' must not be null"); } @Test void deleteDeletesContainer() throws Exception { VolumeName name = VolumeName.of("test"); URI removeUri = new URI(VOLUMES_URL + "/test"); given(http().delete(removeUri)).willReturn(emptyResponse()); this.api.delete(name, false); then(http()).should().delete(removeUri); } @Test void deleteWhenForceIsTrueDeletesContainer() throws Exception { VolumeName name = VolumeName.of("test"); URI removeUri = new URI(VOLUMES_URL + "/test?force=1"); given(http().delete(removeUri)).willReturn(emptyResponse()); this.api.delete(name, true); then(http()).should().delete(removeUri); } } @Nested class SystemDockerApiTests { private SystemApi api; @BeforeEach void setup() { this.api = DockerApiTests.this.dockerApi.system(); } @Test void getApiVersionWithVersionHeaderReturnsVersion() throws Exception { given(http().head(eq(new URI(PING_URL)))) .willReturn(responseWithHeaders(new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, "1.44"))); assertThat(this.api.getApiVersion()).isEqualTo(ApiVersion.of(1, 44)); } @Test void getApiVersionWithEmptyVersionHeaderReturnsUnknownVersion() throws Exception { given(http().head(eq(new URI(PING_URL)))) .willReturn(responseWithHeaders(new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, ""))); assertThat(this.api.getApiVersion()).isEqualTo(DockerApi.UNKNOWN_API_VERSION); } @Test void getApiVersionWithNoVersionHeaderReturnsUnknownVersion() throws Exception { given(http().head(eq(new URI(PING_URL)))).willReturn(emptyResponse()); assertThat(this.api.getApiVersion()).isEqualTo(DockerApi.UNKNOWN_API_VERSION); } @Test void getApiVersionWithExceptionReturnsUnknownVersion(CapturedOutput output) throws Exception { given(http().head(eq(new URI(PING_URL)))).willThrow(new IOException("simulated error")); assertThat(this.api.getApiVersion()).isEqualTo(DockerApi.UNKNOWN_API_VERSION); assertThat(output).contains("Warning: Failed to determine Docker API version: simulated error"); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerLogTests.java ================================================ /* * 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.buildpack.platform.docker; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.testsupport.system.CapturedOutput; import org.springframework.boot.testsupport.system.OutputCaptureExtension; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link DockerLog}. * * @author Dmytro nosan */ @ExtendWith(OutputCaptureExtension.class) class DockerLogTests { @Test void toSystemOutPrintsToSystemOut(CapturedOutput output) { DockerLog logger = DockerLog.toSystemOut(); logger.log("Hello world"); assertThat(output.getErr()).isEmpty(); assertThat(output.getOut()).contains("Hello world"); } @Test void toPrintsToOutput(CapturedOutput output) { DockerLog logger = DockerLog.to(System.err); logger.log("Hello world"); assertThat(output.getOut()).isEmpty(); assertThat(output.getErr()).contains("Hello world"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ExportedImageTarTests.java ================================================ /* * 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.buildpack.platform.docker; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.io.TarArchive.Compression; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ExportedImageTar}. * * @author Phillip Webb * @author Scott Frederick */ class ExportedImageTarTests { @ParameterizedTest @ValueSource(strings = { "export-docker-desktop.tar", "export-docker-desktop-containerd.tar", "export-docker-desktop-containerd-manifest-list.tar", "export-docker-engine.tar", "export-podman.tar", "export-docker-desktop-nested-index.tar", "export-docker-desktop-containerd-alt-mediatype.tar" }) void test(String tarFile) throws Exception { ImageReference reference = ImageReference.of("test:latest"); try (ExportedImageTar exportedImageTar = new ExportedImageTar(reference, getClass().getResourceAsStream(tarFile))) { Compression expectedCompression = (!tarFile.contains("containerd")) ? Compression.NONE : Compression.GZIP; String expectedName = (expectedCompression != Compression.GZIP) ? "5caae51697b248b905dca1a4160864b0e1a15c300981736555cdce6567e8d477" : "f0f1fd1bdc71ac6a4dc99cea5f5e45c86c5ec26fe4d1daceeb78207303606429"; List names = new ArrayList<>(); exportedImageTar.exportLayers((name, tarArchive) -> { names.add(name); assertThat(tarArchive.getCompression()).isEqualTo(expectedCompression); }); assertThat(names).filteredOn((name) -> name.contains(expectedName)).isNotEmpty(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ImagePlatformTests.java ================================================ /* * 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.buildpack.platform.docker; import java.io.IOException; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; class ImagePlatformTests extends AbstractJsonTests { @Test void ofWithOsParses() { ImagePlatform platform = ImagePlatform.of("linux"); assertThat(platform.toString()).isEqualTo("linux"); } @Test void ofWithOsAndArchitectureParses() { ImagePlatform platform = ImagePlatform.of("linux/amd64"); assertThat(platform.toString()).isEqualTo("linux/amd64"); } @Test void ofWithOsAndArchitectureAndVariantParses() { ImagePlatform platform = ImagePlatform.of("linux/amd64/v1"); assertThat(platform.toString()).isEqualTo("linux/amd64/v1"); } @Test void ofWithEmptyValueFails() { assertThatIllegalArgumentException().isThrownBy(() -> ImagePlatform.of("")) .withMessageContaining("'value' must not be empty"); } @Test void ofWithTooManySegmentsFails() { assertThatIllegalArgumentException().isThrownBy(() -> ImagePlatform.of("linux/amd64/v1/extra")) .withMessageContaining("'value' [linux/amd64/v1/extra] must be in the form"); } @Test void fromImageMatchesImage() throws IOException { ImagePlatform platform = ImagePlatform.from(getImage()); assertThat(platform.toString()).isEqualTo("linux/amd64/v1"); } @Test void toJsonString() { ImagePlatform platform = ImagePlatform.of("linux/amd64/v1"); assertThat(platform.toJson()).isEqualTo(""" {"os":"linux","architecture":"amd64","variant":"v1"}"""); } @Test void toJsonStringWhenOnlyOs() { ImagePlatform platform = ImagePlatform.of("linux"); assertThat(platform.toJson()).isEqualTo(""" {"os":"linux"}"""); } private Image getImage() throws IOException { return Image.of(getContent("type/image.json")); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/LoadImageUpdateEventTests.java ================================================ /* * 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.buildpack.platform.docker; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.LoadImageUpdateEvent.ErrorDetail; import org.springframework.boot.buildpack.platform.docker.ProgressUpdateEvent.ProgressDetail; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link LoadImageUpdateEvent}. * * @author Phillip Webb * @author Scott Frederick */ class LoadImageUpdateEventTests extends ProgressUpdateEventTests { @Test void getStreamReturnsStream() { LoadImageUpdateEvent event = createEvent(); assertThat(event.getStream()).isEqualTo("stream"); } @Test void getErrorDetailReturnsErrorDetail() { LoadImageUpdateEvent event = createEvent(); assertThat(event.getErrorDetail()).extracting(ErrorDetail::getMessage).isEqualTo("max depth exceeded"); } @Override protected LoadImageUpdateEvent createEvent(String status, ProgressDetail progressDetail, String progress) { return new LoadImageUpdateEvent("stream", status, progressDetail, progress, new ErrorDetail("max depth exceeded")); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/LogUpdateEventTests.java ================================================ /* * 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.buildpack.platform.docker; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link LogUpdateEvent}. * * @author Phillip Webb */ class LogUpdateEventTests { @Test void readAllWhenSimpleStreamReturnsEvents() throws Exception { List events = readAll("log-update-event.stream"); assertThat(events).hasSize(7); assertThat(events.get(0)) .hasToString("Analyzing image '307c032c4ceaa6330b6c02af945a1fe56a8c3c27c28268574b217c1d38b093cf'"); assertThat(events.get(1)) .hasToString("Writing metadata for uncached layer 'org.cloudfoundry.openjdk:openjdk-jre'"); assertThat(events.get(2)) .hasToString("Using cached launch layer 'org.cloudfoundry.jvmapplication:executable-jar'"); } @Test void readAllWhenAnsiStreamReturnsEvents() throws Exception { List events = readAll("log-update-event-ansi.stream"); assertThat(events).hasSize(20); assertThat(events.get(0).toString()).isEmpty(); assertThat(events.get(1)).hasToString("Cloud Foundry OpenJDK Buildpack v1.0.64"); assertThat(events.get(2)).hasToString(" OpenJDK JRE 11.0.5: Reusing cached layer"); } @Test void readSucceedsWhenStreamTypeIsInvalid() throws IOException { List events = readAll("log-update-event-invalid-stream-type.stream"); assertThat(events).hasSize(1); assertThat(events.get(0)).hasToString("Stream type is out of bounds. Must be >= 0 and < 3, but was 3"); } private List readAll(String name) throws IOException { List events = new ArrayList<>(); try (InputStream inputStream = getClass().getResourceAsStream(name)) { LogUpdateEvent.readAll(inputStream, events::add); } return events; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ProgressUpdateEventTests.java ================================================ /* * 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.buildpack.platform.docker; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.ProgressUpdateEvent.ProgressDetail; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ProgressUpdateEvent}. * * @param The event type * @author Phillip Webb * @author Scott Frederick * @author Wolfgang Kronberg */ abstract class ProgressUpdateEventTests { @Test void getStatusReturnsStatus() { ProgressUpdateEvent event = createEvent(); assertThat(event.getStatus()).isEqualTo("status"); } @Test void getProgressDetailReturnsProgressDetails() { ProgressUpdateEvent event = createEvent(); ProgressDetail progressDetail = event.getProgressDetail(); assertThat(progressDetail).isNotNull(); assertThat(progressDetail.asPercentage()).isEqualTo(50); } @Test void getProgressDetailReturnsProgressDetailsForLongNumbers() { ProgressUpdateEvent event = createEvent("status", new ProgressDetail(4000000000L, 8000000000L), "progress"); ProgressDetail progressDetail = event.getProgressDetail(); assertThat(progressDetail).isNotNull(); assertThat(progressDetail.asPercentage()).isEqualTo(50); } @Test void getProgressReturnsProgress() { ProgressUpdateEvent event = createEvent(); assertThat(event.getProgress()).isEqualTo("progress"); } protected E createEvent() { return createEvent("status", new ProgressDetail(1L, 2L), "progress"); } protected abstract E createEvent(String status, ProgressDetail progressDetail, String progress); } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/PullImageUpdateEventTests.java ================================================ /* * 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.buildpack.platform.docker; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.ProgressUpdateEvent.ProgressDetail; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link PullImageUpdateEvent}. * * @author Phillip Webb * @author Scott Frederick */ class PullImageUpdateEventTests extends ProgressUpdateEventTests { @Test void getIdReturnsId() { PullImageUpdateEvent event = createEvent(); assertThat(event.getId()).isEqualTo("id"); } @Override protected PullImageUpdateEvent createEvent(String status, ProgressDetail progressDetail, String progress) { return new PullImageUpdateEvent("id", status, progressDetail, progress); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/PullUpdateEventTests.java ================================================ /* * 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.buildpack.platform.docker; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.ProgressUpdateEvent.ProgressDetail; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link PullImageUpdateEvent}. * * @author Phillip Webb */ class PullUpdateEventTests extends AbstractJsonTests { @Test @SuppressWarnings("removal") void readValueWhenFullDeserializesJson() throws Exception { PullImageUpdateEvent event = getJsonMapper().readValue(getContent("pull-update-full.json"), PullImageUpdateEvent.class); assertThat(event.getId()).isEqualTo("4f4fb700ef54"); assertThat(event.getStatus()).isEqualTo("Extracting"); ProgressDetail progressDetail = event.getProgressDetail(); assertThat(progressDetail).isNotNull(); assertThat(progressDetail.asPercentage()).isEqualTo(50); assertThat(event.getProgress()).isEqualTo("[==================================================>] 32B/32B"); } @Test void readValueWhenMinimalDeserializesJson() throws Exception { PullImageUpdateEvent event = getJsonMapper().readValue(getContent("pull-update-minimal.json"), PullImageUpdateEvent.class); assertThat(event.getId()).isNull(); assertThat(event.getStatus()).isEqualTo("Status: Downloaded newer image for paketo-buildpacks/cnb:base"); assertThat(event.getProgressDetail()).isNull(); assertThat(event.getProgress()).isNull(); } @Test void readValueWhenEmptyDetailsDeserializesJson() throws Exception { PullImageUpdateEvent event = getJsonMapper().readValue(getContent("pull-with-empty-details.json"), PullImageUpdateEvent.class); assertThat(event.getId()).isEqualTo("d837a2a1365e"); assertThat(event.getStatus()).isEqualTo("Pulling fs layer"); assertThat(event.getProgressDetail()).isNull(); assertThat(event.getProgress()).isNull(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/PushImageUpdateEventTests.java ================================================ /* * 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.buildpack.platform.docker; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.ProgressUpdateEvent.ProgressDetail; import org.springframework.boot.buildpack.platform.docker.PushImageUpdateEvent.ErrorDetail; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link PushImageUpdateEvent}. * * @author Scott Frederick */ class PushImageUpdateEventTests extends ProgressUpdateEventTests { @Test void getIdReturnsId() { PushImageUpdateEvent event = createEvent(); assertThat(event.getId()).isEqualTo("id"); } @Test void getErrorReturnsErrorDetail() { PushImageUpdateEvent event = new PushImageUpdateEvent("id", "status", new ProgressDetail(null, null), "progress", new PushImageUpdateEvent.ErrorDetail("test message")); ErrorDetail errorDetail = event.getErrorDetail(); assertThat(errorDetail).isNotNull(); assertThat(errorDetail.getMessage()).isEqualTo("test message"); } @Override protected PushImageUpdateEvent createEvent(String status, ProgressDetail progressDetail, String progress) { return new PushImageUpdateEvent("id", status, progressDetail, progress, null); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/TotalProgressBarTests.java ================================================ /* * 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.buildpack.platform.docker; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link TotalProgressBar}. * * @author Phillip Webb */ class TotalProgressBarTests { @Test void withPrefixAndBookends() { TestPrintStream out = new TestPrintStream(); TotalProgressBar bar = new TotalProgressBar("prefix:", '#', true, out); assertThat(out).hasToString("prefix: [ "); bar.accept(new TotalProgressEvent(10)); assertThat(out).hasToString("prefix: [ #####"); bar.accept(new TotalProgressEvent(50)); assertThat(out).hasToString("prefix: [ #########################"); bar.accept(new TotalProgressEvent(100)); assertThat(out).hasToString(String.format("prefix: [ ################################################## ]%n")); } @Test void withoutPrefix() { TestPrintStream out = new TestPrintStream(); TotalProgressBar bar = new TotalProgressBar(null, '#', true, out); assertThat(out).hasToString("[ "); bar.accept(new TotalProgressEvent(10)); assertThat(out).hasToString("[ #####"); bar.accept(new TotalProgressEvent(50)); assertThat(out).hasToString("[ #########################"); bar.accept(new TotalProgressEvent(100)); assertThat(out).hasToString(String.format("[ ################################################## ]%n")); } @Test void withoutBookends() { TestPrintStream out = new TestPrintStream(); TotalProgressBar bar = new TotalProgressBar("", '.', false, out); assertThat(out).hasToString(""); bar.accept(new TotalProgressEvent(10)); assertThat(out).hasToString("....."); bar.accept(new TotalProgressEvent(50)); assertThat(out).hasToString("........................."); bar.accept(new TotalProgressEvent(100)); assertThat(out).hasToString(String.format("..................................................%n")); } static class TestPrintStream extends PrintStream { TestPrintStream() { super(new ByteArrayOutputStream()); } @Override public String toString() { return this.out.toString(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/TotalProgressEventTests.java ================================================ /* * 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.buildpack.platform.docker; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link TotalProgressEvent}. * * @author Phillip Webb */ class TotalProgressEventTests { @Test void create() { assertThat(new TotalProgressEvent(0).getPercent()).isZero(); assertThat(new TotalProgressEvent(10).getPercent()).isEqualTo(10); assertThat(new TotalProgressEvent(100).getPercent()).isEqualTo(100); } @Test void createWhenPercentLessThanZeroThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> new TotalProgressEvent(-1)) .withMessage("'percent' must be in the range 0 to 100"); } @Test void createWhenEventMoreThanOneHundredThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> new TotalProgressEvent(101)) .withMessage("'percent' must be in the range 0 to 100"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/TotalProgressListenerTests.java ================================================ /* * 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.buildpack.platform.docker; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import com.fasterxml.jackson.annotation.JsonCreator; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import org.springframework.boot.buildpack.platform.json.JsonStream; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link TotalProgressPullListener}. * * @author Phillip Webb * @author Scott Frederick */ class TotalProgressListenerTests extends AbstractJsonTests { @Test void totalProgress() throws Exception { List progress = new ArrayList<>(); TestTotalProgressListener listener = new TestTotalProgressListener((event) -> progress.add(event.getPercent())); run(listener); int last = 0; for (Integer update : progress) { assertThat(update).isGreaterThanOrEqualTo(last); last = update; } assertThat(last).isEqualTo(100); } @Test @Disabled("For visual inspection") void totalProgressUpdatesSmoothly() throws Exception { TestTotalProgressListener listener = new TestTotalProgressListener(new TotalProgressBar("Pulling layers:")); run(listener); } private void run(TestTotalProgressListener listener) throws IOException { JsonStream jsonStream = new JsonStream(getJsonMapper()); listener.onStart(); jsonStream.get(getContent("pull-stream.json"), TestImageUpdateEvent.class, listener::onUpdate); listener.onFinish(); } private static class TestTotalProgressListener extends TotalProgressListener { TestTotalProgressListener(Consumer consumer) { super(consumer, new String[] { "Pulling", "Downloading", "Extracting" }); } @Override public void onUpdate(TestImageUpdateEvent event) { super.onUpdate(event); try { Thread.sleep(10); } catch (InterruptedException ex) { // Ignore } } } private static class TestImageUpdateEvent extends ImageProgressUpdateEvent { @JsonCreator TestImageUpdateEvent(String id, String status, ProgressDetail progressDetail, String progress) { super(id, status, progressDetail, progress); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/CredentialHelperTests.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.util.UUID; import com.sun.jna.Platform; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIOException; /** * Tests for {@link CredentialHelper}. * * @author Dmytro Nosan */ class CredentialHelperTests { private static CredentialHelper helper; @BeforeAll static void setUp() throws Exception { helper = new CredentialHelper(getExecutableName()); } private static String getExecutableName() throws Exception { if (Platform.isWindows()) { String executablePath = geExecutableAbsolutePath("docker-credential-test.bat"); // cmd /c must resolve automatically .bat suffix return executablePath.substring(0, executablePath.lastIndexOf(".bat")); } return geExecutableAbsolutePath("docker-credential-test.sh"); } private static String geExecutableAbsolutePath(String executableName) throws Exception { return new ClassPathResource(executableName, CredentialHelperTests.class).getFile().getAbsolutePath(); } @Test void getWhenKnowUser() throws Exception { Credential credentials = helper.get("user.example.com"); assertThat(credentials).isNotNull(); assertThat(credentials.isIdentityToken()).isFalse(); assertThat(credentials.getServerUrl()).isEqualTo("user.example.com"); assertThat(credentials.getUsername()).isEqualTo("username"); assertThat(credentials.getSecret()).isEqualTo("secret"); } @Test void getWhenKnowToken() throws Exception { Credential credentials = helper.get("token.example.com"); assertThat(credentials).isNotNull(); assertThat(credentials.isIdentityToken()).isTrue(); assertThat(credentials.getServerUrl()).isEqualTo("token.example.com"); assertThat(credentials.getUsername()).isEqualTo(""); assertThat(credentials.getSecret()).isEqualTo("secret"); } @Test void getWhenCredentialsMissingMessageReturnsNull() throws Exception { Credential credentials = helper.get("credentials.missing.example.com"); assertThat(credentials).isNull(); } @Test void getWhenUsernameMissingMessageReturnsNull() throws Exception { Credential credentials = helper.get("username.missing.example.com"); assertThat(credentials).isNull(); } @Test void getWhenUrlMissingMessageReturnsNull() throws Exception { Credential credentials = helper.get("url.missing.example.com"); assertThat(credentials).isNull(); } @Test void getWhenUnknownErrorThrowsException() { assertThatIOException().isThrownBy(() -> helper.get("invalid.example.com")) .withMessageContaining("Unknown error"); } @Test void getWhenExecutableDoesNotExistErrorThrowsException() { String executable = "docker-credential-%s".formatted(UUID.randomUUID().toString()); assertThatIOException().isThrownBy(() -> new CredentialHelper(executable).get("invalid.example.com")) .withMessageContaining(executable) .satisfies((ex) -> { if (Platform.isMac()) { assertThat(ex.getMessage()).doesNotContain("/usr/local/bin/"); assertThat(ex.getSuppressed()).allSatisfy((suppressed) -> assertThat(suppressed) .hasMessageContaining("/usr/local/bin/" + executable)); } }); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/CredentialTests.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.io.IOException; import java.io.InputStream; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.testsupport.classpath.resources.WithResource; import org.springframework.core.io.ClassPathResource; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link Credential}. * * @author Dmytro Nosan */ class CredentialTests { @Test @WithResource(name = "credentials.json", content = """ { "ServerURL": "https://index.docker.io/v1/", "Username": "user", "Secret": "secret" } """) void createWhenUserCredentials() throws Exception { Credential credentials = getCredentials("credentials.json"); assertThat(credentials.getUsername()).isEqualTo("user"); assertThat(credentials.getSecret()).isEqualTo("secret"); assertThat(credentials.getServerUrl()).isEqualTo("https://index.docker.io/v1/"); assertThat(credentials.isIdentityToken()).isFalse(); } @Test @WithResource(name = "credentials.json", content = """ { "ServerURL": "https://index.docker.io/v1/", "Username": "", "Secret": "secret" } """) void createWhenTokenCredentials() throws Exception { Credential credentials = getCredentials("credentials.json"); assertThat(credentials.getUsername()).isEqualTo(""); assertThat(credentials.getSecret()).isEqualTo("secret"); assertThat(credentials.getServerUrl()).isEqualTo("https://index.docker.io/v1/"); assertThat(credentials.isIdentityToken()).isTrue(); } @Test @WithResource(name = "credentials.json", content = """ { "Username": "user", "Secret": "secret" } """) void createWhenNoServerUrl() throws Exception { Credential credentials = getCredentials("credentials.json"); assertThat(credentials.getUsername()).isEqualTo("user"); assertThat(credentials.getSecret()).isEqualTo("secret"); assertThat(credentials.getServerUrl()).isNull(); assertThat(credentials.isIdentityToken()).isFalse(); } private Credential getCredentials(String name) throws IOException { try (InputStream inputStream = new ClassPathResource(name).getInputStream()) { return new Credential(SharedJsonMapper.get().readTree(inputStream)); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.io.File; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Pattern; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerConfig; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerContext; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link DockerConfigurationMetadata}. * * @author Scott Frederick * @author Dmytro Nosan */ class DockerConfigurationMetadataTests extends AbstractJsonTests { private final Map environment = new LinkedHashMap<>(); @Test void configWithContextIsRead() throws Exception { this.environment.put("DOCKER_CONFIG", pathToResource("with-context/config.json")); DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); assertThat(config.getConfiguration().getCurrentContext()).isEqualTo("test-context"); assertThat(config.getConfiguration().getAuths()).isEmpty(); assertThat(config.getConfiguration().getCredHelpers()).isEmpty(); assertThat(config.getConfiguration().getCredsStore()).isNull(); assertThat(config.getContext().getDockerHost()).isEqualTo("unix:///home/user/.docker/docker.sock"); assertThat(config.getContext().isTlsVerify()).isFalse(); assertThat(config.getContext().getTlsPath()).isNull(); } @Test void configWithoutContextIsRead() throws Exception { this.environment.put("DOCKER_CONFIG", pathToResource("without-context/config.json")); DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); assertThat(config.getConfiguration().getCurrentContext()).isNull(); assertThat(config.getConfiguration().getAuths()).isEmpty(); assertThat(config.getConfiguration().getCredHelpers()).isEmpty(); assertThat(config.getConfiguration().getCredsStore()).isNull(); assertThat(config.getContext().getDockerHost()).isNull(); assertThat(config.getContext().isTlsVerify()).isFalse(); assertThat(config.getContext().getTlsPath()).isNull(); } @Test void configWithDefaultContextIsRead() throws Exception { this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); assertThat(config.getConfiguration().getCurrentContext()).isEqualTo("default"); assertThat(config.getConfiguration().getAuths()).isEmpty(); assertThat(config.getConfiguration().getCredHelpers()).isEmpty(); assertThat(config.getConfiguration().getCredsStore()).isNull(); assertThat(config.getContext().getDockerHost()).isNull(); assertThat(config.getContext().isTlsVerify()).isFalse(); assertThat(config.getContext().getTlsPath()).isNull(); } @Test void configIsReadWithProvidedContext() throws Exception { this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); DockerContext context = config.forContext("test-context"); assertThat(context.getDockerHost()).isEqualTo("unix:///home/user/.docker/docker.sock"); assertThat(context.isTlsVerify()).isTrue(); assertThat(context.getTlsPath()).matches(String.join(Pattern.quote(File.separator), "^.*", "with-default-context", "contexts", "tls", "[a-zA-z0-9]*", "docker$")); } @Test void invalidContextThrowsException() throws Exception { this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); assertThatIllegalArgumentException() .isThrownBy(() -> DockerConfigurationMetadata.from(this.environment::get).forContext("invalid-context")) .withMessageContaining("Docker context 'invalid-context' does not exist"); } @Test void configIsEmptyWhenConfigFileDoesNotExist() { this.environment.put("DOCKER_CONFIG", "docker-config-dummy-path"); DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); assertThat(config.getConfiguration().getCurrentContext()).isNull(); assertThat(config.getConfiguration().getAuths()).isEmpty(); assertThat(config.getConfiguration().getCredHelpers()).isEmpty(); assertThat(config.getConfiguration().getCredsStore()).isNull(); assertThat(config.getContext().getDockerHost()).isNull(); assertThat(config.getContext().isTlsVerify()).isFalse(); } @Test void configWithAuthIsRead() throws Exception { this.environment.put("DOCKER_CONFIG", pathToResource("with-auth/config.json")); DockerConfigurationMetadata metadata = DockerConfigurationMetadata.from(this.environment::get); DockerConfig configuration = metadata.getConfiguration(); assertThat(configuration.getCredsStore()).isEqualTo("desktop"); assertThat(configuration.getCredHelpers()).hasSize(3) .containsEntry("azurecr.io", "acr-env") .containsEntry("ecr.us-east-1.amazonaws.com", "ecr-login") .containsEntry("gcr.io", "gcr"); assertThat(configuration.getAuths()).hasSize(3).hasEntrySatisfying("https://index.docker.io/v1/", (auth) -> { assertThat(auth.getUsername()).isEqualTo("username"); assertThat(auth.getPassword()).isEqualTo("pass\u0000word"); assertThat(auth.getEmail()).isEqualTo("test@example.com"); }).hasEntrySatisfying("custom-registry.example.com", (auth) -> { assertThat(auth.getUsername()).isEqualTo("customUser"); assertThat(auth.getPassword()).isEqualTo("customPass"); assertThat(auth.getEmail()).isNull(); }).hasEntrySatisfying("my-registry.example.com", (auth) -> { assertThat(auth.getUsername()).isEqualTo("user"); assertThat(auth.getPassword()).isEqualTo("password"); assertThat(auth.getEmail()).isNull(); }); } private String pathToResource(String resource) throws URISyntaxException { URL url = getClass().getResource(resource); Path parent = Paths.get(url.toURI()).getParent(); assertThat(parent).isNotNull(); return parent.toAbsolutePath().toString(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigAuthenticationTests.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.util.Base64; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import tools.jackson.core.type.TypeReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.json.SharedJsonMapper; import org.springframework.boot.testsupport.classpath.resources.ResourcesRoot; import org.springframework.boot.testsupport.classpath.resources.WithResource; import org.springframework.boot.testsupport.system.OutputCaptureExtension; import org.springframework.core.io.ClassPathResource; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; /** * Tests for {@link DockerRegistryConfigAuthentication}. * * @author Dmytro Nosan * @author Phillip Webb */ @ExtendWith(OutputCaptureExtension.class) class DockerRegistryConfigAuthenticationTests { private final Map environment = new LinkedHashMap<>(); private final Map helperExceptions = new LinkedHashMap<>(); private final Map credentialHelpers = new HashMap<>(); @BeforeEach void cleanup() { DockerRegistryConfigAuthentication.credentialFromHelperCache.clear(); } @WithResource(name = "config.json", content = """ { "auths": { "https://index.docker.io/v1/": { "auth": "dXNlcm5hbWU6cGFzc3dvcmQ=", "email": "test@example.com" } } } """) @Test void getAuthHeaderWhenAuthForDockerDomain(@ResourcesRoot Path directory) { this.environment.put("DOCKER_CONFIG", directory.toString()); ImageReference imageReference = ImageReference.of("docker.io/ubuntu:latest"); String authHeader = getAuthHeader(imageReference); assertThat(authHeader).isNotNull(); assertThat(decode(authHeader)).hasSize(4) .containsEntry("serveraddress", "https://index.docker.io/v1/") .containsEntry("username", "username") .containsEntry("password", "password") .containsEntry("email", "test@example.com"); } @WithResource(name = "config.json", content = """ { "auths": { "https://index.docker.io/v1/": { "auth": "dXNlcm5hbWU6cGFzc3dvcmQ=", "email": "test@example.com" } } } """) @Test void getAuthHeaderWhenAuthForLegacyDockerDomain(@ResourcesRoot Path directory) { this.environment.put("DOCKER_CONFIG", directory.toString()); ImageReference imageReference = ImageReference.of("index.docker.io/ubuntu:latest"); String authHeader = getAuthHeader(imageReference); assertThat(authHeader).isNotNull(); assertThat(decode(authHeader)).hasSize(4) .containsEntry("serveraddress", "https://index.docker.io/v1/") .containsEntry("username", "username") .containsEntry("password", "password") .containsEntry("email", "test@example.com"); } @WithResource(name = "config.json", content = """ { "auths": { "my-registry.example.com": { "auth": "Y3VzdG9tVXNlcjpjdXN0b21QYXNz" } } } """) @Test void getAuthHeaderWhenAuthForCustomDomain(@ResourcesRoot Path directory) { this.environment.put("DOCKER_CONFIG", directory.toString()); ImageReference imageReference = ImageReference.of("my-registry.example.com/ubuntu:latest"); String authHeader = getAuthHeader(imageReference); assertThat(authHeader).isNotNull(); assertThat(decode(authHeader)).hasSize(4) .containsEntry("serveraddress", "my-registry.example.com") .containsEntry("username", "customUser") .containsEntry("password", "customPass") .containsEntry("email", null); } @WithResource(name = "config.json", content = """ { "auths": { "https://my-registry.example.com": { "auth": "Y3VzdG9tVXNlcjpjdXN0b21QYXNz" } } } """) @Test void getAuthHeaderWhenAuthForCustomDomainWithLegacyFormat(@ResourcesRoot Path directory) { this.environment.put("DOCKER_CONFIG", directory.toString()); ImageReference imageReference = ImageReference.of("my-registry.example.com/ubuntu:latest"); String authHeader = getAuthHeader(imageReference); assertThat(authHeader).isNotNull(); assertThat(decode(authHeader)).hasSize(4) .containsEntry("serveraddress", "https://my-registry.example.com") .containsEntry("username", "customUser") .containsEntry("password", "customPass") .containsEntry("email", null); } @WithResource(name = "config.json", content = """ { } """) @Test void getAuthHeaderWhenEmptyConfigDirectoryReturnsFallback(@ResourcesRoot Path directory) { this.environment.put("DOCKER_CONFIG", directory.toString()); ImageReference imageReference = ImageReference.of("docker.io/ubuntu:latest"); String authHeader = getAuthHeader(imageReference, DockerRegistryAuthentication.EMPTY_USER); assertThat(authHeader).isNotNull(); assertThat(decode(authHeader)).hasSize(4) .containsEntry("serveraddress", "") .containsEntry("username", "") .containsEntry("password", "") .containsEntry("email", ""); } @WithResource(name = "config.json", content = """ { "credsStore": "desktop" } """) @WithResource(name = "credentials.json", content = """ { "ServerURL": "https://index.docker.io/v1/", "Username": "", "Secret": "secret" } """) @Test void getAuthHeaderWhenUsingHelperFromCredsStore(@ResourcesRoot Path directory) throws Exception { this.environment.put("DOCKER_CONFIG", directory.toString()); ImageReference imageReference = ImageReference.of("docker.io/ubuntu:latest"); mockHelper("desktop", "https://index.docker.io/v1/", "credentials.json"); String authHeader = getAuthHeader(imageReference); assertThat(authHeader).isNotNull(); assertThat(decode(authHeader)).hasSize(1).containsEntry("identitytoken", "secret"); } @WithResource(name = "config.json", content = """ { "auths": { "gcr.io": { "email": "test@example.com" } }, "credsStore": "desktop", "credHelpers": { "gcr.io": "gcr" } } """) @WithResource(name = "credentials.json", content = """ { "ServerURL": "https://my-gcr.io", "Username": "username", "Secret": "secret" } """) @Test void getAuthHeaderWhenUsingHelperFromCredsStoreAndUseEmailFromAuth(@ResourcesRoot Path directory) throws Exception { this.environment.put("DOCKER_CONFIG", directory.toString()); ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest"); mockHelper("gcr", "gcr.io", "credentials.json"); String authHeader = getAuthHeader(imageReference); assertThat(authHeader).isNotNull(); assertThat(decode(authHeader)).hasSize(4) .containsEntry("serveraddress", "https://my-gcr.io") .containsEntry("username", "username") .containsEntry("password", "secret") .containsEntry("email", "test@example.com"); } @WithResource(name = "config.json", content = """ { "credsStore": "desktop", "credHelpers": { "gcr.io": "gcr" } } """) @WithResource(name = "credentials.json", content = """ { "Username": "username", "Secret": "secret" } """) @Test void getAuthHeaderWhenUsingHelperFromCredHelpersUsesProvidedServerUrl(@ResourcesRoot Path directory) throws Exception { this.environment.put("DOCKER_CONFIG", directory.toString()); ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest"); mockHelper("gcr", "gcr.io", "credentials.json"); String authHeader = getAuthHeader(imageReference); assertThat(authHeader).isNotNull(); assertThat(decode(authHeader)).hasSize(4) .containsEntry("serveraddress", "gcr.io") .containsEntry("username", "username") .containsEntry("password", "secret") .containsEntry("email", null); } @WithResource(name = "config.json", content = """ { "auths": { "gcr.io": { "auth": "dXNlcm5hbWU6cGFzc3dvcmQ=", "email": "test@example.com" } }, "credsStore": "desktop", "credHelpers": { "gcr.io": "gcr" } } """) @Test void getAuthHeaderWhenUsingHelperThatFailsLogsErrorAndReturnsFromAuths(@ResourcesRoot Path directory) throws Exception { this.environment.put("DOCKER_CONFIG", directory.toString()); ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest"); CredentialHelper helper = mockHelper("gcr"); given(helper.get("gcr.io")).willThrow(new IOException("Failed to obtain credentials for registry")); String authHeader = getAuthHeader(imageReference); assertThat(authHeader).isNotNull(); assertThat(decode(authHeader)).hasSize(4) .containsEntry("serveraddress", "gcr.io") .containsEntry("username", "username") .containsEntry("password", "password") .containsEntry("email", "test@example.com"); assertThat(this.helperExceptions).hasSize(1); assertThat(this.helperExceptions.keySet().iterator().next()) .contains("Error retrieving credentials for 'gcr.io' due to: Failed to obtain credentials for registry"); } @WithResource(name = "config.json", content = """ { "credsStore": "desktop", "credHelpers": { "gcr.io": "gcr" } } """) @Test void getAuthHeaderWhenUsingHelperThatFailsAndNoAuthLogsErrorAndReturnsFallback(@ResourcesRoot Path directory) throws Exception { this.environment.put("DOCKER_CONFIG", directory.toString()); ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest"); CredentialHelper helper = mockHelper("gcr"); given(helper.get("gcr.io")).willThrow(new IOException("Failed to obtain credentials for registry")); String authHeader = getAuthHeader(imageReference, DockerRegistryAuthentication.EMPTY_USER); assertThat(authHeader).isNotNull(); assertThat(this.helperExceptions).hasSize(1); assertThat(this.helperExceptions.keySet().iterator().next()) .contains("Error retrieving credentials for 'gcr.io' due to: Failed to obtain credentials for registry"); assertThat(decode(authHeader)).hasSize(4) .containsEntry("serveraddress", "") .containsEntry("username", "") .containsEntry("password", "") .containsEntry("email", ""); } @WithResource(name = "config.json", content = """ { "credsStore": "desktop", "credHelpers": { "gcr.io": "" } } """) @Test void getAuthHeaderWhenEmptyCredHelperReturnsFallbackAndDoesNotUseCredStore(@ResourcesRoot Path directory) throws Exception { this.environment.put("DOCKER_CONFIG", directory.toString()); ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest"); CredentialHelper desktopHelper = mockHelper("desktop"); String authHeader = getAuthHeader(imageReference, DockerRegistryAuthentication.EMPTY_USER); assertThat(authHeader).isNotNull(); // The Docker CLI appears to prioritize the credential helper over the // credential store, even when the helper is empty. assertThat(decode(authHeader)).hasSize(4) .containsEntry("serveraddress", "") .containsEntry("username", "") .containsEntry("password", "") .containsEntry("email", ""); then(desktopHelper).should(never()).get(any(String.class)); } @WithResource(name = "config.json", content = """ { "credsStore": "desktop" } """) @Test void getAuthHeaderReturnsFallbackWhenImageReferenceNull(@ResourcesRoot Path directory) throws Exception { this.environment.put("DOCKER_CONFIG", directory.toString()); CredentialHelper desktopHelper = mockHelper("desktop"); String authHeader = getAuthHeader(null, DockerRegistryAuthentication.EMPTY_USER); assertThat(authHeader).isNotNull(); assertThat(decode(authHeader)).hasSize(4) .containsEntry("serveraddress", "") .containsEntry("username", "") .containsEntry("password", "") .containsEntry("email", ""); then(desktopHelper).should(never()).get(any(String.class)); } @WithResource(name = "config.json", content = """ { "auths": { "https://my-registry.example.com": { "email": "test@example.com" } }, "credsStore": "desktop" } """) @WithResource(name = "credentials.json", content = """ { "Username": "username", "Secret": "secret" } """) @Test void getAuthHeaderWhenUsingHelperFromCredHelpersUsesImageReferenceServerUrlAsFallback(@ResourcesRoot Path directory) throws Exception { this.environment.put("DOCKER_CONFIG", directory.toString()); mockHelper("desktop", "my-registry.example.com", "credentials.json"); ImageReference imageReference = ImageReference.of("my-registry.example.com/ubuntu:latest"); String authHeader = getAuthHeader(imageReference); assertThat(authHeader).isNotNull(); assertThat(decode(authHeader)).hasSize(4) .containsEntry("serveraddress", "my-registry.example.com") .containsEntry("username", "username") .containsEntry("password", "secret") .containsEntry("email", "test@example.com"); } private @Nullable String getAuthHeader(@Nullable ImageReference imageReference) { return getAuthHeader(imageReference, null); } private @Nullable String getAuthHeader(@Nullable ImageReference imageReference, @Nullable DockerRegistryAuthentication fallback) { DockerRegistryConfigAuthentication authentication = getAuthentication(fallback); return authentication.getAuthHeader(imageReference); } private DockerRegistryConfigAuthentication getAuthentication(@Nullable DockerRegistryAuthentication fallback) { Function credentialHelperFactory = this.credentialHelpers::get; return new DockerRegistryConfigAuthentication(fallback, this.helperExceptions::put, this.environment::get, credentialHelperFactory); } private void mockHelper(String name, String serverUrl, String credentialsResourceName) throws Exception { CredentialHelper helper = mockHelper(name); given(helper.get(serverUrl)).willReturn(getCredentials(credentialsResourceName)); } private CredentialHelper mockHelper(String name) { CredentialHelper helper = mock(CredentialHelper.class); this.credentialHelpers.put(name, helper); return helper; } private Credential getCredentials(String resourceName) throws Exception { try (InputStream inputStream = new ClassPathResource(resourceName).getInputStream()) { return new Credential(SharedJsonMapper.get().readTree(inputStream)); } } private Map decode(String authHeader) { return SharedJsonMapper.get().readValue(Base64.getDecoder().decode(authHeader), new TypeReference<>() { }); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryTokenAuthenticationTests.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Base64; import org.json.JSONException; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import org.springframework.util.StreamUtils; /** * Tests for {@link DockerRegistryTokenAuthentication}. * * @author Scott Frederick */ class DockerRegistryTokenAuthenticationTests extends AbstractJsonTests { @Test void createAuthHeaderReturnsEncodedHeader() throws IOException, JSONException { DockerRegistryTokenAuthentication auth = new DockerRegistryTokenAuthentication("tokenvalue"); String header = auth.getAuthHeader(); String expectedJson = StreamUtils.copyToString(getContent("auth-token.json"), StandardCharsets.UTF_8); JSONAssert.assertEquals(expectedJson, new String(Base64.getUrlDecoder().decode(header)), true); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryUserAuthenticationTests.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Base64; import org.json.JSONException; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link DockerRegistryUserAuthentication}. * * @author Scott Frederick */ class DockerRegistryUserAuthenticationTests extends AbstractJsonTests { @Test void createMinimalAuthHeaderReturnsEncodedHeader() throws IOException, JSONException { DockerRegistryUserAuthentication auth = new DockerRegistryUserAuthentication("user", "secret", "https://docker.example.com", "docker@example.com"); String authHeader = auth.getAuthHeader(); assertThat(authHeader).isNotNull(); JSONAssert.assertEquals(jsonContent("auth-user-full.json"), decoded(authHeader), true); } @Test void createFullAuthHeaderReturnsEncodedHeader() throws IOException, JSONException { DockerRegistryUserAuthentication auth = new DockerRegistryUserAuthentication("user", "secret", null, null); String authHeader = auth.getAuthHeader(); assertThat(authHeader).isNotNull(); JSONAssert.assertEquals(jsonContent("auth-user-minimal.json"), decoded(authHeader), false); } private String jsonContent(String s) throws IOException { return StreamUtils.copyToString(getContent(s), StandardCharsets.UTF_8); } private String decoded(String header) { return new String(Base64.getUrlDecoder().decode(header)); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java ================================================ /* * 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.buildpack.platform.docker.configuration; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ResolvedDockerHost}. * * @author Scott Frederick * @author Moritz Halbritter */ class ResolvedDockerHostTests { private final Map environment = new LinkedHashMap<>(); @Test @DisabledOnOs(OS.WINDOWS) void resolveWhenDockerHostIsNullReturnsLinuxDefault() throws Exception { this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); assertThat(dockerHost.getAddress()).isEqualTo("/var/run/docker.sock"); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); } @Test @EnabledOnOs(OS.WINDOWS) void resolveWhenDockerHostIsNullReturnsWindowsDefault() throws Exception { this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); assertThat(dockerHost.getAddress()).isEqualTo("//./pipe/docker_engine"); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); } @Test @EnabledOnOs(OS.WINDOWS) void resolveWhenUsingDefaultContextReturnsWindowsDefault() { this.environment.put("DOCKER_CONTEXT", "default"); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); assertThat(dockerHost.getAddress()).isEqualTo("//./pipe/docker_engine"); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); } @Test @DisabledOnOs(OS.WINDOWS) void resolveWhenUsingDefaultContextReturnsLinuxDefault() { this.environment.put("DOCKER_CONTEXT", "default"); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); assertThat(dockerHost.getAddress()).isEqualTo("/var/run/docker.sock"); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); } @Test void resolveWhenDockerHostAddressIsLocalReturnsAddress(@TempDir Path tempDir) throws IOException { String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerConnectionConfiguration.Host(socketFilePath)); assertThat(dockerHost.isLocalFileReference()).isTrue(); assertThat(dockerHost.isRemote()).isFalse(); assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); } @Test void resolveWhenDockerHostAddressIsLocalWithSchemeReturnsAddress(@TempDir Path tempDir) throws IOException { String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerConnectionConfiguration.Host("unix://" + socketFilePath)); assertThat(dockerHost.isLocalFileReference()).isTrue(); assertThat(dockerHost.isRemote()).isFalse(); assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); } @Test void resolveWhenDockerHostAddressIsHttpReturnsAddress() { ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerConnectionConfiguration.Host("http://docker.example.com")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("http://docker.example.com"); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); } @Test void resolveWhenDockerHostAddressIsHttpsReturnsAddress() { ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerConnectionConfiguration.Host("https://docker.example.com", true, "/cert-path")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("https://docker.example.com"); assertThat(dockerHost.isSecure()).isTrue(); assertThat(dockerHost.getCertificatePath()).isEqualTo("/cert-path"); } @Test void resolveWhenDockerHostAddressIsTcpReturnsAddress() { ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerConnectionConfiguration.Host("tcp://192.168.99.100:2376", true, "/cert-path")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("tcp://192.168.99.100:2376"); assertThat(dockerHost.isSecure()).isTrue(); assertThat(dockerHost.getCertificatePath()).isEqualTo("/cert-path"); } @Test void resolveWhenDockerHostAddressIsTcpWithTrailingReturnsAddress() { ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerConnectionConfiguration.Host("tcp://192.168.99.100:2376/", true, "/cert-path")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("tcp://192.168.99.100:2376"); assertThat(dockerHost.isSecure()).isTrue(); assertThat(dockerHost.getCertificatePath()).isEqualTo("/cert-path"); } @Test void resolveWhenEnvironmentAddressIsLocalReturnsAddress(@TempDir Path tempDir) throws IOException { String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); this.environment.put("DOCKER_HOST", socketFilePath); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerConnectionConfiguration.Host("/unused")); assertThat(dockerHost.isLocalFileReference()).isTrue(); assertThat(dockerHost.isRemote()).isFalse(); assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); } @Test void resolveWhenEnvironmentAddressIsLocalWithSchemeReturnsAddress(@TempDir Path tempDir) throws IOException { String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); this.environment.put("DOCKER_HOST", "unix://" + socketFilePath); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerConnectionConfiguration.Host("/unused")); assertThat(dockerHost.isLocalFileReference()).isTrue(); assertThat(dockerHost.isRemote()).isFalse(); assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); } @Test void resolveWhenEnvironmentAddressIsTcpReturnsAddress() { this.environment.put("DOCKER_HOST", "tcp://192.168.99.100:2376"); this.environment.put("DOCKER_TLS_VERIFY", "1"); this.environment.put("DOCKER_CERT_PATH", "/cert-path"); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerConnectionConfiguration.Host("tcp://1.1.1.1")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("tcp://192.168.99.100:2376"); assertThat(dockerHost.isSecure()).isTrue(); assertThat(dockerHost.getCertificatePath()).isEqualTo("/cert-path"); } @Test void resolveWhenEnvironmentAddressIsTcpWithTrailingSlashReturnsAddress() { this.environment.put("DOCKER_HOST", "tcp://192.168.99.100:2376/"); this.environment.put("DOCKER_TLS_VERIFY", "1"); this.environment.put("DOCKER_CERT_PATH", "/cert-path"); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerConnectionConfiguration.Host("tcp://1.1.1.1")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("tcp://192.168.99.100:2376"); assertThat(dockerHost.isSecure()).isTrue(); assertThat(dockerHost.getCertificatePath()).isEqualTo("/cert-path"); } @Test void resolveWithDockerHostContextReturnsAddress() throws Exception { this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerConnectionConfiguration.Context("test-context")); assertThat(dockerHost.getAddress()).isEqualTo("/home/user/.docker/docker.sock"); assertThat(dockerHost.isSecure()).isTrue(); assertThat(dockerHost.getCertificatePath()).isNotNull(); } @Test void resolveWithDockerConfigMetadataContextReturnsAddress() throws Exception { this.environment.put("DOCKER_CONFIG", pathToResource("with-context/config.json")); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); assertThat(dockerHost.getAddress()).isEqualTo("/home/user/.docker/docker.sock"); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); } @Test void resolveWhenEnvironmentHasAddressAndContextPrefersContext() throws Exception { this.environment.put("DOCKER_CONFIG", pathToResource("with-context/config.json")); this.environment.put("DOCKER_CONTEXT", "test-context"); this.environment.put("DOCKER_HOST", "notused"); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); assertThat(dockerHost.getAddress()).isEqualTo("/home/user/.docker/docker.sock"); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); } private String pathToResource(String resource) throws URISyntaxException { URL url = getClass().getResource(resource); Path parent = Paths.get(url.toURI()).getParent(); assertThat(parent).isNotNull(); return parent.toAbsolutePath().toString(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/KeyStoreFactoryTests.java ================================================ /* * 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.buildpack.platform.docker.ssl; import java.io.IOException; import java.nio.file.Path; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link KeyStoreFactory}. * * @author Scott Frederick */ class KeyStoreFactoryTests { private PemFileWriter fileWriter; @BeforeEach void setUp() throws IOException { this.fileWriter = new PemFileWriter(); } @AfterEach void tearDown() throws IOException { this.fileWriter.cleanup(); } @Test void createKeyStoreWithCertChain() throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException { Path certPath = this.fileWriter.writeFile("cert.pem", PemFileWriter.CA_CERTIFICATE, PemFileWriter.CERTIFICATE); KeyStore keyStore = KeyStoreFactory.create(certPath, null, "test-alias"); assertThat(keyStore.containsAlias("test-alias-0")).isTrue(); assertThat(keyStore.getCertificate("test-alias-0")).isNotNull(); assertThat(keyStore.getKey("test-alias-0", new char[] {})).isNull(); assertThat(keyStore.containsAlias("test-alias-1")).isTrue(); assertThat(keyStore.getCertificate("test-alias-1")).isNotNull(); assertThat(keyStore.getKey("test-alias-1", new char[] {})).isNull(); } @Test void createKeyStoreWithCertChainAndRsaPrivateKey() throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException { Path certPath = this.fileWriter.writeFile("cert.pem", PemFileWriter.CA_CERTIFICATE, PemFileWriter.CERTIFICATE); Path keyPath = this.fileWriter.writeFile("key.pem", PemFileWriter.PRIVATE_RSA_KEY); KeyStore keyStore = KeyStoreFactory.create(certPath, keyPath, "test-alias"); assertThat(keyStore.containsAlias("test-alias")).isTrue(); assertThat(keyStore.getCertificate("test-alias")).isNotNull(); assertThat(keyStore.getKey("test-alias", new char[] {})).isNotNull(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/PemCertificateParserTests.java ================================================ /* * 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.buildpack.platform.docker.ssl; import java.io.IOException; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link PemCertificateParser}. * * @author Phillip Webb */ class PemCertificateParserTests { private static final String SOURCE = "PemCertificateParser.java"; @Test void codeShouldMatchSpringBootSslPackage() throws IOException { String buildpackVersion = SslSource.loadBuildpackVersion(SOURCE); String springBootVersion = SslSource.loadSpringBootVersion(SOURCE); assertThat(buildpackVersion).isEqualTo(springBootVersion); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/PemFileWriter.java ================================================ /* * 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.buildpack.platform.docker.ssl; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import org.springframework.util.FileSystemUtils; /** * Utility to write certificate and key PEM files for testing. * * @author Scott Frederick * @author Moritz Halbritter */ public class PemFileWriter { private static final String EXAMPLE_SECRET_QUALIFIER = "example"; public static final String CA_CERTIFICATE = """ -----BEGIN TRUSTED CERTIFICATE----- MIIClzCCAgACCQCPbjkRoMVEQDANBgkqhkiG9w0BAQUFADCBjzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x DTALBgNVBAoMBFRlc3QxDTALBgNVBAsMBFRlc3QxFDASBgNVBAMMC2V4YW1wbGUu Y29tMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTIwMDMyNzIx NTgwNFoXDTIxMDMyNzIxNTgwNFowgY8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD YWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARUZXN0 MQ0wCwYDVQQLDARUZXN0MRQwEgYDVQQDDAtleGFtcGxlLmNvbTEfMB0GCSqGSIb3 DQEJARYQdGVzdEBleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC gYEA1YzixWEoyzrd20C2R1gjyPCoPfFLlG6UYTyT0tueNy6yjv6qbJ8lcZg7616O 3I9LuOHhZh9U+fCDCgPfiDdyJfDEW/P+dsOMFyMUXPrJPze2yPpOnvV8iJ5DM93u fEVhCCyzLdYu0P2P3hU2W+T3/Im9DA7FOPA2vF1SrIJ2qtUCAwEAATANBgkqhkiG 9w0BAQUFAAOBgQBdShkwUv78vkn1jAdtfbB+7mpV9tufVdo29j7pmotTCz3ny5fc zLEfeu6JPugAR71JYbc2CqGrMneSk1zT91EH6ohIz8OR5VNvzB7N7q65Ci7OFMPl ly6k3rHpMCBtHoyNFhNVfPLxGJ9VlWFKLgIAbCmL4OIQm1l6Fr1MSM38Zw== -----END TRUSTED CERTIFICATE----- """; public static final String CERTIFICATE = """ -----BEGIN CERTIFICATE----- MIICjzCCAfgCAQEwDQYJKoZIhvcNAQEFBQAwgY8xCzAJBgNVBAYTAlVTMRMwEQYD VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQK DARUZXN0MQ0wCwYDVQQLDARUZXN0MRQwEgYDVQQDDAtleGFtcGxlLmNvbTEfMB0G CSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTAeFw0yMDAzMjcyMjAxNDZaFw0y MTAzMjcyMjAxNDZaMIGPMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5p YTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwEVGVzdDENMAsGA1UE CwwEVGVzdDEUMBIGA1UEAwwLZXhhbXBsZS5jb20xHzAdBgkqhkiG9w0BCQEWEHRl c3RAZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM7kd2cj F49wm1+OQ7Q5GE96cXueWNPr/Nwei71tf6G4BmE0B+suXHEvnLpHTj9pdX/ZzBIK 8jIZ/x8RnSduK/Ky+zm1QMYUWZtWCAgCW8WzgB69Cn/hQG8KSX3S9bqODuQAvP54 GQJD7+4kVuNBGjFb4DaD4nvMmPtALSZf8ZCZAgMBAAEwDQYJKoZIhvcNAQEFBQAD gYEAOn6X8+0VVlDjF+TvTgI0KIasA6nDm+KXe7LVtfvqWqQZH4qyd2uiwcDM3Aux a/OsPdOw0j+NqFDBd3mSMhSVgfvXdK6j9WaxY1VGXyaidLARgvn63wfzgr857sQW c8eSxbwEQxwlMvVxW6Os4VhCfUQr8VrBrvPa2zs+6IlK+Ug= -----END CERTIFICATE----- """; public static final String PRIVATE_RSA_KEY = """ %s-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQDO5HdnIxePcJtfjkO0ORhPenF7nljT6/zcHou9bX+huAZhNAfr LlxxL5y6R04/aXV/2cwSCvIyGf8fEZ0nbivysvs5tUDGFFmbVggIAlvFs4AevQp/ 4UBvCkl90vW6jg7kALz+eBkCQ+/uJFbjQRoxW+A2g+J7zJj7QC0mX/GQmQIDAQAB AoGAIWPsBWA7gDHrUYuzT5XbX5BiWlIfAezXPWtMoEDY1W/Oz8dG8+TilH3brJCv hzps9TpgXhUYK4/Yhdog4+k6/EEY80RvcObOnflazTCVS041B0Ipm27uZjIq2+1F ZfbWP+B3crpzh8wvIYA+6BCcZV9zi8Od32NEs39CtrOrFPUCQQDxnt9+JlWjtteR VttRSKjtzKIF08BzNuZlRP9HNWveLhphIvdwBfjASwqgtuslqziEnGG8kniWzyYB a/ZZVoT3AkEA2zSBMpvGPDkGbOMqbnR8UL3uijkOj+blQe1gsyu3dUa9T42O1u9h Iz5SdCYlSFHbDNRFrwuW2QnhippqIQqC7wJAbVeyWEpM0yu5XiJqWdyB5iuG3xA2 tW0Q0p9ozvbT+9XtRiwmweFR8uOCybw9qexURV7ntAis3cKctmP/Neq7fQJBAKGa 59UjutYTRIVqRJICFtR/8ii9P9sfYs1j7/KnvC0d5duMhU44VOjivW8b4Eic8F1Y 8bbHWILSIhFJHg0V7skCQDa8/YkRWF/3pwIZNWQr4ce4OzvYsFMkRvGRdX8B2a0p wSKcVTdEdO2DhBlYddN0zG0rjq4vDMtdmldEl4BdldQ= -----END RSA PRIVATE KEY----- """.formatted(EXAMPLE_SECRET_QUALIFIER); public static final String PRIVATE_EC_KEY = EXAMPLE_SECRET_QUALIFIER + "-----BEGIN EC PRIVATE KEY-----\n" + "MIGkAgEBBDB21WGGOb1DokKW0MUHO7RQ6jZSUYXfO2iyfCbjmSJhyK8fSuq1V0N2\n" + "Bj7X+XYhS6ygBwYFK4EEACKhZANiAATsRaYri/tDMvrrB2NJlxWFOZ4YBLYdSM+a\n" + "FlGh1FuLjOHW9cx8w0iRHd1Hxn4sxqsa62KzGoCj63lGoaJgi67YNCF0lBa/zCLy\n" + "ktaMsQePDOR8UR0Cfi2J9bh+IjxXd+o=\n" + "-----END EC PRIVATE KEY-----"; public static final String PRIVATE_EC_KEY_PRIME_256_V1 = EXAMPLE_SECRET_QUALIFIER + "-----BEGIN EC PRIVATE KEY-----\n" + "MHcCAQEEIIwZkO8Zjbggzi8wwrk5rzSPzUX31gqTRhBYw4AL6w44oAoGCCqGSM49\n" + "AwEHoUQDQgAE8y28khug747bA68M90IAMCPHAYyen+RsN6i84LORpNDUhv00QZWd\n" + "hOhjWFCQjnewR98Y8pEb1fnORll4LhHPlQ==\n" + "-----END EC PRIVATE KEY-----"; public static final String PRIVATE_DSA_KEY = EXAMPLE_SECRET_QUALIFIER + "-----BEGIN PRIVATE KEY-----\n" + "MIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQCPeTXZuarpv6vtiHrPSVG28y7F\n" + "njuvNxjo6sSWHz79NgbnQ1GpxBgzObgJ58KuHFObp0dbhdARrbi0eYd1SYRpXKwO\n" + "jxSzNggooi/6JxEKPWKpk0U0CaD+aWxGWPhL3SCBnDcJoBBXsZWtzQAjPbpUhLYp\n" + "H51kjviDRIZ3l5zsBLQ0pqwudemYXeI9sCkvwRGMn/qdgYHnM423krcw17njSVkv\n" + "aAmYchU5Feo9a4tGU8YzRY+AOzKkwuDycpAlbk4/ijsIOKHEUOThjBopo33fXqFD\n" + "3ktm/wSQPtXPFiPhWNSHxgjpfyEc2B3KI8tuOAdl+CLjQr5ITAV2OTlgHNZnAh0A\n" + "uvaWpoV499/e5/pnyXfHhe8ysjO65YDAvNVpXQKCAQAWplxYIEhQcE51AqOXVwQN\n" + "NNo6NHjBVNTkpcAtJC7gT5bmHkvQkEq9rI837rHgnzGC0jyQQ8tkL4gAQWDt+coJ\n" + "syB2p5wypifyRz6Rh5uixOdEvSCBVEy1W4AsNo0fqD7UielOD6BojjJCilx4xHjG\n" + "jQUntxyaOrsLC+EsRGiWOefTznTbEBplqiuH9kxoJts+xy9LVZmDS7TtsC98kOmk\n" + "ltOlXVNb6/xF1PYZ9j897buHOSXC8iTgdzEpbaiH7B5HSPh++1/et1SEMWsiMt7l\n" + "U92vAhErDR8C2jCXMiT+J67ai51LKSLZuovjntnhA6Y8UoELxoi34u1DFuHvF9ve\n" + "BB4CHHBQgJ3ST6U8rIxoTqGe42TiVckPf1PoSiJy8GY=\n" + "-----END PRIVATE KEY-----\n"; public static final String PKCS8_PRIVATE_EC_NIST_P256_KEY = EXAMPLE_SECRET_QUALIFIER + "-----BEGIN PRIVATE KEY-----\n" + "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgd6SePFfpaTKFd1Gm\n" + "+WeHZNkORkot5hx6X9elPdICL9ygCgYIKoZIzj0DAQehRANCAASnMAMgeFBv9ks0\n" + "d0jP+utQ3mohwmxY93xljfaBofdg1IeHgDd4I4pBzPxEnvXrU3kcz+SgPZyH1ybl\n" + "P6mSXDXu\n" + "-----END PRIVATE KEY-----\n"; public static final String PKCS8_PRIVATE_EC_NIST_P384_KEY = EXAMPLE_SECRET_QUALIFIER + "-----BEGIN PRIVATE KEY-----\n" + "MIG/AgEAMBAGByqGSM49AgEGBSuBBAAiBIGnMIGkAgEBBDCexXiWKrtrqV1+d1Tv\n" + "t1n5huuw2A+204mQHRuPL9UC8l0XniJjx/PVELCciyJM/7+gBwYFK4EEACKhZANi\n" + "AASHEELZSdrHiSXqU1B+/jrOCr6yjxCMqQsetTb0q5WZdCXOhggGXfbzlRynqphQ\n" + "i4G7azBUklgLaXfxN5eFk6C+E38SYOR7iippcQsSR2ZsCiTk7rnur4b40gQ7IgLA\n" + "/sU=\n" + "-----END PRIVATE KEY-----\n"; public static final String PKCS8_PRIVATE_EC_PRIME256V1_KEY = EXAMPLE_SECRET_QUALIFIER + "-----BEGIN PRIVATE KEY-----\n" + "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg4dVuddgQ6enDvPPw\n" + "Dd1mmS6FMm/kzTJjDVsltrNmRuSgCgYIKoZIzj0DAQehRANCAAR1WMrRADEaVj9m\n" + "uoUfPhUefJK+lS89NHikQ0ZdkHkybyVKLFMLe1hCynhzpKQmnpgud3E10F0P2PZQ\n" + "L9RCEpGf\n" + "-----END PRIVATE KEY-----\n"; public static final String PKCS8_PRIVATE_EC_SECP256R1_KEY = EXAMPLE_SECRET_QUALIFIER + "-----BEGIN PRIVATE KEY-----\n" + "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgU9+v5hUNnTKix8fe\n" + "Pfz+NfXFlGxQZMReSCT2Id9PfKagCgYIKoZIzj0DAQehRANCAATeJg+YS4BrJ35A\n" + "KgRlZ59yKLDpmENCMoaYUuWbQ9hqHzdybQGzQsrNJqgH0nzWghPwP4nFaLPN+pgB\n" + "bqiRgbjG\n" + "-----END PRIVATE KEY-----\n"; public static final String PKCS8_PRIVATE_RSA_KEY = EXAMPLE_SECRET_QUALIFIER + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDR0KfxUw7MF/8R\n" + "B5/YXOM7yLnoHYb/M/6dyoulMbtEdKKhQhU28o5FiDkHcEG9PJQLgqrRgAjl3VmC\n" + "C9omtfZJQ2EpfkTttkJjnKOOroXhYE51/CYSckapBYCVh8GkjUEJuEfnp07cTfYZ\n" + "FqViIgIWPZyjkzl3w4girS7kCuzNdDntVJVx5F/EsFwMA8n3C0QazHQoM5s00Fer\n" + "6aTwd6AW0JD5QkADavpfzZ554e4HrVGwHlM28WKQQkFzzGu44FFXyVuEF3HeyVPu\n" + "g8GRHAc8UU7ijVgJB5TmbvRGYowIErD5i4VvGLuOv9mgR3aVyN0SdJ1N7aJnXpeS\n" + "QjAgf03jAgMBAAECggEBAIhQyzwj3WJGWOZkkLqOpufJotcmj/Wwf0VfOdkq9WMl\n" + "cB/bAlN/xWVxerPVgDCFch4EWBzi1WUaqbOvJZ2u7QNubmr56aiTmJCFTVI/GyZx\n" + "XqiTGN01N6lKtN7xo6LYTyAUhUsBTWAemrx0FSErvTVb9C/mUBj6hbEZ2XQ5kN5t\n" + "7qYX4Lu0zyn7s1kX5SLtm5I+YRq7HSwB6wLy+DSroO71izZ/VPwME3SwT5SN+c87\n" + "3dkklR7fumNd9dOpSWKrLPnq4aMko00rvIGc63xD1HrEpXUkB5v24YEn7HwCLEH7\n" + "b8jrp79j2nCvvR47inpf+BR8FIWAHEOUUqCEzjQkdiECgYEA6ifjMM0f02KPeIs7\n" + "zXd1lI7CUmJmzkcklCIpEbKWf/t/PHv3QgqIkJzERzRaJ8b+GhQ4zrSwAhrGUmI8\n" + "kDkXIqe2/2ONgIOX2UOHYHyTDQZHnlXyDecvHUTqs2JQZCGBZkXyZ9i0j3BnTymC\n" + "iZ8DvEa0nxsbP+U3rgzPQmXiQVMCgYEA5WN2Y/RndbriNsNrsHYRldbPO5nfV9rp\n" + "cDzcQU66HRdK5VIdbXT9tlMYCJIZsSqE0tkOwTgEB/sFvF/tIHSCY5iO6hpIyk6g\n" + "kkUzPcld4eM0dEPAge7SYUbakB9CMvA7MkDQSXQNFyZ0mH83+UikwT6uYHFh7+ox\n" + "N1P+psDhXzECgYEA1gXLVQnIcy/9LxMkgDMWV8j8uMyUZysDthpbK3/uq+A2dhRg\n" + "9g4msPd5OBQT65OpIjElk1n4HpRWfWqpLLHiAZ0GWPynk7W0D7P3gyuaRSdeQs0P\n" + "x8FtgPVDCN9t13gAjHiWjnC26Py2kNbCKAQeJ/MAmQTvrUFX2VCACJKTcV0CgYAj\n" + "xJWSUmrLfb+GQISLOG3Xim434e9keJsLyEGj4U29+YLRLTOvfJ2PD3fg5j8hU/rw\n" + "Ea5uTHi8cdTcIa0M8X3fX8txD3YoLYh2JlouGTcNYOst8d6TpBSj3HN6I5Wj8beZ\n" + "R2fy/CiKYpGtsbCdq0kdZNO18BgQW9kewncjs1GxEQKBgQCf8q34h6KuHpHSDh9h\n" + "YkDTypk0FReWBAVJCzDNDUMhVLFivjcwtaMd2LiC3FMKZYodr52iKg60cj43vbYI\n" + "frmFFxoL37rTmUocCTBKc0LhWj6MicI+rcvQYe1uwTrpWdFf1aZJMYRLRczeKtev\n" + "OWaE/9hVZ5+9pild1NukGpOydw==\n" + "-----END PRIVATE KEY-----\n"; public static final String PKCS8_PRIVATE_EC_ED25519_KEY = EXAMPLE_SECRET_QUALIFIER + "-----BEGIN PRIVATE KEY-----\n" + "MC4CAQAwBQYDK2VwBCIEIJOKNTaIJQTVuEqZ+yvclnjnlWJG6F+K+VsNCOlWRda+\n" + "-----END PRIVATE KEY-----"; private final Path tempDir; public PemFileWriter() throws IOException { this.tempDir = Files.createTempDirectory("buildpack-platform-docker-ssl-tests"); } Path writeFile(String name, String... contents) throws IOException { Path path = Paths.get(this.tempDir.toString(), name); for (String content : contents) { Files.write(path, content.replace(EXAMPLE_SECRET_QUALIFIER, "").getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND); } return path; } public Path getTempDir() { return this.tempDir; } void cleanup() throws IOException { FileSystemUtils.deleteRecursively(this.tempDir); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/PemPrivateKeyParserTests.java ================================================ /* * 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.buildpack.platform.docker.ssl; import java.io.IOException; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link PemPrivateKeyParser}. * * @author Phillip Webb */ class PemPrivateKeyParserTests { private static final String SOURCE = "PemPrivateKeyParser.java"; @Test void codeShouldMatchSpringBootSslPackage() throws IOException { String buildpackVersion = SslSource.loadBuildpackVersion(SOURCE); String springBootVersion = SslSource.loadSpringBootVersion(SOURCE); assertThat(buildpackVersion).isEqualTo(springBootVersion); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/SslContextFactoryTests.java ================================================ /* * 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.buildpack.platform.docker.ssl; import java.io.IOException; import javax.net.ssl.SSLContext; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link SslContextFactory}. * * @author Scott Frederick */ class SslContextFactoryTests { private PemFileWriter fileWriter; @BeforeEach void setUp() throws IOException { this.fileWriter = new PemFileWriter(); } @AfterEach void tearDown() throws IOException { this.fileWriter.cleanup(); } @Test void createKeyStoreWithCertChain() throws IOException { this.fileWriter.writeFile("cert.pem", PemFileWriter.CERTIFICATE); this.fileWriter.writeFile("key.pem", PemFileWriter.PRIVATE_RSA_KEY); this.fileWriter.writeFile("ca.pem", PemFileWriter.CA_CERTIFICATE); SSLContext sslContext = new SslContextFactory().forDirectory(this.fileWriter.getTempDir().toString()); assertThat(sslContext).isNotNull(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/SslSource.java ================================================ /* * 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.buildpack.platform.docker.ssl; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; /** * Utility to compare SSL source code. * * @author Phillip Webb */ final class SslSource { private static final Path BUILDPACK_LOCATION = Path .of("src/main/java/org/springframework/boot/buildpack/platform/docker/ssl"); private static final Path SPRINGBOOT_LOCATION = Path .of("../../core/spring-boot/src/main/java/org/springframework/boot/ssl/pem"); private SslSource() { } static String loadBuildpackVersion(String name) throws IOException { return load(BUILDPACK_LOCATION.resolve(name)); } static String loadSpringBootVersion(String name) throws IOException { return load(SPRINGBOOT_LOCATION.resolve(name)); } private static String load(Path path) throws IOException { String code = Files.readString(path); int firstBrace = code.indexOf("{"); int lastBrace = code.lastIndexOf("}"); return code.substring(firstBrace, lastBrace + 1); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/DockerConnectionExceptionTests.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.io.IOException; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link DockerEngineException}. * * @author Scott Frederick */ class DockerConnectionExceptionTests { private static final String HOST = "docker://localhost/"; @Test @SuppressWarnings("NullAway") // Test null check void createWhenHostIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> new DockerConnectionException(null, null)) .withMessage("'host' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void createWhenCauseIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> new DockerConnectionException(HOST, null)) .withMessage("'cause' must not be null"); } @Test void createWithIOException() { DockerConnectionException exception = new DockerConnectionException(HOST, new IOException("error")); assertThat(exception.getMessage()) .contains("Connection to the Docker daemon at 'docker://localhost/' failed with error \"error\""); } @Test void createWithLastErrorException() { DockerConnectionException exception = new DockerConnectionException(HOST, new IOException(new com.sun.jna.LastErrorException("root cause"))); assertThat(exception.getMessage()) .contains("Connection to the Docker daemon at 'docker://localhost/' failed with error \"root cause\""); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/DockerEngineExceptionTests.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.Collections; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link DockerEngineException}. * * @author Phillip Webb * @author Scott Frederick */ class DockerEngineExceptionTests { private static final String HOST = "docker://localhost/"; private static final URI URI; static { try { URI = new URI("example"); } catch (URISyntaxException ex) { throw new IllegalStateException(ex); } } private static final Errors NO_ERRORS = new Errors(Collections.emptyList()); private static final Errors ERRORS = new Errors(Collections.singletonList(new Errors.Error("code", "message"))); private static final Message NO_MESSAGE = new Message(null); private static final Message MESSAGE = new Message("response message"); @Test @SuppressWarnings("NullAway") // Test null check void createWhenHostIsNullThrowsException() { assertThatIllegalArgumentException() .isThrownBy(() -> new DockerEngineException(null, null, 404, null, NO_ERRORS, NO_MESSAGE, null)) .withMessage("'host' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void createWhenUriIsNullThrowsException() { assertThatIllegalArgumentException() .isThrownBy(() -> new DockerEngineException(HOST, null, 404, null, NO_ERRORS, NO_MESSAGE, null)) .withMessage("'uri' must not be null"); } @Test void create() { DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", ERRORS, MESSAGE, null); assertThat(exception.getMessage()).isEqualTo( "Docker API call to 'docker://localhost/example' failed with status code 404 \"missing\" and message \"response message\" [code: message]"); assertThat(exception.getStatusCode()).isEqualTo(404); assertThat(exception.getReasonPhrase()).isEqualTo("missing"); assertThat(exception.getErrors()).isSameAs(ERRORS); assertThat(exception.getResponseMessage()).isSameAs(MESSAGE); } @Test void createWhenReasonPhraseIsNull() { DockerEngineException exception = new DockerEngineException(HOST, URI, 404, null, ERRORS, MESSAGE, null); assertThat(exception.getMessage()).isEqualTo( "Docker API call to 'docker://localhost/example' failed with status code 404 and message \"response message\" [code: message]"); assertThat(exception.getStatusCode()).isEqualTo(404); assertThat(exception.getReasonPhrase()).isNull(); assertThat(exception.getErrors()).isSameAs(ERRORS); assertThat(exception.getResponseMessage()).isSameAs(MESSAGE); } @Test void createWhenErrorsIsNull() { DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", null, MESSAGE, null); assertThat(exception.getMessage()).isEqualTo( "Docker API call to 'docker://localhost/example' failed with status code 404 \"missing\" and message \"response message\""); assertThat(exception.getErrors()).isNull(); } @Test void createWhenErrorsIsEmpty() { DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", NO_ERRORS, MESSAGE, null); assertThat(exception.getMessage()).isEqualTo( "Docker API call to 'docker://localhost/example' failed with status code 404 \"missing\" and message \"response message\""); assertThat(exception.getStatusCode()).isEqualTo(404); assertThat(exception.getReasonPhrase()).isEqualTo("missing"); assertThat(exception.getErrors()).isSameAs(NO_ERRORS); } @Test void createWhenMessageIsNull() { DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", ERRORS, null, null); assertThat(exception.getMessage()).isEqualTo( "Docker API call to 'docker://localhost/example' failed with status code 404 \"missing\" [code: message]"); assertThat(exception.getResponseMessage()).isNull(); } @Test void createWhenMessageIsEmpty() { DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", ERRORS, NO_MESSAGE, null); assertThat(exception.getMessage()).isEqualTo( "Docker API call to 'docker://localhost/example' failed with status code 404 \"missing\" [code: message]"); assertThat(exception.getResponseMessage()).isSameAs(NO_MESSAGE); } @Test void createWhenProxyAuthFailureWithTextContent() { DockerEngineException exception = new DockerEngineException(HOST, URI, 407, "Proxy Authentication Required", null, null, "Badness".getBytes(StandardCharsets.UTF_8)); assertThat(exception.getMessage()) .isEqualTo("Docker API call to 'docker://localhost/example' failed with status code 407 " + "\"Proxy Authentication Required\" and content \"Badness\""); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/ErrorsTests.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.util.Iterator; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.transport.Errors.Error; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link Errors}. * * @author Phillip Webb */ class ErrorsTests extends AbstractJsonTests { @Test void readValueDeserializesJson() throws Exception { Errors errors = getJsonMapper().readValue(getContent("errors.json"), Errors.class); Iterator iterator = errors.iterator(); Error error1 = iterator.next(); Error error2 = iterator.next(); assertThat(iterator.hasNext()).isFalse(); assertThat(error1.getCode()).isEqualTo("TEST1"); assertThat(error1.getMessage()).isEqualTo("Test One"); assertThat(error2.getCode()).isEqualTo("TEST2"); assertThat(error2.getMessage()).isEqualTo("Test Two"); } @Test void toStringHasErrorDetails() throws Exception { Errors errors = getJsonMapper().readValue(getContent("errors.json"), Errors.class); assertThat(errors).hasToString("[TEST1: Test One, TEST2: Test Two]"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransportTests.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.classic.methods.HttpDelete; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.classic.methods.HttpPut; import org.apache.hc.client5.http.classic.methods.HttpUriRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpStatus; import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response; import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.assertArg; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; /** * Tests for {@link HttpClientTransport}. * * @author Phillip Webb * @author Mike Smithson * @author Scott Frederick * @author Moritz Halbritter */ @ExtendWith(MockitoExtension.class) class HttpClientTransportTests { private static final String APPLICATION_JSON = "application/json"; private static final String APPLICATION_X_TAR = "application/x-tar"; @Mock @SuppressWarnings("NullAway.Init") private HttpClient client; @Mock @SuppressWarnings("NullAway.Init") private ClassicHttpResponse response; @Mock @SuppressWarnings("NullAway.Init") private HttpEntity entity; @Mock @SuppressWarnings("NullAway.Init") private InputStream content; private HttpClientTransport http; private URI uri; @BeforeEach void setup() throws Exception { this.http = new TestHttpClientTransport(this.client); this.uri = new URI("example"); } @Test void getShouldExecuteHttpGet() throws Exception { givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(this.content); given(this.response.getCode()).willReturn(200); Response response = this.http.get(this.uri); then(this.client).should().executeOpen(any(HttpHost.class), assertArg((request) -> { try { assertThat(request).isInstanceOf(HttpGet.class); assertThat(request.getUri()).isEqualTo(this.uri); assertThat(request.getFirstHeader(HttpHeaders.CONTENT_TYPE)).isNull(); assertThat(response.getContent()).isSameAs(this.content); } catch (Exception ex) { throw new RuntimeException(ex); } }), isNull()); } @Test void postShouldExecuteHttpPost() throws Exception { givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(this.content); given(this.response.getCode()).willReturn(200); Response response = this.http.post(this.uri); then(this.client).should() .executeOpen(any(HttpHost.class), assertArg((ThrowingConsumer) (request) -> { assertThat(request).isInstanceOf(HttpPost.class); assertThat(request.getUri()).isEqualTo(this.uri); assertThat(request.getFirstHeader(HttpHeaders.CONTENT_TYPE)).isNull(); assertThat(request.getFirstHeader(HttpClientTransport.REGISTRY_AUTH_HEADER)).isNull(); assertThat(response.getContent()).isSameAs(this.content); }), isNull()); } @Test void postWithRegistryAuthShouldExecuteHttpPostWithHeader() throws Exception { givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(this.content); given(this.response.getCode()).willReturn(200); Response response = this.http.post(this.uri, "auth token"); then(this.client).should() .executeOpen(any(HttpHost.class), assertArg((ThrowingConsumer) (request) -> { assertThat(request).isInstanceOf(HttpPost.class); assertThat(request.getUri()).isEqualTo(this.uri); assertThat(request.getFirstHeader(HttpHeaders.CONTENT_TYPE)).isNull(); assertThat(request.getFirstHeader(HttpClientTransport.REGISTRY_AUTH_HEADER).getValue()) .isEqualTo("auth token"); assertThat(response.getContent()).isSameAs(this.content); }), isNull()); } @Test void postWithEmptyRegistryAuthShouldExecuteHttpPostWithoutHeader() throws Exception { givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(this.content); given(this.response.getCode()).willReturn(200); Response response = this.http.post(this.uri, ""); then(this.client).should() .executeOpen(any(HttpHost.class), assertArg((ThrowingConsumer) (request) -> { assertThat(request).isInstanceOf(HttpPost.class); assertThat(request.getUri()).isEqualTo(this.uri); assertThat(request.getFirstHeader(HttpHeaders.CONTENT_TYPE)).isNull(); assertThat(request.getFirstHeader(HttpClientTransport.REGISTRY_AUTH_HEADER)).isNull(); assertThat(response.getContent()).isSameAs(this.content); }), isNull()); } @Test void postWithJsonContentShouldExecuteHttpPost() throws Exception { String content = "test"; givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(this.content); given(this.response.getCode()).willReturn(200); Response response = this.http.post(this.uri, APPLICATION_JSON, (out) -> StreamUtils.copy(content, StandardCharsets.UTF_8, out)); then(this.client).should() .executeOpen(any(HttpHost.class), assertArg((ThrowingConsumer) (request) -> { HttpEntity entity = request.getEntity(); assertThat(request).isInstanceOf(HttpPost.class); assertThat(request.getUri()).isEqualTo(this.uri); assertThat(entity.isRepeatable()).isFalse(); assertThat(entity.getContentLength()).isEqualTo(content.length()); assertThat(entity.getContentType()).isEqualTo(APPLICATION_JSON); assertThat(entity.isStreaming()).isTrue(); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(entity::getContent); assertThat(writeToString(entity)).isEqualTo(content); assertThat(response.getContent()).isSameAs(this.content); }), isNull()); } @Test void postWithArchiveContentShouldExecuteHttpPost() throws Exception { String content = "test"; givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(this.content); given(this.response.getCode()).willReturn(200); Response response = this.http.post(this.uri, APPLICATION_X_TAR, (out) -> StreamUtils.copy(content, StandardCharsets.UTF_8, out)); then(this.client).should() .executeOpen(any(HttpHost.class), assertArg((ThrowingConsumer) (request) -> { HttpEntity entity = request.getEntity(); assertThat(request).isInstanceOf(HttpPost.class); assertThat(request.getUri()).isEqualTo(this.uri); assertThat(entity.isRepeatable()).isFalse(); assertThat(entity.getContentLength()).isEqualTo(-1); assertThat(entity.getContentType()).isEqualTo(APPLICATION_X_TAR); assertThat(entity.isStreaming()).isTrue(); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(entity::getContent); assertThat(writeToString(entity)).isEqualTo(content); assertThat(response.getContent()).isSameAs(this.content); }), isNull()); } @Test void putWithJsonContentShouldExecuteHttpPut() throws Exception { String content = "test"; givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(this.content); given(this.response.getCode()).willReturn(200); Response response = this.http.put(this.uri, APPLICATION_JSON, (out) -> StreamUtils.copy(content, StandardCharsets.UTF_8, out)); then(this.client).should().executeOpen(any(HttpHost.class), assertArg((ThrowingConsumer) (request) -> { HttpEntity entity = request.getEntity(); assertThat(request).isInstanceOf(HttpPut.class); assertThat(request.getUri()).isEqualTo(this.uri); assertThat(entity.isRepeatable()).isFalse(); assertThat(entity.getContentLength()).isEqualTo(content.length()); assertThat(entity.getContentType()).isEqualTo(APPLICATION_JSON); assertThat(entity.isStreaming()).isTrue(); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(entity::getContent); assertThat(writeToString(entity)).isEqualTo(content); assertThat(response.getContent()).isSameAs(this.content); }), isNull()); } @Test void putWithArchiveContentShouldExecuteHttpPut() throws Exception { String content = "test"; givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(this.content); given(this.response.getCode()).willReturn(200); Response response = this.http.put(this.uri, APPLICATION_X_TAR, (out) -> StreamUtils.copy(content, StandardCharsets.UTF_8, out)); then(this.client).should().executeOpen(any(HttpHost.class), assertArg((ThrowingConsumer) (request) -> { HttpEntity entity = request.getEntity(); assertThat(request).isInstanceOf(HttpPut.class); assertThat(request.getUri()).isEqualTo(this.uri); assertThat(entity.isRepeatable()).isFalse(); assertThat(entity.getContentLength()).isEqualTo(-1); assertThat(entity.getContentType()).isEqualTo(APPLICATION_X_TAR); assertThat(entity.isStreaming()).isTrue(); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(entity::getContent); assertThat(writeToString(entity)).isEqualTo(content); assertThat(response.getContent()).isSameAs(this.content); }), isNull()); } @Test void deleteShouldExecuteHttpDelete() throws Exception { givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(this.content); given(this.response.getCode()).willReturn(200); Response response = this.http.delete(this.uri); then(this.client).should() .executeOpen(any(HttpHost.class), assertArg((ThrowingConsumer) (request) -> { assertThat(request).isInstanceOf(HttpDelete.class); assertThat(request.getUri()).isEqualTo(this.uri); assertThat(request.getFirstHeader(HttpHeaders.CONTENT_TYPE)).isNull(); assertThat(response.getContent()).isSameAs(this.content); }), isNull()); } @Test void executeWhenResponseIsIn400RangeShouldThrowDockerException() throws IOException { givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(getClass().getResourceAsStream("errors.json")); given(this.response.getCode()).willReturn(404); assertThatExceptionOfType(DockerEngineException.class).isThrownBy(() -> this.http.get(this.uri)) .satisfies((ex) -> { assertThat(ex.getErrors()).hasSize(2); assertThat(ex.getResponseMessage()).isNull(); }); } @Test void executeWhenResponseIsIn500RangeWithNoContentShouldThrowDockerException() throws IOException { givenClientWillReturnResponse(); given(this.response.getCode()).willReturn(500); assertThatExceptionOfType(DockerEngineException.class).isThrownBy(() -> this.http.get(this.uri)) .satisfies((ex) -> { assertThat(ex.getErrors()).isNull(); assertThat(ex.getResponseMessage()).isNull(); }); } @Test void executeWhenResponseIsIn500RangeWithMessageShouldThrowDockerException() throws IOException { givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(getClass().getResourceAsStream("message.json")); given(this.response.getCode()).willReturn(500); assertThatExceptionOfType(DockerEngineException.class).isThrownBy(() -> this.http.get(this.uri)) .satisfies((ex) -> { assertThat(ex.getErrors()).isNull(); Message responseMessage = ex.getResponseMessage(); assertThat(responseMessage).isNotNull(); assertThat(responseMessage.getMessage()).contains("test message"); }); } @Test void executeWhenResponseIsIn500RangeWithOtherContentShouldThrowDockerException() throws IOException { givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(this.content); given(this.response.getCode()).willReturn(500); assertThatExceptionOfType(DockerEngineException.class).isThrownBy(() -> this.http.get(this.uri)) .satisfies((ex) -> { assertThat(ex.getErrors()).isNull(); assertThat(ex.getResponseMessage()).isNull(); }); } @Test void shouldReturnErrorsAndMessage() throws IOException { givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(getClass().getResourceAsStream("message-and-errors.json")); given(this.response.getCode()).willReturn(404); assertThatExceptionOfType(DockerEngineException.class).isThrownBy(() -> this.http.get(this.uri)) .satisfies((ex) -> { assertThat(ex.getErrors()).hasSize(2); Message responseMessage = ex.getResponseMessage(); assertThat(responseMessage).isNotNull(); assertThat(responseMessage.getMessage()).contains("test message"); }); } @Test void shouldReturnContentIfProxyAuthError() throws IOException { givenClientWillReturnResponse(); given(this.entity.getContent()).willReturn(getClass().getResourceAsStream("proxy-error.txt")); given(this.response.getCode()).willReturn(HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED); assertThatExceptionOfType(DockerEngineException.class).isThrownBy(() -> this.http.get(this.uri)) .satisfies((ex) -> { assertThat(ex.getErrors()).isNull(); assertThat(ex.getResponseMessage()).isNull(); assertThat(ex.getMessage()).contains("Some kind of proxy auth problem!"); }); } @Test void executeWhenClientThrowsIOExceptionRethrowsAsDockerException() throws IOException { given(this.client.executeOpen(any(HttpHost.class), any(HttpUriRequest.class), isNull())) .willThrow(new IOException("test IO exception")); assertThatExceptionOfType(DockerConnectionException.class).isThrownBy(() -> this.http.get(this.uri)) .satisfies((ex) -> assertThat(ex.getMessage()).contains("test IO exception")); } private String writeToString(HttpEntity entity) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); entity.writeTo(out); return out.toString(StandardCharsets.UTF_8); } private void givenClientWillReturnResponse() throws IOException { given(this.client.executeOpen(any(HttpHost.class), any(HttpUriRequest.class), isNull())) .willReturn(this.response); given(this.response.getEntity()).willReturn(this.entity); } /** * Test {@link HttpClientTransport} implementation. */ static class TestHttpClientTransport extends HttpClientTransport { protected TestHttpClientTransport(HttpClient client) throws URISyntaxException { super(client, HttpHost.create("docker://localhost")); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link HttpTransport}. * * @author Phillip Webb * @author Scott Frederick */ class HttpTransportTests { @Test void createWhenDockerHostVariableIsAddressReturnsRemote() { HttpTransport transport = HttpTransport.create(new DockerConnectionConfiguration.Host("tcp://192.168.1.0")); assertThat(transport).isInstanceOf(RemoteHttpClientTransport.class); } @Test void createWhenDockerHostVariableIsFileReturnsLocal(@TempDir Path tempDir) throws IOException { String dummySocketFilePath = Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath().toString(); HttpTransport transport = HttpTransport.create(new DockerConnectionConfiguration.Host(dummySocketFilePath)); assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); } @Test void createWhenDockerHostVariableIsUnixSchemePrefixedFileReturnsLocal(@TempDir Path tempDir) throws IOException { String dummySocketFilePath = "unix://" + Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath(); HttpTransport transport = HttpTransport.create(new DockerConnectionConfiguration.Host(dummySocketFilePath)); assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link LocalHttpClientTransport} * * @author Scott Frederick */ class LocalHttpClientTransportTests { @Test void createWhenDockerHostIsFileReturnsTransport(@TempDir Path tempDir) throws IOException { String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerConnectionConfiguration.Host(socketFilePath)); LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost); assertThat(transport).isNotNull(); assertThat(transport.getHost().toHostString()).isEqualTo(socketFilePath); } @Test void createWhenDockerHostIsFileThatDoesNotExistReturnsTransport(@TempDir Path tempDir) { String socketFilePath = Paths.get(tempDir.toString(), "dummy").toAbsolutePath().toString(); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerConnectionConfiguration.Host(socketFilePath)); LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost); assertThat(transport).isNotNull(); assertThat(transport.getHost().toHostString()).isEqualTo(socketFilePath); } @Test void createWhenDockerHostIsAddressReturnsTransport() { ResolvedDockerHost dockerHost = ResolvedDockerHost .from(new DockerConnectionConfiguration.Host("tcp://192.168.1.2:2376")); LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost); assertThat(transport).isNotNull(); assertThat(transport.getHost().toHostString()).isEqualTo("tcp://192.168.1.2:2376"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/MessageTests.java ================================================ /* * 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.buildpack.platform.docker.transport; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link Message}. * * @author Scott Frederick */ class MessageTests extends AbstractJsonTests { @Test void readValueDeserializesJson() throws Exception { Message message = getJsonMapper().readValue(getContent("message.json"), Message.class); assertThat(message.getMessage()).isEqualTo("test message"); } @Test void toStringHasErrorDetails() throws Exception { Message errors = getJsonMapper().readValue(getContent("message.json"), Message.class); assertThat(errors).hasToString("test message"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.util.function.Consumer; import javax.net.ssl.SSLContext; import org.apache.hc.core5.http.HttpHost; import org.junit.jupiter.api.Test; 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.ssl.SslContextFactory; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link RemoteHttpClientTransport} * * @author Scott Frederick * @author Phillip Webb */ class RemoteHttpClientTransportTests { @Test void createIfPossibleWhenDockerHostIsNotSetReturnsNull() { ResolvedDockerHost dockerHost = ResolvedDockerHost.from((DockerConnectionConfiguration) null); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNull(); } @Test void createIfPossibleWhenDockerHostIsFileReturnsNull() { ResolvedDockerHost dockerHost = ResolvedDockerHost .from(new DockerConnectionConfiguration.Host("unix:///var/run/socket.sock")); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNull(); } @Test void createIfPossibleWhenDockerHostIsAddressReturnsTransport() { ResolvedDockerHost dockerHost = ResolvedDockerHost .from(new DockerConnectionConfiguration.Host("tcp://192.168.1.2:2376")); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNotNull(); } @Test void createIfPossibleWhenNoTlsVerifyUsesHttp() { ResolvedDockerHost dockerHost = ResolvedDockerHost .from(new DockerConnectionConfiguration.Host("tcp://192.168.1.2:2376")); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNotNull(); assertThat(transport.getHost()).satisfies(hostOf("http", "192.168.1.2", 2376)); } @Test void createIfPossibleWhenTlsVerifyUsesHttps() throws Exception { SslContextFactory sslContextFactory = mock(SslContextFactory.class); given(sslContextFactory.forDirectory("/test-cert-path")).willReturn(SSLContext.getDefault()); ResolvedDockerHost dockerHost = ResolvedDockerHost .from(new DockerConnectionConfiguration.Host("tcp://192.168.1.2:2376", true, "/test-cert-path")); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost, sslContextFactory); assertThat(transport).isNotNull(); assertThat(transport.getHost()).satisfies(hostOf("https", "192.168.1.2", 2376)); } @Test void createIfPossibleWhenTlsVerifyWithMissingCertPathThrowsException() { ResolvedDockerHost dockerHost = ResolvedDockerHost .from(new DockerConnectionConfiguration.Host("tcp://192.168.1.2:2376", true, null)); assertThatIllegalStateException().isThrownBy(() -> RemoteHttpClientTransport.createIfPossible(dockerHost)) .withMessageContaining("Docker host TLS verification requires trust material"); } private Consumer hostOf(String scheme, String hostName, int port) { return (host) -> { assertThat(host).isNotNull(); assertThat(host.getSchemeName()).isEqualTo(scheme); assertThat(host.getHostName()).isEqualTo(hostName); assertThat(host.getPort()).isEqualTo(port); }; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/TestDockerEngineException.java ================================================ /* * 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.buildpack.platform.docker.transport; import java.net.URI; import org.jspecify.annotations.Nullable; /** * Subclass of {@link DockerEngineException} for testing. * * @author Phillip Webb */ public class TestDockerEngineException extends DockerEngineException { public TestDockerEngineException(String host, URI uri, int statusCode, @Nullable String reasonPhrase, @Nullable Errors errors, @Nullable Message responseMessage, byte @Nullable [] content) { super(host, uri, statusCode, reasonPhrase, errors, responseMessage, content); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/BindingTests.java ================================================ /* * 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.buildpack.platform.docker.type; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link Binding}. * * @author Scott Frederick * @author Moritz Halbritter */ class BindingTests { @Test void ofReturnsValue() { Binding binding = Binding.of("host-src:container-dest:ro"); assertThat(binding).hasToString("host-src:container-dest:ro"); } @Test @SuppressWarnings("NullAway") // Test null check void ofWithNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> Binding.of(null)) .withMessageContaining("'value' must not be null"); } @Test void fromReturnsValue() { Binding binding = Binding.from("host-src", "container-dest"); assertThat(binding).hasToString("host-src:container-dest"); } @Test @SuppressWarnings("NullAway") // Test null check void fromWithNullSourceThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> Binding.from((String) null, "container-dest")) .withMessageContaining("'source' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void fromWithNullDestinationThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> Binding.from("host-src", null)) .withMessageContaining("'destination' must not be null"); } @Test void fromVolumeNameSourceReturnsValue() { Binding binding = Binding.from(VolumeName.of("host-src"), "container-dest"); assertThat(binding).hasToString("host-src:container-dest"); } @Test @SuppressWarnings("NullAway") // Test null check void fromVolumeNameSourceWithNullSourceThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> Binding.from((VolumeName) null, "container-dest")) .withMessageContaining("'sourceVolume' must not be null"); } @Test void shouldReturnContainerDestinationPath() { Binding binding = Binding.from("/host", "/container"); assertThat(binding.getContainerDestinationPath()).isEqualTo("/container"); } @Test void shouldReturnContainerDestinationPathWithOptions() { Binding binding = Binding.of("/host:/container:ro"); assertThat(binding.getContainerDestinationPath()).isEqualTo("/container"); } @Test void shouldReturnContainerDestinationPathOnWindows() { Binding binding = Binding.from("C:\\host", "C:\\container"); assertThat(binding.getContainerDestinationPath()).isEqualTo("C:\\container"); } @Test void shouldReturnContainerDestinationPathOnWindowsWithOptions() { Binding binding = Binding.of("C:\\host:C:\\container:ro"); assertThat(binding.getContainerDestinationPath()).isEqualTo("C:\\container"); } @Test void shouldFailIfBindingIsMalformed() { Binding binding = Binding.of("some-invalid-binding"); assertThatIllegalStateException().isThrownBy(binding::getContainerDestinationPath) .withMessage("Expected 2 or more parts, but found 1"); } @ParameterizedTest @CsvSource(textBlock = """ /cnb, true /layers, true /workspace, true /something, false c:\\cnb, true c:\\layers, true c:\\workspace, true c:\\something, false """) void shouldDetectSensitiveContainerPaths(String containerPath, boolean sensitive) { Binding binding = Binding.from("/host", containerPath); assertThat(binding.usesSensitiveContainerPath()).isEqualTo(sensitive); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfigTests.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link ContainerConfig}. * * @author Phillip Webb * @author Scott Frederick * @author Jeroen Meijer */ class ContainerConfigTests extends AbstractJsonTests { @Test @SuppressWarnings("NullAway") // Test null check void ofWhenImageReferenceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> ContainerConfig.of(null, (update) -> { })).withMessage("'imageReference' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void ofWhenUpdateIsNullThrowsException() { ImageReference imageReference = ImageReference.of("ubuntu:bionic"); assertThatIllegalArgumentException().isThrownBy(() -> ContainerConfig.of(imageReference, null)) .withMessage("'update' must not be null"); } @Test void writeToWritesJson() throws Exception { ImageReference imageReference = ImageReference.of("ubuntu:bionic"); ContainerConfig containerConfig = ContainerConfig.of(imageReference, (update) -> { update.withUser("root"); update.withCommand("ls", "-l"); update.withArgs("-h"); update.withLabel("spring", "boot"); update.withBinding(Binding.from("bind-source", "bind-dest")); update.withEnv("name1", "value1"); update.withEnv("name2", "value2"); update.withNetworkMode("test"); update.withSecurityOption("option=value"); }); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); containerConfig.writeTo(outputStream); String actualJson = outputStream.toString(StandardCharsets.UTF_8); String expectedJson = StreamUtils.copyToString(getContent("container-config.json"), StandardCharsets.UTF_8); JSONAssert.assertEquals(expectedJson, actualJson, true); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ContainerContentTests.java ================================================ /* * 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.buildpack.platform.docker.type; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.io.TarArchive; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; /** * Tests for {@link ContainerContent}. * * @author Phillip Webb */ class ContainerContentTests { @Test @SuppressWarnings("NullAway") // Test null check void ofWhenArchiveIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> ContainerContent.of(null)) .withMessage("'archive' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void ofWhenDestinationPathIsNullThrowsException() { TarArchive archive = mock(TarArchive.class); assertThatIllegalArgumentException().isThrownBy(() -> ContainerContent.of(archive, null)) .withMessage("'destinationPath' must not be empty"); } @Test void ofWhenDestinationPathIsEmptyThrowsException() { TarArchive archive = mock(TarArchive.class); assertThatIllegalArgumentException().isThrownBy(() -> ContainerContent.of(archive, "")) .withMessage("'destinationPath' must not be empty"); } @Test void ofCreatesContainerContent() { TarArchive archive = mock(TarArchive.class); ContainerContent content = ContainerContent.of(archive); assertThat(content.getArchive()).isSameAs(archive); assertThat(content.getDestinationPath()).isEqualTo("/"); } @Test void ofWithDestinationPathCreatesContainerContent() { TarArchive archive = mock(TarArchive.class); ContainerContent content = ContainerContent.of(archive, "/test"); assertThat(content.getArchive()).isSameAs(archive); assertThat(content.getDestinationPath()).isEqualTo("/test"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ContainerReferenceTests.java ================================================ /* * 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.buildpack.platform.docker.type; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link ContainerReference}. * * @author Phillip Webb */ class ContainerReferenceTests { @Test void ofCreatesInstance() { ContainerReference reference = ContainerReference .of("92691aec176333f7ae890de9aaeeafef11166efcaa3908edf83eb44a5c943781"); assertThat(reference).hasToString("92691aec176333f7ae890de9aaeeafef11166efcaa3908edf83eb44a5c943781"); } @Test @SuppressWarnings("NullAway") // Test null check void ofWhenNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> ContainerReference.of(null)) .withMessage("'value' must not be empty"); } @Test void ofWhenEmptyThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> ContainerReference.of("")) .withMessage("'value' must not be empty"); } @Test void hashCodeAndEquals() { ContainerReference r1 = ContainerReference .of("92691aec176333f7ae890de9aaeeafef11166efcaa3908edf83eb44a5c943781"); ContainerReference r2 = ContainerReference .of("92691aec176333f7ae890de9aaeeafef11166efcaa3908edf83eb44a5c943781"); ContainerReference r3 = ContainerReference .of("02691aec176333f7ae890de9aaeeafef11166efcaa3908edf83eb44a5c943781"); assertThat(r1).hasSameHashCodeAs(r2); assertThat(r1).isEqualTo(r1).isEqualTo(r2).isNotEqualTo(r3); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ContainerStatusTests.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.IOException; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ContainerStatus}. * * @author Scott Frederick */ class ContainerStatusTests { @Test void ofCreatesFromJson() throws IOException { ContainerStatus status = ContainerStatus.of(getClass().getResourceAsStream("container-status-error.json")); assertThat(status.getStatusCode()).isOne(); assertThat(status.getWaitingErrorMessage()).isEqualTo("error detail"); } @Test void ofCreatesFromValues() { ContainerStatus status = ContainerStatus.of(1, "error detail"); assertThat(status.getStatusCode()).isOne(); assertThat(status.getWaitingErrorMessage()).isEqualTo("error detail"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndexTests.java ================================================ /* * 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.buildpack.platform.docker.type; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ImageArchiveIndex}. * * @author Phillip Webb */ class ImageArchiveIndexTests extends AbstractJsonTests { @Test void loadJson() { String content = getContentAsString("image-archive-index.json"); ImageArchiveIndex index = getIndex(content); assertThat(index.getSchemaVersion()).isEqualTo(2); assertThat(index.getManifests()).hasSize(1); BlobReference manifest = index.getManifests().get(0); assertThat(manifest.getMediaType()).isEqualTo("application/vnd.docker.distribution.manifest.list.v2+json"); assertThat(manifest.getDigest()) .isEqualTo("sha256:3bbe02431d8e5124ffe816ec27bf6508b50edd1d10218be1a03e799a186b9004"); } private ImageArchiveIndex getIndex(String content) { return new ImageArchiveIndex(getJsonMapper().readTree(content)); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifestTests.java ================================================ /* * 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.buildpack.platform.docker.type; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ImageArchiveManifest}. * * @author Scott Frederick * @author Andy Wilkinson */ class ImageArchiveManifestTests extends AbstractJsonTests { @Test void getLayersReturnsLayers() { String content = getContentAsString("image-archive-manifest.json"); ImageArchiveManifest manifest = getManifest(content); List expectedLayers = new ArrayList<>(); for (int blankLayersCount = 0; blankLayersCount < 46; blankLayersCount++) { expectedLayers.add("blank_" + blankLayersCount); } expectedLayers.add("bb09e17fd1bd2ee47155f1349645fcd9fff31e1247c7ed99cad469f1c16a4216.tar"); assertThat(manifest.getEntries()).hasSize(1); assertThat(manifest.getEntries().get(0).getLayers()).hasSize(47); assertThat(manifest.getEntries().get(0).getLayers()).isEqualTo(expectedLayers); } @Test void getLayersWithNoLayersReturnsEmptyList() { String content = "[{\"Layers\": []}]"; ImageArchiveManifest manifest = getManifest(content); assertThat(manifest.getEntries()).hasSize(1); assertThat(manifest.getEntries().get(0).getLayers()).isEmpty(); } @Test void getLayersWithEmptyManifestReturnsEmptyList() { String content = "[]"; ImageArchiveManifest manifest = getManifest(content); assertThat(manifest.getEntries()).isEmpty(); } private ImageArchiveManifest getManifest(String content) { return new ImageArchiveManifest(getJsonMapper().readTree(content)); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveTests.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ImageArchive}. * * @author Phillip Webb * @author Scott Frederick */ class ImageArchiveTests extends AbstractJsonTests { private static final int EXISTING_IMAGE_LAYER_COUNT = 46; @Test void fromImageWritesToValidArchiveTar() throws Exception { Image image = Image.of(getContent("image.json")); ImageArchive archive = ImageArchive.from(image, (update) -> { update.withNewLayer(Layer.of((layout) -> layout.directory("/spring", Owner.ROOT))); update.withTag(ImageReference.of("pack.local/builder/6b7874626575656b6162")); }); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); archive.writeTo(outputStream); try (TarArchiveInputStream tar = new TarArchiveInputStream( new ByteArrayInputStream(outputStream.toByteArray()))) { for (int i = 0; i < EXISTING_IMAGE_LAYER_COUNT; i++) { TarArchiveEntry blankEntry = tar.getNextEntry(); assertThat(blankEntry.getName()).isEqualTo("blank_" + i); } TarArchiveEntry layerEntry = tar.getNextEntry(); byte[] layerContent = read(tar, layerEntry.getSize()); TarArchiveEntry configEntry = tar.getNextEntry(); byte[] configContent = read(tar, configEntry.getSize()); TarArchiveEntry manifestEntry = tar.getNextEntry(); byte[] manifestContent = read(tar, manifestEntry.getSize()); assertExpectedLayer(layerEntry, layerContent); assertExpectedConfig(configEntry, configContent); assertExpectedManifest(manifestEntry, manifestContent); } } private void assertExpectedLayer(TarArchiveEntry entry, byte[] content) throws Exception { assertThat(entry.getName()).isEqualTo("bb09e17fd1bd2ee47155f1349645fcd9fff31e1247c7ed99cad469f1c16a4216.tar"); try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(content))) { TarArchiveEntry contentEntry = tar.getNextEntry(); assertThat(contentEntry.getName()).isEqualTo("/spring/"); } } private void assertExpectedConfig(TarArchiveEntry entry, byte[] content) throws Exception { assertThat(entry.getName()).isEqualTo("416c76dc7f691f91e80516ff039e056f32f996b59af4b1cb8114e6ae8171a374.json"); String actualJson = new String(content, StandardCharsets.UTF_8); String expectedJson = StreamUtils.copyToString(getContent("image-archive-config.json"), StandardCharsets.UTF_8); JSONAssert.assertEquals(expectedJson, actualJson, false); } private void assertExpectedManifest(TarArchiveEntry entry, byte[] content) throws Exception { assertThat(entry.getName()).isEqualTo("manifest.json"); String actualJson = new String(content, StandardCharsets.UTF_8); String expectedJson = StreamUtils.copyToString(getContent("image-archive-manifest.json"), StandardCharsets.UTF_8); JSONAssert.assertEquals(expectedJson, actualJson, false); } private byte[] read(TarArchiveInputStream tar, long size) throws IOException { byte[] content = new byte[(int) size]; tar.read(content); return content; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageConfigTests.java ================================================ /* * 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.buildpack.platform.docker.type; import java.util.Map; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; /** * Tests for {@link ImageConfig}. * * @author Phillip Webb * @author Andy Wilkinson */ class ImageConfigTests extends AbstractJsonTests { @Test void getEnvContainsParsedValues() { ImageConfig imageConfig = getImageConfig(); Map env = imageConfig.getEnv(); assertThat(env).contains(entry("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"), entry("CNB_USER_ID", "2000"), entry("CNB_GROUP_ID", "2000"), entry("CNB_STACK_ID", "org.cloudfoundry.stacks.cflinuxfs3")); } @Test void whenConfigHasNoEnvThenImageConfigEnvIsEmpty() { ImageConfig imageConfig = getMinimalImageConfig(); Map env = imageConfig.getEnv(); assertThat(env).isEmpty(); } @Test void whenConfigHasNoLabelsThenImageConfigLabelsIsEmpty() { ImageConfig imageConfig = getMinimalImageConfig(); Map env = imageConfig.getLabels(); assertThat(env).isEmpty(); } @Test void getLabelsReturnsLabels() { ImageConfig imageConfig = getImageConfig(); Map labels = imageConfig.getLabels(); assertThat(labels).hasSize(4).contains(entry("io.buildpacks.stack.id", "org.cloudfoundry.stacks.cflinuxfs3")); } @Test void updateWithLabelUpdatesLabels() { ImageConfig imageConfig = getImageConfig(); ImageConfig updatedImageConfig = imageConfig .copy((update) -> update.withLabel("io.buildpacks.stack.id", "test")); assertThat(imageConfig.getLabels()).hasSize(4) .contains(entry("io.buildpacks.stack.id", "org.cloudfoundry.stacks.cflinuxfs3")); assertThat(updatedImageConfig.getLabels()).hasSize(4).contains(entry("io.buildpacks.stack.id", "test")); } private ImageConfig getImageConfig() { return new ImageConfig(getJsonMapper().readTree(getContent("image-config.json"))); } private ImageConfig getMinimalImageConfig() { return new ImageConfig(getJsonMapper().readTree(getContent("minimal-image-config.json"))); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageNameTests.java ================================================ /* * 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.buildpack.platform.docker.type; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link ImageName}. * * @author Phillip Webb * @author Scott Frederick */ class ImageNameTests { @Test void ofWhenNameOnlyCreatesImageName() { ImageName imageName = ImageName.of("ubuntu"); assertThat(imageName).hasToString("docker.io/library/ubuntu"); assertThat(imageName.getDomain()).isEqualTo("docker.io"); assertThat(imageName.getName()).isEqualTo("library/ubuntu"); } @Test void ofWhenSlashedNameCreatesImageName() { ImageName imageName = ImageName.of("canonical/ubuntu"); assertThat(imageName).hasToString("docker.io/canonical/ubuntu"); assertThat(imageName.getDomain()).isEqualTo("docker.io"); assertThat(imageName.getName()).isEqualTo("canonical/ubuntu"); } @Test void ofWhenLocalhostNameCreatesImageName() { ImageName imageName = ImageName.of("localhost/canonical/ubuntu"); assertThat(imageName).hasToString("localhost/canonical/ubuntu"); assertThat(imageName.getDomain()).isEqualTo("localhost"); assertThat(imageName.getName()).isEqualTo("canonical/ubuntu"); } @Test void ofWhenDomainAndNameCreatesImageName() { ImageName imageName = ImageName.of("repo.spring.io/canonical/ubuntu"); assertThat(imageName).hasToString("repo.spring.io/canonical/ubuntu"); assertThat(imageName.getDomain()).isEqualTo("repo.spring.io"); assertThat(imageName.getName()).isEqualTo("canonical/ubuntu"); } @Test void ofWhenDomainNameAndPortCreatesImageName() { ImageName imageName = ImageName.of("repo.spring.io:8080/canonical/ubuntu"); assertThat(imageName).hasToString("repo.spring.io:8080/canonical/ubuntu"); assertThat(imageName.getDomain()).isEqualTo("repo.spring.io:8080"); assertThat(imageName.getName()).isEqualTo("canonical/ubuntu"); } @Test void ofWhenSimpleNameAndPortCreatesImageName() { ImageName imageName = ImageName.of("repo:8080/ubuntu"); assertThat(imageName).hasToString("repo:8080/ubuntu"); assertThat(imageName.getDomain()).isEqualTo("repo:8080"); assertThat(imageName.getName()).isEqualTo("ubuntu"); } @Test void ofWhenSimplePathAndPortCreatesImageName() { ImageName imageName = ImageName.of("repo:8080/canonical/ubuntu"); assertThat(imageName).hasToString("repo:8080/canonical/ubuntu"); assertThat(imageName.getDomain()).isEqualTo("repo:8080"); assertThat(imageName.getName()).isEqualTo("canonical/ubuntu"); } @Test void ofWhenNameWithLongPathCreatesImageName() { ImageName imageName = ImageName.of("path1/path2/path3/ubuntu"); assertThat(imageName).hasToString("docker.io/path1/path2/path3/ubuntu"); assertThat(imageName.getDomain()).isEqualTo("docker.io"); assertThat(imageName.getName()).isEqualTo("path1/path2/path3/ubuntu"); } @Test void ofWhenLocalhostDomainCreatesImageName() { ImageName imageName = ImageName.of("localhost/ubuntu"); assertThat(imageName.getDomain()).isEqualTo("localhost"); assertThat(imageName.getName()).isEqualTo("ubuntu"); } @Test void ofWhenLocalhostDomainAndPathCreatesImageName() { ImageName imageName = ImageName.of("localhost/library/ubuntu"); assertThat(imageName.getDomain()).isEqualTo("localhost"); assertThat(imageName.getName()).isEqualTo("library/ubuntu"); } @Test void ofWhenLegacyDomainUsesNewDomain() { ImageName imageName = ImageName.of("index.docker.io/ubuntu"); assertThat(imageName).hasToString("docker.io/library/ubuntu"); assertThat(imageName.getDomain()).isEqualTo("docker.io"); assertThat(imageName.getName()).isEqualTo("library/ubuntu"); } @Test @SuppressWarnings("NullAway") // Test null check void ofWhenNameIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> ImageName.of(null)) .withMessage("'value' must not be empty"); } @Test void ofWhenNameIsEmptyThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> ImageName.of("")) .withMessage("'value' must not be empty"); } @Test void ofWhenContainsUppercaseThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> ImageName.of("Test")) .withMessageContaining("must be a parsable name") .withMessageContaining("Test"); } @Test void ofWhenNameIncludesTagThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> ImageName.of("ubuntu:latest")) .withMessageContaining("must be a parsable name") .withMessageContaining(":latest"); } @Test void ofWhenNameIncludeDigestThrowsException() { assertThatIllegalArgumentException().isThrownBy( () -> ImageName.of("ubuntu@sha256:47bfdb88c3ae13e488167607973b7688f69d9e8c142c2045af343ec199649c09")) .withMessageContaining("must be a parsable name") .withMessageContaining("@sha256:47b"); } @Test void hashCodeAndEquals() { ImageName n1 = ImageName.of("ubuntu"); ImageName n2 = ImageName.of("library/ubuntu"); ImageName n3 = ImageName.of("docker.io/ubuntu"); ImageName n4 = ImageName.of("docker.io/library/ubuntu"); ImageName n5 = ImageName.of("index.docker.io/library/ubuntu"); ImageName n6 = ImageName.of("alpine"); assertThat(n1).hasSameHashCodeAs(n2).hasSameHashCodeAs(n3).hasSameHashCodeAs(n4).hasSameHashCodeAs(n5); assertThat(n1).isEqualTo(n1).isEqualTo(n2).isEqualTo(n3).isEqualTo(n4).isNotEqualTo(n6); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.File; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.Timeout.ThreadMode; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link ImageReference}. * * @author Phillip Webb * @author Scott Frederick * @author Moritz Halbritter */ class ImageReferenceTests { @Test void ofSimpleName() { ImageReference reference = ImageReference.of("ubuntu"); assertThat(reference.getDomain()).isEqualTo("docker.io"); assertThat(reference.getName()).isEqualTo("library/ubuntu"); assertThat(reference.getTag()).isNull(); assertThat(reference.getDigest()).isNull(); assertThat(reference).hasToString("docker.io/library/ubuntu"); } @Test void ofSimpleNameWithSingleCharacterSuffix() { ImageReference reference = ImageReference.of("ubuntu-a"); assertThat(reference.getDomain()).isEqualTo("docker.io"); assertThat(reference.getName()).isEqualTo("library/ubuntu-a"); assertThat(reference.getTag()).isNull(); assertThat(reference.getDigest()).isNull(); assertThat(reference).hasToString("docker.io/library/ubuntu-a"); } @Test void ofLibrarySlashName() { ImageReference reference = ImageReference.of("library/ubuntu"); assertThat(reference.getDomain()).isEqualTo("docker.io"); assertThat(reference.getName()).isEqualTo("library/ubuntu"); assertThat(reference.getTag()).isNull(); assertThat(reference.getDigest()).isNull(); assertThat(reference).hasToString("docker.io/library/ubuntu"); } @Test void ofSlashName() { ImageReference reference = ImageReference.of("adoptopenjdk/openjdk11"); assertThat(reference.getDomain()).isEqualTo("docker.io"); assertThat(reference.getName()).isEqualTo("adoptopenjdk/openjdk11"); assertThat(reference.getTag()).isNull(); assertThat(reference.getDigest()).isNull(); assertThat(reference).hasToString("docker.io/adoptopenjdk/openjdk11"); } @Test void ofCustomDomain() { ImageReference reference = ImageReference.of("repo.example.com/java/jdk"); assertThat(reference.getDomain()).isEqualTo("repo.example.com"); assertThat(reference.getName()).isEqualTo("java/jdk"); assertThat(reference.getTag()).isNull(); assertThat(reference.getDigest()).isNull(); assertThat(reference).hasToString("repo.example.com/java/jdk"); } @Test void ofCustomDomainAndPort() { ImageReference reference = ImageReference.of("repo.example.com:8080/java/jdk"); assertThat(reference.getDomain()).isEqualTo("repo.example.com:8080"); assertThat(reference.getName()).isEqualTo("java/jdk"); assertThat(reference.getTag()).isNull(); assertThat(reference.getDigest()).isNull(); assertThat(reference).hasToString("repo.example.com:8080/java/jdk"); } @Test void ofLegacyDomain() { ImageReference reference = ImageReference.of("index.docker.io/ubuntu"); assertThat(reference.getDomain()).isEqualTo("docker.io"); assertThat(reference.getName()).isEqualTo("library/ubuntu"); assertThat(reference.getTag()).isNull(); assertThat(reference.getDigest()).isNull(); assertThat(reference).hasToString("docker.io/library/ubuntu"); } @Test void ofNameAndTag() { ImageReference reference = ImageReference.of("ubuntu:bionic"); assertThat(reference.getDomain()).isEqualTo("docker.io"); assertThat(reference.getName()).isEqualTo("library/ubuntu"); assertThat(reference.getTag()).isEqualTo("bionic"); assertThat(reference.getDigest()).isNull(); assertThat(reference).hasToString("docker.io/library/ubuntu:bionic"); } @Test void ofDomainPortAndTag() { ImageReference reference = ImageReference.of("repo.example.com:8080/library/ubuntu:v1"); assertThat(reference.getDomain()).isEqualTo("repo.example.com:8080"); assertThat(reference.getName()).isEqualTo("library/ubuntu"); assertThat(reference.getTag()).isEqualTo("v1"); assertThat(reference.getDigest()).isNull(); assertThat(reference).hasToString("repo.example.com:8080/library/ubuntu:v1"); } @Test void ofNameAndDigest() { ImageReference reference = ImageReference .of("ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); assertThat(reference.getDomain()).isEqualTo("docker.io"); assertThat(reference.getName()).isEqualTo("library/ubuntu"); assertThat(reference.getTag()).isNull(); assertThat(reference.getDigest()) .isEqualTo("sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); assertThat(reference).hasToString( "docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); } @Test void ofNameAndTagAndDigest() { ImageReference reference = ImageReference .of("ubuntu:bionic@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); assertThat(reference.getDomain()).isEqualTo("docker.io"); assertThat(reference.getName()).isEqualTo("library/ubuntu"); assertThat(reference.getTag()).isEqualTo("bionic"); assertThat(reference.getDigest()) .isEqualTo("sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); assertThat(reference).hasToString( "docker.io/library/ubuntu:bionic@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); } @Test void ofCustomDomainAndPortWithTag() { ImageReference reference = ImageReference .of("example.com:8080/canonical/ubuntu:bionic@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); assertThat(reference.getDomain()).isEqualTo("example.com:8080"); assertThat(reference.getName()).isEqualTo("canonical/ubuntu"); assertThat(reference.getTag()).isEqualTo("bionic"); assertThat(reference.getDigest()) .isEqualTo("sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); assertThat(reference).hasToString( "example.com:8080/canonical/ubuntu:bionic@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); } @Test void ofImageName() { ImageReference reference = ImageReference.of(ImageName.of("ubuntu")); assertThat(reference).hasToString("docker.io/library/ubuntu"); } @Test void ofImageNameAndTag() { ImageReference reference = ImageReference.of(ImageName.of("ubuntu"), "bionic"); assertThat(reference).hasToString("docker.io/library/ubuntu:bionic"); } @Test void ofImageNameTagAndDigest() { ImageReference reference = ImageReference.of(ImageName.of("ubuntu"), "bionic", "sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); assertThat(reference).hasToString( "docker.io/library/ubuntu:bionic@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); } @Test void ofWhenHasIllegalCharacterThrowsException() { assertThatIllegalArgumentException() .isThrownBy(() -> ImageReference .of("registry.example.com/example/example-app:1.6.0-dev.2.uncommitted+wip.foo.c75795d")) .withMessageContaining("must be an image reference"); } @Test void ofWhenContainsUpperCaseThrowsException() { assertThatIllegalArgumentException() .isThrownBy(() -> ImageReference .of("europe-west1-docker.pkg.dev/aaaaaa-bbbbb-123456/docker-registry/bootBuildImage:0.0.1")) .withMessageContaining("must be an image reference"); } @Test @Timeout(value = 1, threadMode = ThreadMode.SEPARATE_THREAD) void ofWhenIsVeryLongAndHasIllegalCharacter() { assertThatIllegalArgumentException().isThrownBy(() -> ImageReference .of("docker.io/library/this-image-has-a-long-name-with-an-invalid-tag-which-is-at-danger-of-catastrophic-backtracking:1.0.0+1234")) .withMessageContaining("must be an image reference"); } @Test void forJarFile() { assertForJarFile("spring-boot.2.0.0.BUILD-SNAPSHOT.jar", "library/spring-boot", "2.0.0.BUILD-SNAPSHOT"); assertForJarFile("spring-boot.2.0.0.M1.jar", "library/spring-boot", "2.0.0.M1"); assertForJarFile("spring-boot.2.0.0.RC1.jar", "library/spring-boot", "2.0.0.RC1"); assertForJarFile("spring-boot.2.0.0.RELEASE.jar", "library/spring-boot", "2.0.0.RELEASE"); assertForJarFile("sample-0.0.1-SNAPSHOT.jar", "library/sample", "0.0.1-SNAPSHOT"); assertForJarFile("sample-0.0.1.jar", "library/sample", "0.0.1"); } private void assertForJarFile(String jarFile, String expectedName, String expectedTag) { ImageReference reference = ImageReference.forJarFile(new File(jarFile)); assertThat(reference.getName()).isEqualTo(expectedName); assertThat(reference.getTag()).isEqualTo(expectedTag); } @Test void randomGeneratesRandomName() { String prefix = "pack.local/builder/"; ImageReference random = ImageReference.random(prefix); assertThat(random.toString()).startsWith(prefix).hasSize(prefix.length() + 10); ImageReference another = ImageReference.random(prefix); int attempts = 0; while (another.equals(random)) { assertThat(attempts).as("Duplicate results").isLessThan(10); another = ImageReference.random(prefix); attempts++; } } @Test void randomWithLengthGeneratesRandomName() { String prefix = "pack.local/builder/"; ImageReference random = ImageReference.random(prefix, 20); assertThat(random.toString()).startsWith(prefix).hasSize(prefix.length() + 20); } @Test @SuppressWarnings("NullAway") // Test null check void randomWherePrefixIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> ImageReference.random(null)) .withMessage("'prefix' must not be null"); } @Test void inTaggedFormWhenHasDigestThrowsException() { ImageReference reference = ImageReference .of("ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); assertThatIllegalStateException().isThrownBy(reference::inTaggedForm) .withMessage( "Image reference 'docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d' cannot contain a digest"); } @Test void inTaggedFormWhenHasNoTagUsesLatest() { ImageReference reference = ImageReference.of("ubuntu"); assertThat(reference.inTaggedForm()).hasToString("docker.io/library/ubuntu:latest"); } @Test void inTaggedFormWhenHasTagUsesTag() { ImageReference reference = ImageReference.of("ubuntu:bionic"); assertThat(reference.inTaggedForm()).hasToString("docker.io/library/ubuntu:bionic"); } @Test void inTaggedOrDigestFormWhenHasDigestUsesDigest() { ImageReference reference = ImageReference .of("ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); assertThat(reference.inTaggedOrDigestForm()).hasToString( "docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); } @Test void inTaggedOrDigestFormWhenHasTagUsesTag() { ImageReference reference = ImageReference.of("ubuntu:bionic"); assertThat(reference.inTaggedOrDigestForm()).hasToString("docker.io/library/ubuntu:bionic"); } @Test void inTaggedOrDigestFormWhenHasNoTagOrDigestUsesLatest() { ImageReference reference = ImageReference.of("ubuntu"); assertThat(reference.inTaggedOrDigestForm()).hasToString("docker.io/library/ubuntu:latest"); } @Test void equalsAndHashCode() { ImageReference r1 = ImageReference.of("ubuntu:bionic"); ImageReference r2 = ImageReference.of("docker.io/library/ubuntu:bionic"); ImageReference r3 = ImageReference.of("docker.io/library/ubuntu:latest"); assertThat(r1).hasSameHashCodeAs(r2); assertThat(r1).isEqualTo(r1).isEqualTo(r2).isNotEqualTo(r3); } @Test void withDigest() { ImageReference reference = ImageReference.of("docker.io/library/ubuntu:bionic"); ImageReference updated = reference .withDigest("sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); assertThat(updated).hasToString( "docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); } @Test void inTaglessFormWithDigest() { ImageReference reference = ImageReference .of("docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); ImageReference updated = reference.inTaglessForm(); assertThat(updated).hasToString( "docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); } @Test void inTaglessForm() { ImageReference reference = ImageReference.of("docker.io/library/ubuntu:bionic"); ImageReference updated = reference.inTaglessForm(); assertThat(updated).hasToString("docker.io/library/ubuntu"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageTests.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.IOException; import java.util.List; import java.util.Map; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.type.Image.Descriptor; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; /** * Tests for {@link Image}. * * @author Phillip Webb */ class ImageTests extends AbstractJsonTests { @Test void getConfigEnvContainsParsedValues() throws Exception { Image image = getImage(); Map env = image.getConfig().getEnv(); assertThat(env).contains(entry("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"), entry("CNB_USER_ID", "2000"), entry("CNB_GROUP_ID", "2000"), entry("CNB_STACK_ID", "org.cloudfoundry.stacks.cflinuxfs3")); } @Test void getConfigLabelsReturnsLabels() throws Exception { Image image = getImage(); Map labels = image.getConfig().getLabels(); assertThat(labels).contains(entry("io.buildpacks.stack.id", "org.cloudfoundry.stacks.cflinuxfs3")); } @Test void getLayersReturnsImageLayers() throws Exception { Image image = getImage(); List layers = image.getLayers(); assertThat(layers).hasSize(46); assertThat(layers.get(0)) .hasToString("sha256:733a8e5ce32984099ef675fce04730f6e2a6dcfdf5bd292fea01a8f936265342"); assertThat(layers.get(45)) .hasToString("sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"); } @Test void getOsReturnsOs() throws Exception { Image image = getImage(); assertThat(image.getOs()).isEqualTo("linux"); } @Test void getOsWhenOsIsNotDefaultOsReturnsOs() throws Exception { Image image = Image.of(getContent("image-non-default-os.json")); assertThat(image.getOs()).isEqualTo("windows"); } @Test void getOsWhenOsIsEmptyReturnsDefaultOs() throws Exception { Image image = Image.of(getContent("image-empty-os.json")); assertThat(image.getOs()).isEqualTo("linux"); } @Test void getArchitectureReturnsArchitecture() throws Exception { Image image = getImage(); assertThat(image.getArchitecture()).isEqualTo("amd64"); } @Test void getVariantReturnsVariant() throws Exception { Image image = getImage(); assertThat(image.getVariant()).isEqualTo("v1"); } @Test void getCreatedReturnsDate() throws Exception { Image image = getImage(); assertThat(image.getCreated()).isEqualTo("2019-10-30T19:34:56.296666503Z"); } @Test void getDescriptorReturnsDescriptor() throws Exception { Image image = getImage(); Descriptor descriptor = image.getDescriptor(); assertThat(descriptor).isNotNull(); assertThat(descriptor.getDigest()) .isEqualTo("sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96"); } @Test void getPrimaryDigestWhenHasDescriptor() throws Exception { Image image = getImage(); assertThat(image.getPrimaryDigest()) .isEqualTo("sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96"); } @Test void getPrimaryDigestWhenNoDescriptor() throws Exception { Image image = Image.of(getContent("image-no-descriptor.json")); assertThat(image.getPrimaryDigest()) .isEqualTo("sha256:21635a6b4880772f3fabbf8b660907fa38636558cf787cc26f1779fc4b4e2cba"); } @Test void getPrimaryDigestWhenNoDigest() throws Exception { Image image = Image.of(getContent("image-no-digest.json")); assertThat(image.getPrimaryDigest()).isNull(); } private Image getImage() throws IOException { return Image.of(getContent("image.json")); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/LayerIdTests.java ================================================ /* * 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.buildpack.platform.docker.type; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Arrays; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Test for {@link LayerId}. * * @author Phillip Webb */ class LayerIdTests { @Test void ofReturnsLayerId() { LayerId id = LayerId.of("sha256:9a183e56c86d376b408bdf922746d0a657f62b0e18c7c8f82a496b87710c576f"); assertThat(id.getAlgorithm()).isEqualTo("sha256"); assertThat(id.getHash()).isEqualTo("9a183e56c86d376b408bdf922746d0a657f62b0e18c7c8f82a496b87710c576f"); assertThat(id).hasToString("sha256:9a183e56c86d376b408bdf922746d0a657f62b0e18c7c8f82a496b87710c576f"); } @Test void hashCodeAndEquals() { LayerId id1 = LayerId.of("sha256:9a183e56c86d376b408bdf922746d0a657f62b0e18c7c8f82a496b87710c576f"); LayerId id2 = LayerId.of("sha256:9a183e56c86d376b408bdf922746d0a657f62b0e18c7c8f82a496b87710c576f"); LayerId id3 = LayerId.of("sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); assertThat(id1).hasSameHashCodeAs(id2); assertThat(id1).isEqualTo(id1).isEqualTo(id2).isNotEqualTo(id3); } @Test @SuppressWarnings("NullAway") // Test null check void ofWhenValueIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> LayerId.of((String) null)) .withMessage("'value' must not be empty"); } @Test void ofWhenValueIsEmptyThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> LayerId.of(" ")).withMessage("'value' must not be empty"); } @Test void ofSha256Digest() throws Exception { MessageDigest digest = MessageDigest.getInstance("SHA-256"); digest.update("test".getBytes(StandardCharsets.UTF_8)); LayerId id = LayerId.ofSha256Digest(digest.digest()); assertThat(id).hasToString("sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"); } @Test void ofSha256DigestWithZeroPadding() { byte[] digest = new byte[32]; Arrays.fill(digest, (byte) 127); digest[0] = 1; LayerId id = LayerId.ofSha256Digest(digest); assertThat(id).hasToString("sha256:017f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"); } @Test @SuppressWarnings("NullAway") // Test null check void ofSha256DigestWhenNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> LayerId.ofSha256Digest((byte[]) null)) .withMessage("'digest' must not be null"); } @Test void ofSha256DigestWhenWrongLengthThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> LayerId.ofSha256Digest(new byte[31])) .withMessage("'digest' must be exactly 32 bytes"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/LayerTests.java ================================================ /* * 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.buildpack.platform.docker.type; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.Layout; import org.springframework.boot.buildpack.platform.io.Owner; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link Layer}. * * @author Phillip Webb */ class LayerTests { @Test @SuppressWarnings("NullAway") // Test null check void ofWhenLayoutIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> Layer.of((IOConsumer) null)) .withMessage("'layout' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void fromTarArchiveWhenTarArchiveIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> Layer.fromTarArchive(null)) .withMessage("'tarArchive' must not be null"); } @Test void ofCreatesLayer() throws Exception { Layer layer = Layer.of((layout) -> { layout.directory("/directory", Owner.ROOT); layout.file("/directory/file", Owner.ROOT, Content.of("test")); }); assertThat(layer.getId()) .hasToString("sha256:d03a34f73804698c875eb56ff694fc2fceccc69b645e4adceb004ed13588613b"); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); layer.writeTo(outputStream); try (TarArchiveInputStream tarStream = new TarArchiveInputStream( new ByteArrayInputStream(outputStream.toByteArray()))) { assertThat(tarStream.getNextEntry().getName()).isEqualTo("/directory/"); assertThat(tarStream.getNextEntry().getName()).isEqualTo("/directory/file"); assertThat(tarStream.getNextEntry()).isNull(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestListTests.java ================================================ /* * 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.buildpack.platform.docker.type; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ManifestList}. * * @author Phillip Webb */ class ManifestListTests extends AbstractJsonTests { @Test void loadJsonFromDistributionManifestList() { String content = getContentAsString("distribution-manifest-list.json"); ManifestList manifestList = getManifestList(content); assertThat(manifestList.getSchemaVersion()).isEqualTo(2); assertThat(manifestList.getMediaType()).isEqualTo("application/vnd.docker.distribution.manifest.list.v2+json"); assertThat(manifestList.getManifests()).hasSize(2); } private ManifestList getManifestList(String content) { return new ManifestList(getJsonMapper().readTree(content)); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestTests.java ================================================ /* * 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.buildpack.platform.docker.type; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link Manifest}. * * @author Phillip Webb */ class ManifestTests extends AbstractJsonTests { @Test void loadJsonFromDistributionManifest() { String content = getContentAsString("distribution-manifest.json"); Manifest manifestList = getManifest(content); assertThat(manifestList.getSchemaVersion()).isEqualTo(2); assertThat(manifestList.getMediaType()).isEqualTo("application/vnd.docker.distribution.manifest.v2+json"); assertThat(manifestList.getLayers()).hasSize(1); } @Test void loadJsonFromImageManifest() { String content = getContentAsString("image-manifest.json"); Manifest manifestList = getManifest(content); assertThat(manifestList.getSchemaVersion()).isEqualTo(2); assertThat(manifestList.getMediaType()).isEqualTo("application/vnd.oci.image.manifest.v1+json"); assertThat(manifestList.getLayers()).hasSize(1); } private Manifest getManifest(String content) { return new Manifest(getJsonMapper().readTree(content)); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/RandomStringTests.java ================================================ /* * 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.buildpack.platform.docker.type; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link RandomString}. * * @author Phillip Webb */ class RandomStringTests { @Test @SuppressWarnings("NullAway") // Test null check void generateWhenPrefixIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> RandomString.generate(null, 10)) .withMessage("'prefix' must not be null"); } @Test void generateGeneratesRandomString() { String s1 = RandomString.generate("abc-", 10); String s2 = RandomString.generate("abc-", 10); String s3 = RandomString.generate("abc-", 20); assertThat(s1).hasSize(14).startsWith("abc-").isNotEqualTo(s2); assertThat(s3).hasSize(24).startsWith("abc-"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/VolumeNameTests.java ================================================ /* * 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.buildpack.platform.docker.type; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link VolumeName}. * * @author Phillip Webb */ class VolumeNameTests { @Test @SuppressWarnings("NullAway") // Test null check void randomWhenPrefixIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> VolumeName.random(null)) .withMessage("'prefix' must not be null"); } @Test void randomGeneratesRandomString() { VolumeName v1 = VolumeName.random("abc-"); VolumeName v2 = VolumeName.random("abc-"); assertThat(v1.toString()).startsWith("abc-").hasSize(14); assertThat(v2.toString()).startsWith("abc-").hasSize(14); assertThat(v1).isNotEqualTo(v2); assertThat(v1.toString()).isNotEqualTo(v2.toString()); } @Test void randomStringWithLengthGeneratesRandomString() { VolumeName v1 = VolumeName.random("abc-", 20); VolumeName v2 = VolumeName.random("abc-", 20); assertThat(v1.toString()).startsWith("abc-").hasSize(24); assertThat(v2.toString()).startsWith("abc-").hasSize(24); assertThat(v1).isNotEqualTo(v2); assertThat(v1.toString()).isNotEqualTo(v2.toString()); } @Test @SuppressWarnings("NullAway") // Test null check void basedOnWhenSourceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> VolumeName.basedOn(null, "prefix", "suffix", 6)) .withMessage("'source' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void basedOnWhenNameExtractorIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> VolumeName.basedOn("test", null, "prefix", "suffix", 6)) .withMessage("'nameExtractor' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void basedOnWhenPrefixIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> VolumeName.basedOn("test", null, "suffix", 6)) .withMessage("'prefix' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void basedOnWhenSuffixIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> VolumeName.basedOn("test", "prefix", null, 6)) .withMessage("'suffix' must not be null"); } @Test void basedOnGeneratesHashBasedName() { VolumeName name = VolumeName.basedOn("index.docker.io/library/myapp:latest", "pack-cache-", ".build", 6); assertThat(name).hasToString("pack-cache-40a311b545d7.build"); } @Test void basedOnWhenSizeIsTooBigThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> VolumeName.basedOn("name", "prefix", "suffix", 33)) .withMessage("'digestLength' must be less than or equal to 32"); } @Test @SuppressWarnings("NullAway") // Test null check void ofWhenValueIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> VolumeName.of(null)) .withMessage("'value' must not be null"); } @Test void ofGeneratesValue() { VolumeName name = VolumeName.of("test"); assertThat(name).hasToString("test"); } @Test void equalsAndHashCode() { VolumeName n1 = VolumeName.of("test1"); VolumeName n2 = VolumeName.of("test1"); VolumeName n3 = VolumeName.of("test2"); assertThat(n1).hasSameHashCodeAs(n2); assertThat(n1).isEqualTo(n1).isEqualTo(n2).isNotEqualTo(n3); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/ContentTests.java ================================================ /* * 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.buildpack.platform.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link Content}. * * @author Phillip Webb */ class ContentTests { @Test @SuppressWarnings("NullAway") // Test null check void ofWhenSupplierIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> Content.of(1, (IOSupplier) null)) .withMessage("'supplier' must not be null"); } @Test void ofWhenStreamReturnsWritable() throws Exception { byte[] bytes = { 1, 2, 3, 4 }; ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); Content writable = Content.of(4, () -> inputStream); assertThat(writeToAndGetBytes(writable)).isEqualTo(bytes); } @Test @SuppressWarnings("NullAway") // Test null check void ofWhenStringIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> Content.of((String) null)) .withMessage("'string' must not be null"); } @Test void ofWhenStringReturnsWritable() throws Exception { Content writable = Content.of("spring"); assertThat(writeToAndGetBytes(writable)).isEqualTo("spring".getBytes(StandardCharsets.UTF_8)); } @Test @SuppressWarnings("NullAway") // Test null check void ofWhenBytesIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> Content.of((byte[]) null)) .withMessage("'bytes' must not be null"); } @Test void ofWhenBytesReturnsWritable() throws Exception { byte[] bytes = { 1, 2, 3, 4 }; Content writable = Content.of(bytes); assertThat(writeToAndGetBytes(writable)).isEqualTo(bytes); } private byte[] writeToAndGetBytes(Content writable) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); writable.writeTo(outputStream); return outputStream.toByteArray(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/DefaultOwnerTests.java ================================================ /* * 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.buildpack.platform.io; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link DefaultOwner}. * * @author Phillip Webb */ class DefaultOwnerTests { @Test void getUidReturnsUid() { DefaultOwner owner = new DefaultOwner(123, 456); assertThat(owner.getUid()).isEqualTo(123); } @Test void getGidReturnsGid() { DefaultOwner owner = new DefaultOwner(123, 456); assertThat(owner.getGid()).isEqualTo(456); } @Test void toStringReturnsString() { DefaultOwner owner = new DefaultOwner(123, 456); assertThat(owner).hasToString("123/456"); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/FilePermissionsTests.java ================================================ /* * 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.buildpack.platform.io; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.util.Collections; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIOException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link FilePermissions}. * * @author Scott Frederick */ class FilePermissionsTests { @TempDir @SuppressWarnings("NullAway.Init") Path tempDir; @Test @DisabledOnOs(OS.WINDOWS) void umaskForPath() throws IOException { FileAttribute> fileAttribute = PosixFilePermissions .asFileAttribute(PosixFilePermissions.fromString("rw-r-----")); Path tempFile = Files.createTempFile(this.tempDir, "umask", null, fileAttribute); assertThat(FilePermissions.umaskForPath(tempFile)).isEqualTo(0640); } @Test @DisabledOnOs(OS.WINDOWS) void umaskForPathWithNonExistentFile() { assertThatIOException() .isThrownBy(() -> FilePermissions.umaskForPath(Paths.get(this.tempDir.toString(), "does-not-exist"))); } @Test @EnabledOnOs(OS.WINDOWS) void umaskForPathOnWindowsFails() throws IOException { Path tempFile = Files.createTempFile("umask", null); assertThatIllegalStateException().isThrownBy(() -> FilePermissions.umaskForPath(tempFile)) .withMessageContaining("Unsupported file type for retrieving Posix attributes"); } @Test @SuppressWarnings("NullAway") // Test null check void umaskForPathWithNullPath() { assertThatIllegalArgumentException().isThrownBy(() -> FilePermissions.umaskForPath(null)); } @Test void posixPermissionsToUmask() { Set permissions = PosixFilePermissions.fromString("rwxrw-r--"); assertThat(FilePermissions.posixPermissionsToUmask(permissions)).isEqualTo(0764); } @Test void posixPermissionsToUmaskWithEmptyPermissions() { Set permissions = Collections.emptySet(); assertThat(FilePermissions.posixPermissionsToUmask(permissions)).isZero(); } @Test @SuppressWarnings("NullAway") // Test null check void posixPermissionsToUmaskWithNullPermissions() { assertThatIllegalArgumentException().isThrownBy(() -> FilePermissions.posixPermissionsToUmask(null)); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/InspectedContentTests.java ================================================ /* * 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.buildpack.platform.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link InspectedContent}. * * @author Phillip Webb */ class InspectedContentTests { @Test @SuppressWarnings("NullAway") // Test null check void ofWhenInputStreamThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> InspectedContent.of((InputStream) null)) .withMessage("'inputStream' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void ofWhenContentIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> InspectedContent.of((Content) null)) .withMessage("'content' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void ofWhenConsumerIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> InspectedContent.of((IOConsumer) null)) .withMessage("'writer' must not be null"); } @Test void ofFromContent() throws Exception { InspectedContent content = InspectedContent.of(Content.of("test")); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); content.writeTo(outputStream); assertThat(outputStream.toByteArray()).containsExactly("test".getBytes(StandardCharsets.UTF_8)); } @Test void ofSmallContent() throws Exception { InputStream inputStream = new ByteArrayInputStream(new byte[] { 0, 1, 2 }); InspectedContent content = InspectedContent.of(inputStream); assertThat(content.size()).isEqualTo(3); assertThat(readBytes(content)).containsExactly(0, 1, 2); } @Test void ofLargeContent() throws Exception { byte[] bytes = new byte[InspectedContent.MEMORY_LIMIT + 3]; System.arraycopy(new byte[] { 0, 1, 2 }, 0, bytes, 0, 3); InputStream inputStream = new ByteArrayInputStream(bytes); InspectedContent content = InspectedContent.of(inputStream); assertThat(content.size()).isEqualTo(bytes.length); assertThat(readBytes(content)).isEqualTo(bytes); } @Test void ofWithInspector() throws Exception { InputStream inputStream = new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8)); MessageDigest digest = MessageDigest.getInstance("SHA-256"); InspectedContent.of(inputStream, digest::update); assertThat(digest.digest()).inHexadecimal() .contains(0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0x0b, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0x0a, 0x08); } @Test void ofWritingSingleBytesShouldWork() throws Exception { InspectedContent content = InspectedContent.of((outputStream) -> { outputStream.write('A'); outputStream.write('B'); outputStream.write('C'); }); assertThat(content.size()).isEqualTo(3); assertThat(readBytes(content)).containsExactly('A', 'B', 'C'); } private byte[] readBytes(InspectedContent content) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); content.writeTo(outputStream); return outputStream.toByteArray(); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/OwnerTests.java ================================================ /* * 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.buildpack.platform.io; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link Owner}. * * @author Phillip Webb */ class OwnerTests { @Test void ofReturnsNewOwner() { Owner owner = Owner.of(123, 456); assertThat(owner.getUid()).isEqualTo(123); assertThat(owner.getGid()).isEqualTo(456); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/TarArchiveTests.java ================================================ /* * 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.buildpack.platform.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link TarArchive}. * * @author Phillip Webb */ class TarArchiveTests { @TempDir @SuppressWarnings("NullAway.Init") File tempDir; @Test void ofWritesTarContent() throws Exception { Owner owner = Owner.of(123, 456); TarArchive tarArchive = TarArchive.of((content) -> { content.directory("/workspace", owner); content.directory("/layers", owner); content.directory("/cnb", Owner.ROOT); content.directory("/cnb/buildpacks", Owner.ROOT); content.directory("/platform", Owner.ROOT); content.directory("/platform/env", Owner.ROOT); }); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); tarArchive.writeTo(outputStream); try (TarArchiveInputStream tarStream = new TarArchiveInputStream( new ByteArrayInputStream(outputStream.toByteArray()))) { List entries = new ArrayList<>(); TarArchiveEntry entry = tarStream.getNextEntry(); while (entry != null) { entries.add(entry); entry = tarStream.getNextEntry(); } assertThat(entries).hasSize(6); assertThat(entries.get(0).getName()).isEqualTo("/workspace/"); assertThat(entries.get(0).getLongUserId()).isEqualTo(123); assertThat(entries.get(0).getLongGroupId()).isEqualTo(456); assertThat(entries.get(2).getName()).isEqualTo("/cnb/"); assertThat(entries.get(2).getLongUserId()).isZero(); assertThat(entries.get(2).getLongGroupId()).isZero(); } } @Test void fromZipFileReturnsZipFileAdapter() throws Exception { Owner owner = Owner.of(123, 456); File file = new File(this.tempDir, "test.zip"); writeTestZip(file); TarArchive tarArchive = TarArchive.fromZip(file, owner); assertThat(tarArchive).isInstanceOf(ZipFileTarArchive.class); } private void writeTestZip(File file) throws IOException { try (ZipArchiveOutputStream zip = new ZipArchiveOutputStream(file)) { ZipArchiveEntry dirEntry = new ZipArchiveEntry("spring/"); zip.putArchiveEntry(dirEntry); zip.closeArchiveEntry(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/TarLayoutWriterTests.java ================================================ /* * 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.buildpack.platform.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.util.Date; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link TarLayoutWriter}. * * @author Phillip Webb * @author Scott Frederick */ class TarLayoutWriterTests { @Test void writesTarArchive() throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try (TarLayoutWriter writer = new TarLayoutWriter(outputStream)) { writer.directory("/foo", Owner.ROOT); writer.file("/foo/bar.txt", Owner.of(1, 1), 0777, Content.of("test")); } try (TarArchiveInputStream tarInputStream = new TarArchiveInputStream( new ByteArrayInputStream(outputStream.toByteArray()))) { TarArchiveEntry directoryEntry = tarInputStream.getNextEntry(); TarArchiveEntry fileEntry = tarInputStream.getNextEntry(); byte[] fileContent = new byte[(int) fileEntry.getSize()]; tarInputStream.read(fileContent); assertThat(tarInputStream.getNextEntry()).isNull(); assertThat(directoryEntry.getName()).isEqualTo("/foo/"); assertThat(directoryEntry.getMode()).isEqualTo(0755); assertThat(directoryEntry.getLongUserId()).isZero(); assertThat(directoryEntry.getLongGroupId()).isZero(); assertThat(directoryEntry.getModTime()).isEqualTo(new Date(TarLayoutWriter.NORMALIZED_MOD_TIME)); assertThat(fileEntry.getName()).isEqualTo("/foo/bar.txt"); assertThat(fileEntry.getMode()).isEqualTo(0777); assertThat(fileEntry.getLongUserId()).isOne(); assertThat(fileEntry.getLongGroupId()).isOne(); assertThat(fileEntry.getModTime()).isEqualTo(new Date(TarLayoutWriter.NORMALIZED_MOD_TIME)); assertThat(fileContent).isEqualTo("test".getBytes(StandardCharsets.UTF_8)); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/ZipFileTarArchiveTests.java ================================================ /* * 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.buildpack.platform.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link ZipFileTarArchive}. * * @author Phillip Webb * @author Scott Frederick */ class ZipFileTarArchiveTests { @TempDir @SuppressWarnings("NullAway.Init") File tempDir; @Test @SuppressWarnings("NullAway") // Test null check void createWhenZipIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> new ZipFileTarArchive(null, Owner.ROOT)) .withMessage("'zip' must not be null"); } @Test @SuppressWarnings("NullAway") // Test null check void createWhenOwnerIsNullThrowsException() throws Exception { File file = new File(this.tempDir, "test.zip"); writeTestZip(file); assertThatIllegalArgumentException().isThrownBy(() -> new ZipFileTarArchive(file, null)) .withMessage("'owner' must not be null"); } @Test void writeToAdaptsContent() throws Exception { Owner owner = Owner.of(123, 456); File file = new File(this.tempDir, "test.zip"); writeTestZip(file); TarArchive tarArchive = TarArchive.fromZip(file, owner); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); tarArchive.writeTo(outputStream); try (TarArchiveInputStream tarStream = new TarArchiveInputStream( new ByteArrayInputStream(outputStream.toByteArray()))) { TarArchiveEntry dirEntry = tarStream.getNextEntry(); assertThat(dirEntry.getName()).isEqualTo("spring/"); assertThat(dirEntry.getLongUserId()).isEqualTo(123); assertThat(dirEntry.getLongGroupId()).isEqualTo(456); TarArchiveEntry fileEntry = tarStream.getNextEntry(); assertThat(fileEntry.getName()).isEqualTo("spring/boot"); assertThat(fileEntry.getLongUserId()).isEqualTo(123); assertThat(fileEntry.getLongGroupId()).isEqualTo(456); assertThat(fileEntry.getSize()).isEqualTo(4); assertThat(fileEntry.getMode()).isEqualTo(0755); assertThat(tarStream).hasContent("test"); } } private void writeTestZip(File file) throws IOException { try (ZipArchiveOutputStream zip = new ZipArchiveOutputStream(file)) { ZipArchiveEntry dirEntry = new ZipArchiveEntry("spring/"); zip.putArchiveEntry(dirEntry); zip.closeArchiveEntry(); ZipArchiveEntry fileEntry = new ZipArchiveEntry("spring/boot"); fileEntry.setUnixMode(0755); zip.putArchiveEntry(fileEntry); zip.write("test".getBytes(StandardCharsets.UTF_8)); zip.closeArchiveEntry(); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/AbstractJsonTests.java ================================================ /* * 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.buildpack.platform.json; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; import tools.jackson.databind.json.JsonMapper; import static org.assertj.core.api.Assertions.assertThat; /** * Abstract base class for JSON based tests. * * @author Phillip Webb * @author Scott Frederick */ public abstract class AbstractJsonTests { protected final JsonMapper getJsonMapper() { return SharedJsonMapper.get(); } protected final InputStream getContent(String name) { InputStream result = getClass().getResourceAsStream(name); assertThat(result).as("JSON source " + name).isNotNull(); return result; } protected final String getContentAsString(String name) { try (InputStream in = getContent(name)) { return new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)).lines() .collect(Collectors.joining("\n")); } catch (IOException ex) { throw new UncheckedIOException(ex); } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/JsonStreamTests.java ================================================ /* * 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.buildpack.platform.json; import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.annotation.JsonCreator; import org.junit.jupiter.api.Test; import tools.jackson.databind.node.ObjectNode; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link JsonStream}. * * @author Phillip Webb * @author Scott Frederick */ class JsonStreamTests extends AbstractJsonTests { private final JsonStream jsonStream; JsonStreamTests() { this.jsonStream = new JsonStream(getJsonMapper()); } @Test void getWhenReadingObjectNodeReturnsNodes() throws Exception { List result = new ArrayList<>(); this.jsonStream.get(getContent("stream.json"), result::add); assertThat(result).hasSize(595); assertThat(result.get(594).get("status").asString()) .contains("Status: Downloaded newer image for paketo-buildpacks/cnb:base"); } @Test void getWhenReadTypesReturnsTypes() throws Exception { List result = new ArrayList<>(); this.jsonStream.get(getContent("stream.json"), TestEvent.class, result::add); assertThat(result).hasSize(595); assertThat(result.get(1).getId()).isEqualTo("5667fdb72017"); assertThat(result.get(594).getStatus()) .isEqualTo("Status: Downloaded newer image for paketo-buildpacks/cnb:base"); } /** * Event for type deserialization tests. */ static class TestEvent { private final String id; private final String status; @JsonCreator TestEvent(String id, String status) { this.id = id; this.status = status; } String getId() { return this.id; } String getStatus() { return this.status; } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/MappedObjectTests.java ================================================ /* * 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.buildpack.platform.json; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; import org.junit.jupiter.api.Test; import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObjectTests.TestMappedObject.Person; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link MappedObject}. * * @author Phillip Webb */ class MappedObjectTests extends AbstractJsonTests { private final TestMappedObject mapped; MappedObjectTests() throws IOException { this.mapped = TestMappedObject.of(getContent("test-mapped-object.json")); } @Test void ofReadsJson() { assertThat(this.mapped.getNode()).isNotNull(); } @Test void valueAtWhenStringReturnsValue() { assertThat(this.mapped.valueAt("/string", String.class)).isEqualTo("stringvalue"); } @Test void valueAtWhenStringArrayReturnsValue() { assertThat(this.mapped.valueAt("/stringarray", String[].class)).containsExactly("a", "b"); } @Test void valueAtWhenMissingReturnsNull() { assertThat(this.mapped.valueAt("/missing", String.class)).isNull(); } @Test void valueAtWhenInterfaceReturnsProxy() { Person person = this.mapped.valueAt("/person", Person.class); assertThat(person).isNotNull(); assertThat(person.getName().getFirst()).isEqualTo("spring"); assertThat(person.getName().getLast()).isEqualTo("boot"); } @Test void valueAtWhenInterfaceAndMissingReturnsProxy() { Person person = this.mapped.valueAt("/missing", Person.class); assertThat(person).isNotNull(); assertThat(person.getName().getFirst()).isNull(); assertThat(person.getName().getLast()).isNull(); } @Test void valueAtWhenActualPropertyStartsWithUppercaseReturnsValue() { assertThat(this.mapped.valueAt("/startsWithUppercase", String.class)).isEqualTo("value"); } @Test void valueAtWhenDefaultMethodReturnsValue() { Person person = this.mapped.valueAt("/person", Person.class); assertThat(person).isNotNull(); assertThat(person.getName().getFullName()).isEqualTo("dr spring boot"); } /** * {@link MappedObject} for testing. */ static class TestMappedObject extends MappedObject { TestMappedObject(JsonNode node) { super(node, MethodHandles.lookup()); } static TestMappedObject of(InputStream content) throws IOException { return of(content, TestMappedObject::new); } interface Person { Name getName(); interface Name { String getFirst(); String getLast(); default String getFullName() { String title = valueAt(this, "/title", String.class); return title + " " + getFirst() + " " + getLast(); } } } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/SharedJsonMapperTests.java ================================================ /* * 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.buildpack.platform.json; import org.junit.jupiter.api.Test; import tools.jackson.databind.DeserializationFeature; import tools.jackson.databind.PropertyNamingStrategies; import tools.jackson.databind.SerializationFeature; import tools.jackson.databind.json.JsonMapper; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link SharedJsonMapper}. * * @author Phillip Webb */ class SharedJsonMapperTests { @Test void getReturnsConfiguredJsonMapper() { JsonMapper mapper = SharedJsonMapper.get(); assertThat(mapper).isNotNull(); assertThat( SerializationFeature.INDENT_OUTPUT.enabledIn(mapper.serializationConfig().getSerializationFeatures())) .isTrue(); assertThat(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES .enabledIn(mapper.deserializationConfig().getDeserializationFeatures())).isFalse(); assertThat(mapper.serializationConfig().getPropertyNamingStrategy()) .isEqualTo(PropertyNamingStrategies.LOWER_CAMEL_CASE); assertThat(mapper.deserializationConfig().getPropertyNamingStrategy()) .isEqualTo(PropertyNamingStrategies.LOWER_CAMEL_CASE); } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/socket/FileDescriptorTests.java ================================================ /* * 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.buildpack.platform.socket; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.socket.FileDescriptor.Handle; import static org.assertj.core.api.Assertions.assertThat; /** * Test for {@link FileDescriptor}. * * @author Phillip Webb */ class FileDescriptorTests { private final int sourceHandle = 123; private int closedHandle; @Test void acquireReturnsHandle() throws Exception { FileDescriptor descriptor = new FileDescriptor(this.sourceHandle, this::close); try (Handle handle = descriptor.acquire()) { assertThat(handle.intValue()).isEqualTo(this.sourceHandle); assertThat(handle.isClosed()).isFalse(); } } @Test void acquireWhenClosedReturnsClosedHandle() throws Exception { FileDescriptor descriptor = new FileDescriptor(this.sourceHandle, this::close); descriptor.close(); try (Handle handle = descriptor.acquire()) { assertThat(handle.intValue()).isEqualTo(-1); assertThat(handle.isClosed()).isTrue(); } } @Test void acquireWhenPendingCloseReturnsClosedHandle() throws Exception { FileDescriptor descriptor = new FileDescriptor(this.sourceHandle, this::close); try (Handle handle1 = descriptor.acquire()) { descriptor.close(); try (Handle handle2 = descriptor.acquire()) { assertThat(handle2.intValue()).isEqualTo(-1); assertThat(handle2.isClosed()).isTrue(); } } } @Test void finalizeTriggersClose() { FileDescriptor descriptor = new FileDescriptor(this.sourceHandle, this::close); descriptor.close(); assertThat(this.closedHandle).isEqualTo(this.sourceHandle); } @Test void closeWhenHandleAcquiredClosesOnRelease() throws Exception { FileDescriptor descriptor = new FileDescriptor(this.sourceHandle, this::close); try (Handle handle = descriptor.acquire()) { descriptor.close(); assertThat(this.closedHandle).isZero(); } assertThat(this.closedHandle).isEqualTo(this.sourceHandle); } @Test void closeWhenHandleNotAcquiredClosesImmediately() { FileDescriptor descriptor = new FileDescriptor(this.sourceHandle, this::close); descriptor.close(); assertThat(this.closedHandle).isEqualTo(this.sourceHandle); } private void close(int handle) { this.closedHandle = handle; } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/builder-metadata-platform-api-0.3.json ================================================ { "description": "Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang", "buildpacks": [ { "id": "org.cloudfoundry.googlestackdriver", "version": "v1.1.11" }, { "id": "org.cloudfoundry.springboot", "version": "v1.2.13" }, { "id": "org.cloudfoundry.debug", "version": "v1.2.11" }, { "id": "org.cloudfoundry.tomcat", "version": "v1.3.18" }, { "id": "org.cloudfoundry.go", "version": "v0.0.4" }, { "id": "org.cloudfoundry.openjdk", "version": "v1.2.14" }, { "id": "org.cloudfoundry.buildsystem", "version": "v1.2.15" }, { "id": "org.cloudfoundry.jvmapplication", "version": "v1.1.12" }, { "id": "org.cloudfoundry.springautoreconfiguration", "version": "v1.1.11" }, { "id": "org.cloudfoundry.archiveexpanding", "version": "v1.0.102" }, { "id": "org.cloudfoundry.jmx", "version": "v1.1.12" }, { "id": "org.cloudfoundry.nodejs", "version": "v2.0.8" }, { "id": "org.cloudfoundry.jdbc", "version": "v1.1.14" }, { "id": "org.cloudfoundry.procfile", "version": "v1.1.12" }, { "id": "org.cloudfoundry.dotnet-core", "version": "v0.0.6" }, { "id": "org.cloudfoundry.azureapplicationinsights", "version": "v1.1.12" }, { "id": "org.cloudfoundry.distzip", "version": "v1.1.12" }, { "id": "org.cloudfoundry.dep", "version": "0.0.101" }, { "id": "org.cloudfoundry.go-compiler", "version": "0.0.105" }, { "id": "org.cloudfoundry.go-mod", "version": "0.0.89" }, { "id": "org.cloudfoundry.node-engine", "version": "0.0.163" }, { "id": "org.cloudfoundry.npm", "version": "0.1.3" }, { "id": "org.cloudfoundry.yarn-install", "version": "0.1.10" }, { "id": "org.cloudfoundry.dotnet-core-aspnet", "version": "0.0.118" }, { "id": "org.cloudfoundry.dotnet-core-build", "version": "0.0.68" }, { "id": "org.cloudfoundry.dotnet-core-conf", "version": "0.0.115" }, { "id": "org.cloudfoundry.dotnet-core-runtime", "version": "0.0.127" }, { "id": "org.cloudfoundry.dotnet-core-sdk", "version": "0.0.122" }, { "id": "org.cloudfoundry.icu", "version": "0.0.43" }, { "id": "org.cloudfoundry.node-engine", "version": "0.0.158" } ], "stack": { "runImage": { "image": "cloudfoundry/run:base-cnb", "mirrors": null } }, "lifecycle": { "version": "0.7.2", "api": { "buildpack": "0.2", "platform": "0.3" } }, "createdBy": { "name": "Pack CLI", "version": "v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/builder-metadata-supported-apis.json ================================================ { "description": "Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang", "buildpacks": [ { "id": "org.cloudfoundry.springboot", "version": "v1.2.13" } ], "stack": { "runImage": { "image": "cloudfoundry/run:base-cnb", "mirrors": null } }, "lifecycle": { "version": "0.7.2", "api": { "buildpack": "0.2", "platform": "0.8" }, "apis": { "buildpack": { "deprecated": [], "supported": [ "0.1", "0.2", "0.3" ] }, "platform": { "deprecated": [], "supported": [ "0.3", "0.4", "0.5", "0.6", "0.7", "0.8" ] } } }, "createdBy": { "name": "Pack CLI", "version": "v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/builder-metadata-unsupported-api.json ================================================ { "description": "Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang", "buildpacks": [ { "id": "org.cloudfoundry.springboot", "version": "v1.2.13" } ], "stack": { "runImage": { "image": "cloudfoundry/run:base-cnb", "mirrors": null } }, "lifecycle": { "version": "0.7.2", "api": { "buildpack": "0.2", "platform": "0.2" } }, "createdBy": { "name": "Pack CLI", "version": "v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/builder-metadata-unsupported-apis.json ================================================ { "description": "Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang", "buildpacks": [ { "id": "org.cloudfoundry.springboot", "version": "v1.2.13" } ], "stack": { "runImage": { "image": "cloudfoundry/run:base-cnb", "mirrors": null } }, "lifecycle": { "version": "0.7.2", "api": { "buildpack": "0.2", "platform": "0.3" }, "apis": { "buildpack": { "deprecated": [], "supported": [ "0.1", "0.2", "0.3" ] }, "platform": { "deprecated": [], "supported": [ "0.1", "0.2" ] } } }, "createdBy": { "name": "Pack CLI", "version": "v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/builder-metadata.json ================================================ { "description": "Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang", "buildpacks": [ { "id": "paketo-buildpacks/dotnet-core", "version": "0.0.9", "homepage": "https://github.com/paketo-buildpacks/dotnet-core" }, { "id": "paketo-buildpacks/dotnet-core-runtime", "version": "0.0.201", "homepage": "https://github.com/paketo-buildpacks/dotnet-core-runtime" }, { "id": "paketo-buildpacks/dotnet-core-sdk", "version": "0.0.196", "homepage": "https://github.com/paketo-buildpacks/dotnet-core-sdk" }, { "id": "paketo-buildpacks/dotnet-execute", "version": "0.0.180", "homepage": "https://github.com/paketo-buildpacks/dotnet-execute" }, { "id": "paketo-buildpacks/dotnet-publish", "version": "0.0.121", "homepage": "https://github.com/paketo-buildpacks/dotnet-publish" }, { "id": "paketo-buildpacks/dotnet-core-aspnet", "version": "0.0.196", "homepage": "https://github.com/paketo-buildpacks/dotnet-core-aspnet" }, { "id": "paketo-buildpacks/java-native-image", "version": "4.7.0", "homepage": "https://github.com/paketo-buildpacks/java-native-image" }, { "id": "paketo-buildpacks/spring-boot", "version": "3.5.0", "homepage": "https://github.com/paketo-buildpacks/spring-boot" }, { "id": "paketo-buildpacks/executable-jar", "version": "3.1.3", "homepage": "https://github.com/paketo-buildpacks/executable-jar" }, { "id": "paketo-buildpacks/graalvm", "version": "4.1.0", "homepage": "https://github.com/paketo-buildpacks/graalvm" }, { "id": "paketo-buildpacks/gradle", "version": "3.5.0", "homepage": "https://github.com/paketo-buildpacks/gradle" }, { "id": "paketo-buildpacks/leiningen", "version": "1.2.1", "homepage": "https://github.com/paketo-buildpacks/leiningen" }, { "id": "paketo-buildpacks/procfile", "version": "3.0.0", "homepage": "https://github.com/paketo-buildpacks/procfile" }, { "id": "paketo-buildpacks/sbt", "version": "3.6.0", "homepage": "https://github.com/paketo-buildpacks/sbt" }, { "id": "paketo-buildpacks/spring-boot-native-image", "version": "2.0.1", "homepage": "https://github.com/paketo-buildpacks/spring-boot-native-image" }, { "id": "paketo-buildpacks/environment-variables", "version": "2.1.2", "homepage": "https://github.com/paketo-buildpacks/environment-variables" }, { "id": "paketo-buildpacks/image-labels", "version": "2.0.7", "homepage": "https://github.com/paketo-buildpacks/image-labels" }, { "id": "paketo-buildpacks/maven", "version": "3.2.1", "homepage": "https://github.com/paketo-buildpacks/maven" }, { "id": "paketo-buildpacks/java", "version": "4.10.0", "homepage": "https://github.com/paketo-buildpacks/java" }, { "id": "paketo-buildpacks/ca-certificates", "version": "1.0.1", "homepage": "https://github.com/paketo-buildpacks/ca-certificates" }, { "id": "paketo-buildpacks/environment-variables", "version": "2.1.2", "homepage": "https://github.com/paketo-buildpacks/environment-variables" }, { "id": "paketo-buildpacks/executable-jar", "version": "3.1.3", "homepage": "https://github.com/paketo-buildpacks/executable-jar" }, { "id": "paketo-buildpacks/procfile", "version": "3.0.0", "homepage": "https://github.com/paketo-buildpacks/procfile" }, { "id": "paketo-buildpacks/apache-tomcat", "version": "3.2.0", "homepage": "https://github.com/paketo-buildpacks/apache-tomcat" }, { "id": "paketo-buildpacks/gradle", "version": "3.5.0", "homepage": "https://github.com/paketo-buildpacks/gradle" }, { "id": "paketo-buildpacks/maven", "version": "3.2.1", "homepage": "https://github.com/paketo-buildpacks/maven" }, { "id": "paketo-buildpacks/sbt", "version": "3.6.0", "homepage": "https://github.com/paketo-buildpacks/sbt" }, { "id": "paketo-buildpacks/bellsoft-liberica", "version": "6.2.0", "homepage": "https://github.com/paketo-buildpacks/bellsoft-liberica" }, { "id": "paketo-buildpacks/image-labels", "version": "2.0.7", "homepage": "https://github.com/paketo-buildpacks/image-labels" }, { "id": "paketo-buildpacks/debug", "version": "2.1.4", "homepage": "https://github.com/paketo-buildpacks/debug" }, { "id": "paketo-buildpacks/dist-zip", "version": "2.2.2", "homepage": "https://github.com/paketo-buildpacks/dist-zip" }, { "id": "paketo-buildpacks/spring-boot", "version": "3.5.0", "homepage": "https://github.com/paketo-buildpacks/spring-boot" }, { "id": "paketo-buildpacks/jmx", "version": "2.1.4", "homepage": "https://github.com/paketo-buildpacks/jmx" }, { "id": "paketo-buildpacks/leiningen", "version": "1.2.1", "homepage": "https://github.com/paketo-buildpacks/leiningen" } ], "stack": { "runImage": { "image": "cloudfoundry/run:base-cnb", "mirrors": null } }, "lifecycle": { "version": "0.7.2", "api": { "buildpack": "0.2", "platform": "0.8" } }, "createdBy": { "name": "Pack CLI", "version": "v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/buildpack-image.json ================================================ { "Id": "sha256:a266647e285b52403b556adc963f1809556aa999f2f694e8dc54098c570ee55a", "RepoTags": [ "example/hello-universe:latest" ], "RepoDigests": [], "Parent": "", "Comment": "", "Created": "1980-01-01T00:00:01Z", "Container": "", "ContainerConfig": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": null, "Image": "", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null }, "DockerVersion": "", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": null, "Image": "", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.buildpackage.metadata": "{\"id\":\"example/hello-universe\",\"version\":\"0.0.1\",\"homepage\":\"https://github.com/buildpacks/example/tree/main/buildpacks/hello-universe\",\"stacks\":[{\"id\":\"io.buildpacks.example.stacks.alpine\"},{\"id\":\"io.buildpacks.stacks.bionic\"}]}", "io.buildpacks.buildpack.layers": "{\"example/hello-moon\":{\"0.0.3\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.alpine\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:4bfdc8714aee68da6662c43bc28d3b41202c88e915641c356523dabe729814c2\",\"homepage\":\"https://github.com/example/tree/main/buildpacks/hello-moon\"}},\"example/hello-universe\":{\"0.0.1\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"example/hello-world\",\"version\":\"0.0.2\"},{\"id\":\"example/hello-moon\",\"version\":\"0.0.2\"}]}],\"layerDiffID\":\"sha256:739b4e8f3caae7237584a1bfe029ebdb05403752b1a60a4f9be991b1d51dbb69\",\"homepage\":\"https://github.com/example/tree/main/buildpacks/hello-universe\"}},\"example/hello-world\":{\"0.0.2\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.alpine\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:f752fe099c846e501bdc991d1a22f98c055ddc62f01cfc0495fff2c69f8eb940\",\"homepage\":\"https://github.com/example/tree/main/buildpacks/hello-world\"}}}" } }, "Architecture": "amd64", "Os": "linux", "Size": 4654, "VirtualSize": 4654, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/cbf39b4508463beeb1d0a553c3e2baa84b8cd8dbc95681aaecc243e3ca77bcf4/diff:/var/lib/docker/overlay2/15e3d01b65c962b50a3da1b6663b8196284fb3c7e7f8497f2c1a0a736d0ec237/diff", "MergedDir": "/var/lib/docker/overlay2/1425ea68b0daff01bcc32e55e09eeeada2318d7dd1dc4e184711359da8425bb7/merged", "UpperDir": "/var/lib/docker/overlay2/1425ea68b0daff01bcc32e55e09eeeada2318d7dd1dc4e184711359da8425bb7/diff", "WorkDir": "/var/lib/docker/overlay2/1425ea68b0daff01bcc32e55e09eeeada2318d7dd1dc4e184711359da8425bb7/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:4bfdc8714aee68da6662c43bc28d3b41202c88e915641c356523dabe729814c2", "sha256:f752fe099c846e501bdc991d1a22f98c055ddc62f01cfc0495fff2c69f8eb940", "sha256:739b4e8f3caae7237584a1bfe029ebdb05403752b1a60a4f9be991b1d51dbb69" ] }, "Metadata": { "LastTagTime": "2021-01-27T22:56:06.4599859Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/buildpack-layers-metadata.json ================================================ { "example/hello-moon": { "0.0.3": { "api": "0.2", "stacks": [ { "id": "io.buildpacks.stacks.alpine" }, { "id": "io.buildpacks.stacks.bionic" } ], "name": "Example hello-moon buildpack", "layerDiffID": "sha256:4bfdc8714aee68da6662c43bc28d3b41202c88e915641c356523dabe729814c2", "homepage": "https://github.com/example/tree/main/buildpacks/hello-moon" } }, "example/hello-universe": { "0.0.1": { "api": "0.2", "order": [ { "group": [ { "id": "example/hello-world", "version": "0.0.2" }, { "id": "example/hello-moon", "version": "0.0.2" } ] } ], "name": "Example hello-universe buildpack", "layerDiffID": "sha256:739b4e8f3caae7237584a1bfe029ebdb05403752b1a60a4f9be991b1d51dbb69", "homepage": "https://github.com/example/tree/main/buildpacks/hello-universe" } }, "example/hello-world": { "0.0.1": { "api": "0.2", "stacks": [ { "id": "io.buildpacks.stacks.alpine" }, { "id": "io.buildpacks.stacks.bionic" } ], "name": "Example hello-world buildpack", "layerDiffID": "sha256:1c90e0b80d92555a0523c9ee6500845328fc39ba9dca9d30a877ff759ffbff28", "homepage": "https://github.com/example/tree/main/buildpacks/hello-world" }, "0.0.2": { "api": "0.2", "stacks": [ { "id": "io.buildpacks.stacks.alpine" }, { "id": "io.buildpacks.stacks.bionic" } ], "name": "Example hello-world buildpack", "layerDiffID": "sha256:f752fe099c846e501bdc991d1a22f98c055ddc62f01cfc0495fff2c69f8eb940", "homepage": "https://github.com/example/tree/main/buildpacks/hello-world" } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/buildpack-metadata.json ================================================ { "id": "example/hello-universe", "version": "0.0.1", "homepage": "https://github.com/example/tree/main/buildpacks/hello-universe", "stacks": [ { "id": "io.buildpacks.stacks.alpine" }, { "id": "io.buildpacks.stacks.bionic" } ] } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/buildpack.toml ================================================ [buildpack] id = "test"; version = "1.0.0" name = "Example buildpack" homepage = "https://github.com/example/example-buildpack" [[stacks]] id = "io.buildpacks.stacks.bionic" ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-empty-stack.json ================================================ { "Id": "sha256:44cc64492fb6a6d78d3e6d087f380ae6e479aa1b2c79823b32cdacfcc2f3d715", "RepoTags": [ "paketo-buildpacks/cnb:base", "paketo-buildpacks/builder:base-platform-api-0.2" ], "RepoDigests": [ "paketo-buidpacks/cnb@sha256:5b03a853e636b78c44e475bbc514e2b7b140cc41cca8ab907e9753431ae8c0b0" ], "Parent": "", "Comment": "", "Created": "1980-01-01T00:00:01Z", "Container": "", "ContainerConfig": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": null, "Image": "", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null }, "DockerVersion": "", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "1000:1000", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=1000", "CNB_GROUP_ID=1000", "CNB_STACK_ID=io.buildpacks.stacks.bionic" ], "Cmd": [ "/bin/bash" ], "ArgsEscaped": true, "Image": "sha256:2d153261a5e359c632a17377cfb5d1986c27b96c8b6e95334bf80f1029dbd4bb", "Volumes": null, "WorkingDir": "/layers", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.builder.metadata": "{\"description\":\"Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang\",\"buildpacks\":[{\"id\":\"paketo-buildpacks/dotnet-core\",\"version\":\"0.0.9\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core\"},{\"id\":\"paketo-buildpacks/dotnet-core-runtime\",\"version\":\"0.0.201\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core-runtime\"},{\"id\":\"paketo-buildpacks/dotnet-core-sdk\",\"version\":\"0.0.196\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core-sdk\"},{\"id\":\"paketo-buildpacks/dotnet-execute\",\"version\":\"0.0.180\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-execute\"},{\"id\":\"paketo-buildpacks/dotnet-publish\",\"version\":\"0.0.121\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-publish\"},{\"id\":\"paketo-buildpacks/dotnet-core-aspnet\",\"version\":\"0.0.196\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core-aspnet\"},{\"id\":\"paketo-buildpacks/java-native-image\",\"version\":\"4.7.0\",\"homepage\":\"https://github.com/paketo-buildpacks/java-native-image\"},{\"id\":\"paketo-buildpacks/spring-boot\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/spring-boot\"},{\"id\":\"paketo-buildpacks/executable-jar\",\"version\":\"3.1.3\",\"homepage\":\"https://github.com/paketo-buildpacks/executable-jar\"},{\"id\":\"paketo-buildpacks/graalvm\",\"version\":\"4.1.0\",\"homepage\":\"https://github.com/paketo-buildpacks/graalvm\"},{\"id\":\"paketo-buildpacks/gradle\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/gradle\"},{\"id\":\"paketo-buildpacks/leiningen\",\"version\":\"1.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/leiningen\"},{\"id\":\"paketo-buildpacks/sbt\",\"version\":\"3.6.0\",\"homepage\":\"https://github.com/paketo-buildpacks/sbt\"},{\"id\":\"paketo-buildpacks/spring-boot-native-image\",\"version\":\"2.0.1\",\"homepage\":\"https://github.com/paketo-buildpacks/spring-boot-native-image\"},{\"id\":\"paketo-buildpacks/environment-variables\",\"version\":\"2.1.2\",\"homepage\":\"https://github.com/paketo-buildpacks/environment-variables\"},{\"id\":\"paketo-buildpacks/image-labels\",\"version\":\"2.0.7\",\"homepage\":\"https://github.com/paketo-buildpacks/image-labels\"},{\"id\":\"paketo-buildpacks/maven\",\"version\":\"3.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/maven\"},{\"id\":\"paketo-buildpacks/java\",\"version\":\"4.10.0\",\"homepage\":\"https://github.com/paketo-buildpacks/java\"},{\"id\":\"paketo-buildpacks/ca-certificates\",\"version\":\"1.0.1\",\"homepage\":\"https://github.com/paketo-buildpacks/ca-certificates\"},{\"id\":\"paketo-buildpacks/environment-variables\",\"version\":\"2.1.2\",\"homepage\":\"https://github.com/paketo-buildpacks/environment-variables\"},{\"id\":\"paketo-buildpacks/executable-jar\",\"version\":\"3.1.3\",\"homepage\":\"https://github.com/paketo-buildpacks/executable-jar\"},{\"id\":\"paketo-buildpacks/procfile\",\"version\":\"3.0.0\",\"homepage\":\"https://github.com/paketo-buildpacks/procfile\"},{\"id\":\"paketo-buildpacks/apache-tomcat\",\"version\":\"3.2.0\",\"homepage\":\"https://github.com/paketo-buildpacks/apache-tomcat\"},{\"id\":\"paketo-buildpacks/gradle\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/gradle\"},{\"id\":\"paketo-buildpacks/maven\",\"version\":\"3.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/maven\"},{\"id\":\"paketo-buildpacks/sbt\",\"version\":\"3.6.0\",\"homepage\":\"https://github.com/paketo-buildpacks/sbt\"},{\"id\":\"paketo-buildpacks/bellsoft-liberica\",\"version\":\"6.2.0\",\"homepage\":\"https://github.com/paketo-buildpacks/bellsoft-liberica\"},{\"id\":\"paketo-buildpacks/google-stackdriver\",\"version\":\"2.16.0\",\"homepage\":\"https://github.com/paketo-buildpacks/google-stackdriver\"},{\"id\":\"paketo-buildpacks/image-labels\",\"version\":\"2.0.7\",\"homepage\":\"https://github.com/paketo-buildpacks/image-labels\"},{\"id\":\"paketo-buildpacks/dist-zip\",\"version\":\"2.2.2\",\"homepage\":\"https://github.com/paketo-buildpacks/dist-zip\"},{\"id\":\"paketo-buildpacks/spring-boot\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/spring-boot\"},{\"id\":\"paketo-buildpacks/jmx\",\"version\":\"2.1.4\",\"homepage\":\"https://github.com/paketo-buildpacks/jmx\"},{\"id\":\"paketo-buildpacks/leiningen\",\"version\":\"1.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/leiningen\"}],\"stack\":{\"runImage\":{\"image\":\"\",\"mirrors\":null}},\"images\":[{\"image\":\"cloudfoundry/run:base-cnb\",\"mirrors\":null}],\"lifecycle\":{\"version\":\"0.7.2\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.3\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)\"}}", "io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.102\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab\"}},\"org.cloudfoundry.buildsystem\":{\"v1.2.15\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9\"}},\"org.cloudfoundry.debug\":{\"v1.2.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7\"}},\"org.cloudfoundry.dep\":{\"0.0.101\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8\"}},\"org.cloudfoundry.distzip\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.6\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\",\"optional\":true},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"}]}],\"layerDiffID\":\"sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357\"}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.118\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.68\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.115\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.127\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.122\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f\"}},\"org.cloudfoundry.go\":{\"v0.0.4\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"}]}],\"layerDiffID\":\"sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb\"}},\"org.cloudfoundry.go-compiler\":{\"0.0.105\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24\"}},\"org.cloudfoundry.go-mod\":{\"0.0.89\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41\"}},\"org.cloudfoundry.icu\":{\"0.0.43\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347\"}},\"org.cloudfoundry.jdbc\":{\"v1.1.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62\"}},\"org.cloudfoundry.jmx\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22\"}},\"org.cloudfoundry.node-engine\":{\"0.0.158\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339\"},\"0.0.163\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777\"}},\"org.cloudfoundry.nodejs\":{\"v2.0.8\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"}]}],\"layerDiffID\":\"sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109\"}},\"org.cloudfoundry.npm\":{\"0.1.3\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1\"}},\"org.cloudfoundry.openjdk\":{\"v1.2.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151\"}},\"org.cloudfoundry.procfile\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab\"}},\"org.cloudfoundry.springboot\":{\"v1.2.13\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f\"}},\"org.cloudfoundry.tomcat\":{\"v1.3.18\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2\"}},\"org.cloudfoundry.yarn-install\":{\"0.1.10\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7\"}}}", "io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.procfile\"}]}]" } }, "Architecture": "amd64", "Os": "linux", "Size": 688884758, "VirtualSize": 688884758, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/6a79181b2840da2706624f46ce5abd4448973b4f951925d5a276b273256063b2/diff:/var/lib/docker/overlay2/429419a203100f60ab16ec6c879fce975c8138422b9053f80accd6124c730fc2/diff:/var/lib/docker/overlay2/6e45ed6daf4f4f3b90fd1ec5fa958775000875661d3e8be3f1af218d192b058d/diff:/var/lib/docker/overlay2/22928ad308cdd55b3fe849d92b6e38c6bc303ba7c9beb8c0e79aa958e16b1864/diff:/var/lib/docker/overlay2/2ca9ec213226a1604f57c8e141d6f1168134a5cb2ccd8f91ee9be5a39036e6bf/diff:/var/lib/docker/overlay2/96ae944fe00ec20cf5b4441b112ebcc9395faaf08108c9ee38c62e1da33af1c8/diff:/var/lib/docker/overlay2/13ee52e300e476e27350c9ac6274dedf26af85c3079b42a41f9dfc92eff57a80/diff:/var/lib/docker/overlay2/223edb4cc62a2ba2b8bda866905a55c4798c6c32e31d22d60e6ed4f3169ce85e/diff:/var/lib/docker/overlay2/a41235cd7277299cb74ead47def3771885948719e24075ea3bf37580f3af7ae2/diff:/var/lib/docker/overlay2/ed0438e8e2c27b9d62ad21a0761237c350a2ffc9e52f47c019e4f627091c832e/diff:/var/lib/docker/overlay2/0c27c8229b31eafc57ab739b44962dcc07b72f3d8950888873ecb3cfd385032f/diff:/var/lib/docker/overlay2/0957cbcca052cd58bcf9a3d945b0e6876b0df79c1c534da1872c3415a019427d/diff:/var/lib/docker/overlay2/b621414d53d71349c07df8ed45e3e04b2e97bfbaf4bf0d86463f46e0f810eeb4/diff:/var/lib/docker/overlay2/ad521bc47f0bb44262358cf47c3d81a544d098494cf24a5b510620d34eb9c353/diff:/var/lib/docker/overlay2/081501d5bfbd927e69c10eb320513c7c0d5f00bea8cf9e55faa90579fd33adf4/diff:/var/lib/docker/overlay2/fb1ba66bee5568f5700c72865d020d4171a62bfdd099c3cc05b9a253d36a35a4/diff:/var/lib/docker/overlay2/06bcc6b3adeca727d554f1a745ee33242dfe1b3c6392023ac947666057303288/diff:/var/lib/docker/overlay2/1c5397d63d893202dffde29013ee826fb695bda26c718ee03ddde376be4da0a3/diff:/var/lib/docker/overlay2/76075fb7fd3c6b3fb116fb3b464e220918e56d94461c61af9a1aff288ebdba60/diff:/var/lib/docker/overlay2/43d1026bb7b618393912ecc9ddf57b604336184d5f8dc70bcf6332b5f08a3e8d/diff:/var/lib/docker/overlay2/ee27d1fba3deaca0556f7bab171cb3368f169011dd132cf335b5308728f6db8f/diff:/var/lib/docker/overlay2/464d3ec8d86ff31dcb5063ea25521368ea8e9c7964f65e15ff5e0e1ecdbe991e/diff:/var/lib/docker/overlay2/a4a80c33c8b78f68bdc9dbd5903cc2ba1d48e78b9a97d43acb018823ece8e6cb/diff:/var/lib/docker/overlay2/6494f2f1693cff8b16d51fa95620eb0bb691a76fb39b5175d953649577791297/diff:/var/lib/docker/overlay2/9d49e146f82eb5fc4fd81613538e9c5f5f95091fbbc8c49729c6c9140ae356de/diff:/var/lib/docker/overlay2/2934818c52bcd017abe000e71342d67fbc9ccb7dbc165ce05e3250e2110229a5/diff:/var/lib/docker/overlay2/651ca06b2bf75e2122855264287fc937f30d2b49229d628909895be7128b4eb6/diff:/var/lib/docker/overlay2/c93bab59be44fa1b66689dc059d26742d00d2e787d06c3236e1f116199c9807e/diff:/var/lib/docker/overlay2/d0a8e2a0c7e0df172f7a8ebe75e2dce371bb6cc65531b06799bc677c5b5e3627/diff:/var/lib/docker/overlay2/7d14bac240e0d7936351e3fac80b7fbe2a209f4de8992091c4f75e41f9627852/diff:/var/lib/docker/overlay2/d6b192ea137a4ae95e309d263ee8c890e35da02aacd9bdcf5adbd4c28a0c0a3f/diff:/var/lib/docker/overlay2/335bfb632ab7723e25fb5dc7b67389e6ec38178ef10bfbf83337501403e61574/diff:/var/lib/docker/overlay2/0293c7e3472da58f51cbdf15fb293ff71e32c1f80f83f00fb09f8941deef5e43/diff:/var/lib/docker/overlay2/55faa8b47bcb0dd29c3836580f451a0461dd499065af9c830beff6e8329ab484/diff:/var/lib/docker/overlay2/afcb6e109c1ba7d71b8a8b7e573d4ce04f22da3fe0ee523359db5cfb95e65bb6/diff:/var/lib/docker/overlay2/b42eefd9bf6629ae9d16e7aba6ba3939d37816aba7a0999f6d639012a3119be1/diff:/var/lib/docker/overlay2/a9832c8f81ee889a622ce4d95d9f4bab2f91d30e18f69bfd7cfc385c781068d4/diff:/var/lib/docker/overlay2/224041c135f13881a98b9e833584bedab81d5650061457f522a1ebd1daa2c77a/diff:/var/lib/docker/overlay2/73dfd4e2075fccb239b3d5e9b33b32b8e410bdc3cd5a620b41346f44cc5c51f7/diff:/var/lib/docker/overlay2/b3924ed7c91730f6714d33c455db888604b59ab093033b3f59ac16ecdd777987/diff:/var/lib/docker/overlay2/e36a32cd0ab20b216a8db1a8a166b17464399e4d587d22504088a7a6ef0a68a4/diff:/var/lib/docker/overlay2/3334e94fe191333b65f571912c0fcfbbf31aeb090a2fb9b4cfdbc32a37c0fe5f/diff", "MergedDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/merged", "UpperDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/diff", "WorkDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853", "sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f", "sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d", "sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75", "sha256:2df36adfe1af661aebb75a0db796b074bb8f861fbc8f98f6f642570692b3b133", "sha256:f499c7d34e01d860492ef1cc34b7d7e1319b3c3c81ee7d23258b21605b5902ca", "sha256:c4bf1d4e5d4adb566b173a0769d247f67c5dd8ff90dfdcebd8c7060f1c06caa9", "sha256:15259abd479904cbe0d8d421e5b05b2e5745e2bf82e62cdd7fb6d3eafbe4168a", "sha256:6aa3691a73805f608e5fce69fb6bc89aec8362f58a6b4be2682515e9cfa3cc1a", "sha256:2d6ad1b66f5660dd860c1fe2d90d26398fcfab4dc1c87c3d5e7c0fc24f8d6fb2", "sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41", "sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f", "sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7", "sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2", "sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb", "sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151", "sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9", "sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22", "sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab", "sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c", "sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469", "sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109", "sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62", "sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33", "sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357", "sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab", "sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df", "sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8", "sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24", "sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add", "sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777", "sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1", "sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7", "sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad", "sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9", "sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d", "sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990", "sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f", "sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347", "sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339", "sha256:6cf0f8f815d5371cf5c04e7ebf76c62467948d693b8343184d1446036980d261", "sha256:7cbffcbb09fc5e9d00372e80990016609c09cc3113429ddc951c4a19b1a5ec72", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-no-run-image-tag.json ================================================ { "Id": "sha256:44cc64492fb6a6d78d3e6d087f380ae6e479aa1b2c79823b32cdacfcc2f3d715", "RepoTags": [ "paketo-buildpacks/cnb:base", "paketo-buildpacks/builder:base-platform-api-0.2" ], "RepoDigests": [ "paketo-buidpacks/cnb@sha256:5b03a853e636b78c44e475bbc514e2b7b140cc41cca8ab907e9753431ae8c0b0" ], "Parent": "", "Comment": "", "Created": "1980-01-01T00:00:01Z", "Container": "", "ContainerConfig": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": null, "Image": "", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null }, "DockerVersion": "", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "1000:1000", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=1000", "CNB_GROUP_ID=1000", "CNB_STACK_ID=io.buildpacks.stacks.bionic" ], "Cmd": [ "/bin/bash" ], "ArgsEscaped": true, "Image": "sha256:2d153261a5e359c632a17377cfb5d1986c27b96c8b6e95334bf80f1029dbd4bb", "Volumes": null, "WorkingDir": "/layers", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.builder.metadata": "{\"description\":\"Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang\",\"buildpacks\":[{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.1.11\"},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.2.13\"},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.2.11\"},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.3.18\"},{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.4\"},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.2.14\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.2.15\"},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.1.11\"},{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.102\"},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v2.0.8\"},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.1.14\"},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.6\"},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"},{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\"},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\"},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\"},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\"},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\"},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\"}],\"stack\":{\"runImage\":{\"image\":\"cloudfoundry/run\",\"mirrors\":null}},\"lifecycle\":{\"version\":\"0.7.2\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.3\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)\"}}", "io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.102\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab\"}},\"org.cloudfoundry.buildsystem\":{\"v1.2.15\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9\"}},\"org.cloudfoundry.debug\":{\"v1.2.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7\"}},\"org.cloudfoundry.dep\":{\"0.0.101\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8\"}},\"org.cloudfoundry.distzip\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.6\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\",\"optional\":true},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"}]}],\"layerDiffID\":\"sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357\"}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.118\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.68\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.115\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.127\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.122\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f\"}},\"org.cloudfoundry.go\":{\"v0.0.4\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"}]}],\"layerDiffID\":\"sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb\"}},\"org.cloudfoundry.go-compiler\":{\"0.0.105\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24\"}},\"org.cloudfoundry.go-mod\":{\"0.0.89\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41\"}},\"org.cloudfoundry.icu\":{\"0.0.43\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347\"}},\"org.cloudfoundry.jdbc\":{\"v1.1.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62\"}},\"org.cloudfoundry.jmx\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22\"}},\"org.cloudfoundry.node-engine\":{\"0.0.158\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339\"},\"0.0.163\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777\"}},\"org.cloudfoundry.nodejs\":{\"v2.0.8\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"}]}],\"layerDiffID\":\"sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109\"}},\"org.cloudfoundry.npm\":{\"0.1.3\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1\"}},\"org.cloudfoundry.openjdk\":{\"v1.2.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151\"}},\"org.cloudfoundry.procfile\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab\"}},\"org.cloudfoundry.springboot\":{\"v1.2.13\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f\"}},\"org.cloudfoundry.tomcat\":{\"v1.3.18\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2\"}},\"org.cloudfoundry.yarn-install\":{\"0.1.10\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7\"}}}", "io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.procfile\"}]}]", "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic", "io.buildpacks.stack.mixins": "[\"build:git\",\"build:build-essential\"]" } }, "Architecture": "amd64", "Os": "linux", "Size": 688884758, "VirtualSize": 688884758, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/6a79181b2840da2706624f46ce5abd4448973b4f951925d5a276b273256063b2/diff:/var/lib/docker/overlay2/429419a203100f60ab16ec6c879fce975c8138422b9053f80accd6124c730fc2/diff:/var/lib/docker/overlay2/6e45ed6daf4f4f3b90fd1ec5fa958775000875661d3e8be3f1af218d192b058d/diff:/var/lib/docker/overlay2/22928ad308cdd55b3fe849d92b6e38c6bc303ba7c9beb8c0e79aa958e16b1864/diff:/var/lib/docker/overlay2/2ca9ec213226a1604f57c8e141d6f1168134a5cb2ccd8f91ee9be5a39036e6bf/diff:/var/lib/docker/overlay2/96ae944fe00ec20cf5b4441b112ebcc9395faaf08108c9ee38c62e1da33af1c8/diff:/var/lib/docker/overlay2/13ee52e300e476e27350c9ac6274dedf26af85c3079b42a41f9dfc92eff57a80/diff:/var/lib/docker/overlay2/223edb4cc62a2ba2b8bda866905a55c4798c6c32e31d22d60e6ed4f3169ce85e/diff:/var/lib/docker/overlay2/a41235cd7277299cb74ead47def3771885948719e24075ea3bf37580f3af7ae2/diff:/var/lib/docker/overlay2/ed0438e8e2c27b9d62ad21a0761237c350a2ffc9e52f47c019e4f627091c832e/diff:/var/lib/docker/overlay2/0c27c8229b31eafc57ab739b44962dcc07b72f3d8950888873ecb3cfd385032f/diff:/var/lib/docker/overlay2/0957cbcca052cd58bcf9a3d945b0e6876b0df79c1c534da1872c3415a019427d/diff:/var/lib/docker/overlay2/b621414d53d71349c07df8ed45e3e04b2e97bfbaf4bf0d86463f46e0f810eeb4/diff:/var/lib/docker/overlay2/ad521bc47f0bb44262358cf47c3d81a544d098494cf24a5b510620d34eb9c353/diff:/var/lib/docker/overlay2/081501d5bfbd927e69c10eb320513c7c0d5f00bea8cf9e55faa90579fd33adf4/diff:/var/lib/docker/overlay2/fb1ba66bee5568f5700c72865d020d4171a62bfdd099c3cc05b9a253d36a35a4/diff:/var/lib/docker/overlay2/06bcc6b3adeca727d554f1a745ee33242dfe1b3c6392023ac947666057303288/diff:/var/lib/docker/overlay2/1c5397d63d893202dffde29013ee826fb695bda26c718ee03ddde376be4da0a3/diff:/var/lib/docker/overlay2/76075fb7fd3c6b3fb116fb3b464e220918e56d94461c61af9a1aff288ebdba60/diff:/var/lib/docker/overlay2/43d1026bb7b618393912ecc9ddf57b604336184d5f8dc70bcf6332b5f08a3e8d/diff:/var/lib/docker/overlay2/ee27d1fba3deaca0556f7bab171cb3368f169011dd132cf335b5308728f6db8f/diff:/var/lib/docker/overlay2/464d3ec8d86ff31dcb5063ea25521368ea8e9c7964f65e15ff5e0e1ecdbe991e/diff:/var/lib/docker/overlay2/a4a80c33c8b78f68bdc9dbd5903cc2ba1d48e78b9a97d43acb018823ece8e6cb/diff:/var/lib/docker/overlay2/6494f2f1693cff8b16d51fa95620eb0bb691a76fb39b5175d953649577791297/diff:/var/lib/docker/overlay2/9d49e146f82eb5fc4fd81613538e9c5f5f95091fbbc8c49729c6c9140ae356de/diff:/var/lib/docker/overlay2/2934818c52bcd017abe000e71342d67fbc9ccb7dbc165ce05e3250e2110229a5/diff:/var/lib/docker/overlay2/651ca06b2bf75e2122855264287fc937f30d2b49229d628909895be7128b4eb6/diff:/var/lib/docker/overlay2/c93bab59be44fa1b66689dc059d26742d00d2e787d06c3236e1f116199c9807e/diff:/var/lib/docker/overlay2/d0a8e2a0c7e0df172f7a8ebe75e2dce371bb6cc65531b06799bc677c5b5e3627/diff:/var/lib/docker/overlay2/7d14bac240e0d7936351e3fac80b7fbe2a209f4de8992091c4f75e41f9627852/diff:/var/lib/docker/overlay2/d6b192ea137a4ae95e309d263ee8c890e35da02aacd9bdcf5adbd4c28a0c0a3f/diff:/var/lib/docker/overlay2/335bfb632ab7723e25fb5dc7b67389e6ec38178ef10bfbf83337501403e61574/diff:/var/lib/docker/overlay2/0293c7e3472da58f51cbdf15fb293ff71e32c1f80f83f00fb09f8941deef5e43/diff:/var/lib/docker/overlay2/55faa8b47bcb0dd29c3836580f451a0461dd499065af9c830beff6e8329ab484/diff:/var/lib/docker/overlay2/afcb6e109c1ba7d71b8a8b7e573d4ce04f22da3fe0ee523359db5cfb95e65bb6/diff:/var/lib/docker/overlay2/b42eefd9bf6629ae9d16e7aba6ba3939d37816aba7a0999f6d639012a3119be1/diff:/var/lib/docker/overlay2/a9832c8f81ee889a622ce4d95d9f4bab2f91d30e18f69bfd7cfc385c781068d4/diff:/var/lib/docker/overlay2/224041c135f13881a98b9e833584bedab81d5650061457f522a1ebd1daa2c77a/diff:/var/lib/docker/overlay2/73dfd4e2075fccb239b3d5e9b33b32b8e410bdc3cd5a620b41346f44cc5c51f7/diff:/var/lib/docker/overlay2/b3924ed7c91730f6714d33c455db888604b59ab093033b3f59ac16ecdd777987/diff:/var/lib/docker/overlay2/e36a32cd0ab20b216a8db1a8a166b17464399e4d587d22504088a7a6ef0a68a4/diff:/var/lib/docker/overlay2/3334e94fe191333b65f571912c0fcfbbf31aeb090a2fb9b4cfdbc32a37c0fe5f/diff", "MergedDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/merged", "UpperDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/diff", "WorkDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853", "sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f", "sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d", "sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75", "sha256:2df36adfe1af661aebb75a0db796b074bb8f861fbc8f98f6f642570692b3b133", "sha256:f499c7d34e01d860492ef1cc34b7d7e1319b3c3c81ee7d23258b21605b5902ca", "sha256:c4bf1d4e5d4adb566b173a0769d247f67c5dd8ff90dfdcebd8c7060f1c06caa9", "sha256:15259abd479904cbe0d8d421e5b05b2e5745e2bf82e62cdd7fb6d3eafbe4168a", "sha256:6aa3691a73805f608e5fce69fb6bc89aec8362f58a6b4be2682515e9cfa3cc1a", "sha256:2d6ad1b66f5660dd860c1fe2d90d26398fcfab4dc1c87c3d5e7c0fc24f8d6fb2", "sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41", "sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f", "sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7", "sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2", "sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb", "sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151", "sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9", "sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22", "sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab", "sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c", "sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469", "sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109", "sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62", "sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33", "sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357", "sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab", "sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df", "sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8", "sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24", "sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add", "sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777", "sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1", "sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7", "sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad", "sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9", "sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d", "sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990", "sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f", "sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347", "sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339", "sha256:6cf0f8f815d5371cf5c04e7ebf76c62467948d693b8343184d1446036980d261", "sha256:7cbffcbb09fc5e9d00372e80990016609c09cc3113429ddc951c4a19b1a5ec72", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-platform.json ================================================ { "Id": "sha256:44cc64492fb6a6d78d3e6d087f380ae6e479aa1b2c79823b32cdacfcc2f3d715", "RepoTags": [ "paketo-buildpacks/cnb:base", "paketo-buildpacks/builder:base-platform-api-0.2" ], "RepoDigests": [ "paketo-buidpacks/cnb@sha256:5b03a853e636b78c44e475bbc514e2b7b140cc41cca8ab907e9753431ae8c0b0" ], "Parent": "", "Comment": "", "Created": "1980-01-01T00:00:01Z", "Container": "", "ContainerConfig": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": null, "Image": "", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null }, "DockerVersion": "", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "1000:1000", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=1000", "CNB_GROUP_ID=1000", "CNB_STACK_ID=io.buildpacks.stacks.bionic" ], "Cmd": [ "/bin/bash" ], "ArgsEscaped": true, "Image": "sha256:2d153261a5e359c632a17377cfb5d1986c27b96c8b6e95334bf80f1029dbd4bb", "Volumes": null, "WorkingDir": "/layers", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.builder.metadata": "{\"description\":\"Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang\",\"buildpacks\":[{\"id\":\"paketo-buildpacks/dotnet-core\",\"version\":\"0.0.9\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core\"},{\"id\":\"paketo-buildpacks/dotnet-core-runtime\",\"version\":\"0.0.201\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core-runtime\"},{\"id\":\"paketo-buildpacks/dotnet-core-sdk\",\"version\":\"0.0.196\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core-sdk\"},{\"id\":\"paketo-buildpacks/dotnet-execute\",\"version\":\"0.0.180\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-execute\"},{\"id\":\"paketo-buildpacks/dotnet-publish\",\"version\":\"0.0.121\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-publish\"},{\"id\":\"paketo-buildpacks/dotnet-core-aspnet\",\"version\":\"0.0.196\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core-aspnet\"},{\"id\":\"paketo-buildpacks/java-native-image\",\"version\":\"4.7.0\",\"homepage\":\"https://github.com/paketo-buildpacks/java-native-image\"},{\"id\":\"paketo-buildpacks/spring-boot\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/spring-boot\"},{\"id\":\"paketo-buildpacks/executable-jar\",\"version\":\"3.1.3\",\"homepage\":\"https://github.com/paketo-buildpacks/executable-jar\"},{\"id\":\"paketo-buildpacks/graalvm\",\"version\":\"4.1.0\",\"homepage\":\"https://github.com/paketo-buildpacks/graalvm\"},{\"id\":\"paketo-buildpacks/gradle\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/gradle\"},{\"id\":\"paketo-buildpacks/leiningen\",\"version\":\"1.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/leiningen\"},{\"id\":\"paketo-buildpacks/sbt\",\"version\":\"3.6.0\",\"homepage\":\"https://github.com/paketo-buildpacks/sbt\"},{\"id\":\"paketo-buildpacks/spring-boot-native-image\",\"version\":\"2.0.1\",\"homepage\":\"https://github.com/paketo-buildpacks/spring-boot-native-image\"},{\"id\":\"paketo-buildpacks/environment-variables\",\"version\":\"2.1.2\",\"homepage\":\"https://github.com/paketo-buildpacks/environment-variables\"},{\"id\":\"paketo-buildpacks/image-labels\",\"version\":\"2.0.7\",\"homepage\":\"https://github.com/paketo-buildpacks/image-labels\"},{\"id\":\"paketo-buildpacks/maven\",\"version\":\"3.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/maven\"},{\"id\":\"paketo-buildpacks/java\",\"version\":\"4.10.0\",\"homepage\":\"https://github.com/paketo-buildpacks/java\"},{\"id\":\"paketo-buildpacks/ca-certificates\",\"version\":\"1.0.1\",\"homepage\":\"https://github.com/paketo-buildpacks/ca-certificates\"},{\"id\":\"paketo-buildpacks/environment-variables\",\"version\":\"2.1.2\",\"homepage\":\"https://github.com/paketo-buildpacks/environment-variables\"},{\"id\":\"paketo-buildpacks/executable-jar\",\"version\":\"3.1.3\",\"homepage\":\"https://github.com/paketo-buildpacks/executable-jar\"},{\"id\":\"paketo-buildpacks/procfile\",\"version\":\"3.0.0\",\"homepage\":\"https://github.com/paketo-buildpacks/procfile\"},{\"id\":\"paketo-buildpacks/apache-tomcat\",\"version\":\"3.2.0\",\"homepage\":\"https://github.com/paketo-buildpacks/apache-tomcat\"},{\"id\":\"paketo-buildpacks/gradle\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/gradle\"},{\"id\":\"paketo-buildpacks/maven\",\"version\":\"3.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/maven\"},{\"id\":\"paketo-buildpacks/sbt\",\"version\":\"3.6.0\",\"homepage\":\"https://github.com/paketo-buildpacks/sbt\"},{\"id\":\"paketo-buildpacks/bellsoft-liberica\",\"version\":\"6.2.0\",\"homepage\":\"https://github.com/paketo-buildpacks/bellsoft-liberica\"},{\"id\":\"paketo-buildpacks/google-stackdriver\",\"version\":\"2.16.0\",\"homepage\":\"https://github.com/paketo-buildpacks/google-stackdriver\"},{\"id\":\"paketo-buildpacks/image-labels\",\"version\":\"2.0.7\",\"homepage\":\"https://github.com/paketo-buildpacks/image-labels\"},{\"id\":\"paketo-buildpacks/dist-zip\",\"version\":\"2.2.2\",\"homepage\":\"https://github.com/paketo-buildpacks/dist-zip\"},{\"id\":\"paketo-buildpacks/spring-boot\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/spring-boot\"},{\"id\":\"paketo-buildpacks/jmx\",\"version\":\"2.1.4\",\"homepage\":\"https://github.com/paketo-buildpacks/jmx\"},{\"id\":\"paketo-buildpacks/leiningen\",\"version\":\"1.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/leiningen\"}],\"stack\":{\"runImage\":{\"image\":\"cloudfoundry/run:base-cnb\",\"mirrors\":null}},\"lifecycle\":{\"version\":\"0.7.2\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.3\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)\"}}", "io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.102\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab\"}},\"org.cloudfoundry.buildsystem\":{\"v1.2.15\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9\"}},\"org.cloudfoundry.debug\":{\"v1.2.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7\"}},\"org.cloudfoundry.dep\":{\"0.0.101\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8\"}},\"org.cloudfoundry.distzip\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.6\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\",\"optional\":true},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"}]}],\"layerDiffID\":\"sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357\"}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.118\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.68\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.115\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.127\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.122\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f\"}},\"org.cloudfoundry.go\":{\"v0.0.4\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"}]}],\"layerDiffID\":\"sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb\"}},\"org.cloudfoundry.go-compiler\":{\"0.0.105\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24\"}},\"org.cloudfoundry.go-mod\":{\"0.0.89\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41\"}},\"org.cloudfoundry.icu\":{\"0.0.43\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347\"}},\"org.cloudfoundry.jdbc\":{\"v1.1.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62\"}},\"org.cloudfoundry.jmx\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22\"}},\"org.cloudfoundry.node-engine\":{\"0.0.158\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339\"},\"0.0.163\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777\"}},\"org.cloudfoundry.nodejs\":{\"v2.0.8\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"}]}],\"layerDiffID\":\"sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109\"}},\"org.cloudfoundry.npm\":{\"0.1.3\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1\"}},\"org.cloudfoundry.openjdk\":{\"v1.2.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151\"}},\"org.cloudfoundry.procfile\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab\"}},\"org.cloudfoundry.springboot\":{\"v1.2.13\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f\"}},\"org.cloudfoundry.tomcat\":{\"v1.3.18\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2\"}},\"org.cloudfoundry.yarn-install\":{\"0.1.10\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7\"}}}", "io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.procfile\"}]}]", "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic", "io.buildpacks.stack.mixins": "[\"build:git\",\"build:build-essential\"]" } }, "Architecture": "arm64", "Os": "linux", "Variant": "v1", "Size": 688884758, "VirtualSize": 688884758, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/6a79181b2840da2706624f46ce5abd4448973b4f951925d5a276b273256063b2/diff:/var/lib/docker/overlay2/429419a203100f60ab16ec6c879fce975c8138422b9053f80accd6124c730fc2/diff:/var/lib/docker/overlay2/6e45ed6daf4f4f3b90fd1ec5fa958775000875661d3e8be3f1af218d192b058d/diff:/var/lib/docker/overlay2/22928ad308cdd55b3fe849d92b6e38c6bc303ba7c9beb8c0e79aa958e16b1864/diff:/var/lib/docker/overlay2/2ca9ec213226a1604f57c8e141d6f1168134a5cb2ccd8f91ee9be5a39036e6bf/diff:/var/lib/docker/overlay2/96ae944fe00ec20cf5b4441b112ebcc9395faaf08108c9ee38c62e1da33af1c8/diff:/var/lib/docker/overlay2/13ee52e300e476e27350c9ac6274dedf26af85c3079b42a41f9dfc92eff57a80/diff:/var/lib/docker/overlay2/223edb4cc62a2ba2b8bda866905a55c4798c6c32e31d22d60e6ed4f3169ce85e/diff:/var/lib/docker/overlay2/a41235cd7277299cb74ead47def3771885948719e24075ea3bf37580f3af7ae2/diff:/var/lib/docker/overlay2/ed0438e8e2c27b9d62ad21a0761237c350a2ffc9e52f47c019e4f627091c832e/diff:/var/lib/docker/overlay2/0c27c8229b31eafc57ab739b44962dcc07b72f3d8950888873ecb3cfd385032f/diff:/var/lib/docker/overlay2/0957cbcca052cd58bcf9a3d945b0e6876b0df79c1c534da1872c3415a019427d/diff:/var/lib/docker/overlay2/b621414d53d71349c07df8ed45e3e04b2e97bfbaf4bf0d86463f46e0f810eeb4/diff:/var/lib/docker/overlay2/ad521bc47f0bb44262358cf47c3d81a544d098494cf24a5b510620d34eb9c353/diff:/var/lib/docker/overlay2/081501d5bfbd927e69c10eb320513c7c0d5f00bea8cf9e55faa90579fd33adf4/diff:/var/lib/docker/overlay2/fb1ba66bee5568f5700c72865d020d4171a62bfdd099c3cc05b9a253d36a35a4/diff:/var/lib/docker/overlay2/06bcc6b3adeca727d554f1a745ee33242dfe1b3c6392023ac947666057303288/diff:/var/lib/docker/overlay2/1c5397d63d893202dffde29013ee826fb695bda26c718ee03ddde376be4da0a3/diff:/var/lib/docker/overlay2/76075fb7fd3c6b3fb116fb3b464e220918e56d94461c61af9a1aff288ebdba60/diff:/var/lib/docker/overlay2/43d1026bb7b618393912ecc9ddf57b604336184d5f8dc70bcf6332b5f08a3e8d/diff:/var/lib/docker/overlay2/ee27d1fba3deaca0556f7bab171cb3368f169011dd132cf335b5308728f6db8f/diff:/var/lib/docker/overlay2/464d3ec8d86ff31dcb5063ea25521368ea8e9c7964f65e15ff5e0e1ecdbe991e/diff:/var/lib/docker/overlay2/a4a80c33c8b78f68bdc9dbd5903cc2ba1d48e78b9a97d43acb018823ece8e6cb/diff:/var/lib/docker/overlay2/6494f2f1693cff8b16d51fa95620eb0bb691a76fb39b5175d953649577791297/diff:/var/lib/docker/overlay2/9d49e146f82eb5fc4fd81613538e9c5f5f95091fbbc8c49729c6c9140ae356de/diff:/var/lib/docker/overlay2/2934818c52bcd017abe000e71342d67fbc9ccb7dbc165ce05e3250e2110229a5/diff:/var/lib/docker/overlay2/651ca06b2bf75e2122855264287fc937f30d2b49229d628909895be7128b4eb6/diff:/var/lib/docker/overlay2/c93bab59be44fa1b66689dc059d26742d00d2e787d06c3236e1f116199c9807e/diff:/var/lib/docker/overlay2/d0a8e2a0c7e0df172f7a8ebe75e2dce371bb6cc65531b06799bc677c5b5e3627/diff:/var/lib/docker/overlay2/7d14bac240e0d7936351e3fac80b7fbe2a209f4de8992091c4f75e41f9627852/diff:/var/lib/docker/overlay2/d6b192ea137a4ae95e309d263ee8c890e35da02aacd9bdcf5adbd4c28a0c0a3f/diff:/var/lib/docker/overlay2/335bfb632ab7723e25fb5dc7b67389e6ec38178ef10bfbf83337501403e61574/diff:/var/lib/docker/overlay2/0293c7e3472da58f51cbdf15fb293ff71e32c1f80f83f00fb09f8941deef5e43/diff:/var/lib/docker/overlay2/55faa8b47bcb0dd29c3836580f451a0461dd499065af9c830beff6e8329ab484/diff:/var/lib/docker/overlay2/afcb6e109c1ba7d71b8a8b7e573d4ce04f22da3fe0ee523359db5cfb95e65bb6/diff:/var/lib/docker/overlay2/b42eefd9bf6629ae9d16e7aba6ba3939d37816aba7a0999f6d639012a3119be1/diff:/var/lib/docker/overlay2/a9832c8f81ee889a622ce4d95d9f4bab2f91d30e18f69bfd7cfc385c781068d4/diff:/var/lib/docker/overlay2/224041c135f13881a98b9e833584bedab81d5650061457f522a1ebd1daa2c77a/diff:/var/lib/docker/overlay2/73dfd4e2075fccb239b3d5e9b33b32b8e410bdc3cd5a620b41346f44cc5c51f7/diff:/var/lib/docker/overlay2/b3924ed7c91730f6714d33c455db888604b59ab093033b3f59ac16ecdd777987/diff:/var/lib/docker/overlay2/e36a32cd0ab20b216a8db1a8a166b17464399e4d587d22504088a7a6ef0a68a4/diff:/var/lib/docker/overlay2/3334e94fe191333b65f571912c0fcfbbf31aeb090a2fb9b4cfdbc32a37c0fe5f/diff", "MergedDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/merged", "UpperDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/diff", "WorkDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853", "sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f", "sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d", "sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75", "sha256:2df36adfe1af661aebb75a0db796b074bb8f861fbc8f98f6f642570692b3b133", "sha256:f499c7d34e01d860492ef1cc34b7d7e1319b3c3c81ee7d23258b21605b5902ca", "sha256:c4bf1d4e5d4adb566b173a0769d247f67c5dd8ff90dfdcebd8c7060f1c06caa9", "sha256:15259abd479904cbe0d8d421e5b05b2e5745e2bf82e62cdd7fb6d3eafbe4168a", "sha256:6aa3691a73805f608e5fce69fb6bc89aec8362f58a6b4be2682515e9cfa3cc1a", "sha256:2d6ad1b66f5660dd860c1fe2d90d26398fcfab4dc1c87c3d5e7c0fc24f8d6fb2", "sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41", "sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f", "sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7", "sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2", "sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb", "sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151", "sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9", "sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22", "sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab", "sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c", "sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469", "sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109", "sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62", "sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33", "sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357", "sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab", "sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df", "sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8", "sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24", "sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add", "sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777", "sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1", "sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7", "sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad", "sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9", "sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d", "sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990", "sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f", "sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347", "sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339", "sha256:6cf0f8f815d5371cf5c04e7ebf76c62467948d693b8343184d1446036980d261", "sha256:7cbffcbb09fc5e9d00372e80990016609c09cc3113429ddc951c4a19b1a5ec72", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-run-image-different-registry.json ================================================ { "Id": "sha256:44cc64492fb6a6d78d3e6d087f380ae6e479aa1b2c79823b32cdacfcc2f3d715", "RepoTags": [ "paketo-buildpacks/cnb:base", "paketo-buildpacks/builder:base-platform-api-0.2" ], "RepoDigests": [ "paketo-buidpacks/cnb@sha256:5b03a853e636b78c44e475bbc514e2b7b140cc41cca8ab907e9753431ae8c0b0" ], "Parent": "", "Comment": "", "Created": "1980-01-01T00:00:01Z", "Container": "", "ContainerConfig": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": null, "Image": "", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null }, "DockerVersion": "", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "1000:1000", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=1000", "CNB_GROUP_ID=1000", "CNB_STACK_ID=io.buildpacks.stacks.bionic" ], "Cmd": [ "/bin/bash" ], "ArgsEscaped": true, "Image": "sha256:2d153261a5e359c632a17377cfb5d1986c27b96c8b6e95334bf80f1029dbd4bb", "Volumes": null, "WorkingDir": "/layers", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.builder.metadata": "{\"description\":\"Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang\",\"buildpacks\":[{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.1.11\"},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.2.13\"},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.2.11\"},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.3.18\"},{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.4\"},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.2.14\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.2.15\"},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.1.11\"},{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.102\"},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v2.0.8\"},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.1.14\"},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.6\"},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"},{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\"},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\"},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\"},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\"},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\"},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\"}],\"stack\":{\"runImage\":{\"image\":\"example.com/custom/run:latest\",\"mirrors\":null}},\"lifecycle\":{\"version\":\"0.7.2\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.3\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)\"}}", "io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.102\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab\"}},\"org.cloudfoundry.buildsystem\":{\"v1.2.15\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9\"}},\"org.cloudfoundry.debug\":{\"v1.2.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7\"}},\"org.cloudfoundry.dep\":{\"0.0.101\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8\"}},\"org.cloudfoundry.distzip\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.6\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\",\"optional\":true},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"}]}],\"layerDiffID\":\"sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357\"}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.118\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.68\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.115\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.127\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.122\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f\"}},\"org.cloudfoundry.go\":{\"v0.0.4\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"}]}],\"layerDiffID\":\"sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb\"}},\"org.cloudfoundry.go-compiler\":{\"0.0.105\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24\"}},\"org.cloudfoundry.go-mod\":{\"0.0.89\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41\"}},\"org.cloudfoundry.icu\":{\"0.0.43\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347\"}},\"org.cloudfoundry.jdbc\":{\"v1.1.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62\"}},\"org.cloudfoundry.jmx\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22\"}},\"org.cloudfoundry.node-engine\":{\"0.0.158\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339\"},\"0.0.163\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777\"}},\"org.cloudfoundry.nodejs\":{\"v2.0.8\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"}]}],\"layerDiffID\":\"sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109\"}},\"org.cloudfoundry.npm\":{\"0.1.3\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1\"}},\"org.cloudfoundry.openjdk\":{\"v1.2.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151\"}},\"org.cloudfoundry.procfile\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab\"}},\"org.cloudfoundry.springboot\":{\"v1.2.13\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f\"}},\"org.cloudfoundry.tomcat\":{\"v1.3.18\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2\"}},\"org.cloudfoundry.yarn-install\":{\"0.1.10\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7\"}}}", "io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.procfile\"}]}]", "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic", "io.buildpacks.stack.mixins": "[\"build:git\",\"build:build-essential\"]" } }, "Architecture": "amd64", "Os": "linux", "Size": 688884758, "VirtualSize": 688884758, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/6a79181b2840da2706624f46ce5abd4448973b4f951925d5a276b273256063b2/diff:/var/lib/docker/overlay2/429419a203100f60ab16ec6c879fce975c8138422b9053f80accd6124c730fc2/diff:/var/lib/docker/overlay2/6e45ed6daf4f4f3b90fd1ec5fa958775000875661d3e8be3f1af218d192b058d/diff:/var/lib/docker/overlay2/22928ad308cdd55b3fe849d92b6e38c6bc303ba7c9beb8c0e79aa958e16b1864/diff:/var/lib/docker/overlay2/2ca9ec213226a1604f57c8e141d6f1168134a5cb2ccd8f91ee9be5a39036e6bf/diff:/var/lib/docker/overlay2/96ae944fe00ec20cf5b4441b112ebcc9395faaf08108c9ee38c62e1da33af1c8/diff:/var/lib/docker/overlay2/13ee52e300e476e27350c9ac6274dedf26af85c3079b42a41f9dfc92eff57a80/diff:/var/lib/docker/overlay2/223edb4cc62a2ba2b8bda866905a55c4798c6c32e31d22d60e6ed4f3169ce85e/diff:/var/lib/docker/overlay2/a41235cd7277299cb74ead47def3771885948719e24075ea3bf37580f3af7ae2/diff:/var/lib/docker/overlay2/ed0438e8e2c27b9d62ad21a0761237c350a2ffc9e52f47c019e4f627091c832e/diff:/var/lib/docker/overlay2/0c27c8229b31eafc57ab739b44962dcc07b72f3d8950888873ecb3cfd385032f/diff:/var/lib/docker/overlay2/0957cbcca052cd58bcf9a3d945b0e6876b0df79c1c534da1872c3415a019427d/diff:/var/lib/docker/overlay2/b621414d53d71349c07df8ed45e3e04b2e97bfbaf4bf0d86463f46e0f810eeb4/diff:/var/lib/docker/overlay2/ad521bc47f0bb44262358cf47c3d81a544d098494cf24a5b510620d34eb9c353/diff:/var/lib/docker/overlay2/081501d5bfbd927e69c10eb320513c7c0d5f00bea8cf9e55faa90579fd33adf4/diff:/var/lib/docker/overlay2/fb1ba66bee5568f5700c72865d020d4171a62bfdd099c3cc05b9a253d36a35a4/diff:/var/lib/docker/overlay2/06bcc6b3adeca727d554f1a745ee33242dfe1b3c6392023ac947666057303288/diff:/var/lib/docker/overlay2/1c5397d63d893202dffde29013ee826fb695bda26c718ee03ddde376be4da0a3/diff:/var/lib/docker/overlay2/76075fb7fd3c6b3fb116fb3b464e220918e56d94461c61af9a1aff288ebdba60/diff:/var/lib/docker/overlay2/43d1026bb7b618393912ecc9ddf57b604336184d5f8dc70bcf6332b5f08a3e8d/diff:/var/lib/docker/overlay2/ee27d1fba3deaca0556f7bab171cb3368f169011dd132cf335b5308728f6db8f/diff:/var/lib/docker/overlay2/464d3ec8d86ff31dcb5063ea25521368ea8e9c7964f65e15ff5e0e1ecdbe991e/diff:/var/lib/docker/overlay2/a4a80c33c8b78f68bdc9dbd5903cc2ba1d48e78b9a97d43acb018823ece8e6cb/diff:/var/lib/docker/overlay2/6494f2f1693cff8b16d51fa95620eb0bb691a76fb39b5175d953649577791297/diff:/var/lib/docker/overlay2/9d49e146f82eb5fc4fd81613538e9c5f5f95091fbbc8c49729c6c9140ae356de/diff:/var/lib/docker/overlay2/2934818c52bcd017abe000e71342d67fbc9ccb7dbc165ce05e3250e2110229a5/diff:/var/lib/docker/overlay2/651ca06b2bf75e2122855264287fc937f30d2b49229d628909895be7128b4eb6/diff:/var/lib/docker/overlay2/c93bab59be44fa1b66689dc059d26742d00d2e787d06c3236e1f116199c9807e/diff:/var/lib/docker/overlay2/d0a8e2a0c7e0df172f7a8ebe75e2dce371bb6cc65531b06799bc677c5b5e3627/diff:/var/lib/docker/overlay2/7d14bac240e0d7936351e3fac80b7fbe2a209f4de8992091c4f75e41f9627852/diff:/var/lib/docker/overlay2/d6b192ea137a4ae95e309d263ee8c890e35da02aacd9bdcf5adbd4c28a0c0a3f/diff:/var/lib/docker/overlay2/335bfb632ab7723e25fb5dc7b67389e6ec38178ef10bfbf83337501403e61574/diff:/var/lib/docker/overlay2/0293c7e3472da58f51cbdf15fb293ff71e32c1f80f83f00fb09f8941deef5e43/diff:/var/lib/docker/overlay2/55faa8b47bcb0dd29c3836580f451a0461dd499065af9c830beff6e8329ab484/diff:/var/lib/docker/overlay2/afcb6e109c1ba7d71b8a8b7e573d4ce04f22da3fe0ee523359db5cfb95e65bb6/diff:/var/lib/docker/overlay2/b42eefd9bf6629ae9d16e7aba6ba3939d37816aba7a0999f6d639012a3119be1/diff:/var/lib/docker/overlay2/a9832c8f81ee889a622ce4d95d9f4bab2f91d30e18f69bfd7cfc385c781068d4/diff:/var/lib/docker/overlay2/224041c135f13881a98b9e833584bedab81d5650061457f522a1ebd1daa2c77a/diff:/var/lib/docker/overlay2/73dfd4e2075fccb239b3d5e9b33b32b8e410bdc3cd5a620b41346f44cc5c51f7/diff:/var/lib/docker/overlay2/b3924ed7c91730f6714d33c455db888604b59ab093033b3f59ac16ecdd777987/diff:/var/lib/docker/overlay2/e36a32cd0ab20b216a8db1a8a166b17464399e4d587d22504088a7a6ef0a68a4/diff:/var/lib/docker/overlay2/3334e94fe191333b65f571912c0fcfbbf31aeb090a2fb9b4cfdbc32a37c0fe5f/diff", "MergedDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/merged", "UpperDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/diff", "WorkDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853", "sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f", "sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d", "sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75", "sha256:2df36adfe1af661aebb75a0db796b074bb8f861fbc8f98f6f642570692b3b133", "sha256:f499c7d34e01d860492ef1cc34b7d7e1319b3c3c81ee7d23258b21605b5902ca", "sha256:c4bf1d4e5d4adb566b173a0769d247f67c5dd8ff90dfdcebd8c7060f1c06caa9", "sha256:15259abd479904cbe0d8d421e5b05b2e5745e2bf82e62cdd7fb6d3eafbe4168a", "sha256:6aa3691a73805f608e5fce69fb6bc89aec8362f58a6b4be2682515e9cfa3cc1a", "sha256:2d6ad1b66f5660dd860c1fe2d90d26398fcfab4dc1c87c3d5e7c0fc24f8d6fb2", "sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41", "sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f", "sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7", "sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2", "sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb", "sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151", "sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9", "sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22", "sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab", "sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c", "sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469", "sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109", "sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62", "sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33", "sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357", "sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab", "sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df", "sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8", "sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24", "sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add", "sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777", "sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1", "sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7", "sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad", "sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9", "sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d", "sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990", "sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f", "sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347", "sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339", "sha256:6cf0f8f815d5371cf5c04e7ebf76c62467948d693b8343184d1446036980d261", "sha256:7cbffcbb09fc5e9d00372e80990016609c09cc3113429ddc951c4a19b1a5ec72", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-run-image-digest.json ================================================ { "Id": "sha256:44cc64492fb6a6d78d3e6d087f380ae6e479aa1b2c79823b32cdacfcc2f3d715", "RepoTags": [ "paketo-buildpacks/cnb:base", "paketo-buildpacks/builder:base-platform-api-0.2" ], "RepoDigests": [ "paketo-buidpacks/cnb@sha256:5b03a853e636b78c44e475bbc514e2b7b140cc41cca8ab907e9753431ae8c0b0" ], "Parent": "", "Comment": "", "Created": "1980-01-01T00:00:01Z", "Container": "", "ContainerConfig": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": null, "Image": "", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null }, "DockerVersion": "", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "1000:1000", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=1000", "CNB_GROUP_ID=1000", "CNB_STACK_ID=io.buildpacks.stacks.bionic" ], "Cmd": [ "/bin/bash" ], "ArgsEscaped": true, "Image": "sha256:2d153261a5e359c632a17377cfb5d1986c27b96c8b6e95334bf80f1029dbd4bb", "Volumes": null, "WorkingDir": "/layers", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.builder.metadata": "{\"description\":\"Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang\",\"buildpacks\":[{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.1.11\"},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.2.13\"},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.2.11\"},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.3.18\"},{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.4\"},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.2.14\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.2.15\"},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.1.11\"},{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.102\"},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v2.0.8\"},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.1.14\"},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.6\"},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"},{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\"},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\"},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\"},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\"},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\"},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\"}],\"stack\":{\"runImage\":{\"image\":\"cloudfoundry/run@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d\",\"mirrors\":null}},\"lifecycle\":{\"version\":\"0.7.2\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.3\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)\"}}", "io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.102\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab\"}},\"org.cloudfoundry.buildsystem\":{\"v1.2.15\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9\"}},\"org.cloudfoundry.debug\":{\"v1.2.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7\"}},\"org.cloudfoundry.dep\":{\"0.0.101\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8\"}},\"org.cloudfoundry.distzip\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.6\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\",\"optional\":true},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"}]}],\"layerDiffID\":\"sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357\"}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.118\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.68\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.115\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.127\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.122\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f\"}},\"org.cloudfoundry.go\":{\"v0.0.4\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"}]}],\"layerDiffID\":\"sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb\"}},\"org.cloudfoundry.go-compiler\":{\"0.0.105\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24\"}},\"org.cloudfoundry.go-mod\":{\"0.0.89\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41\"}},\"org.cloudfoundry.icu\":{\"0.0.43\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347\"}},\"org.cloudfoundry.jdbc\":{\"v1.1.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62\"}},\"org.cloudfoundry.jmx\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22\"}},\"org.cloudfoundry.node-engine\":{\"0.0.158\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339\"},\"0.0.163\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777\"}},\"org.cloudfoundry.nodejs\":{\"v2.0.8\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"}]}],\"layerDiffID\":\"sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109\"}},\"org.cloudfoundry.npm\":{\"0.1.3\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1\"}},\"org.cloudfoundry.openjdk\":{\"v1.2.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151\"}},\"org.cloudfoundry.procfile\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab\"}},\"org.cloudfoundry.springboot\":{\"v1.2.13\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f\"}},\"org.cloudfoundry.tomcat\":{\"v1.3.18\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2\"}},\"org.cloudfoundry.yarn-install\":{\"0.1.10\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7\"}}}", "io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.procfile\"}]}]", "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic", "io.buildpacks.stack.mixins": "[\"build:git\",\"build:build-essential\"]" } }, "Architecture": "amd64", "Os": "linux", "Size": 688884758, "VirtualSize": 688884758, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/6a79181b2840da2706624f46ce5abd4448973b4f951925d5a276b273256063b2/diff:/var/lib/docker/overlay2/429419a203100f60ab16ec6c879fce975c8138422b9053f80accd6124c730fc2/diff:/var/lib/docker/overlay2/6e45ed6daf4f4f3b90fd1ec5fa958775000875661d3e8be3f1af218d192b058d/diff:/var/lib/docker/overlay2/22928ad308cdd55b3fe849d92b6e38c6bc303ba7c9beb8c0e79aa958e16b1864/diff:/var/lib/docker/overlay2/2ca9ec213226a1604f57c8e141d6f1168134a5cb2ccd8f91ee9be5a39036e6bf/diff:/var/lib/docker/overlay2/96ae944fe00ec20cf5b4441b112ebcc9395faaf08108c9ee38c62e1da33af1c8/diff:/var/lib/docker/overlay2/13ee52e300e476e27350c9ac6274dedf26af85c3079b42a41f9dfc92eff57a80/diff:/var/lib/docker/overlay2/223edb4cc62a2ba2b8bda866905a55c4798c6c32e31d22d60e6ed4f3169ce85e/diff:/var/lib/docker/overlay2/a41235cd7277299cb74ead47def3771885948719e24075ea3bf37580f3af7ae2/diff:/var/lib/docker/overlay2/ed0438e8e2c27b9d62ad21a0761237c350a2ffc9e52f47c019e4f627091c832e/diff:/var/lib/docker/overlay2/0c27c8229b31eafc57ab739b44962dcc07b72f3d8950888873ecb3cfd385032f/diff:/var/lib/docker/overlay2/0957cbcca052cd58bcf9a3d945b0e6876b0df79c1c534da1872c3415a019427d/diff:/var/lib/docker/overlay2/b621414d53d71349c07df8ed45e3e04b2e97bfbaf4bf0d86463f46e0f810eeb4/diff:/var/lib/docker/overlay2/ad521bc47f0bb44262358cf47c3d81a544d098494cf24a5b510620d34eb9c353/diff:/var/lib/docker/overlay2/081501d5bfbd927e69c10eb320513c7c0d5f00bea8cf9e55faa90579fd33adf4/diff:/var/lib/docker/overlay2/fb1ba66bee5568f5700c72865d020d4171a62bfdd099c3cc05b9a253d36a35a4/diff:/var/lib/docker/overlay2/06bcc6b3adeca727d554f1a745ee33242dfe1b3c6392023ac947666057303288/diff:/var/lib/docker/overlay2/1c5397d63d893202dffde29013ee826fb695bda26c718ee03ddde376be4da0a3/diff:/var/lib/docker/overlay2/76075fb7fd3c6b3fb116fb3b464e220918e56d94461c61af9a1aff288ebdba60/diff:/var/lib/docker/overlay2/43d1026bb7b618393912ecc9ddf57b604336184d5f8dc70bcf6332b5f08a3e8d/diff:/var/lib/docker/overlay2/ee27d1fba3deaca0556f7bab171cb3368f169011dd132cf335b5308728f6db8f/diff:/var/lib/docker/overlay2/464d3ec8d86ff31dcb5063ea25521368ea8e9c7964f65e15ff5e0e1ecdbe991e/diff:/var/lib/docker/overlay2/a4a80c33c8b78f68bdc9dbd5903cc2ba1d48e78b9a97d43acb018823ece8e6cb/diff:/var/lib/docker/overlay2/6494f2f1693cff8b16d51fa95620eb0bb691a76fb39b5175d953649577791297/diff:/var/lib/docker/overlay2/9d49e146f82eb5fc4fd81613538e9c5f5f95091fbbc8c49729c6c9140ae356de/diff:/var/lib/docker/overlay2/2934818c52bcd017abe000e71342d67fbc9ccb7dbc165ce05e3250e2110229a5/diff:/var/lib/docker/overlay2/651ca06b2bf75e2122855264287fc937f30d2b49229d628909895be7128b4eb6/diff:/var/lib/docker/overlay2/c93bab59be44fa1b66689dc059d26742d00d2e787d06c3236e1f116199c9807e/diff:/var/lib/docker/overlay2/d0a8e2a0c7e0df172f7a8ebe75e2dce371bb6cc65531b06799bc677c5b5e3627/diff:/var/lib/docker/overlay2/7d14bac240e0d7936351e3fac80b7fbe2a209f4de8992091c4f75e41f9627852/diff:/var/lib/docker/overlay2/d6b192ea137a4ae95e309d263ee8c890e35da02aacd9bdcf5adbd4c28a0c0a3f/diff:/var/lib/docker/overlay2/335bfb632ab7723e25fb5dc7b67389e6ec38178ef10bfbf83337501403e61574/diff:/var/lib/docker/overlay2/0293c7e3472da58f51cbdf15fb293ff71e32c1f80f83f00fb09f8941deef5e43/diff:/var/lib/docker/overlay2/55faa8b47bcb0dd29c3836580f451a0461dd499065af9c830beff6e8329ab484/diff:/var/lib/docker/overlay2/afcb6e109c1ba7d71b8a8b7e573d4ce04f22da3fe0ee523359db5cfb95e65bb6/diff:/var/lib/docker/overlay2/b42eefd9bf6629ae9d16e7aba6ba3939d37816aba7a0999f6d639012a3119be1/diff:/var/lib/docker/overlay2/a9832c8f81ee889a622ce4d95d9f4bab2f91d30e18f69bfd7cfc385c781068d4/diff:/var/lib/docker/overlay2/224041c135f13881a98b9e833584bedab81d5650061457f522a1ebd1daa2c77a/diff:/var/lib/docker/overlay2/73dfd4e2075fccb239b3d5e9b33b32b8e410bdc3cd5a620b41346f44cc5c51f7/diff:/var/lib/docker/overlay2/b3924ed7c91730f6714d33c455db888604b59ab093033b3f59ac16ecdd777987/diff:/var/lib/docker/overlay2/e36a32cd0ab20b216a8db1a8a166b17464399e4d587d22504088a7a6ef0a68a4/diff:/var/lib/docker/overlay2/3334e94fe191333b65f571912c0fcfbbf31aeb090a2fb9b4cfdbc32a37c0fe5f/diff", "MergedDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/merged", "UpperDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/diff", "WorkDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853", "sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f", "sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d", "sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75", "sha256:2df36adfe1af661aebb75a0db796b074bb8f861fbc8f98f6f642570692b3b133", "sha256:f499c7d34e01d860492ef1cc34b7d7e1319b3c3c81ee7d23258b21605b5902ca", "sha256:c4bf1d4e5d4adb566b173a0769d247f67c5dd8ff90dfdcebd8c7060f1c06caa9", "sha256:15259abd479904cbe0d8d421e5b05b2e5745e2bf82e62cdd7fb6d3eafbe4168a", "sha256:6aa3691a73805f608e5fce69fb6bc89aec8362f58a6b4be2682515e9cfa3cc1a", "sha256:2d6ad1b66f5660dd860c1fe2d90d26398fcfab4dc1c87c3d5e7c0fc24f8d6fb2", "sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41", "sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f", "sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7", "sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2", "sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb", "sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151", "sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9", "sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22", "sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab", "sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c", "sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469", "sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109", "sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62", "sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33", "sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357", "sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab", "sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df", "sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8", "sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24", "sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add", "sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777", "sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1", "sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7", "sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad", "sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9", "sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d", "sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990", "sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f", "sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347", "sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339", "sha256:6cf0f8f815d5371cf5c04e7ebf76c62467948d693b8343184d1446036980d261", "sha256:7cbffcbb09fc5e9d00372e80990016609c09cc3113429ddc951c4a19b1a5ec72", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image.json ================================================ { "Id": "sha256:44cc64492fb6a6d78d3e6d087f380ae6e479aa1b2c79823b32cdacfcc2f3d715", "RepoTags": [ "paketo-buildpacks/cnb:base", "paketo-buildpacks/builder:base-platform-api-0.2" ], "RepoDigests": [ "paketo-buidpacks/cnb@sha256:5b03a853e636b78c44e475bbc514e2b7b140cc41cca8ab907e9753431ae8c0b0" ], "Parent": "", "Comment": "", "Created": "1980-01-01T00:00:01Z", "Container": "", "ContainerConfig": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": null, "Image": "", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null }, "DockerVersion": "", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "1000:1000", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=1000", "CNB_GROUP_ID=1000", "CNB_STACK_ID=io.buildpacks.stacks.bionic" ], "Cmd": [ "/bin/bash" ], "ArgsEscaped": true, "Image": "sha256:2d153261a5e359c632a17377cfb5d1986c27b96c8b6e95334bf80f1029dbd4bb", "Volumes": null, "WorkingDir": "/layers", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.builder.metadata": "{\"description\":\"Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang\",\"buildpacks\":[{\"id\":\"paketo-buildpacks/dotnet-core\",\"version\":\"0.0.9\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core\"},{\"id\":\"paketo-buildpacks/dotnet-core-runtime\",\"version\":\"0.0.201\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core-runtime\"},{\"id\":\"paketo-buildpacks/dotnet-core-sdk\",\"version\":\"0.0.196\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core-sdk\"},{\"id\":\"paketo-buildpacks/dotnet-execute\",\"version\":\"0.0.180\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-execute\"},{\"id\":\"paketo-buildpacks/dotnet-publish\",\"version\":\"0.0.121\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-publish\"},{\"id\":\"paketo-buildpacks/dotnet-core-aspnet\",\"version\":\"0.0.196\",\"homepage\":\"https://github.com/paketo-buildpacks/dotnet-core-aspnet\"},{\"id\":\"paketo-buildpacks/java-native-image\",\"version\":\"4.7.0\",\"homepage\":\"https://github.com/paketo-buildpacks/java-native-image\"},{\"id\":\"paketo-buildpacks/spring-boot\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/spring-boot\"},{\"id\":\"paketo-buildpacks/executable-jar\",\"version\":\"3.1.3\",\"homepage\":\"https://github.com/paketo-buildpacks/executable-jar\"},{\"id\":\"paketo-buildpacks/graalvm\",\"version\":\"4.1.0\",\"homepage\":\"https://github.com/paketo-buildpacks/graalvm\"},{\"id\":\"paketo-buildpacks/gradle\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/gradle\"},{\"id\":\"paketo-buildpacks/leiningen\",\"version\":\"1.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/leiningen\"},{\"id\":\"paketo-buildpacks/sbt\",\"version\":\"3.6.0\",\"homepage\":\"https://github.com/paketo-buildpacks/sbt\"},{\"id\":\"paketo-buildpacks/spring-boot-native-image\",\"version\":\"2.0.1\",\"homepage\":\"https://github.com/paketo-buildpacks/spring-boot-native-image\"},{\"id\":\"paketo-buildpacks/environment-variables\",\"version\":\"2.1.2\",\"homepage\":\"https://github.com/paketo-buildpacks/environment-variables\"},{\"id\":\"paketo-buildpacks/image-labels\",\"version\":\"2.0.7\",\"homepage\":\"https://github.com/paketo-buildpacks/image-labels\"},{\"id\":\"paketo-buildpacks/maven\",\"version\":\"3.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/maven\"},{\"id\":\"paketo-buildpacks/java\",\"version\":\"4.10.0\",\"homepage\":\"https://github.com/paketo-buildpacks/java\"},{\"id\":\"paketo-buildpacks/ca-certificates\",\"version\":\"1.0.1\",\"homepage\":\"https://github.com/paketo-buildpacks/ca-certificates\"},{\"id\":\"paketo-buildpacks/environment-variables\",\"version\":\"2.1.2\",\"homepage\":\"https://github.com/paketo-buildpacks/environment-variables\"},{\"id\":\"paketo-buildpacks/executable-jar\",\"version\":\"3.1.3\",\"homepage\":\"https://github.com/paketo-buildpacks/executable-jar\"},{\"id\":\"paketo-buildpacks/procfile\",\"version\":\"3.0.0\",\"homepage\":\"https://github.com/paketo-buildpacks/procfile\"},{\"id\":\"paketo-buildpacks/apache-tomcat\",\"version\":\"3.2.0\",\"homepage\":\"https://github.com/paketo-buildpacks/apache-tomcat\"},{\"id\":\"paketo-buildpacks/gradle\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/gradle\"},{\"id\":\"paketo-buildpacks/maven\",\"version\":\"3.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/maven\"},{\"id\":\"paketo-buildpacks/sbt\",\"version\":\"3.6.0\",\"homepage\":\"https://github.com/paketo-buildpacks/sbt\"},{\"id\":\"paketo-buildpacks/bellsoft-liberica\",\"version\":\"6.2.0\",\"homepage\":\"https://github.com/paketo-buildpacks/bellsoft-liberica\"},{\"id\":\"paketo-buildpacks/google-stackdriver\",\"version\":\"2.16.0\",\"homepage\":\"https://github.com/paketo-buildpacks/google-stackdriver\"},{\"id\":\"paketo-buildpacks/image-labels\",\"version\":\"2.0.7\",\"homepage\":\"https://github.com/paketo-buildpacks/image-labels\"},{\"id\":\"paketo-buildpacks/dist-zip\",\"version\":\"2.2.2\",\"homepage\":\"https://github.com/paketo-buildpacks/dist-zip\"},{\"id\":\"paketo-buildpacks/spring-boot\",\"version\":\"3.5.0\",\"homepage\":\"https://github.com/paketo-buildpacks/spring-boot\"},{\"id\":\"paketo-buildpacks/jmx\",\"version\":\"2.1.4\",\"homepage\":\"https://github.com/paketo-buildpacks/jmx\"},{\"id\":\"paketo-buildpacks/leiningen\",\"version\":\"1.2.1\",\"homepage\":\"https://github.com/paketo-buildpacks/leiningen\"}],\"stack\":{\"runImage\":{\"image\":\"cloudfoundry/run:base-cnb\",\"mirrors\":null}},\"lifecycle\":{\"version\":\"0.7.2\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.3\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)\"}}", "io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.102\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab\"}},\"org.cloudfoundry.buildsystem\":{\"v1.2.15\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9\"}},\"org.cloudfoundry.debug\":{\"v1.2.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7\"}},\"org.cloudfoundry.dep\":{\"0.0.101\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8\"}},\"org.cloudfoundry.distzip\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.6\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\",\"optional\":true},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"}]}],\"layerDiffID\":\"sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357\"}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.118\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.68\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.115\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.127\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.122\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f\"}},\"org.cloudfoundry.go\":{\"v0.0.4\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"}]}],\"layerDiffID\":\"sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb\"}},\"org.cloudfoundry.go-compiler\":{\"0.0.105\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24\"}},\"org.cloudfoundry.go-mod\":{\"0.0.89\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41\"}},\"org.cloudfoundry.icu\":{\"0.0.43\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347\"}},\"org.cloudfoundry.jdbc\":{\"v1.1.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62\"}},\"org.cloudfoundry.jmx\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22\"}},\"org.cloudfoundry.node-engine\":{\"0.0.158\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339\"},\"0.0.163\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777\"}},\"org.cloudfoundry.nodejs\":{\"v2.0.8\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"}]}],\"layerDiffID\":\"sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109\"}},\"org.cloudfoundry.npm\":{\"0.1.3\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1\"}},\"org.cloudfoundry.openjdk\":{\"v1.2.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151\"}},\"org.cloudfoundry.procfile\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab\"}},\"org.cloudfoundry.springboot\":{\"v1.2.13\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f\"}},\"org.cloudfoundry.tomcat\":{\"v1.3.18\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2\"}},\"org.cloudfoundry.yarn-install\":{\"0.1.10\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7\"}}}", "io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.procfile\"}]}]", "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic", "io.buildpacks.stack.mixins": "[\"build:git\",\"build:build-essential\"]" } }, "Architecture": "amd64", "Os": "linux", "Size": 688884758, "VirtualSize": 688884758, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/6a79181b2840da2706624f46ce5abd4448973b4f951925d5a276b273256063b2/diff:/var/lib/docker/overlay2/429419a203100f60ab16ec6c879fce975c8138422b9053f80accd6124c730fc2/diff:/var/lib/docker/overlay2/6e45ed6daf4f4f3b90fd1ec5fa958775000875661d3e8be3f1af218d192b058d/diff:/var/lib/docker/overlay2/22928ad308cdd55b3fe849d92b6e38c6bc303ba7c9beb8c0e79aa958e16b1864/diff:/var/lib/docker/overlay2/2ca9ec213226a1604f57c8e141d6f1168134a5cb2ccd8f91ee9be5a39036e6bf/diff:/var/lib/docker/overlay2/96ae944fe00ec20cf5b4441b112ebcc9395faaf08108c9ee38c62e1da33af1c8/diff:/var/lib/docker/overlay2/13ee52e300e476e27350c9ac6274dedf26af85c3079b42a41f9dfc92eff57a80/diff:/var/lib/docker/overlay2/223edb4cc62a2ba2b8bda866905a55c4798c6c32e31d22d60e6ed4f3169ce85e/diff:/var/lib/docker/overlay2/a41235cd7277299cb74ead47def3771885948719e24075ea3bf37580f3af7ae2/diff:/var/lib/docker/overlay2/ed0438e8e2c27b9d62ad21a0761237c350a2ffc9e52f47c019e4f627091c832e/diff:/var/lib/docker/overlay2/0c27c8229b31eafc57ab739b44962dcc07b72f3d8950888873ecb3cfd385032f/diff:/var/lib/docker/overlay2/0957cbcca052cd58bcf9a3d945b0e6876b0df79c1c534da1872c3415a019427d/diff:/var/lib/docker/overlay2/b621414d53d71349c07df8ed45e3e04b2e97bfbaf4bf0d86463f46e0f810eeb4/diff:/var/lib/docker/overlay2/ad521bc47f0bb44262358cf47c3d81a544d098494cf24a5b510620d34eb9c353/diff:/var/lib/docker/overlay2/081501d5bfbd927e69c10eb320513c7c0d5f00bea8cf9e55faa90579fd33adf4/diff:/var/lib/docker/overlay2/fb1ba66bee5568f5700c72865d020d4171a62bfdd099c3cc05b9a253d36a35a4/diff:/var/lib/docker/overlay2/06bcc6b3adeca727d554f1a745ee33242dfe1b3c6392023ac947666057303288/diff:/var/lib/docker/overlay2/1c5397d63d893202dffde29013ee826fb695bda26c718ee03ddde376be4da0a3/diff:/var/lib/docker/overlay2/76075fb7fd3c6b3fb116fb3b464e220918e56d94461c61af9a1aff288ebdba60/diff:/var/lib/docker/overlay2/43d1026bb7b618393912ecc9ddf57b604336184d5f8dc70bcf6332b5f08a3e8d/diff:/var/lib/docker/overlay2/ee27d1fba3deaca0556f7bab171cb3368f169011dd132cf335b5308728f6db8f/diff:/var/lib/docker/overlay2/464d3ec8d86ff31dcb5063ea25521368ea8e9c7964f65e15ff5e0e1ecdbe991e/diff:/var/lib/docker/overlay2/a4a80c33c8b78f68bdc9dbd5903cc2ba1d48e78b9a97d43acb018823ece8e6cb/diff:/var/lib/docker/overlay2/6494f2f1693cff8b16d51fa95620eb0bb691a76fb39b5175d953649577791297/diff:/var/lib/docker/overlay2/9d49e146f82eb5fc4fd81613538e9c5f5f95091fbbc8c49729c6c9140ae356de/diff:/var/lib/docker/overlay2/2934818c52bcd017abe000e71342d67fbc9ccb7dbc165ce05e3250e2110229a5/diff:/var/lib/docker/overlay2/651ca06b2bf75e2122855264287fc937f30d2b49229d628909895be7128b4eb6/diff:/var/lib/docker/overlay2/c93bab59be44fa1b66689dc059d26742d00d2e787d06c3236e1f116199c9807e/diff:/var/lib/docker/overlay2/d0a8e2a0c7e0df172f7a8ebe75e2dce371bb6cc65531b06799bc677c5b5e3627/diff:/var/lib/docker/overlay2/7d14bac240e0d7936351e3fac80b7fbe2a209f4de8992091c4f75e41f9627852/diff:/var/lib/docker/overlay2/d6b192ea137a4ae95e309d263ee8c890e35da02aacd9bdcf5adbd4c28a0c0a3f/diff:/var/lib/docker/overlay2/335bfb632ab7723e25fb5dc7b67389e6ec38178ef10bfbf83337501403e61574/diff:/var/lib/docker/overlay2/0293c7e3472da58f51cbdf15fb293ff71e32c1f80f83f00fb09f8941deef5e43/diff:/var/lib/docker/overlay2/55faa8b47bcb0dd29c3836580f451a0461dd499065af9c830beff6e8329ab484/diff:/var/lib/docker/overlay2/afcb6e109c1ba7d71b8a8b7e573d4ce04f22da3fe0ee523359db5cfb95e65bb6/diff:/var/lib/docker/overlay2/b42eefd9bf6629ae9d16e7aba6ba3939d37816aba7a0999f6d639012a3119be1/diff:/var/lib/docker/overlay2/a9832c8f81ee889a622ce4d95d9f4bab2f91d30e18f69bfd7cfc385c781068d4/diff:/var/lib/docker/overlay2/224041c135f13881a98b9e833584bedab81d5650061457f522a1ebd1daa2c77a/diff:/var/lib/docker/overlay2/73dfd4e2075fccb239b3d5e9b33b32b8e410bdc3cd5a620b41346f44cc5c51f7/diff:/var/lib/docker/overlay2/b3924ed7c91730f6714d33c455db888604b59ab093033b3f59ac16ecdd777987/diff:/var/lib/docker/overlay2/e36a32cd0ab20b216a8db1a8a166b17464399e4d587d22504088a7a6ef0a68a4/diff:/var/lib/docker/overlay2/3334e94fe191333b65f571912c0fcfbbf31aeb090a2fb9b4cfdbc32a37c0fe5f/diff", "MergedDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/merged", "UpperDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/diff", "WorkDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853", "sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f", "sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d", "sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75", "sha256:2df36adfe1af661aebb75a0db796b074bb8f861fbc8f98f6f642570692b3b133", "sha256:f499c7d34e01d860492ef1cc34b7d7e1319b3c3c81ee7d23258b21605b5902ca", "sha256:c4bf1d4e5d4adb566b173a0769d247f67c5dd8ff90dfdcebd8c7060f1c06caa9", "sha256:15259abd479904cbe0d8d421e5b05b2e5745e2bf82e62cdd7fb6d3eafbe4168a", "sha256:6aa3691a73805f608e5fce69fb6bc89aec8362f58a6b4be2682515e9cfa3cc1a", "sha256:2d6ad1b66f5660dd860c1fe2d90d26398fcfab4dc1c87c3d5e7c0fc24f8d6fb2", "sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41", "sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f", "sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7", "sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2", "sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb", "sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151", "sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9", "sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22", "sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab", "sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c", "sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469", "sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109", "sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62", "sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33", "sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357", "sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab", "sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df", "sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8", "sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24", "sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add", "sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777", "sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1", "sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7", "sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad", "sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9", "sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d", "sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990", "sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f", "sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347", "sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339", "sha256:6cf0f8f815d5371cf5c04e7ebf76c62467948d693b8343184d1446036980d261", "sha256:7cbffcbb09fc5e9d00372e80990016609c09cc3113429ddc951c4a19b1a5ec72", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-cache-bind-mounts.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/analyzer", "-daemon", "-launch-cache", "/launch-cache", "-layers", "/layers", "-run-image", "docker.io/cloudfoundry/run:latest", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "/tmp/launch-cache:/launch-cache", "/tmp/work-layers:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-cache-volumes.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/analyzer", "-daemon", "-launch-cache", "/launch-cache", "-layers", "/layers", "-run-image", "docker.io/cloudfoundry/run:latest", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "launch-volume:/launch-cache", "work-volume-layers:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-inherit-local.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/analyzer", "-daemon", "-launch-cache", "/launch-cache", "-layers", "/layers", "-run-image", "docker.io/cloudfoundry/run:latest", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/alt.sock:/var/run/docker.sock", "pack-cache-b35197ac41ea.launch:/launch-cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-inherit-remote.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/analyzer", "-daemon", "-launch-cache", "/launch-cache", "-layers", "/layers", "-run-image", "docker.io/cloudfoundry/run:latest", "docker.io/library/my-application:latest" ], "Env": [ "DOCKER_HOST=tcp://192.168.1.2:2376", "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-cache-b35197ac41ea.launch:/launch-cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer-security-opts.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/analyzer", "-daemon", "-launch-cache", "/launch-cache", "-layers", "/layers", "-run-image", "docker.io/cloudfoundry/run:latest", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "pack-cache-b35197ac41ea.launch:/launch-cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=user:USER", "label=role:ROLE" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-analyzer.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/analyzer", "-daemon", "-launch-cache", "/launch-cache", "-layers", "/layers", "-run-image", "docker.io/cloudfoundry/run:latest", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "pack-cache-b35197ac41ea.launch:/launch-cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder-app-dir.json ================================================ { "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/builder", "-app", "/application", "-layers", "/layers", "-platform", "/platform" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/application", "pack-layers-aaaaaaaaaa:/layers" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder-cache-bind-mounts.json ================================================ { "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/builder", "-app", "/workspace", "-layers", "/layers", "-platform", "/platform" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/tmp/work-app:/workspace", "/tmp/work-layers:/layers" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder-cache-volumes.json ================================================ { "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/builder", "-app", "/workspace", "-layers", "/layers", "-platform", "/platform" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "work-volume-app:/workspace", "work-volume-layers:/layers" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-builder.json ================================================ { "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/builder", "-app", "/workspace", "-layers", "/layers", "-platform", "/platform" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/workspace", "pack-layers-aaaaaaaaaa:/layers" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-app-dir.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/creator", "-app", "/application", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/application", "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-bindings.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/workspace", "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "/var/run/docker.sock:/var/run/docker.sock", "/host/src/path:/container/dest/path:ro", "volume-name:/container/volume/path:rw" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-cache-bind-mounts.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/tmp/work-app:/workspace", "/tmp/work-layers:/layers", "/tmp/build-cache:/cache", "/tmp/launch-cache:/launch-cache", "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-cache-volumes.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "work-volume-app:/workspace", "work-volume-layers:/layers", "build-volume:/cache", "launch-volume:/launch-cache", "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-clean-cache.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "-skip-restore", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/workspace", "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-created-date.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8", "SOURCE_DATE_EPOCH=1593606896" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/workspace", "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-local.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/workspace", "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "/var/alt.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-remote.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], "Env": [ "DOCKER_HOST=tcp://192.168.1.2:2376", "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/workspace", "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-network.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "NetworkMode": "test", "Binds": [ "pack-app-aaaaaaaaaa:/workspace", "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-platform-api-0.3.json ================================================ { "User" : "root", "Image" : "pack.local/ephemeral-builder", "Cmd" : [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], "Env" : [ "CNB_PLATFORM_API=0.3" ], "Labels" : { "author" : "spring-boot" }, "HostConfig" : { "Binds" : [ "pack-app-aaaaaaaaaa:/workspace", "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-security-opts.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/workspace", "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=user:USER", "label=role:ROLE" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/workspace", "pack-layers-aaaaaaaaaa:/layers", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "/var/run/docker.sock:/var/run/docker.sock" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector-app-dir.json ================================================ { "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/detector", "-app", "/application", "-layers", "/layers", "-platform", "/platform" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/application", "pack-layers-aaaaaaaaaa:/layers" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector-cache-bind-mounts.json ================================================ { "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/detector", "-app", "/workspace", "-layers", "/layers", "-platform", "/platform" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/tmp/work-app:/workspace", "/tmp/work-layers:/layers" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector-cache-volumes.json ================================================ { "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/detector", "-app", "/workspace", "-layers", "/layers", "-platform", "/platform" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "work-volume-app:/workspace", "work-volume-layers:/layers" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-detector.json ================================================ { "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/detector", "-app", "/workspace", "-layers", "/layers", "-platform", "/platform" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/workspace", "pack-layers-aaaaaaaaaa:/layers" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-app-dir.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/exporter", "-daemon", "-app", "/application", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-layers", "/layers", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "pack-app-aaaaaaaaaa:/application", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-cache-bind-mounts.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/exporter", "-daemon", "-app", "/workspace", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-layers", "/layers", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "/tmp/work-app:/workspace", "/tmp/build-cache:/cache", "/tmp/launch-cache:/launch-cache", "/tmp/work-layers:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-cache-volumes.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/exporter", "-daemon", "-app", "/workspace", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-layers", "/layers", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "work-volume-app:/workspace", "build-volume:/cache", "launch-volume:/launch-cache", "work-volume-layers:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-created-date.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/exporter", "-daemon", "-app", "/workspace", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-layers", "/layers", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8", "SOURCE_DATE_EPOCH=1593606896" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-inherit-local.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/exporter", "-daemon", "-app", "/workspace", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-layers", "/layers", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/alt.sock:/var/run/docker.sock", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-inherit-remote.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/exporter", "-daemon", "-app", "/workspace", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-layers", "/layers", "docker.io/library/my-application:latest" ], "Env": [ "DOCKER_HOST=tcp://192.168.1.2:2376", "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter-security-opts.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/exporter", "-daemon", "-app", "/workspace", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-layers", "/layers", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=user:USER", "label=role:ROLE" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-exporter.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/exporter", "-daemon", "-app", "/workspace", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-layers", "/layers", "docker.io/library/my-application:latest" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-cache-bind-mounts.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/restorer", "-daemon", "-cache-dir", "/cache", "-layers", "/layers" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "/tmp/build-cache:/cache", "/tmp/work-layers:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-cache-volumes.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/restorer", "-daemon", "-cache-dir", "/cache", "-layers", "/layers" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "build-volume:/cache", "work-volume-layers:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-inherit-local.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/restorer", "-daemon", "-cache-dir", "/cache", "-layers", "/layers" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/alt.sock:/var/run/docker.sock", "pack-cache-b35197ac41ea.build:/cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-inherit-remote.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/restorer", "-daemon", "-cache-dir", "/cache", "-layers", "/layers" ], "Env": [ "DOCKER_HOST=tcp://192.168.1.2:2376", "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "pack-cache-b35197ac41ea.build:/cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer-security-opts.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/restorer", "-daemon", "-cache-dir", "/cache", "-layers", "/layers" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "pack-cache-b35197ac41ea.build:/cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=user:USER", "label=role:ROLE" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-restorer.json ================================================ { "User": "root", "Image": "pack.local/ephemeral-builder", "Cmd": [ "/cnb/lifecycle/restorer", "-daemon", "-cache-dir", "/cache", "-layers", "/layers" ], "Env": [ "CNB_PLATFORM_API=0.8" ], "Labels": { "author": "spring-boot" }, "HostConfig": { "Binds": [ "/var/run/docker.sock:/var/run/docker.sock", "pack-cache-b35197ac41ea.build:/cache", "pack-layers-aaaaaaaaaa:/layers" ], "SecurityOpt" : [ "label=disable" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/order.toml ================================================ [[order]] [[order.group]] id = "example/buildpack1" version = "0.0.1" [[order.group]] id = "example/buildpack2" version = "0.0.2" [[order.group]] id = "example/buildpack3" version = "0.0.3" ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/print-stream-build-log.txt ================================================ Building image 'docker.io/library/my-app:latest' > Pulling builder image 'docker.io/cnb/builder' .................................................. > Pulled builder image '00000001' > Pulling run image 'docker.io/cnb/runner' for platform 'linux/arm64/v1' .................................................. > Pulled run image '00000002' > Executing lifecycle version v0.5.0 > Using build cache volume 'pack-abc.cache' > Running alphabet [alphabet] one [alphabet] two [alphabet] three > Running basket [basket] spring [basket] boot Successfully built image 'docker.io/library/my-app:latest' Successfully created image tag 'docker.io/library/my-app:1.0' ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/run-image-with-bad-stack.json ================================================ { "Id": "sha256:9b450bffdb05bcf660d464d0bfdf344ee6ca38e9b8de4f408c8080b0c9319349", "RepoTags": [ "paketo-buildpacks/cnb:latest" ], "RepoDigests": [ "paketo-buildpacks/run@sha256:715806bb793b66e3fc1a5a8f5584c6a1b6db05425e573887673bddcf426f1b90" ], "Parent": "", "Comment": "", "Created": "2019-10-30T19:34:56.296666503Z", "Container": "84597380a7968131ab47dd1b8183a96dcfe9e1e4acff1efe5824dcd762184a67", "ContainerConfig": { "Hostname": "84597380a796", "Domainname": "", "User": "vcap", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=2000", "CNB_GROUP_ID=2000", "CNB_STACK_ID=org.cloudfoundry.stacks.cflinuxfs3" ], "Cmd": [ "/bin/sh", "-c", "#(nop) ", "LABEL io.buildpacks.stack.id=org.cloudfoundry.stacks.cflinuxfs3" ], "Image": "sha256:523c8ade6e06f814469b2cf04c8045a74becee17088955f2657958476d3fba1f", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.stack.id": "org.cloudfoundry.stacks.cflinuxfs3" } }, "DockerVersion": "18.09.6", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "vcap", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=2000", "CNB_GROUP_ID=2000", "CNB_STACK_ID=org.cloudfoundry.stacks.cflinuxfs3" ], "Cmd": null, "Image": "sha256:523c8ade6e06f814469b2cf04c8045a74becee17088955f2657958476d3fba1f", "Volumes": null, "WorkingDir": "/layers", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.builder.metadata": "{\"description\":\"cflinuxfs3 base image with buildpacks for Java, .NET, NodeJS, Python, Golang, PHP, HTTPD and NGINX\",\"buildpacks\":[{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.0.40\",\"latest\":true},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.0.114\",\"latest\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.0.94\",\"latest\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.0.100\",\"latest\":true},{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.87\",\"latest\":true},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.0.72\",\"latest\":true},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.0.92\",\"latest\":true},{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.1\",\"latest\":true},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.0.37\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.2\",\"latest\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.0.94\",\"latest\":true},{\"id\":\"org.cloudfoundry.php\",\"version\":\"v0.0.0-RC1\",\"latest\":true},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.1.9\",\"latest\":true},{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v0.0.3\",\"latest\":true},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.0.94\",\"latest\":true},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.0.97\",\"latest\":true},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.0.89\",\"latest\":true},{\"id\":\"org.cloudfoundry.python\",\"version\":\"v0.0.1\",\"latest\":true},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.51\",\"latest\":true},{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.48\",\"latest\":true},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.44\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.18\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.57\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.66\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.55\",\"latest\":true},{\"id\":\"org.cloudfoundry.httpd\",\"version\":\"0.0.21\",\"latest\":true},{\"id\":\"org.cloudfoundry.nginx\",\"version\":\"0.0.25\",\"latest\":true},{\"id\":\"org.cloudfoundry.php-composer\",\"version\":\"0.0.16\",\"latest\":true},{\"id\":\"org.cloudfoundry.php-dist\",\"version\":\"0.0.30\",\"latest\":true},{\"id\":\"org.cloudfoundry.php-web\",\"version\":\"0.0.24\",\"latest\":true},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.85\",\"latest\":true},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.yarn\",\"version\":\"0.0.58\",\"latest\":true},{\"id\":\"org.cloudfoundry.conda\",\"version\":\"0.0.37\",\"latest\":true},{\"id\":\"org.cloudfoundry.pip\",\"version\":\"0.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.pipenv\",\"version\":\"0.0.38\",\"latest\":true},{\"id\":\"org.cloudfoundry.python-runtime\",\"version\":\"0.0.57\",\"latest\":true}],\"groups\":[{\"buildpacks\":[{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.87\",\"optional\":true},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.0.53\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.0.114\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.0.72\"},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.1.9\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.0.97\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.0.89\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.0.37\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.0.94\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.0.92\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.0.40\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.0.94\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.0.94\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.0.100\",\"optional\":true}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v0.0.3\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.python\",\"version\":\"v0.0.1\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.1\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.2\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.php\",\"version\":\"v0.0.0-RC1\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.httpd\",\"version\":\"0.0.21\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.nginx\",\"version\":\"0.0.25\"}]}],\"stack\":{\"runImage\":{\"image\":\"cloudfoundry/run:full-cnb\",\"mirrors\":null}},\"lifecycle\":{\"version\":\"0.5.0\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.1\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.5.0 (git sha: c9cfac75b49609524e1ea33f809c12071406547c)\"}}", "io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.87\":{\"layerDiffID\":\"sha256:391d950d763a33d8ae0373f218aa59907599f51e42cd864129591887e1291034\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.0.94\":{\"layerDiffID\":\"sha256:3544ba1fa82d1e89619ed04c2485fab3445b1603959d224792d1183dd658033d\"}},\"org.cloudfoundry.buildsystem\":{\"v1.0.114\":{\"layerDiffID\":\"sha256:0c6ddab305e5452850f3c09fe15310dff8dc7221702d736dc7705882c1df9658\"}},\"org.cloudfoundry.conda\":{\"0.0.37\":{\"layerDiffID\":\"sha256:0943c634f5c24311ebdeca6fef5682a4a374c89a831700d188bff7f987470004\"}},\"org.cloudfoundry.debug\":{\"v1.0.92\":{\"layerDiffID\":\"sha256:ef935546e2c99da3e8962f2eb3cd6813e9e9a8b19bc8d15b56d1cac37f0342d5\"}},\"org.cloudfoundry.dep\":{\"0.0.51\":{\"layerDiffID\":\"sha256:996aee90e29ed78d80a5a0c0e50d60a732a18fddae06f87b68bef183beddd2c4\"}},\"org.cloudfoundry.distzip\":{\"v1.0.89\":{\"layerDiffID\":\"sha256:e5df92d3db931488225ca9f7290de0334225d4bd7c48fc2dcd380d0921bb6680\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.2\":{\"layerDiffID\":\"sha256:96c7f369c29bbf11b971e4dbb6473e8991b666f9de046a414317634eb0a25d2a\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.66\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.53\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.55\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.18\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.57\"}]}]}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.53\":{\"layerDiffID\":\"sha256:ab9aaff2160873663388faea6d987cd8f2b5935137b81c64fde145bf2a330d54\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.18\":{\"layerDiffID\":\"sha256:e1f3ab860045b96235cbc1b89a3e73add955a303eb42905b570b6012b73b9184\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.57\":{\"layerDiffID\":\"sha256:0b260d90d097379d4351132b45110d013b98f4a335795baeb95788fcebcb7f3c\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.66\":{\"layerDiffID\":\"sha256:f0f5ecd72b4e0a38d3ad73b5756d8f209955932e9615715502a61dffe56f401a\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.55\":{\"layerDiffID\":\"sha256:b4cd790490e41c808e8d65f9ac8f2e58c79bc1a9919a713c4519e77b26dc2053\"}},\"org.cloudfoundry.go\":{\"v0.0.1\":{\"layerDiffID\":\"sha256:6d644992d62bd09a2bbf490b7fe3aa1e35e6d0d2479583c2decec7092f193310\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.48\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.44\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.48\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.51\"}]}]}},\"org.cloudfoundry.go-compiler\":{\"0.0.48\":{\"layerDiffID\":\"sha256:30f6a316d4da01d694d8c17aa84b37f468cccc7184248e255486eb3095ebb87c\"}},\"org.cloudfoundry.go-mod\":{\"0.0.44\":{\"layerDiffID\":\"sha256:c694476a7241ba4e4a0663606d4d6eec7ed8624252c010fbef2713968e8f9436\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.0.40\":{\"layerDiffID\":\"sha256:8debe4b6b4290dbbfecea9edea61c22fb455e69e3cbc7d63b17f8e1ab8ea669b\"}},\"org.cloudfoundry.httpd\":{\"0.0.21\":{\"layerDiffID\":\"sha256:16b88c0e7f950c32c7496117d1efad90a8557a2badcb267d99a19676b1f0b76a\"}},\"org.cloudfoundry.jdbc\":{\"v1.0.94\":{\"layerDiffID\":\"sha256:a9527973bb5d7ccdf88b5be8eb81e024094be1709df659af3127865463c1c188\"}},\"org.cloudfoundry.jmx\":{\"v1.0.94\":{\"layerDiffID\":\"sha256:52845fb94361dad36cc4136e49b92c79ca59c16c579e2f51df0c58ba355c4367\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.0.72\":{\"layerDiffID\":\"sha256:5b3ec0a6ed9e3de93bb082151f56b1cde5d7e31f2809039a1b5b55a5052fe873\"}},\"org.cloudfoundry.nginx\":{\"0.0.25\":{\"layerDiffID\":\"sha256:49d36ba00b17fb605f374ca7877ae129678de925d10fd1955f07c2b6f74dd1c9\"}},\"org.cloudfoundry.node-engine\":{\"0.0.85\":{\"layerDiffID\":\"sha256:3d12e651068a0ff19afdd568b5d14ee5292f849542b31d6c9b099a09344e1f4d\"}},\"org.cloudfoundry.nodejs\":{\"v0.0.3\":{\"layerDiffID\":\"sha256:27ad0fc48c381eb77f69b4e80edccb4d8a2399f5cebd5a8c5a3e1c32313343a6\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.85\"},{\"id\":\"org.cloudfoundry.yarn\",\"version\":\"0.0.58\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.85\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.0.53\"}]}]}},\"org.cloudfoundry.npm\":{\"0.0.53\":{\"layerDiffID\":\"sha256:f01e41975a9335f5983021b081bc700e46b85efb262670223c4db61eea0a3ebd\"}},\"org.cloudfoundry.openjdk\":{\"v1.0.53\":{\"layerDiffID\":\"sha256:59d817c36a25078c8ec1f6de0d8336aec598037f89708ed13dbf661557a25084\"}},\"org.cloudfoundry.php\":{\"v0.0.0-RC1\":{\"layerDiffID\":\"sha256:12d99406b52b526af152628cd72ba6eacf5d18484dc79cfdacd4b38a21620a2b\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.php-dist\",\"version\":\"0.0.30\"},{\"id\":\"org.cloudfoundry.php-composer\",\"version\":\"0.0.16\",\"optional\":true},{\"id\":\"org.cloudfoundry.httpd\",\"version\":\"0.0.21\",\"optional\":true},{\"id\":\"org.cloudfoundry.php-web\",\"version\":\"0.0.24\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.php-dist\",\"version\":\"0.0.30\"},{\"id\":\"org.cloudfoundry.php-composer\",\"version\":\"0.0.16\",\"optional\":true},{\"id\":\"org.cloudfoundry.nginx\",\"version\":\"0.0.25\",\"optional\":true},{\"id\":\"org.cloudfoundry.php-web\",\"version\":\"0.0.24\"}]}]}},\"org.cloudfoundry.php-composer\":{\"0.0.16\":{\"layerDiffID\":\"sha256:b31d189a88ca43fee6077c25bcb623582d569193ed6ac11b4e5623558911e3de\"}},\"org.cloudfoundry.php-dist\":{\"0.0.30\":{\"layerDiffID\":\"sha256:3ecfd2822cf64c609c9c8489e2accfbc0b1de0f2a3637ff1b5d30768fb34b40c\"}},\"org.cloudfoundry.php-web\":{\"0.0.24\":{\"layerDiffID\":\"sha256:a7f09c3e09b29c5503962a068f29e8726cb91d1dbce2fab688aee0a98189b2be\"}},\"org.cloudfoundry.pip\":{\"0.0.53\":{\"layerDiffID\":\"sha256:9a183e56c86d376b408bdf922746d0a657f62b0e18c7c8f82a496b87710c576f\"}},\"org.cloudfoundry.pipenv\":{\"0.0.38\":{\"layerDiffID\":\"sha256:d919f3c2f534ddbb0b6057f82bca36051ce80a2a9cd3016c320ae276884311f5\"}},\"org.cloudfoundry.procfile\":{\"v1.0.37\":{\"layerDiffID\":\"sha256:6636ce01d12372e56a89ec77ea8d9ed510f8c701df1220750add4613764c05a4\"}},\"org.cloudfoundry.python\":{\"v0.0.1\":{\"layerDiffID\":\"sha256:290ac64fbae3288821551371c8dda38fcf5dfa063a54cb270dcc395a090f5173\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.python-runtime\",\"version\":\"0.0.57\"},{\"id\":\"org.cloudfoundry.pipenv\",\"version\":\"0.0.38\",\"optional\":true},{\"id\":\"org.cloudfoundry.pip\",\"version\":\"0.0.53\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.conda\",\"version\":\"0.0.37\"}]}]}},\"org.cloudfoundry.python-runtime\":{\"0.0.57\":{\"layerDiffID\":\"sha256:108a3eb288f8094aab6ffd822c593902e48e85c8a37b7da2bd21b15f785d92c5\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.0.100\":{\"layerDiffID\":\"sha256:480cd420e43c6895240c87c88969b87417549c02393cde1b6f71a3a3d5a2a620\"}},\"org.cloudfoundry.springboot\":{\"v1.0.97\":{\"layerDiffID\":\"sha256:7bf3a57229276fb913155b077d00a18ec6cba92c7f062728ca1c3bc3503c0b55\"}},\"org.cloudfoundry.tomcat\":{\"v1.1.9\":{\"layerDiffID\":\"sha256:3b3cda9eceb0fca56c274e3be93daf53f59501e6b3628fabbaea8ea416eb757a\"}},\"org.cloudfoundry.yarn\":{\"0.0.58\":{\"layerDiffID\":\"sha256:2b1b655bb8752f631e786c4c55670315d8569acccfe26402942977c216f2803a\"}}}", "io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.archiveexpanding\",\"optional\":true},{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.python\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.php\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.httpd\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.nginx\"}]}]", "io.buildpacks.stack.id": "org.cloudfoundry.stacks.cfwindowsfs3" } }, "Architecture": "amd64", "Os": "linux", "Size": 1559461360, "VirtualSize": 1559461360, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/58e30cd9f3a4da4e0d30f20c3b50de7655e261fb3d32f04818f1bd960c1e8b6c/diff:/var/lib/docker/overlay2/ad95d738069aa405ff17a9ebb1fdc32f8490b0dd885c3ba3a28e2c3b25d64641/diff:/var/lib/docker/overlay2/74d2896cfe9efc6945ff18870a7213583b987ecf4306e189ff6b793f77af5dcd/diff:/var/lib/docker/overlay2/1052615e5c240724e10928048f735cc9e7a7676a9af5f173b895df57c6921a40/diff:/var/lib/docker/overlay2/b5a62216c4282e7568e84427073f096551977c8c6f80d3a04ebb04c25730edde/diff:/var/lib/docker/overlay2/016a36bf7d7d7258eca08da62c01e47bf8e531531f914dde7cae33e191ab2218/diff:/var/lib/docker/overlay2/a585012bf1cf9da0472b2bbe86c4919355593e1a02cf399a9b012928eb816bcd/diff:/var/lib/docker/overlay2/b4aa8b70bd59d7b7dc6d6fb2e655c2334dc8360c764232f83d036d1f241e3298/diff:/var/lib/docker/overlay2/5f4cab16092522163e2dba6587b48d53ee3b09c8778b0736999bc120dd3753b1/diff:/var/lib/docker/overlay2/90e60622603d230f238976f4d9f65797fc9f070df62b1d2ccad0cefe4e205b43/diff:/var/lib/docker/overlay2/c43877934a580e47cc477ed46e71246468d7b6d7151abc5f1a97bb1e8c8104cf/diff:/var/lib/docker/overlay2/8734b165cabb3ff234a08d488f622135aeae9b7347cf41273445ff7d07aa4565/diff:/var/lib/docker/overlay2/2743cd9d4b7da84925b1b530732dad97108fe77e75865de580255579ba2cdb92/diff:/var/lib/docker/overlay2/68308d057b24bbcde7a4880f5db0e653743debdcc0ff3e736d1776296c4168a1/diff:/var/lib/docker/overlay2/7a4411dc4ac1ed7a1da9aabf088985b8b131e0db047e513f9890eb9c001c1895/diff:/var/lib/docker/overlay2/7f7c262fea8dea5ec86507188848ea391354a76468b09ec93523920e18a400ea/diff:/var/lib/docker/overlay2/8b3bfa567fb956204ad866e49489dacd2fdf5fbfa4f9b05ed3668e1106a5383b/diff:/var/lib/docker/overlay2/31bbc4f1616a35b7ce157266e44513963502e30d836a8fd7b7ee18436a8c46cf/diff:/var/lib/docker/overlay2/149b8e9f1142cdf6dcdfe17ea286ec17197f1a329cf23d5c82958a2032facf54/diff:/var/lib/docker/overlay2/92fb1e680083eb8314c5310bf10ced63ec2b0a98afbf84cc5175a98b3d44507a/diff:/var/lib/docker/overlay2/175a35b6f7af6eb91ca500dbd3d7e798f6d174cf8549881ffe5eed8e92a70b9f/diff:/var/lib/docker/overlay2/48ca54bbd27f7df19acf2b6cc719d05dd3b63f8133038a55d216a4498d4dc913/diff:/var/lib/docker/overlay2/ffe3cc3b93c9030f9dcb0e64c258d1e554f1f0cf27a0f8d4e98bb7ece5ffe882/diff:/var/lib/docker/overlay2/1fb2d962bb27e95c40a9a2c1aa910ca847d186d04e3d7dcdf93967101cc30dde/diff:/var/lib/docker/overlay2/10b34138f9e9e8d70c684d0a564452b1309363441b9d7e048f75e0e1179411dc/diff:/var/lib/docker/overlay2/1d888c7e9c62c22ccda6478f03f3df4b43d43fa3b32a2c2fdc9345fdc7193cd9/diff:/var/lib/docker/overlay2/649fc275c002d7336b277365636e1c8e5651bb3ed1557806d26dd6dfa1d9119a/diff:/var/lib/docker/overlay2/4484c2c0ee4a20aa17017c8cd54c842c876fea32afb297e88614d759ec5410dc/diff:/var/lib/docker/overlay2/bd5f374e0ea6749c90535d778f2689c076b7290ad9d3f050af0a40c9626fdea4/diff:/var/lib/docker/overlay2/c6ba97531b15be65bccaf7ebc866d8bc0b88ce838b224aceb196a55824b289a5/diff:/var/lib/docker/overlay2/6c65fab249fe652cd20a6391b2e0786379b6d2c7d4fde02914dfb4fac84035bd/diff:/var/lib/docker/overlay2/f391b54493024e0183331b8ec7835107bc1b84b8a6e77d852f5357724eb940ff/diff:/var/lib/docker/overlay2/8044f9e3ceb529c80531fa2fe52ad550286f788e69843f235e7d756b90c213b8/diff:/var/lib/docker/overlay2/7d3b5539c46c9f0e7c4f6f733f435d1bf6428a8ca81ba71f4da1031cef58aa6c/diff:/var/lib/docker/overlay2/b8080b36b0ddec4e4d738571ddf9d89815f6a95a555d282cfebb73519b4835a0/diff:/var/lib/docker/overlay2/8a737007d5862aa43119254122eb7050c8bd110a3b653c8d6afca23e76fc4042/diff:/var/lib/docker/overlay2/3bb8f3670831e2031be2173381caf02874ad72e664716a990a330bcc3454f4a2/diff:/var/lib/docker/overlay2/cbd675efde19ccac72d3566404e5df8b152a9063c1668d8154711c7db398f852/diff:/var/lib/docker/overlay2/84fb9095136cb645f7f15aeeeba1db6fae3999cb48a559daf8dd46bf3befbeba/diff:/var/lib/docker/overlay2/cbc51912822c4a3fb8624e0cf678e5dedeb76dc2fa0e5bc56f3cbfbfefb26d68/diff:/var/lib/docker/overlay2/d08d5bdcf39aaf46bdf1e0f4576bb64931af646213ff350065b4d306e00f7e28/diff:/var/lib/docker/overlay2/cf180c218fe181bdf836065c5e85103816ea9e8dbb8ab54fb311209c33455eb2/diff:/var/lib/docker/overlay2/b0aef801fd38973eaf116001e05e7c3f8e2eb58ccc7ed37a4bd8d4fcc2ad172b/diff:/var/lib/docker/overlay2/f73c585ae34bd962e1fee2c3e2d95d47b9daf68b23cf469fb13bc3282cf77238/diff:/var/lib/docker/overlay2/c071c8471b26e55a90b6573a21c581dec43b6c7683a3fe87cb33a0734c83342a/diff", "MergedDir": "/var/lib/docker/overlay2/41ced64ea40f3382f7a475030a5bc89b9c86e2a03d43031c5eba3c5c72616c2b/merged", "UpperDir": "/var/lib/docker/overlay2/41ced64ea40f3382f7a475030a5bc89b9c86e2a03d43031c5eba3c5c72616c2b/diff", "WorkDir": "/var/lib/docker/overlay2/41ced64ea40f3382f7a475030a5bc89b9c86e2a03d43031c5eba3c5c72616c2b/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:733a8e5ce32984099ef675fce04730f6e2a6dcfdf5bd292fea01a8f936265342", "sha256:7755b972f0b4f49de73ef5114fb3ba9c69d80f217e80da99f56f0d0a5dcb3d70", "sha256:8f0b2d09ab4b38530a1630403967d11a601e56e02e79d3f56370d34fd071fe38", "sha256:8debe4b6b4290dbbfecea9edea61c22fb455e69e3cbc7d63b17f8e1ab8ea669b", "sha256:0c6ddab305e5452850f3c09fe15310dff8dc7221702d736dc7705882c1df9658", "sha256:a9527973bb5d7ccdf88b5be8eb81e024094be1709df659af3127865463c1c188", "sha256:480cd420e43c6895240c87c88969b87417549c02393cde1b6f71a3a3d5a2a620", "sha256:391d950d763a33d8ae0373f218aa59907599f51e42cd864129591887e1291034", "sha256:5b3ec0a6ed9e3de93bb082151f56b1cde5d7e31f2809039a1b5b55a5052fe873", "sha256:ef935546e2c99da3e8962f2eb3cd6813e9e9a8b19bc8d15b56d1cac37f0342d5", "sha256:6d644992d62bd09a2bbf490b7fe3aa1e35e6d0d2479583c2decec7092f193310", "sha256:59d817c36a25078c8ec1f6de0d8336aec598037f89708ed13dbf661557a25084", "sha256:6636ce01d12372e56a89ec77ea8d9ed510f8c701df1220750add4613764c05a4", "sha256:96c7f369c29bbf11b971e4dbb6473e8991b666f9de046a414317634eb0a25d2a", "sha256:3544ba1fa82d1e89619ed04c2485fab3445b1603959d224792d1183dd658033d", "sha256:12d99406b52b526af152628cd72ba6eacf5d18484dc79cfdacd4b38a21620a2b", "sha256:3b3cda9eceb0fca56c274e3be93daf53f59501e6b3628fabbaea8ea416eb757a", "sha256:27ad0fc48c381eb77f69b4e80edccb4d8a2399f5cebd5a8c5a3e1c32313343a6", "sha256:52845fb94361dad36cc4136e49b92c79ca59c16c579e2f51df0c58ba355c4367", "sha256:7bf3a57229276fb913155b077d00a18ec6cba92c7f062728ca1c3bc3503c0b55", "sha256:e5df92d3db931488225ca9f7290de0334225d4bd7c48fc2dcd380d0921bb6680", "sha256:290ac64fbae3288821551371c8dda38fcf5dfa063a54cb270dcc395a090f5173", "sha256:996aee90e29ed78d80a5a0c0e50d60a732a18fddae06f87b68bef183beddd2c4", "sha256:30f6a316d4da01d694d8c17aa84b37f468cccc7184248e255486eb3095ebb87c", "sha256:c694476a7241ba4e4a0663606d4d6eec7ed8624252c010fbef2713968e8f9436", "sha256:ab9aaff2160873663388faea6d987cd8f2b5935137b81c64fde145bf2a330d54", "sha256:e1f3ab860045b96235cbc1b89a3e73add955a303eb42905b570b6012b73b9184", "sha256:0b260d90d097379d4351132b45110d013b98f4a335795baeb95788fcebcb7f3c", "sha256:f0f5ecd72b4e0a38d3ad73b5756d8f209955932e9615715502a61dffe56f401a", "sha256:b4cd790490e41c808e8d65f9ac8f2e58c79bc1a9919a713c4519e77b26dc2053", "sha256:16b88c0e7f950c32c7496117d1efad90a8557a2badcb267d99a19676b1f0b76a", "sha256:49d36ba00b17fb605f374ca7877ae129678de925d10fd1955f07c2b6f74dd1c9", "sha256:b31d189a88ca43fee6077c25bcb623582d569193ed6ac11b4e5623558911e3de", "sha256:3ecfd2822cf64c609c9c8489e2accfbc0b1de0f2a3637ff1b5d30768fb34b40c", "sha256:a7f09c3e09b29c5503962a068f29e8726cb91d1dbce2fab688aee0a98189b2be", "sha256:3d12e651068a0ff19afdd568b5d14ee5292f849542b31d6c9b099a09344e1f4d", "sha256:f01e41975a9335f5983021b081bc700e46b85efb262670223c4db61eea0a3ebd", "sha256:2b1b655bb8752f631e786c4c55670315d8569acccfe26402942977c216f2803a", "sha256:0943c634f5c24311ebdeca6fef5682a4a374c89a831700d188bff7f987470004", "sha256:9a183e56c86d376b408bdf922746d0a657f62b0e18c7c8f82a496b87710c576f", "sha256:d919f3c2f534ddbb0b6057f82bca36051ce80a2a9cd3016c320ae276884311f5", "sha256:108a3eb288f8094aab6ffd822c593902e48e85c8a37b7da2bd21b15f785d92c5", "sha256:f8b5dcfa1d082af23bb2b2c08526131921329d48d1614d9f2f163a997176087a", "sha256:ee13e75c33e0af49fbf6c3aaa5bbd102fc468c2d554c4f94763d35a33964dfe4", "sha256:2571abab1776d4c2e427fba10d61531afff2ab0789f89ef46ce925b6a5d98e0f", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/run-image-with-platform.json ================================================ { "Id": "sha256:1332879bc8e38793a45ebe5a750f2a1c35df07ec2aa9c18f694644a9de77359b", "RepoTags": [ "cloudfoundry/run:base-cnb" ], "RepoDigests": [ "cloudfoundry/run@sha256:fb5ecb90a42b2067a859aab23fc1f5e9d9c2589d07ba285608879e7baa415aad" ], "Parent": "", "Comment": "", "Created": "2020-03-20T20:18:18.117972538Z", "Container": "91d1af87c3bb6163cd9c7cb21e6891cd25f5fa3c7417779047776e288c0bc234", "ContainerConfig": { "Hostname": "91d1af87c3bb", "Domainname": "", "User": "1000:1000", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/sh", "-c", "#(nop) ", "LABEL io.buildpacks.stack.id=io.buildpacks.stacks.bionic" ], "ArgsEscaped": true, "Image": "sha256:fbe314bcb23f15a2a09603b6620acd67c332fd08fbf2a7bc3db8fb2f5078d994", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic" } }, "DockerVersion": "18.09.6", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "1000:1000", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/bash" ], "ArgsEscaped": true, "Image": "sha256:fbe314bcb23f15a2a09603b6620acd67c332fd08fbf2a7bc3db8fb2f5078d994", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic" } }, "Architecture": "arm64", "Os": "linux", "Variant": "v1", "Size": 71248531, "VirtualSize": 71248531, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/17f0a4530fbc3e2982f9dc8feb8c8ddc124473bdd50130dae20856ac597d82dd/diff:/var/lib/docker/overlay2/73dfd4e2075fccb239b3d5e9b33b32b8e410bdc3cd5a620b41346f44cc5c51f7/diff:/var/lib/docker/overlay2/b3924ed7c91730f6714d33c455db888604b59ab093033b3f59ac16ecdd777987/diff:/var/lib/docker/overlay2/e36a32cd0ab20b216a8db1a8a166b17464399e4d587d22504088a7a6ef0a68a4/diff:/var/lib/docker/overlay2/3334e94fe191333b65f571912c0fcfbbf31aeb090a2fb9b4cfdbc32a37c0fe5f/diff", "MergedDir": "/var/lib/docker/overlay2/8d3f9e3c00bc5072f8051ec7884500ca394f2331d8bcc9452f68d04531f50f82/merged", "UpperDir": "/var/lib/docker/overlay2/8d3f9e3c00bc5072f8051ec7884500ca394f2331d8bcc9452f68d04531f50f82/diff", "WorkDir": "/var/lib/docker/overlay2/8d3f9e3c00bc5072f8051ec7884500ca394f2331d8bcc9452f68d04531f50f82/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853", "sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f", "sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d", "sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75", "sha256:c1daeb79beb276c7441d9a1d7281433e9a7edb9f652b8996ecc62b51e88a47b2", "sha256:eb195d29dc1aa6e4239f00e7868deebc5ac12bebe76104e0b774c1ef29ca78e3" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/run-image.json ================================================ { "Id": "sha256:1332879bc8e38793a45ebe5a750f2a1c35df07ec2aa9c18f694644a9de77359b", "RepoTags": [ "cloudfoundry/run:base-cnb" ], "RepoDigests": [ "cloudfoundry/run@sha256:fb5ecb90a42b2067a859aab23fc1f5e9d9c2589d07ba285608879e7baa415aad" ], "Parent": "", "Comment": "", "Created": "2020-03-20T20:18:18.117972538Z", "Container": "91d1af87c3bb6163cd9c7cb21e6891cd25f5fa3c7417779047776e288c0bc234", "ContainerConfig": { "Hostname": "91d1af87c3bb", "Domainname": "", "User": "1000:1000", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/sh", "-c", "#(nop) ", "LABEL io.buildpacks.stack.id=io.buildpacks.stacks.bionic" ], "ArgsEscaped": true, "Image": "sha256:fbe314bcb23f15a2a09603b6620acd67c332fd08fbf2a7bc3db8fb2f5078d994", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic" } }, "DockerVersion": "18.09.6", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "1000:1000", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/bash" ], "ArgsEscaped": true, "Image": "sha256:fbe314bcb23f15a2a09603b6620acd67c332fd08fbf2a7bc3db8fb2f5078d994", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic" } }, "Architecture": "amd64", "Os": "linux", "Size": 71248531, "VirtualSize": 71248531, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/17f0a4530fbc3e2982f9dc8feb8c8ddc124473bdd50130dae20856ac597d82dd/diff:/var/lib/docker/overlay2/73dfd4e2075fccb239b3d5e9b33b32b8e410bdc3cd5a620b41346f44cc5c51f7/diff:/var/lib/docker/overlay2/b3924ed7c91730f6714d33c455db888604b59ab093033b3f59ac16ecdd777987/diff:/var/lib/docker/overlay2/e36a32cd0ab20b216a8db1a8a166b17464399e4d587d22504088a7a6ef0a68a4/diff:/var/lib/docker/overlay2/3334e94fe191333b65f571912c0fcfbbf31aeb090a2fb9b4cfdbc32a37c0fe5f/diff", "MergedDir": "/var/lib/docker/overlay2/8d3f9e3c00bc5072f8051ec7884500ca394f2331d8bcc9452f68d04531f50f82/merged", "UpperDir": "/var/lib/docker/overlay2/8d3f9e3c00bc5072f8051ec7884500ca394f2331d8bcc9452f68d04531f50f82/diff", "WorkDir": "/var/lib/docker/overlay2/8d3f9e3c00bc5072f8051ec7884500ca394f2331d8bcc9452f68d04531f50f82/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853", "sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f", "sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d", "sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75", "sha256:c1daeb79beb276c7441d9a1d7281433e9a7edb9f652b8996ecc62b51e88a47b2", "sha256:eb195d29dc1aa6e4239f00e7868deebc5ac12bebe76104e0b774c1ef29ca78e3" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-token.json ================================================ { "identitytoken": "tokenvalue" } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-user-full.json ================================================ { "username": "user", "password": "secret", "email": "docker@example.com", "serveraddress": "https://docker.example.com" } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-user-minimal.json ================================================ { "username": "user", "password": "secret" } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/docker-credential-test.bat ================================================ @echo off set /p registryUrl= if "%registryUrl%" == "user.example.com" ( echo { echo "ServerURL": "%registryUrl%", echo "Username": "username", echo "Secret": "secret" echo } exit /b 0 ) if "%registryUrl%" == "token.example.com" ( echo { echo "ServerURL": "%registryUrl%", echo "Username": "", echo "Secret": "secret" echo } exit /b 0 ) if "%registryUrl%" == "url.missing.example.com" ( echo no credentials server URL >&2 exit /b 1 ) if "%registryUrl%" == "username.missing.example.com" ( echo no credentials username >&2 exit /b 1 ) if "%registryUrl%" == "credentials.missing.example.com" ( echo credentials not found in native keychain >&2 exit /b 1 ) echo Unknown error >&2 exit /b 1 ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/docker-credential-test.sh ================================================ #!/bin/sh read -r registryUrl if [ "$registryUrl" = "user.example.com" ]; then cat <", "Secret": "secret" } EOF exit 0 fi if [ "$registryUrl" = "url.missing.example.com" ]; then echo "no credentials server URL" >&2 exit 1 fi if [ "$registryUrl" = "username.missing.example.com" ]; then echo "no credentials username" >&2 exit 1 fi if [ "$registryUrl" = "credentials.missing.example.com" ]; then echo "credentials not found in native keychain" >&2 exit 1 fi echo "Unknown error" >&2 exit 1 ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-auth/config.json ================================================ { "auths": { "https://index.docker.io/v1/": { "auth": "dXNlcm5hbWU6AABwYXNzAHdvcmQAAA==", "email": "test@example.com" }, "custom-registry.example.com": { "auth": "Y3VzdG9tVXNlcjpjdXN0b21QYXNz" }, "my-registry.example.com": { "username": "user", "password": "password" } }, "credsStore": "desktop", "credHelpers": { "gcr.io": "gcr", "ecr.us-east-1.amazonaws.com": "ecr-login", "azurecr.io": "acr-env" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/config.json ================================================ { "currentContext": "test-context" } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json ================================================ { "Name": "test-context", "Metadata": { "Description": "A context for testing" }, "Endpoints": { "docker": { "Host": "unix:///home/user/.docker/docker.sock", "SkipTLSVerify": true } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/config.json ================================================ { "currentContext": "default" } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json ================================================ { "Name": "test-context", "Metadata": { "Description": "A context for testing" }, "Endpoints": { "docker": { "Host": "unix:///home/user/.docker/docker.sock", "SkipTLSVerify": false } } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/tls/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/docker/cert.pem ================================================ ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/tls/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/docker/key.pem ================================================ ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/without-context/config.json ================================================ { } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/container-wait-response.json ================================================ { "StatusCode": 1 } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/create-container-response.json ================================================ { "Id": "e90e34656806", "Warnings": [] } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/load-error.json ================================================ {"errorDetail":{"message":"max depth exceeded"}} ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/load-stream.json ================================================ {"stream":"Loaded image: pack.local/builder/auqfjjbaod:latest\n"} ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/pull-stream.json ================================================ { "status": "Pulling from paketo-buildpacks/cnb", "id": "base" } {"status":"Pulling fs layer","progressDetail":{},"id":"5667fdb72017"} {"status":"Pulling fs layer","progressDetail":{},"id":"d83811f270d5"} {"status":"Pulling fs layer","progressDetail":{},"id":"ee671aafb583"} {"status":"Pulling fs layer","progressDetail":{},"id":"7fc152dfb3a6"} {"status":"Pulling fs layer","progressDetail":{},"id":"4ab897fa6fbf"} {"status":"Pulling fs layer","progressDetail":{},"id":"d837a2a1365e"} {"status":"Pulling fs layer","progressDetail":{},"id":"988ae18fe41a"} {"status":"Pulling fs layer","progressDetail":{},"id":"eeb8ef83b565"} {"status":"Pulling fs layer","progressDetail":{},"id":"357fefdf9bc9"} {"status":"Pulling fs layer","progressDetail":{},"id":"45b746196f82"} {"status":"Pulling fs layer","progressDetail":{},"id":"fbf4ce20f8c2"} {"status":"Pulling fs layer","progressDetail":{},"id":"90aca3c647fe"} {"status":"Pulling fs layer","progressDetail":{},"id":"1dd62f37c84c"} {"status":"Pulling fs layer","progressDetail":{},"id":"3192b2fa42db"} {"status":"Pulling fs layer","progressDetail":{},"id":"ae190b8f66a7"} {"status":"Pulling fs layer","progressDetail":{},"id":"97bb6e138460"} {"status":"Waiting","progressDetail":{},"id":"eeb8ef83b565"} {"status":"Pulling fs layer","progressDetail":{},"id":"2edb982d5170"} {"status":"Waiting","progressDetail":{},"id":"357fefdf9bc9"} {"status":"Pulling fs layer","progressDetail":{},"id":"7ddc8e6d6da9"} {"status":"Pulling fs layer","progressDetail":{},"id":"0df6fd234b59"} {"status":"Waiting","progressDetail":{},"id":"45b746196f82"} {"status":"Pulling fs layer","progressDetail":{},"id":"8fc1ba8efe21"} {"status":"Pulling fs layer","progressDetail":{},"id":"1f6f45e783b5"} {"status":"Pulling fs layer","progressDetail":{},"id":"43ea61082f68"} {"status":"Waiting","progressDetail":{},"id":"7fc152dfb3a6"} {"status":"Pulling fs layer","progressDetail":{},"id":"b8cf53bbc6ba"} {"status":"Waiting","progressDetail":{},"id":"fbf4ce20f8c2"} {"status":"Pulling fs layer","progressDetail":{},"id":"25efb07e4521"} {"status":"Waiting","progressDetail":{},"id":"90aca3c647fe"} {"status":"Pulling fs layer","progressDetail":{},"id":"1c3245356213"} {"status":"Waiting","progressDetail":{},"id":"1dd62f37c84c"} {"status":"Pulling fs layer","progressDetail":{},"id":"61ebb123c1eb"} {"status":"Pulling fs layer","progressDetail":{},"id":"0964b769d2c9"} {"status":"Waiting","progressDetail":{},"id":"3192b2fa42db"} {"status":"Pulling fs layer","progressDetail":{},"id":"87f7843f43cd"} {"status":"Pulling fs layer","progressDetail":{},"id":"a89dbf94d794"} {"status":"Waiting","progressDetail":{},"id":"ae190b8f66a7"} {"status":"Pulling fs layer","progressDetail":{},"id":"f0d43ddca77f"} {"status":"Pulling fs layer","progressDetail":{},"id":"7c674f0cb40c"} {"status":"Waiting","progressDetail":{},"id":"97bb6e138460"} {"status":"Pulling fs layer","progressDetail":{},"id":"b48a885b52bc"} {"status":"Pulling fs layer","progressDetail":{},"id":"272cdf839cbb"} {"status":"Pulling fs layer","progressDetail":{},"id":"50d054c97f4f"} {"status":"Pulling fs layer","progressDetail":{},"id":"4c6bbd90b64d"} {"status":"Waiting","progressDetail":{},"id":"2edb982d5170"} {"status":"Pulling fs layer","progressDetail":{},"id":"4f4fb700ef54"} {"status":"Waiting","progressDetail":{},"id":"7ddc8e6d6da9"} {"status":"Waiting","progressDetail":{},"id":"b8cf53bbc6ba"} {"status":"Waiting","progressDetail":{},"id":"25efb07e4521"} {"status":"Waiting","progressDetail":{},"id":"0df6fd234b59"} {"status":"Waiting","progressDetail":{},"id":"1c3245356213"} {"status":"Waiting","progressDetail":{},"id":"61ebb123c1eb"} {"status":"Waiting","progressDetail":{},"id":"8fc1ba8efe21"} {"status":"Waiting","progressDetail":{},"id":"0964b769d2c9"} {"status":"Waiting","progressDetail":{},"id":"87f7843f43cd"} {"status":"Waiting","progressDetail":{},"id":"1f6f45e783b5"} {"status":"Waiting","progressDetail":{},"id":"a89dbf94d794"} {"status":"Waiting","progressDetail":{},"id":"43ea61082f68"} {"status":"Waiting","progressDetail":{},"id":"f0d43ddca77f"} {"status":"Waiting","progressDetail":{},"id":"7c674f0cb40c"} {"status":"Waiting","progressDetail":{},"id":"b48a885b52bc"} {"status":"Waiting","progressDetail":{},"id":"272cdf839cbb"} {"status":"Waiting","progressDetail":{},"id":"50d054c97f4f"} {"status":"Waiting","progressDetail":{},"id":"4c6bbd90b64d"} {"status":"Waiting","progressDetail":{},"id":"4f4fb700ef54"} {"status":"Waiting","progressDetail":{},"id":"4ab897fa6fbf"} {"status":"Waiting","progressDetail":{},"id":"d837a2a1365e"} {"status":"Waiting","progressDetail":{},"id":"988ae18fe41a"} {"status":"Downloading","progressDetail":{"current":487,"total":850},"progress":"[============================\u003e ] 487B/850B","id":"ee671aafb583"} {"status":"Downloading","progressDetail":{"current":485,"total":35355},"progress":"[\u003e ] 485B/35.35kB","id":"d83811f270d5"} {"status":"Downloading","progressDetail":{"current":35355,"total":35355},"progress":"[==================================================\u003e] 35.35kB/35.35kB","id":"d83811f270d5"} {"status":"Verifying Checksum","progressDetail":{},"id":"d83811f270d5"} {"status":"Download complete","progressDetail":{},"id":"d83811f270d5"} {"status":"Downloading","progressDetail":{"current":277600,"total":26683298},"progress":"[\u003e ] 277.6kB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":850,"total":850},"progress":"[==================================================\u003e] 850B/850B","id":"ee671aafb583"} {"status":"Verifying Checksum","progressDetail":{},"id":"ee671aafb583"} {"status":"Download complete","progressDetail":{},"id":"ee671aafb583"} {"status":"Downloading","progressDetail":{"current":2218692,"total":26683298},"progress":"[====\u003e ] 2.219MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":4160196,"total":26683298},"progress":"[=======\u003e ] 4.16MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":6109892,"total":26683298},"progress":"[===========\u003e ] 6.11MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":7772868,"total":26683298},"progress":"[==============\u003e ] 7.773MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":9444036,"total":26683298},"progress":"[=================\u003e ] 9.444MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":163,"total":163},"progress":"[==================================================\u003e] 163B/163B","id":"7fc152dfb3a6"} {"status":"Verifying Checksum","progressDetail":{},"id":"7fc152dfb3a6"} {"status":"Download complete","progressDetail":{},"id":"7fc152dfb3a6"} {"status":"Downloading","progressDetail":{"current":10832580,"total":26683298},"progress":"[====================\u003e ] 10.83MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":531179,"total":88111129},"progress":"[\u003e ] 531.2kB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":11668164,"total":26683298},"progress":"[=====================\u003e ] 11.67MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":1604331,"total":88111129},"progress":"[\u003e ] 1.604MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":12495556,"total":26683298},"progress":"[=======================\u003e ] 12.5MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":3209963,"total":88111129},"progress":"[=\u003e ] 3.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":13331140,"total":26683298},"progress":"[========================\u003e ] 13.33MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":4283115,"total":88111129},"progress":"[==\u003e ] 4.283MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":14166724,"total":26683298},"progress":"[==========================\u003e ] 14.17MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":5888747,"total":88111129},"progress":"[===\u003e ] 5.889MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":15280836,"total":26683298},"progress":"[============================\u003e ] 15.28MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":14318,"total":1391657},"progress":"[\u003e ] 14.32kB/1.392MB","id":"d837a2a1365e"} {"status":"Downloading","progressDetail":{"current":6961899,"total":88111129},"progress":"[===\u003e ] 6.962MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":16116420,"total":26683298},"progress":"[==============================\u003e ] 16.12MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":936688,"total":1391657},"progress":"[=================================\u003e ] 936.7kB/1.392MB","id":"d837a2a1365e"} {"status":"Verifying Checksum","progressDetail":{},"id":"d837a2a1365e"} {"status":"Download complete","progressDetail":{},"id":"d837a2a1365e"} {"status":"Downloading","progressDetail":{"current":8022763,"total":88111129},"progress":"[====\u003e ] 8.023MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":16931524,"total":26683298},"progress":"[===============================\u003e ] 16.93MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":18045636,"total":26683298},"progress":"[=================================\u003e ] 18.05MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":9632491,"total":88111129},"progress":"[=====\u003e ] 9.632MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":10709739,"total":88111129},"progress":"[======\u003e ] 10.71MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":19143364,"total":26683298},"progress":"[===================================\u003e ] 19.14MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":11778795,"total":88111129},"progress":"[======\u003e ] 11.78MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":20249284,"total":26683298},"progress":"[=====================================\u003e ] 20.25MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":12851947,"total":88111129},"progress":"[=======\u003e ] 12.85MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":21072580,"total":26683298},"progress":"[=======================================\u003e ] 21.07MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":14133,"total":1328346},"progress":"[\u003e ] 14.13kB/1.328MB","id":"988ae18fe41a"} {"status":"Downloading","progressDetail":{"current":13933291,"total":88111129},"progress":"[=======\u003e ] 13.93MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":21908164,"total":26683298},"progress":"[=========================================\u003e ] 21.91MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":973511,"total":1328346},"progress":"[====================================\u003e ] 973.5kB/1.328MB","id":"988ae18fe41a"} {"status":"Verifying Checksum","progressDetail":{},"id":"988ae18fe41a"} {"status":"Download complete","progressDetail":{},"id":"988ae18fe41a"} {"status":"Downloading","progressDetail":{"current":15014635,"total":88111129},"progress":"[========\u003e ] 15.01MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":22747844,"total":26683298},"progress":"[==========================================\u003e ] 22.75MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":16075499,"total":88111129},"progress":"[=========\u003e ] 16.08MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":23575236,"total":26683298},"progress":"[============================================\u003e ] 23.58MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":24414916,"total":26683298},"progress":"[=============================================\u003e ] 24.41MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":17132267,"total":88111129},"progress":"[=========\u003e ] 17.13MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":25250500,"total":26683298},"progress":"[===============================================\u003e ] 25.25MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":18213611,"total":88111129},"progress":"[==========\u003e ] 18.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":26073796,"total":26683298},"progress":"[================================================\u003e ] 26.07MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":19286763,"total":88111129},"progress":"[==========\u003e ] 19.29MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":490,"total":4478},"progress":"[=====\u003e ] 490B/4.478kB","id":"eeb8ef83b565"} {"status":"Downloading","progressDetail":{"current":4478,"total":4478},"progress":"[==================================================\u003e] 4.478kB/4.478kB","id":"eeb8ef83b565"} {"status":"Verifying Checksum","progressDetail":{},"id":"eeb8ef83b565"} {"status":"Download complete","progressDetail":{},"id":"eeb8ef83b565"} {"status":"Verifying Checksum","progressDetail":{},"id":"5667fdb72017"} {"status":"Download complete","progressDetail":{},"id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":20892395,"total":88111129},"progress":"[===========\u003e ] 20.89MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":294912,"total":26683298},"progress":"[\u003e ] 294.9kB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":23050987,"total":88111129},"progress":"[=============\u003e ] 23.05MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":2654208,"total":26683298},"progress":"[====\u003e ] 2.654MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":25205483,"total":88111129},"progress":"[==============\u003e ] 25.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":6193152,"total":26683298},"progress":"[===========\u003e ] 6.193MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":27355883,"total":88111129},"progress":"[===============\u003e ] 27.36MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":8552448,"total":26683298},"progress":"[================\u003e ] 8.552MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":197,"total":197},"progress":"[==================================================\u003e] 197B/197B","id":"357fefdf9bc9"} {"status":"Verifying Checksum","progressDetail":{},"id":"357fefdf9bc9"} {"status":"Download complete","progressDetail":{},"id":"357fefdf9bc9"} {"status":"Extracting","progressDetail":{"current":11796480,"total":26683298},"progress":"[======================\u003e ] 11.8MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":29510379,"total":88111129},"progress":"[================\u003e ] 29.51MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":277600,"total":27504647},"progress":"[\u003e ] 277.6kB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":15040512,"total":26683298},"progress":"[============================\u003e ] 15.04MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":1391300,"total":27504647},"progress":"[==\u003e ] 1.391MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":31132395,"total":88111129},"progress":"[=================\u003e ] 31.13MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":17989632,"total":26683298},"progress":"[=================================\u003e ] 17.99MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":32754411,"total":88111129},"progress":"[==================\u003e ] 32.75MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2230980,"total":27504647},"progress":"[====\u003e ] 2.231MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":22118400,"total":26683298},"progress":"[=========================================\u003e ] 22.12MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":33835755,"total":88111129},"progress":"[===================\u003e ] 33.84MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3078852,"total":27504647},"progress":"[=====\u003e ] 3.079MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":24477696,"total":26683298},"progress":"[=============================================\u003e ] 24.48MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":52419,"total":5205016},"progress":"[\u003e ] 52.42kB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":34917099,"total":88111129},"progress":"[===================\u003e ] 34.92MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3922628,"total":27504647},"progress":"[=======\u003e ] 3.923MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":912096,"total":5205016},"progress":"[========\u003e ] 912.1kB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Extracting","progressDetail":{"current":26247168,"total":26683298},"progress":"[=================================================\u003e ] 26.25MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":4487876,"total":27504647},"progress":"[========\u003e ] 4.488MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":35986155,"total":88111129},"progress":"[====================\u003e ] 35.99MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":26683298,"total":26683298},"progress":"[==================================================\u003e] 26.68MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":1805024,"total":5205016},"progress":"[=================\u003e ] 1.805MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":5044932,"total":27504647},"progress":"[=========\u003e ] 5.045MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":36522731,"total":88111129},"progress":"[====================\u003e ] 36.52MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2550496,"total":5205016},"progress":"[========================\u003e ] 2.55MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":5601988,"total":27504647},"progress":"[==========\u003e ] 5.602MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":3381984,"total":5205016},"progress":"[================================\u003e ] 3.382MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":37063403,"total":88111129},"progress":"[=====================\u003e ] 37.06MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":6159044,"total":27504647},"progress":"[===========\u003e ] 6.159MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":4152032,"total":5205016},"progress":"[=======================================\u003e ] 4.152MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":37604075,"total":88111129},"progress":"[=====================\u003e ] 37.6MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Pull complete","progressDetail":{},"id":"5667fdb72017"} {"status":"Extracting","progressDetail":{"current":32768,"total":35355},"progress":"[==============================================\u003e ] 32.77kB/35.35kB","id":"d83811f270d5"} {"status":"Extracting","progressDetail":{"current":35355,"total":35355},"progress":"[==================================================\u003e] 35.35kB/35.35kB","id":"d83811f270d5"} {"status":"Downloading","progressDetail":{"current":5004000,"total":5205016},"progress":"[================================================\u003e ] 5.004MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":6716100,"total":27504647},"progress":"[============\u003e ] 6.716MB/27.5MB","id":"45b746196f82"} {"status":"Verifying Checksum","progressDetail":{},"id":"fbf4ce20f8c2"} {"status":"Download complete","progressDetail":{},"id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":38144747,"total":88111129},"progress":"[=====================\u003e ] 38.14MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Pull complete","progressDetail":{},"id":"d83811f270d5"} {"status":"Extracting","progressDetail":{"current":850,"total":850},"progress":"[==================================================\u003e] 850B/850B","id":"ee671aafb583"} {"status":"Extracting","progressDetail":{"current":850,"total":850},"progress":"[==================================================\u003e] 850B/850B","id":"ee671aafb583"} {"status":"Downloading","progressDetail":{"current":7293636,"total":27504647},"progress":"[=============\u003e ] 7.294MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":39213803,"total":88111129},"progress":"[======================\u003e ] 39.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":8129220,"total":27504647},"progress":"[==============\u003e ] 8.129MB/27.5MB","id":"45b746196f82"} {"status":"Pull complete","progressDetail":{},"id":"ee671aafb583"} {"status":"Extracting","progressDetail":{"current":163,"total":163},"progress":"[==================================================\u003e] 163B/163B","id":"7fc152dfb3a6"} {"status":"Extracting","progressDetail":{"current":163,"total":163},"progress":"[==================================================\u003e] 163B/163B","id":"7fc152dfb3a6"} {"status":"Downloading","progressDetail":{"current":40295147,"total":88111129},"progress":"[======================\u003e ] 40.3MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":8964804,"total":27504647},"progress":"[================\u003e ] 8.965MB/27.5MB","id":"45b746196f82"} {"status":"Pull complete","progressDetail":{},"id":"7fc152dfb3a6"} {"status":"Downloading","progressDetail":{"current":9800388,"total":27504647},"progress":"[=================\u003e ] 9.8MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":41368299,"total":88111129},"progress":"[=======================\u003e ] 41.37MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":49680,"total":4964709},"progress":"[\u003e ] 49.68kB/4.965MB","id":"90aca3c647fe"} {"status":"Downloading","progressDetail":{"current":10635972,"total":27504647},"progress":"[===================\u003e ] 10.64MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":908013,"total":4964709},"progress":"[=========\u003e ] 908kB/4.965MB","id":"90aca3c647fe"} {"status":"Downloading","progressDetail":{"current":41908971,"total":88111129},"progress":"[=======================\u003e ] 41.91MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":11193028,"total":27504647},"progress":"[====================\u003e ] 11.19MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":2038509,"total":4964709},"progress":"[====================\u003e ] 2.039MB/4.965MB","id":"90aca3c647fe"} {"status":"Downloading","progressDetail":{"current":42449643,"total":88111129},"progress":"[========================\u003e ] 42.45MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":11750084,"total":27504647},"progress":"[=====================\u003e ] 11.75MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":3316461,"total":4964709},"progress":"[=================================\u003e ] 3.316MB/4.965MB","id":"90aca3c647fe"} {"status":"Downloading","progressDetail":{"current":4791021,"total":4964709},"progress":"[================================================\u003e ] 4.791MB/4.965MB","id":"90aca3c647fe"} {"status":"Verifying Checksum","progressDetail":{},"id":"90aca3c647fe"} {"status":"Download complete","progressDetail":{},"id":"90aca3c647fe"} {"status":"Downloading","progressDetail":{"current":12315332,"total":27504647},"progress":"[======================\u003e ] 12.32MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":42990315,"total":88111129},"progress":"[========================\u003e ] 42.99MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":13155012,"total":27504647},"progress":"[=======================\u003e ] 13.16MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":43530987,"total":88111129},"progress":"[========================\u003e ] 43.53MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":13990596,"total":27504647},"progress":"[=========================\u003e ] 13.99MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":44063467,"total":88111129},"progress":"[=========================\u003e ] 44.06MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":15112900,"total":27504647},"progress":"[===========================\u003e ] 15.11MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":45132523,"total":88111129},"progress":"[=========================\u003e ] 45.13MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":16235204,"total":27504647},"progress":"[=============================\u003e ] 16.24MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":52418,"total":5149051},"progress":"[\u003e ] 52.42kB/5.149MB","id":"1dd62f37c84c"} {"status":"Downloading","progressDetail":{"current":1195147,"total":5149051},"progress":"[===========\u003e ] 1.195MB/5.149MB","id":"1dd62f37c84c"} {"status":"Downloading","progressDetail":{"current":16792260,"total":27504647},"progress":"[==============================\u003e ] 16.79MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":45673195,"total":88111129},"progress":"[=========================\u003e ] 45.67MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2702475,"total":5149051},"progress":"[==========================\u003e ] 2.702MB/5.149MB","id":"1dd62f37c84c"} {"status":"Downloading","progressDetail":{"current":4078320,"total":5149051},"progress":"[=======================================\u003e ] 4.078MB/5.149MB","id":"1dd62f37c84c"} {"status":"Downloading","progressDetail":{"current":17349316,"total":27504647},"progress":"[===============================\u003e ] 17.35MB/27.5MB","id":"45b746196f82"} {"status":"Verifying Checksum","progressDetail":{},"id":"1dd62f37c84c"} {"status":"Download complete","progressDetail":{},"id":"1dd62f37c84c"} {"status":"Downloading","progressDetail":{"current":46213867,"total":88111129},"progress":"[==========================\u003e ] 46.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":17918660,"total":27504647},"progress":"[================================\u003e ] 17.92MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":19040964,"total":27504647},"progress":"[==================================\u003e ] 19.04MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":47295211,"total":88111129},"progress":"[==========================\u003e ] 47.3MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":20183748,"total":27504647},"progress":"[====================================\u003e ] 20.18MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":48368363,"total":88111129},"progress":"[===========================\u003e ] 48.37MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":21301956,"total":27504647},"progress":"[======================================\u003e ] 21.3MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":22432452,"total":27504647},"progress":"[========================================\u003e ] 22.43MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":38884,"total":3855277},"progress":"[\u003e ] 38.88kB/3.855MB","id":"3192b2fa42db"} {"status":"Downloading","progressDetail":{"current":49445611,"total":88111129},"progress":"[============================\u003e ] 49.45MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":977632,"total":3855277},"progress":"[============\u003e ] 977.6kB/3.855MB","id":"3192b2fa42db"} {"status":"Downloading","progressDetail":{"current":23268036,"total":27504647},"progress":"[==========================================\u003e ] 23.27MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":49986283,"total":88111129},"progress":"[============================\u003e ] 49.99MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1895136,"total":3855277},"progress":"[========================\u003e ] 1.895MB/3.855MB","id":"3192b2fa42db"} {"status":"Downloading","progressDetail":{"current":23833284,"total":27504647},"progress":"[===========================================\u003e ] 23.83MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":2939616,"total":3855277},"progress":"[======================================\u003e ] 2.94MB/3.855MB","id":"3192b2fa42db"} {"status":"Downloading","progressDetail":{"current":24390340,"total":27504647},"progress":"[============================================\u003e ] 24.39MB/27.5MB","id":"45b746196f82"} {"status":"Download complete","progressDetail":{},"id":"3192b2fa42db"} {"status":"Downloading","progressDetail":{"current":50518763,"total":88111129},"progress":"[============================\u003e ] 50.52MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":24947396,"total":27504647},"progress":"[=============================================\u003e ] 24.95MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":51059435,"total":88111129},"progress":"[============================\u003e ] 51.06MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":25803460,"total":27504647},"progress":"[==============================================\u003e ] 25.8MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":26942148,"total":27504647},"progress":"[================================================\u003e ] 26.94MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":52140779,"total":88111129},"progress":"[=============================\u003e ] 52.14MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":27504647,"total":27504647},"progress":"[==================================================\u003e] 27.5MB/27.5MB","id":"45b746196f82"} {"status":"Verifying Checksum","progressDetail":{},"id":"45b746196f82"} {"status":"Download complete","progressDetail":{},"id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":53222123,"total":88111129},"progress":"[==============================\u003e ] 53.22MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":51194,"total":4983195},"progress":"[\u003e ] 51.19kB/4.983MB","id":"ae190b8f66a7"} {"status":"Downloading","progressDetail":{"current":54299371,"total":88111129},"progress":"[==============================\u003e ] 54.3MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1268464,"total":4983195},"progress":"[============\u003e ] 1.268MB/4.983MB","id":"ae190b8f66a7"} {"status":"Downloading","progressDetail":{"current":54827755,"total":88111129},"progress":"[===============================\u003e ] 54.83MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2767600,"total":4983195},"progress":"[===========================\u003e ] 2.768MB/4.983MB","id":"ae190b8f66a7"} {"status":"Downloading","progressDetail":{"current":4528880,"total":4983195},"progress":"[=============================================\u003e ] 4.529MB/4.983MB","id":"ae190b8f66a7"} {"status":"Downloading","progressDetail":{"current":55368427,"total":88111129},"progress":"[===============================\u003e ] 55.37MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Verifying Checksum","progressDetail":{},"id":"ae190b8f66a7"} {"status":"Download complete","progressDetail":{},"id":"ae190b8f66a7"} {"status":"Downloading","progressDetail":{"current":63614,"total":6103207},"progress":"[\u003e ] 63.61kB/6.103MB","id":"97bb6e138460"} {"status":"Downloading","progressDetail":{"current":56449771,"total":88111129},"progress":"[================================\u003e ] 56.45MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1530606,"total":6103207},"progress":"[============\u003e ] 1.531MB/6.103MB","id":"97bb6e138460"} {"status":"Downloading","progressDetail":{"current":3193582,"total":6103207},"progress":"[==========================\u003e ] 3.194MB/6.103MB","id":"97bb6e138460"} {"status":"Downloading","progressDetail":{"current":56990443,"total":88111129},"progress":"[================================\u003e ] 56.99MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":4786926,"total":6103207},"progress":"[=======================================\u003e ] 4.787MB/6.103MB","id":"97bb6e138460"} {"status":"Downloading","progressDetail":{"current":57531115,"total":88111129},"progress":"[================================\u003e ] 57.53MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Download complete","progressDetail":{},"id":"97bb6e138460"} {"status":"Downloading","progressDetail":{"current":489,"total":787},"progress":"[===============================\u003e ] 489B/787B","id":"2edb982d5170"} {"status":"Downloading","progressDetail":{"current":58612459,"total":88111129},"progress":"[=================================\u003e ] 58.61MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":787,"total":787},"progress":"[==================================================\u003e] 787B/787B","id":"2edb982d5170"} {"status":"Verifying Checksum","progressDetail":{},"id":"2edb982d5170"} {"status":"Download complete","progressDetail":{},"id":"2edb982d5170"} {"status":"Downloading","progressDetail":{"current":60213995,"total":88111129},"progress":"[==================================\u003e ] 60.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":61827819,"total":88111129},"progress":"[===================================\u003e ] 61.83MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":63449835,"total":88111129},"progress":"[====================================\u003e ] 63.45MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":65071851,"total":88111129},"progress":"[====================================\u003e ] 65.07MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":49803,"total":4894860},"progress":"[\u003e ] 49.8kB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":49681,"total":4953791},"progress":"[\u003e ] 49.68kB/4.954MB","id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":912099,"total":4894860},"progress":"[=========\u003e ] 912.1kB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":66145003,"total":88111129},"progress":"[=====================================\u003e ] 66.15MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":748270,"total":4953791},"progress":"[=======\u003e ] 748.3kB/4.954MB","id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":1702627,"total":4894860},"progress":"[=================\u003e ] 1.703MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":67205867,"total":88111129},"progress":"[======================================\u003e ] 67.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1678062,"total":4953791},"progress":"[================\u003e ] 1.678MB/4.954MB","id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":2194147,"total":4894860},"progress":"[======================\u003e ] 2.194MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":67746539,"total":88111129},"progress":"[======================================\u003e ] 67.75MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2648814,"total":4953791},"progress":"[==========================\u003e ] 2.649MB/4.954MB","id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":2743011,"total":4894860},"progress":"[============================\u003e ] 2.743MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":68287211,"total":88111129},"progress":"[======================================\u003e ] 68.29MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3697390,"total":4953791},"progress":"[=====================================\u003e ] 3.697MB/4.954MB","id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":3381987,"total":4894860},"progress":"[==================================\u003e ] 3.382MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":4774638,"total":4953791},"progress":"[================================================\u003e ] 4.775MB/4.954MB","id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":68827883,"total":88111129},"progress":"[=======================================\u003e ] 68.83MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":4953791,"total":4953791},"progress":"[==================================================\u003e] 4.954MB/4.954MB","id":"0df6fd234b59"} {"status":"Verifying Checksum","progressDetail":{},"id":"0df6fd234b59"} {"status":"Download complete","progressDetail":{},"id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":4004579,"total":4894860},"progress":"[========================================\u003e ] 4.005MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":4893411,"total":4894860},"progress":"[=================================================\u003e ] 4.893MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Verifying Checksum","progressDetail":{},"id":"7ddc8e6d6da9"} {"status":"Download complete","progressDetail":{},"id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":69909227,"total":88111129},"progress":"[=======================================\u003e ] 69.91MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":71527147,"total":88111129},"progress":"[========================================\u003e ] 71.53MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":73149163,"total":88111129},"progress":"[=========================================\u003e ] 73.15MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":74771179,"total":88111129},"progress":"[==========================================\u003e ] 74.77MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":63573,"total":6137526},"progress":"[\u003e ] 63.57kB/6.138MB","id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":75311851,"total":88111129},"progress":"[==========================================\u003e ] 75.31MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1317559,"total":6137526},"progress":"[==========\u003e ] 1.318MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":2710199,"total":6137526},"progress":"[======================\u003e ] 2.71MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":38729,"total":3854415},"progress":"[\u003e ] 38.73kB/3.854MB","id":"1f6f45e783b5"} {"status":"Downloading","progressDetail":{"current":76368619,"total":88111129},"progress":"[===========================================\u003e ] 76.37MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3783351,"total":6137526},"progress":"[==============================\u003e ] 3.783MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":658157,"total":3854415},"progress":"[========\u003e ] 658.2kB/3.854MB","id":"1f6f45e783b5"} {"status":"Downloading","progressDetail":{"current":4520631,"total":6137526},"progress":"[====================================\u003e ] 4.521MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":1350381,"total":3854415},"progress":"[=================\u003e ] 1.35MB/3.854MB","id":"1f6f45e783b5"} {"status":"Downloading","progressDetail":{"current":5364407,"total":6137526},"progress":"[===========================================\u003e ] 5.364MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":77445867,"total":88111129},"progress":"[===========================================\u003e ] 77.45MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2153197,"total":3854415},"progress":"[===========================\u003e ] 2.153MB/3.854MB","id":"1f6f45e783b5"} {"status":"Verifying Checksum","progressDetail":{},"id":"8fc1ba8efe21"} {"status":"Download complete","progressDetail":{},"id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":77986539,"total":88111129},"progress":"[============================================\u003e ] 77.99MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3021549,"total":3854415},"progress":"[=======================================\u003e ] 3.022MB/3.854MB","id":"1f6f45e783b5"} {"status":"Verifying Checksum","progressDetail":{},"id":"1f6f45e783b5"} {"status":"Download complete","progressDetail":{},"id":"1f6f45e783b5"} {"status":"Downloading","progressDetail":{"current":79067883,"total":88111129},"progress":"[============================================\u003e ] 79.07MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":80149227,"total":88111129},"progress":"[=============================================\u003e ] 80.15MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":81767147,"total":88111129},"progress":"[==============================================\u003e ] 81.77MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":52419,"total":5222290},"progress":"[\u003e ] 52.42kB/5.222MB","id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":1055455,"total":5222290},"progress":"[==========\u003e ] 1.055MB/5.222MB","id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":83372779,"total":88111129},"progress":"[===============================================\u003e ] 83.37MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2333407,"total":5222290},"progress":"[======================\u003e ] 2.333MB/5.222MB","id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":35991,"total":3564359},"progress":"[\u003e ] 35.99kB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Downloading","progressDetail":{"current":84454123,"total":88111129},"progress":"[===============================================\u003e ] 84.45MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3300063,"total":5222290},"progress":"[===============================\u003e ] 3.3MB/5.222MB","id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":752366,"total":3564359},"progress":"[==========\u003e ] 752.4kB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Downloading","progressDetail":{"current":3979999,"total":5222290},"progress":"[======================================\u003e ] 3.98MB/5.222MB","id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":1743598,"total":3564359},"progress":"[========================\u003e ] 1.744MB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Downloading","progressDetail":{"current":85527275,"total":88111129},"progress":"[================================================\u003e ] 85.53MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":4537055,"total":5222290},"progress":"[===========================================\u003e ] 4.537MB/5.222MB","id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":2833134,"total":3564359},"progress":"[=======================================\u003e ] 2.833MB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Downloading","progressDetail":{"current":5077727,"total":5222290},"progress":"[================================================\u003e ] 5.078MB/5.222MB","id":"43ea61082f68"} {"status":"Verifying Checksum","progressDetail":{},"id":"b8cf53bbc6ba"} {"status":"Download complete","progressDetail":{},"id":"b8cf53bbc6ba"} {"status":"Verifying Checksum","progressDetail":{},"id":"43ea61082f68"} {"status":"Download complete","progressDetail":{},"id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":86067947,"total":88111129},"progress":"[================================================\u003e ] 86.07MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":87132907,"total":88111129},"progress":"[=================================================\u003e ] 87.13MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Verifying Checksum","progressDetail":{},"id":"4ab897fa6fbf"} {"status":"Download complete","progressDetail":{},"id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":557056,"total":88111129},"progress":"[\u003e ] 557.1kB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":52418,"total":5120108},"progress":"[\u003e ] 52.42kB/5.12MB","id":"1c3245356213"} {"status":"Downloading","progressDetail":{"current":489,"total":790},"progress":"[==============================\u003e ] 489B/790B","id":"25efb07e4521"} {"status":"Extracting","progressDetail":{"current":5013504,"total":88111129},"progress":"[==\u003e ] 5.014MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":790,"total":790},"progress":"[==================================================\u003e] 790B/790B","id":"25efb07e4521"} {"status":"Verifying Checksum","progressDetail":{},"id":"25efb07e4521"} {"status":"Download complete","progressDetail":{},"id":"25efb07e4521"} {"status":"Downloading","progressDetail":{"current":1764079,"total":5120108},"progress":"[=================\u003e ] 1.764MB/5.12MB","id":"1c3245356213"} {"status":"Extracting","progressDetail":{"current":8355840,"total":88111129},"progress":"[====\u003e ] 8.356MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3635951,"total":5120108},"progress":"[===================================\u003e ] 3.636MB/5.12MB","id":"1c3245356213"} {"status":"Verifying Checksum","progressDetail":{},"id":"1c3245356213"} {"status":"Download complete","progressDetail":{},"id":"1c3245356213"} {"status":"Extracting","progressDetail":{"current":11141120,"total":88111129},"progress":"[======\u003e ] 11.14MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":52419,"total":5117023},"progress":"[\u003e ] 52.42kB/5.117MB","id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":13369344,"total":88111129},"progress":"[=======\u003e ] 13.37MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1596142,"total":5117023},"progress":"[===============\u003e ] 1.596MB/5.117MB","id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":13926400,"total":88111129},"progress":"[=======\u003e ] 13.93MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3242734,"total":5117023},"progress":"[===============================\u003e ] 3.243MB/5.117MB","id":"61ebb123c1eb"} {"status":"Downloading","progressDetail":{"current":55157,"total":5384215},"progress":"[\u003e ] 55.16kB/5.384MB","id":"0964b769d2c9"} {"status":"Downloading","progressDetail":{"current":4635374,"total":5117023},"progress":"[=============================================\u003e ] 4.635MB/5.117MB","id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":15040512,"total":88111129},"progress":"[========\u003e ] 15.04MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Verifying Checksum","progressDetail":{},"id":"61ebb123c1eb"} {"status":"Download complete","progressDetail":{},"id":"61ebb123c1eb"} {"status":"Downloading","progressDetail":{"current":989937,"total":5384215},"progress":"[=========\u003e ] 989.9kB/5.384MB","id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":15597568,"total":88111129},"progress":"[========\u003e ] 15.6MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2558705,"total":5384215},"progress":"[=======================\u003e ] 2.559MB/5.384MB","id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":18382848,"total":88111129},"progress":"[==========\u003e ] 18.38MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":4311793,"total":5384215},"progress":"[========================================\u003e ] 4.312MB/5.384MB","id":"0964b769d2c9"} {"status":"Downloading","progressDetail":{"current":53788,"total":5252487},"progress":"[\u003e ] 53.79kB/5.252MB","id":"87f7843f43cd"} {"status":"Extracting","progressDetail":{"current":22839296,"total":88111129},"progress":"[============\u003e ] 22.84MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":5212913,"total":5384215},"progress":"[================================================\u003e ] 5.213MB/5.384MB","id":"0964b769d2c9"} {"status":"Downloading","progressDetail":{"current":846577,"total":5252487},"progress":"[========\u003e ] 846.6kB/5.252MB","id":"87f7843f43cd"} {"status":"Verifying Checksum","progressDetail":{},"id":"0964b769d2c9"} {"status":"Download complete","progressDetail":{},"id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":26181632,"total":88111129},"progress":"[==============\u003e ] 26.18MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2628337,"total":5252487},"progress":"[=========================\u003e ] 2.628MB/5.252MB","id":"87f7843f43cd"} {"status":"Extracting","progressDetail":{"current":30638080,"total":88111129},"progress":"[=================\u003e ] 30.64MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":4340465,"total":5252487},"progress":"[=========================================\u003e ] 4.34MB/5.252MB","id":"87f7843f43cd"} {"status":"Download complete","progressDetail":{},"id":"87f7843f43cd"} {"status":"Extracting","progressDetail":{"current":33423360,"total":88111129},"progress":"[==================\u003e ] 33.42MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":51204,"total":5015856},"progress":"[\u003e ] 51.2kB/5.016MB","id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":36208640,"total":88111129},"progress":"[====================\u003e ] 36.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1624816,"total":5015856},"progress":"[================\u003e ] 1.625MB/5.016MB","id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":38436864,"total":88111129},"progress":"[=====================\u003e ] 38.44MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3373808,"total":5015856},"progress":"[=================================\u003e ] 3.374MB/5.016MB","id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":40665088,"total":88111129},"progress":"[=======================\u003e ] 40.67MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":53910,"total":5310566},"progress":"[\u003e ] 53.91kB/5.311MB","id":"f0d43ddca77f"} {"status":"Downloading","progressDetail":{"current":4905712,"total":5015856},"progress":"[================================================\u003e ] 4.906MB/5.016MB","id":"a89dbf94d794"} {"status":"Verifying Checksum","progressDetail":{},"id":"a89dbf94d794"} {"status":"Download complete","progressDetail":{},"id":"a89dbf94d794"} {"status":"Downloading","progressDetail":{"current":1313521,"total":5310566},"progress":"[============\u003e ] 1.314MB/5.311MB","id":"f0d43ddca77f"} {"status":"Extracting","progressDetail":{"current":44007424,"total":88111129},"progress":"[========================\u003e ] 44.01MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":46792704,"total":88111129},"progress":"[==========================\u003e ] 46.79MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3082993,"total":5310566},"progress":"[=============================\u003e ] 3.083MB/5.311MB","id":"f0d43ddca77f"} {"status":"Downloading","progressDetail":{"current":49836,"total":4915049},"progress":"[\u003e ] 49.84kB/4.915MB","id":"7c674f0cb40c"} {"status":"Downloading","progressDetail":{"current":4373233,"total":5310566},"progress":"[=========================================\u003e ] 4.373MB/5.311MB","id":"f0d43ddca77f"} {"status":"Extracting","progressDetail":{"current":48463872,"total":88111129},"progress":"[===========================\u003e ] 48.46MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":711407,"total":4915049},"progress":"[=======\u003e ] 711.4kB/4.915MB","id":"7c674f0cb40c"} {"status":"Verifying Checksum","progressDetail":{},"id":"f0d43ddca77f"} {"status":"Download complete","progressDetail":{},"id":"f0d43ddca77f"} {"status":"Downloading","progressDetail":{"current":1710831,"total":4915049},"progress":"[=================\u003e ] 1.711MB/4.915MB","id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":52363264,"total":88111129},"progress":"[=============================\u003e ] 52.36MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3504879,"total":4915049},"progress":"[===================================\u003e ] 3.505MB/4.915MB","id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":55705600,"total":88111129},"progress":"[===============================\u003e ] 55.71MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":4905711,"total":4915049},"progress":"[=================================================\u003e ] 4.906MB/4.915MB","id":"7c674f0cb40c"} {"status":"Verifying Checksum","progressDetail":{},"id":"7c674f0cb40c"} {"status":"Download complete","progressDetail":{},"id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":58490880,"total":88111129},"progress":"[=================================\u003e ] 58.49MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":52419,"total":5119213},"progress":"[\u003e ] 52.42kB/5.119MB","id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":61276160,"total":88111129},"progress":"[==================================\u003e ] 61.28MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1333999,"total":5119213},"progress":"[=============\u003e ] 1.334MB/5.119MB","id":"b48a885b52bc"} {"status":"Downloading","progressDetail":{"current":2657007,"total":5119213},"progress":"[=========================\u003e ] 2.657MB/5.119MB","id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":64061440,"total":88111129},"progress":"[====================================\u003e ] 64.06MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Download complete","progressDetail":{},"id":"272cdf839cbb"} {"status":"Downloading","progressDetail":{"current":4344559,"total":5119213},"progress":"[==========================================\u003e ] 4.345MB/5.119MB","id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":66289664,"total":88111129},"progress":"[=====================================\u003e ] 66.29MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Verifying Checksum","progressDetail":{},"id":"b48a885b52bc"} {"status":"Download complete","progressDetail":{},"id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":70746112,"total":88111129},"progress":"[========================================\u003e ] 70.75MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":73531392,"total":88111129},"progress":"[=========================================\u003e ] 73.53MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":77430784,"total":88111129},"progress":"[===========================================\u003e ] 77.43MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Download complete","progressDetail":{},"id":"50d054c97f4f"} {"status":"Downloading","progressDetail":{"current":488,"total":1069},"progress":"[======================\u003e ] 488B/1.069kB","id":"4c6bbd90b64d"} {"status":"Extracting","progressDetail":{"current":80216064,"total":88111129},"progress":"[=============================================\u003e ] 80.22MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1069,"total":1069},"progress":"[==================================================\u003e] 1.069kB/1.069kB","id":"4c6bbd90b64d"} {"status":"Verifying Checksum","progressDetail":{},"id":"4c6bbd90b64d"} {"status":"Download complete","progressDetail":{},"id":"4c6bbd90b64d"} {"status":"Downloading","progressDetail":{"current":32,"total":32},"progress":"[==================================================\u003e] 32B/32B","id":"4f4fb700ef54"} {"status":"Verifying Checksum","progressDetail":{},"id":"4f4fb700ef54"} {"status":"Download complete","progressDetail":{},"id":"4f4fb700ef54"} {"status":"Extracting","progressDetail":{"current":81887232,"total":88111129},"progress":"[==============================================\u003e ] 81.89MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":83558400,"total":88111129},"progress":"[===============================================\u003e ] 83.56MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":85229568,"total":88111129},"progress":"[================================================\u003e ] 85.23MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":86900736,"total":88111129},"progress":"[=================================================\u003e ] 86.9MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":88014848,"total":88111129},"progress":"[=================================================\u003e ] 88.01MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":88111129,"total":88111129},"progress":"[==================================================\u003e] 88.11MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Pull complete","progressDetail":{},"id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":32768,"total":1391657},"progress":"[=\u003e ] 32.77kB/1.392MB","id":"d837a2a1365e"} {"status":"Extracting","progressDetail":{"current":327680,"total":1391657},"progress":"[===========\u003e ] 327.7kB/1.392MB","id":"d837a2a1365e"} {"status":"Extracting","progressDetail":{"current":1391657,"total":1391657},"progress":"[==================================================\u003e] 1.392MB/1.392MB","id":"d837a2a1365e"} {"status":"Extracting","progressDetail":{"current":1391657,"total":1391657},"progress":"[==================================================\u003e] 1.392MB/1.392MB","id":"d837a2a1365e"} {"status":"Pull complete","progressDetail":{},"id":"d837a2a1365e"} {"status":"Extracting","progressDetail":{"current":32768,"total":1328346},"progress":"[=\u003e ] 32.77kB/1.328MB","id":"988ae18fe41a"} {"status":"Extracting","progressDetail":{"current":753664,"total":1328346},"progress":"[============================\u003e ] 753.7kB/1.328MB","id":"988ae18fe41a"} {"status":"Extracting","progressDetail":{"current":1328346,"total":1328346},"progress":"[==================================================\u003e] 1.328MB/1.328MB","id":"988ae18fe41a"} {"status":"Extracting","progressDetail":{"current":1328346,"total":1328346},"progress":"[==================================================\u003e] 1.328MB/1.328MB","id":"988ae18fe41a"} {"status":"Pull complete","progressDetail":{},"id":"988ae18fe41a"} {"status":"Extracting","progressDetail":{"current":4478,"total":4478},"progress":"[==================================================\u003e] 4.478kB/4.478kB","id":"eeb8ef83b565"} {"status":"Extracting","progressDetail":{"current":4478,"total":4478},"progress":"[==================================================\u003e] 4.478kB/4.478kB","id":"eeb8ef83b565"} {"status":"Pull complete","progressDetail":{},"id":"eeb8ef83b565"} {"status":"Extracting","progressDetail":{"current":197,"total":197},"progress":"[==================================================\u003e] 197B/197B","id":"357fefdf9bc9"} {"status":"Extracting","progressDetail":{"current":197,"total":197},"progress":"[==================================================\u003e] 197B/197B","id":"357fefdf9bc9"} {"status":"Pull complete","progressDetail":{},"id":"357fefdf9bc9"} {"status":"Extracting","progressDetail":{"current":294912,"total":27504647},"progress":"[\u003e ] 294.9kB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":589824,"total":27504647},"progress":"[=\u003e ] 589.8kB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":5013504,"total":27504647},"progress":"[=========\u003e ] 5.014MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":9142272,"total":27504647},"progress":"[================\u003e ] 9.142MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":13565952,"total":27504647},"progress":"[========================\u003e ] 13.57MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":16515072,"total":27504647},"progress":"[==============================\u003e ] 16.52MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":18579456,"total":27504647},"progress":"[=================================\u003e ] 18.58MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":21528576,"total":27504647},"progress":"[=======================================\u003e ] 21.53MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":25657344,"total":27504647},"progress":"[==============================================\u003e ] 25.66MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":27504647,"total":27504647},"progress":"[==================================================\u003e] 27.5MB/27.5MB","id":"45b746196f82"} {"status":"Pull complete","progressDetail":{},"id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":65536,"total":5205016},"progress":"[\u003e ] 65.54kB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Extracting","progressDetail":{"current":1048576,"total":5205016},"progress":"[==========\u003e ] 1.049MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Extracting","progressDetail":{"current":5205016,"total":5205016},"progress":"[==================================================\u003e] 5.205MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Pull complete","progressDetail":{},"id":"fbf4ce20f8c2"} {"status":"Extracting","progressDetail":{"current":65536,"total":4964709},"progress":"[\u003e ] 65.54kB/4.965MB","id":"90aca3c647fe"} {"status":"Extracting","progressDetail":{"current":1245184,"total":4964709},"progress":"[============\u003e ] 1.245MB/4.965MB","id":"90aca3c647fe"} {"status":"Extracting","progressDetail":{"current":4964709,"total":4964709},"progress":"[==================================================\u003e] 4.965MB/4.965MB","id":"90aca3c647fe"} {"status":"Pull complete","progressDetail":{},"id":"90aca3c647fe"} {"status":"Extracting","progressDetail":{"current":65536,"total":5149051},"progress":"[\u003e ] 65.54kB/5.149MB","id":"1dd62f37c84c"} {"status":"Extracting","progressDetail":{"current":393216,"total":5149051},"progress":"[===\u003e ] 393.2kB/5.149MB","id":"1dd62f37c84c"} {"status":"Extracting","progressDetail":{"current":5149051,"total":5149051},"progress":"[==================================================\u003e] 5.149MB/5.149MB","id":"1dd62f37c84c"} {"status":"Pull complete","progressDetail":{},"id":"1dd62f37c84c"} {"status":"Extracting","progressDetail":{"current":65536,"total":3855277},"progress":"[\u003e ] 65.54kB/3.855MB","id":"3192b2fa42db"} {"status":"Extracting","progressDetail":{"current":851968,"total":3855277},"progress":"[===========\u003e ] 852kB/3.855MB","id":"3192b2fa42db"} {"status":"Extracting","progressDetail":{"current":3855277,"total":3855277},"progress":"[==================================================\u003e] 3.855MB/3.855MB","id":"3192b2fa42db"} {"status":"Extracting","progressDetail":{"current":3855277,"total":3855277},"progress":"[==================================================\u003e] 3.855MB/3.855MB","id":"3192b2fa42db"} {"status":"Pull complete","progressDetail":{},"id":"3192b2fa42db"} {"status":"Extracting","progressDetail":{"current":65536,"total":4983195},"progress":"[\u003e ] 65.54kB/4.983MB","id":"ae190b8f66a7"} {"status":"Extracting","progressDetail":{"current":327680,"total":4983195},"progress":"[===\u003e ] 327.7kB/4.983MB","id":"ae190b8f66a7"} {"status":"Extracting","progressDetail":{"current":4980736,"total":4983195},"progress":"[=================================================\u003e ] 4.981MB/4.983MB","id":"ae190b8f66a7"} {"status":"Extracting","progressDetail":{"current":4983195,"total":4983195},"progress":"[==================================================\u003e] 4.983MB/4.983MB","id":"ae190b8f66a7"} {"status":"Pull complete","progressDetail":{},"id":"ae190b8f66a7"} {"status":"Extracting","progressDetail":{"current":65536,"total":6103207},"progress":"[\u003e ] 65.54kB/6.103MB","id":"97bb6e138460"} {"status":"Extracting","progressDetail":{"current":327680,"total":6103207},"progress":"[==\u003e ] 327.7kB/6.103MB","id":"97bb6e138460"} {"status":"Extracting","progressDetail":{"current":3670016,"total":6103207},"progress":"[==============================\u003e ] 3.67MB/6.103MB","id":"97bb6e138460"} {"status":"Extracting","progressDetail":{"current":6103207,"total":6103207},"progress":"[==================================================\u003e] 6.103MB/6.103MB","id":"97bb6e138460"} {"status":"Pull complete","progressDetail":{},"id":"97bb6e138460"} {"status":"Extracting","progressDetail":{"current":787,"total":787},"progress":"[==================================================\u003e] 787B/787B","id":"2edb982d5170"} {"status":"Extracting","progressDetail":{"current":787,"total":787},"progress":"[==================================================\u003e] 787B/787B","id":"2edb982d5170"} {"status":"Pull complete","progressDetail":{},"id":"2edb982d5170"} {"status":"Extracting","progressDetail":{"current":65536,"total":4894860},"progress":"[\u003e ] 65.54kB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Extracting","progressDetail":{"current":327680,"total":4894860},"progress":"[===\u003e ] 327.7kB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Extracting","progressDetail":{"current":3735552,"total":4894860},"progress":"[======================================\u003e ] 3.736MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Extracting","progressDetail":{"current":4894860,"total":4894860},"progress":"[==================================================\u003e] 4.895MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Pull complete","progressDetail":{},"id":"7ddc8e6d6da9"} {"status":"Extracting","progressDetail":{"current":65536,"total":4953791},"progress":"[\u003e ] 65.54kB/4.954MB","id":"0df6fd234b59"} {"status":"Extracting","progressDetail":{"current":327680,"total":4953791},"progress":"[===\u003e ] 327.7kB/4.954MB","id":"0df6fd234b59"} {"status":"Extracting","progressDetail":{"current":4325376,"total":4953791},"progress":"[===========================================\u003e ] 4.325MB/4.954MB","id":"0df6fd234b59"} {"status":"Extracting","progressDetail":{"current":4953791,"total":4953791},"progress":"[==================================================\u003e] 4.954MB/4.954MB","id":"0df6fd234b59"} {"status":"Pull complete","progressDetail":{},"id":"0df6fd234b59"} {"status":"Extracting","progressDetail":{"current":65536,"total":6137526},"progress":"[\u003e ] 65.54kB/6.138MB","id":"8fc1ba8efe21"} {"status":"Extracting","progressDetail":{"current":327680,"total":6137526},"progress":"[==\u003e ] 327.7kB/6.138MB","id":"8fc1ba8efe21"} {"status":"Extracting","progressDetail":{"current":3801088,"total":6137526},"progress":"[==============================\u003e ] 3.801MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Extracting","progressDetail":{"current":6137526,"total":6137526},"progress":"[==================================================\u003e] 6.138MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Pull complete","progressDetail":{},"id":"8fc1ba8efe21"} {"status":"Extracting","progressDetail":{"current":65536,"total":3854415},"progress":"[\u003e ] 65.54kB/3.854MB","id":"1f6f45e783b5"} {"status":"Extracting","progressDetail":{"current":851968,"total":3854415},"progress":"[===========\u003e ] 852kB/3.854MB","id":"1f6f45e783b5"} {"status":"Extracting","progressDetail":{"current":3854415,"total":3854415},"progress":"[==================================================\u003e] 3.854MB/3.854MB","id":"1f6f45e783b5"} {"status":"Extracting","progressDetail":{"current":3854415,"total":3854415},"progress":"[==================================================\u003e] 3.854MB/3.854MB","id":"1f6f45e783b5"} {"status":"Pull complete","progressDetail":{},"id":"1f6f45e783b5"} {"status":"Extracting","progressDetail":{"current":65536,"total":5222290},"progress":"[\u003e ] 65.54kB/5.222MB","id":"43ea61082f68"} {"status":"Extracting","progressDetail":{"current":458752,"total":5222290},"progress":"[====\u003e ] 458.8kB/5.222MB","id":"43ea61082f68"} {"status":"Extracting","progressDetail":{"current":4849664,"total":5222290},"progress":"[==============================================\u003e ] 4.85MB/5.222MB","id":"43ea61082f68"} {"status":"Extracting","progressDetail":{"current":5222290,"total":5222290},"progress":"[==================================================\u003e] 5.222MB/5.222MB","id":"43ea61082f68"} {"status":"Pull complete","progressDetail":{},"id":"43ea61082f68"} {"status":"Extracting","progressDetail":{"current":65536,"total":3564359},"progress":"[\u003e ] 65.54kB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Extracting","progressDetail":{"current":327680,"total":3564359},"progress":"[====\u003e ] 327.7kB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Extracting","progressDetail":{"current":3564359,"total":3564359},"progress":"[==================================================\u003e] 3.564MB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Pull complete","progressDetail":{},"id":"b8cf53bbc6ba"} {"status":"Extracting","progressDetail":{"current":790,"total":790},"progress":"[==================================================\u003e] 790B/790B","id":"25efb07e4521"} {"status":"Extracting","progressDetail":{"current":790,"total":790},"progress":"[==================================================\u003e] 790B/790B","id":"25efb07e4521"} {"status":"Pull complete","progressDetail":{},"id":"25efb07e4521"} {"status":"Extracting","progressDetail":{"current":65536,"total":5120108},"progress":"[\u003e ] 65.54kB/5.12MB","id":"1c3245356213"} {"status":"Extracting","progressDetail":{"current":327680,"total":5120108},"progress":"[===\u003e ] 327.7kB/5.12MB","id":"1c3245356213"} {"status":"Extracting","progressDetail":{"current":5111808,"total":5120108},"progress":"[=================================================\u003e ] 5.112MB/5.12MB","id":"1c3245356213"} {"status":"Extracting","progressDetail":{"current":5120108,"total":5120108},"progress":"[==================================================\u003e] 5.12MB/5.12MB","id":"1c3245356213"} {"status":"Pull complete","progressDetail":{},"id":"1c3245356213"} {"status":"Extracting","progressDetail":{"current":65536,"total":5117023},"progress":"[\u003e ] 65.54kB/5.117MB","id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":655360,"total":5117023},"progress":"[======\u003e ] 655.4kB/5.117MB","id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":4259840,"total":5117023},"progress":"[=========================================\u003e ] 4.26MB/5.117MB","id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":5117023,"total":5117023},"progress":"[==================================================\u003e] 5.117MB/5.117MB","id":"61ebb123c1eb"} {"status":"Pull complete","progressDetail":{},"id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":65536,"total":5384215},"progress":"[\u003e ] 65.54kB/5.384MB","id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":327680,"total":5384215},"progress":"[===\u003e ] 327.7kB/5.384MB","id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":5177344,"total":5384215},"progress":"[================================================\u003e ] 5.177MB/5.384MB","id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":5384215,"total":5384215},"progress":"[==================================================\u003e] 5.384MB/5.384MB","id":"0964b769d2c9"} {"status":"Pull complete","progressDetail":{},"id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":65536,"total":5252487},"progress":"[\u003e ] 65.54kB/5.252MB","id":"87f7843f43cd"} {"status":"Extracting","progressDetail":{"current":655360,"total":5252487},"progress":"[======\u003e ] 655.4kB/5.252MB","id":"87f7843f43cd"} {"status":"Extracting","progressDetail":{"current":5252487,"total":5252487},"progress":"[==================================================\u003e] 5.252MB/5.252MB","id":"87f7843f43cd"} {"status":"Pull complete","progressDetail":{},"id":"87f7843f43cd"} {"status":"Extracting","progressDetail":{"current":65536,"total":5015856},"progress":"[\u003e ] 65.54kB/5.016MB","id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":327680,"total":5015856},"progress":"[===\u003e ] 327.7kB/5.016MB","id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":3997696,"total":5015856},"progress":"[=======================================\u003e ] 3.998MB/5.016MB","id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":5015856,"total":5015856},"progress":"[==================================================\u003e] 5.016MB/5.016MB","id":"a89dbf94d794"} {"status":"Pull complete","progressDetail":{},"id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":65536,"total":5310566},"progress":"[\u003e ] 65.54kB/5.311MB","id":"f0d43ddca77f"} {"status":"Extracting","progressDetail":{"current":393216,"total":5310566},"progress":"[===\u003e ] 393.2kB/5.311MB","id":"f0d43ddca77f"} {"status":"Extracting","progressDetail":{"current":3407872,"total":5310566},"progress":"[================================\u003e ] 3.408MB/5.311MB","id":"f0d43ddca77f"} {"status":"Extracting","progressDetail":{"current":5310566,"total":5310566},"progress":"[==================================================\u003e] 5.311MB/5.311MB","id":"f0d43ddca77f"} {"status":"Pull complete","progressDetail":{},"id":"f0d43ddca77f"} {"status":"Extracting","progressDetail":{"current":65536,"total":4915049},"progress":"[\u003e ] 65.54kB/4.915MB","id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":786432,"total":4915049},"progress":"[========\u003e ] 786.4kB/4.915MB","id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":4915049,"total":4915049},"progress":"[==================================================\u003e] 4.915MB/4.915MB","id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":4915049,"total":4915049},"progress":"[==================================================\u003e] 4.915MB/4.915MB","id":"7c674f0cb40c"} {"status":"Pull complete","progressDetail":{},"id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":65536,"total":5119213},"progress":"[\u003e ] 65.54kB/5.119MB","id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":327680,"total":5119213},"progress":"[===\u003e ] 327.7kB/5.119MB","id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":4390912,"total":5119213},"progress":"[==========================================\u003e ] 4.391MB/5.119MB","id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":5119213,"total":5119213},"progress":"[==================================================\u003e] 5.119MB/5.119MB","id":"b48a885b52bc"} {"status":"Pull complete","progressDetail":{},"id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":395,"total":395},"progress":"[==================================================\u003e] 395B/395B","id":"272cdf839cbb"} {"status":"Extracting","progressDetail":{"current":395,"total":395},"progress":"[==================================================\u003e] 395B/395B","id":"272cdf839cbb"} {"status":"Pull complete","progressDetail":{},"id":"272cdf839cbb"} {"status":"Extracting","progressDetail":{"current":155,"total":155},"progress":"[==================================================\u003e] 155B/155B","id":"50d054c97f4f"} {"status":"Extracting","progressDetail":{"current":155,"total":155},"progress":"[==================================================\u003e] 155B/155B","id":"50d054c97f4f"} {"status":"Pull complete","progressDetail":{},"id":"50d054c97f4f"} {"status":"Extracting","progressDetail":{"current":1069,"total":1069},"progress":"[==================================================\u003e] 1.069kB/1.069kB","id":"4c6bbd90b64d"} {"status":"Extracting","progressDetail":{"current":1069,"total":1069},"progress":"[==================================================\u003e] 1.069kB/1.069kB","id":"4c6bbd90b64d"} {"status":"Pull complete","progressDetail":{},"id":"4c6bbd90b64d"} {"status":"Extracting","progressDetail":{"current":32,"total":32},"progress":"[==================================================\u003e] 32B/32B","id":"4f4fb700ef54"} {"status":"Extracting","progressDetail":{"current":32,"total":32},"progress":"[==================================================\u003e] 32B/32B","id":"4f4fb700ef54"} {"status":"Pull complete","progressDetail":{},"id":"4f4fb700ef54"} {"status":"Digest: sha256:4acb6bfd6c4f0cabaf7f3690e444afe51f1c7de54d51da7e63fac709c56f1c30"} {"status":"Status: Downloaded newer image for paketo-buildpacks/cnb:base"} ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/pull-update-full.json ================================================ { "status": "Extracting", "progressDetail": { "current": 16, "total": 32 }, "progress": "[==================================================\u003e] 32B/32B", "id": "4f4fb700ef54" } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/pull-update-minimal.json ================================================ { "status": "Status: Downloaded newer image for paketo-buildpacks/cnb:base" } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/pull-with-empty-details.json ================================================ { "status": "Pulling fs layer", "progressDetail": { }, "id": "d837a2a1365e" } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/push-stream-with-error.json ================================================ { "status":"The push refers to repository [localhost:5000/ubuntu]" } {"status":"Preparing","progressDetail":{},"id":"782f5f011dda"} {"status":"Preparing","progressDetail":{},"id":"90ac32a0d9ab"} {"status":"Preparing","progressDetail":{},"id":"d42a4fdf4b2a"} {"errorDetail":{"message":"test message"},"error":"test error"} ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/push-stream.json ================================================ { "status":"The push refers to repository [localhost:5000/ubuntu]" } {"status":"Preparing","progressDetail":{},"id":"782f5f011dda"} {"status":"Preparing","progressDetail":{},"id":"90ac32a0d9ab"} {"status":"Preparing","progressDetail":{},"id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":512,"total":7},"progress":"[==================================================\u003e] 512B","id":"782f5f011dda"} {"status":"Pushing","progressDetail":{"current":512,"total":811},"progress":"[===============================\u003e ] 512B/811B","id":"90ac32a0d9ab"} {"status":"Pushing","progressDetail":{"current":3072,"total":7},"progress":"[==================================================\u003e] 3.072kB","id":"782f5f011dda"} {"status":"Pushing","progressDetail":{"current":15360,"total":811},"progress":"[==================================================\u003e] 15.36kB","id":"90ac32a0d9ab"} {"status":"Pushing","progressDetail":{"current":543232,"total":72874905},"progress":"[\u003e ] 543.2kB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushed","progressDetail":{},"id":"90ac32a0d9ab"} {"status":"Pushed","progressDetail":{},"id":"782f5f011dda"} {"status":"Pushing","progressDetail":{"current":2713600,"total":72874905},"progress":"[=\u003e ] 2.714MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":4870656,"total":72874905},"progress":"[===\u003e ] 4.871MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":7069184,"total":72874905},"progress":"[====\u003e ] 7.069MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":9238528,"total":72874905},"progress":"[======\u003e ] 9.239MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":11354112,"total":72874905},"progress":"[=======\u003e ] 11.35MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":13582336,"total":72874905},"progress":"[=========\u003e ] 13.58MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":16336248,"total":72874905},"progress":"[===========\u003e ] 16.34MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":19036160,"total":72874905},"progress":"[=============\u003e ] 19.04MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":21762560,"total":72874905},"progress":"[==============\u003e ] 21.76MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":24480256,"total":72874905},"progress":"[================\u003e ] 24.48MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":28756480,"total":72874905},"progress":"[===================\u003e ] 28.76MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":32001024,"total":72874905},"progress":"[=====================\u003e ] 32MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":34195456,"total":72874905},"progress":"[=======================\u003e ] 34.2MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":36393984,"total":72874905},"progress":"[========================\u003e ] 36.39MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":38587904,"total":72874905},"progress":"[==========================\u003e ] 38.59MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":41290752,"total":72874905},"progress":"[============================\u003e ] 41.29MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":43487744,"total":72874905},"progress":"[=============================\u003e ] 43.49MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":45683200,"total":72874905},"progress":"[===============================\u003e ] 45.68MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":48413184,"total":72874905},"progress":"[=================================\u003e ] 48.41MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":51119104,"total":72874905},"progress":"[===================================\u003e ] 51.12MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":53327360,"total":72874905},"progress":"[====================================\u003e ] 53.33MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":54964224,"total":72874905},"progress":"[=====================================\u003e ] 54.96MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":57169408,"total":72874905},"progress":"[=======================================\u003e ] 57.17MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":59355825,"total":72874905},"progress":"[========================================\u003e ] 59.36MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":62002592,"total":72874905},"progress":"[==========================================\u003e ] 62MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":64700928,"total":72874905},"progress":"[============================================\u003e ] 64.7MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":67435688,"total":72874905},"progress":"[==============================================\u003e ] 67.44MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":70095743,"total":72874905},"progress":"[================================================\u003e ] 70.1MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":72823808,"total":72874905},"progress":"[=================================================\u003e ] 72.82MB/72.87MB","id":"d42a4fdf4b2a"} {"status":"Pushing","progressDetail":{"current":75247104,"total":72874905},"progress":"[==================================================\u003e] 75.25MB","id":"d42a4fdf4b2a"} {"status":"Pushed","progressDetail":{},"id":"d42a4fdf4b2a"} {"status":"latest: digest: sha256:2e70e9c81838224b5311970dbf7ed16802fbfe19e7a70b3cbfa3d7522aa285b4 size: 943"} {"progressDetail":{},"aux":{"Tag":"latest","Digest":"sha256:2e70e9c81838224b5311970dbf7ed16802fbfe19e7a70b3cbfa3d7522aa285b4","Size":943}} ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/transport/errors.json ================================================ { "errors": [ { "code": "TEST1", "message": "Test One", "detail": 123 }, { "code": "TEST2", "message": "Test Two", "detail": "fail" } ] } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/transport/message-and-errors.json ================================================ { "message": "test message", "errors": [ { "code": "TEST1", "message": "Test One", "detail": 123 }, { "code": "TEST2", "message": "Test Two", "detail": "fail" } ] } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/transport/message.json ================================================ { "message": "test message" } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/transport/proxy-error.txt ================================================ Some kind of proxy auth problem! ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/container-config.json ================================================ { "User": "root", "Image": "docker.io/library/ubuntu:bionic", "Cmd": [ "ls", "-l", "-h" ], "Env": [ "name1=value1", "name2=value2" ], "Labels": { "spring": "boot" }, "HostConfig": { "Binds": [ "bind-source:bind-dest" ], "NetworkMode": "test", "SecurityOpt": [ "option=value" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/container-status-error.json ================================================ { "StatusCode": 1, "Error": { "Message": "error detail" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/container-status-success.json ================================================ { "StatusCode": 0 } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/distribution-manifest-list.json ================================================ { "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", "manifests": [ { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 428, "digest": "sha256:6dba064234a3aa60f7da2e0f1f8b86dccb7df2841136f577b08bd6a89004cb23", "platform": { "architecture": "amd64", "os": "linux" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 428, "digest": "sha256:c036aba2c51a86a7a338f60af4730df725c2abff1b8b565d753896fd9533dfad", "platform": { "architecture": "arm64", "os": "linux" } } ] } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/distribution-manifest.json ================================================ { "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { "mediaType": "application/vnd.docker.container.image.v1+json", "size": 1175, "digest": "sha256:b2160a0f9037918d3ca2270fb90f656f425760b337a5ed3813c3a48c09825065" }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 4872935, "digest": "sha256:13ac7da0441b95b1960de1b87ed2c1ef129026cc69b926ffbe734a7dcc4fa40c" } ] } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-archive-config.json ================================================ { "Config": { "Hostname": "", "Domainname": "", "User": "vcap", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=2000", "CNB_GROUP_ID=2000", "CNB_STACK_ID=org.cloudfoundry.stacks.cflinuxfs3" ], "Cmd": null, "Image": "sha256:523c8ade6e06f814469b2cf04c8045a74becee17088955f2657958476d3fba1f", "Volumes": null, "WorkingDir": "/layers", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.stack.id": "org.cloudfoundry.stacks.cflinuxfs3" } }, "Created": "1980-01-01T00:00:01Z", "History": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "Architecture": "amd64", "Os": "linux", "Variant": "v1", "RootFS": { "diff_ids": [ "sha256:733a8e5ce32984099ef675fce04730f6e2a6dcfdf5bd292fea01a8f936265342", "sha256:7755b972f0b4f49de73ef5114fb3ba9c69d80f217e80da99f56f0d0a5dcb3d70", "sha256:8f0b2d09ab4b38530a1630403967d11a601e56e02e79d3f56370d34fd071fe38", "sha256:8debe4b6b4290dbbfecea9edea61c22fb455e69e3cbc7d63b17f8e1ab8ea669b", "sha256:0c6ddab305e5452850f3c09fe15310dff8dc7221702d736dc7705882c1df9658", "sha256:a9527973bb5d7ccdf88b5be8eb81e024094be1709df659af3127865463c1c188", "sha256:480cd420e43c6895240c87c88969b87417549c02393cde1b6f71a3a3d5a2a620", "sha256:391d950d763a33d8ae0373f218aa59907599f51e42cd864129591887e1291034", "sha256:5b3ec0a6ed9e3de93bb082151f56b1cde5d7e31f2809039a1b5b55a5052fe873", "sha256:ef935546e2c99da3e8962f2eb3cd6813e9e9a8b19bc8d15b56d1cac37f0342d5", "sha256:6d644992d62bd09a2bbf490b7fe3aa1e35e6d0d2479583c2decec7092f193310", "sha256:59d817c36a25078c8ec1f6de0d8336aec598037f89708ed13dbf661557a25084", "sha256:6636ce01d12372e56a89ec77ea8d9ed510f8c701df1220750add4613764c05a4", "sha256:96c7f369c29bbf11b971e4dbb6473e8991b666f9de046a414317634eb0a25d2a", "sha256:3544ba1fa82d1e89619ed04c2485fab3445b1603959d224792d1183dd658033d", "sha256:12d99406b52b526af152628cd72ba6eacf5d18484dc79cfdacd4b38a21620a2b", "sha256:3b3cda9eceb0fca56c274e3be93daf53f59501e6b3628fabbaea8ea416eb757a", "sha256:27ad0fc48c381eb77f69b4e80edccb4d8a2399f5cebd5a8c5a3e1c32313343a6", "sha256:52845fb94361dad36cc4136e49b92c79ca59c16c579e2f51df0c58ba355c4367", "sha256:7bf3a57229276fb913155b077d00a18ec6cba92c7f062728ca1c3bc3503c0b55", "sha256:e5df92d3db931488225ca9f7290de0334225d4bd7c48fc2dcd380d0921bb6680", "sha256:290ac64fbae3288821551371c8dda38fcf5dfa063a54cb270dcc395a090f5173", "sha256:996aee90e29ed78d80a5a0c0e50d60a732a18fddae06f87b68bef183beddd2c4", "sha256:30f6a316d4da01d694d8c17aa84b37f468cccc7184248e255486eb3095ebb87c", "sha256:c694476a7241ba4e4a0663606d4d6eec7ed8624252c010fbef2713968e8f9436", "sha256:ab9aaff2160873663388faea6d987cd8f2b5935137b81c64fde145bf2a330d54", "sha256:e1f3ab860045b96235cbc1b89a3e73add955a303eb42905b570b6012b73b9184", "sha256:0b260d90d097379d4351132b45110d013b98f4a335795baeb95788fcebcb7f3c", "sha256:f0f5ecd72b4e0a38d3ad73b5756d8f209955932e9615715502a61dffe56f401a", "sha256:b4cd790490e41c808e8d65f9ac8f2e58c79bc1a9919a713c4519e77b26dc2053", "sha256:16b88c0e7f950c32c7496117d1efad90a8557a2badcb267d99a19676b1f0b76a", "sha256:49d36ba00b17fb605f374ca7877ae129678de925d10fd1955f07c2b6f74dd1c9", "sha256:b31d189a88ca43fee6077c25bcb623582d569193ed6ac11b4e5623558911e3de", "sha256:3ecfd2822cf64c609c9c8489e2accfbc0b1de0f2a3637ff1b5d30768fb34b40c", "sha256:a7f09c3e09b29c5503962a068f29e8726cb91d1dbce2fab688aee0a98189b2be", "sha256:3d12e651068a0ff19afdd568b5d14ee5292f849542b31d6c9b099a09344e1f4d", "sha256:f01e41975a9335f5983021b081bc700e46b85efb262670223c4db61eea0a3ebd", "sha256:2b1b655bb8752f631e786c4c55670315d8569acccfe26402942977c216f2803a", "sha256:0943c634f5c24311ebdeca6fef5682a4a374c89a831700d188bff7f987470004", "sha256:9a183e56c86d376b408bdf922746d0a657f62b0e18c7c8f82a496b87710c576f", "sha256:d919f3c2f534ddbb0b6057f82bca36051ce80a2a9cd3016c320ae276884311f5", "sha256:108a3eb288f8094aab6ffd822c593902e48e85c8a37b7da2bd21b15f785d92c5", "sha256:f8b5dcfa1d082af23bb2b2c08526131921329d48d1614d9f2f163a997176087a", "sha256:ee13e75c33e0af49fbf6c3aaa5bbd102fc468c2d554c4f94763d35a33964dfe4", "sha256:2571abab1776d4c2e427fba10d61531afff2ab0789f89ef46ce925b6a5d98e0f", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef", "sha256:bb09e17fd1bd2ee47155f1349645fcd9fff31e1247c7ed99cad469f1c16a4216" ] } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-archive-index.json ================================================ { "schemaVersion": 2, "manifests": [ { "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", "digest": "sha256:3bbe02431d8e5124ffe816ec27bf6508b50edd1d10218be1a03e799a186b9004", "size": 529, "annotations": { "containerd.io/distribution.source.gcr.io": "paketo-buildpacks/adoptium", "io.containerd.image.name": "docker.io/paketobuildpacks/adoptium:latest", "org.opencontainers.image.ref.name": "latest" } } ] } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-archive-manifest.json ================================================ [ { "Config": "416c76dc7f691f91e80516ff039e056f32f996b59af4b1cb8114e6ae8171a374.json", "Layers": [ "blank_0", "blank_1", "blank_2", "blank_3", "blank_4", "blank_5", "blank_6", "blank_7", "blank_8", "blank_9", "blank_10", "blank_11", "blank_12", "blank_13", "blank_14", "blank_15", "blank_16", "blank_17", "blank_18", "blank_19", "blank_20", "blank_21", "blank_22", "blank_23", "blank_24", "blank_25", "blank_26", "blank_27", "blank_28", "blank_29", "blank_30", "blank_31", "blank_32", "blank_33", "blank_34", "blank_35", "blank_36", "blank_37", "blank_38", "blank_39", "blank_40", "blank_41", "blank_42", "blank_43", "blank_44", "blank_45", "bb09e17fd1bd2ee47155f1349645fcd9fff31e1247c7ed99cad469f1c16a4216.tar" ], "RepoTags": [ "pack.local/builder/6b7874626575656b6162:latest" ] } ] ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-config.json ================================================ { "Hostname": "", "Domainname": "", "User": "vcap", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=2000", "CNB_GROUP_ID=2000", "CNB_STACK_ID=org.cloudfoundry.stacks.cflinuxfs3" ], "Cmd": null, "Image": "sha256:523c8ade6e06f814469b2cf04c8045a74becee17088955f2657958476d3fba1f", "Volumes": null, "WorkingDir": "/layers", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.builder.metadata": "{\"description\":\"cflinuxfs3 base image with buildpacks for Java, .NET, NodeJS, Python, Golang, PHP, HTTPD and NGINX\",\"buildpacks\":[{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.0.40\",\"latest\":true},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.0.114\",\"latest\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.0.94\",\"latest\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.0.100\",\"latest\":true},{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.87\",\"latest\":true},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.0.72\",\"latest\":true},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.0.92\",\"latest\":true},{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.1\",\"latest\":true},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.0.37\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.2\",\"latest\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.0.94\",\"latest\":true},{\"id\":\"org.cloudfoundry.php\",\"version\":\"v0.0.0-RC1\",\"latest\":true},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.1.9\",\"latest\":true},{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v0.0.3\",\"latest\":true},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.0.94\",\"latest\":true},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.0.97\",\"latest\":true},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.0.89\",\"latest\":true},{\"id\":\"org.cloudfoundry.python\",\"version\":\"v0.0.1\",\"latest\":true},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.51\",\"latest\":true},{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.48\",\"latest\":true},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.44\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.18\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.57\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.66\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.55\",\"latest\":true},{\"id\":\"org.cloudfoundry.httpd\",\"version\":\"0.0.21\",\"latest\":true},{\"id\":\"org.cloudfoundry.nginx\",\"version\":\"0.0.25\",\"latest\":true},{\"id\":\"org.cloudfoundry.php-composer\",\"version\":\"0.0.16\",\"latest\":true},{\"id\":\"org.cloudfoundry.php-dist\",\"version\":\"0.0.30\",\"latest\":true},{\"id\":\"org.cloudfoundry.php-web\",\"version\":\"0.0.24\",\"latest\":true},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.85\",\"latest\":true},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.yarn\",\"version\":\"0.0.58\",\"latest\":true},{\"id\":\"org.cloudfoundry.conda\",\"version\":\"0.0.37\",\"latest\":true},{\"id\":\"org.cloudfoundry.pip\",\"version\":\"0.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.pipenv\",\"version\":\"0.0.38\",\"latest\":true},{\"id\":\"org.cloudfoundry.python-runtime\",\"version\":\"0.0.57\",\"latest\":true}],\"groups\":[{\"buildpacks\":[{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.87\",\"optional\":true},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.0.53\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.0.114\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.0.72\"},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.1.9\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.0.97\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.0.89\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.0.37\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.0.94\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.0.92\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.0.40\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.0.94\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.0.94\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.0.100\",\"optional\":true}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v0.0.3\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.python\",\"version\":\"v0.0.1\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.1\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.2\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.php\",\"version\":\"v0.0.0-RC1\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.httpd\",\"version\":\"0.0.21\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.nginx\",\"version\":\"0.0.25\"}]}],\"stack\":{\"runImage\":{\"image\":\"cloudfoundry/run:full-cnb\",\"mirrors\":null}},\"lifecycle\":{\"version\":\"0.5.0\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.1\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.5.0 (git sha: c9cfac75b49609524e1ea33f809c12071406547c)\"}}", "io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.87\":{\"layerDiffID\":\"sha256:391d950d763a33d8ae0373f218aa59907599f51e42cd864129591887e1291034\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.0.94\":{\"layerDiffID\":\"sha256:3544ba1fa82d1e89619ed04c2485fab3445b1603959d224792d1183dd658033d\"}},\"org.cloudfoundry.buildsystem\":{\"v1.0.114\":{\"layerDiffID\":\"sha256:0c6ddab305e5452850f3c09fe15310dff8dc7221702d736dc7705882c1df9658\"}},\"org.cloudfoundry.conda\":{\"0.0.37\":{\"layerDiffID\":\"sha256:0943c634f5c24311ebdeca6fef5682a4a374c89a831700d188bff7f987470004\"}},\"org.cloudfoundry.debug\":{\"v1.0.92\":{\"layerDiffID\":\"sha256:ef935546e2c99da3e8962f2eb3cd6813e9e9a8b19bc8d15b56d1cac37f0342d5\"}},\"org.cloudfoundry.dep\":{\"0.0.51\":{\"layerDiffID\":\"sha256:996aee90e29ed78d80a5a0c0e50d60a732a18fddae06f87b68bef183beddd2c4\"}},\"org.cloudfoundry.distzip\":{\"v1.0.89\":{\"layerDiffID\":\"sha256:e5df92d3db931488225ca9f7290de0334225d4bd7c48fc2dcd380d0921bb6680\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.2\":{\"layerDiffID\":\"sha256:96c7f369c29bbf11b971e4dbb6473e8991b666f9de046a414317634eb0a25d2a\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.66\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.53\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.55\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.18\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.57\"}]}]}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.53\":{\"layerDiffID\":\"sha256:ab9aaff2160873663388faea6d987cd8f2b5935137b81c64fde145bf2a330d54\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.18\":{\"layerDiffID\":\"sha256:e1f3ab860045b96235cbc1b89a3e73add955a303eb42905b570b6012b73b9184\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.57\":{\"layerDiffID\":\"sha256:0b260d90d097379d4351132b45110d013b98f4a335795baeb95788fcebcb7f3c\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.66\":{\"layerDiffID\":\"sha256:f0f5ecd72b4e0a38d3ad73b5756d8f209955932e9615715502a61dffe56f401a\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.55\":{\"layerDiffID\":\"sha256:b4cd790490e41c808e8d65f9ac8f2e58c79bc1a9919a713c4519e77b26dc2053\"}},\"org.cloudfoundry.go\":{\"v0.0.1\":{\"layerDiffID\":\"sha256:6d644992d62bd09a2bbf490b7fe3aa1e35e6d0d2479583c2decec7092f193310\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.48\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.44\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.48\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.51\"}]}]}},\"org.cloudfoundry.go-compiler\":{\"0.0.48\":{\"layerDiffID\":\"sha256:30f6a316d4da01d694d8c17aa84b37f468cccc7184248e255486eb3095ebb87c\"}},\"org.cloudfoundry.go-mod\":{\"0.0.44\":{\"layerDiffID\":\"sha256:c694476a7241ba4e4a0663606d4d6eec7ed8624252c010fbef2713968e8f9436\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.0.40\":{\"layerDiffID\":\"sha256:8debe4b6b4290dbbfecea9edea61c22fb455e69e3cbc7d63b17f8e1ab8ea669b\"}},\"org.cloudfoundry.httpd\":{\"0.0.21\":{\"layerDiffID\":\"sha256:16b88c0e7f950c32c7496117d1efad90a8557a2badcb267d99a19676b1f0b76a\"}},\"org.cloudfoundry.jdbc\":{\"v1.0.94\":{\"layerDiffID\":\"sha256:a9527973bb5d7ccdf88b5be8eb81e024094be1709df659af3127865463c1c188\"}},\"org.cloudfoundry.jmx\":{\"v1.0.94\":{\"layerDiffID\":\"sha256:52845fb94361dad36cc4136e49b92c79ca59c16c579e2f51df0c58ba355c4367\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.0.72\":{\"layerDiffID\":\"sha256:5b3ec0a6ed9e3de93bb082151f56b1cde5d7e31f2809039a1b5b55a5052fe873\"}},\"org.cloudfoundry.nginx\":{\"0.0.25\":{\"layerDiffID\":\"sha256:49d36ba00b17fb605f374ca7877ae129678de925d10fd1955f07c2b6f74dd1c9\"}},\"org.cloudfoundry.node-engine\":{\"0.0.85\":{\"layerDiffID\":\"sha256:3d12e651068a0ff19afdd568b5d14ee5292f849542b31d6c9b099a09344e1f4d\"}},\"org.cloudfoundry.nodejs\":{\"v0.0.3\":{\"layerDiffID\":\"sha256:27ad0fc48c381eb77f69b4e80edccb4d8a2399f5cebd5a8c5a3e1c32313343a6\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.85\"},{\"id\":\"org.cloudfoundry.yarn\",\"version\":\"0.0.58\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.85\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.0.53\"}]}]}},\"org.cloudfoundry.npm\":{\"0.0.53\":{\"layerDiffID\":\"sha256:f01e41975a9335f5983021b081bc700e46b85efb262670223c4db61eea0a3ebd\"}},\"org.cloudfoundry.openjdk\":{\"v1.0.53\":{\"layerDiffID\":\"sha256:59d817c36a25078c8ec1f6de0d8336aec598037f89708ed13dbf661557a25084\"}},\"org.cloudfoundry.php\":{\"v0.0.0-RC1\":{\"layerDiffID\":\"sha256:12d99406b52b526af152628cd72ba6eacf5d18484dc79cfdacd4b38a21620a2b\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.php-dist\",\"version\":\"0.0.30\"},{\"id\":\"org.cloudfoundry.php-composer\",\"version\":\"0.0.16\",\"optional\":true},{\"id\":\"org.cloudfoundry.httpd\",\"version\":\"0.0.21\",\"optional\":true},{\"id\":\"org.cloudfoundry.php-web\",\"version\":\"0.0.24\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.php-dist\",\"version\":\"0.0.30\"},{\"id\":\"org.cloudfoundry.php-composer\",\"version\":\"0.0.16\",\"optional\":true},{\"id\":\"org.cloudfoundry.nginx\",\"version\":\"0.0.25\",\"optional\":true},{\"id\":\"org.cloudfoundry.php-web\",\"version\":\"0.0.24\"}]}]}},\"org.cloudfoundry.php-composer\":{\"0.0.16\":{\"layerDiffID\":\"sha256:b31d189a88ca43fee6077c25bcb623582d569193ed6ac11b4e5623558911e3de\"}},\"org.cloudfoundry.php-dist\":{\"0.0.30\":{\"layerDiffID\":\"sha256:3ecfd2822cf64c609c9c8489e2accfbc0b1de0f2a3637ff1b5d30768fb34b40c\"}},\"org.cloudfoundry.php-web\":{\"0.0.24\":{\"layerDiffID\":\"sha256:a7f09c3e09b29c5503962a068f29e8726cb91d1dbce2fab688aee0a98189b2be\"}},\"org.cloudfoundry.pip\":{\"0.0.53\":{\"layerDiffID\":\"sha256:9a183e56c86d376b408bdf922746d0a657f62b0e18c7c8f82a496b87710c576f\"}},\"org.cloudfoundry.pipenv\":{\"0.0.38\":{\"layerDiffID\":\"sha256:d919f3c2f534ddbb0b6057f82bca36051ce80a2a9cd3016c320ae276884311f5\"}},\"org.cloudfoundry.procfile\":{\"v1.0.37\":{\"layerDiffID\":\"sha256:6636ce01d12372e56a89ec77ea8d9ed510f8c701df1220750add4613764c05a4\"}},\"org.cloudfoundry.python\":{\"v0.0.1\":{\"layerDiffID\":\"sha256:290ac64fbae3288821551371c8dda38fcf5dfa063a54cb270dcc395a090f5173\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.python-runtime\",\"version\":\"0.0.57\"},{\"id\":\"org.cloudfoundry.pipenv\",\"version\":\"0.0.38\",\"optional\":true},{\"id\":\"org.cloudfoundry.pip\",\"version\":\"0.0.53\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.conda\",\"version\":\"0.0.37\"}]}]}},\"org.cloudfoundry.python-runtime\":{\"0.0.57\":{\"layerDiffID\":\"sha256:108a3eb288f8094aab6ffd822c593902e48e85c8a37b7da2bd21b15f785d92c5\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.0.100\":{\"layerDiffID\":\"sha256:480cd420e43c6895240c87c88969b87417549c02393cde1b6f71a3a3d5a2a620\"}},\"org.cloudfoundry.springboot\":{\"v1.0.97\":{\"layerDiffID\":\"sha256:7bf3a57229276fb913155b077d00a18ec6cba92c7f062728ca1c3bc3503c0b55\"}},\"org.cloudfoundry.tomcat\":{\"v1.1.9\":{\"layerDiffID\":\"sha256:3b3cda9eceb0fca56c274e3be93daf53f59501e6b3628fabbaea8ea416eb757a\"}},\"org.cloudfoundry.yarn\":{\"0.0.58\":{\"layerDiffID\":\"sha256:2b1b655bb8752f631e786c4c55670315d8569acccfe26402942977c216f2803a\"}}}", "io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.archiveexpanding\",\"optional\":true},{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.python\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.php\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.httpd\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.nginx\"}]}]", "io.buildpacks.stack.id": "org.cloudfoundry.stacks.cflinuxfs3" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-empty-os.json ================================================ { "Id": "sha256:21635a6b4880772f3fabbf8b660907fa38636558cf787cc26f1779fc4b4e2cba", "RepoTags": [ "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1" ], "RepoDigests": [ "ghcr.io/spring-io/spring-boot-cnb-test-builder@sha256:21635a6b4880772f3fabbf8b660907fa38636558cf787cc26f1779fc4b4e2cba" ], "Parent": "", "Comment": "", "DockerVersion": "", "Author": "", "Config": null, "Architecture": "", "Os": "", "Size": 166797518, "GraphDriver": { "Data": null, "Name": "overlayfs" }, "RootFS": {}, "Metadata": { "LastTagTime": "2025-04-10T22:41:27.520294922Z" }, "Descriptor": { "mediaType": "application/vnd.oci.image.index.v1+json", "digest": "sha256:21635a6b4880772f3fabbf8b660907fa38636558cf787cc26f1779fc4b4e2cba", "size": 513 } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-manifest.json ================================================ { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "digest": "sha256:ee382dc5c080aa6af5ea716041eaa4442c9d461520388627dfe51709c679043e", "size": 849, "platform": { "architecture": "amd64", "os": "linux" } }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar", "digest": "sha256:5caae51697b248b905dca1a4160864b0e1a15c300981736555cdce6567e8d477", "size": 6656 } ] } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-no-descriptor.json ================================================ { "Id": "sha256:21635a6b4880772f3fabbf8b660907fa38636558cf787cc26f1779fc4b4e2cba", "RepoTags": [ "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1" ], "RepoDigests": [ "ghcr.io/spring-io/spring-boot-cnb-test-builder@sha256:21635a6b4880772f3fabbf8b660907fa38636558cf787cc26f1779fc4b4e2cba" ], "Parent": "", "Comment": "", "DockerVersion": "", "Author": "", "Config": null, "Architecture": "", "Os": "", "Size": 166797518, "GraphDriver": { "Data": null, "Name": "overlayfs" }, "RootFS": {}, "Metadata": { "LastTagTime": "2025-04-10T22:41:27.520294922Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-no-digest.json ================================================ { "Id": "sha256:21635a6b4880772f3fabbf8b660907fa38636558cf787cc26f1779fc4b4e2cba", "RepoTags": [ "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1" ], "Parent": "", "Comment": "", "DockerVersion": "", "Author": "", "Config": null, "Architecture": "", "Os": "", "Size": 166797518, "GraphDriver": { "Data": null, "Name": "overlayfs" }, "RootFS": {}, "Metadata": { "LastTagTime": "2025-04-10T22:41:27.520294922Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-non-default-os.json ================================================ { "Id": "sha256:21635a6b4880772f3fabbf8b660907fa38636558cf787cc26f1779fc4b4e2cba", "RepoTags": [ "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1" ], "RepoDigests": [ "ghcr.io/spring-io/spring-boot-cnb-test-builder@sha256:21635a6b4880772f3fabbf8b660907fa38636558cf787cc26f1779fc4b4e2cba" ], "Parent": "", "Comment": "", "DockerVersion": "", "Author": "", "Config": null, "Architecture": "", "Os": "windows", "Size": 166797518, "GraphDriver": { "Data": null, "Name": "overlayfs" }, "RootFS": {}, "Metadata": { "LastTagTime": "2025-04-10T22:41:27.520294922Z" }, "Descriptor": { "mediaType": "application/vnd.oci.image.index.v1+json", "digest": "sha256:21635a6b4880772f3fabbf8b660907fa38636558cf787cc26f1779fc4b4e2cba", "size": 513 } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-platform.json ================================================ { "Id": "sha256:9b450bffdb05bcf660d464d0bfdf344ee6ca38e9b8de4f408c8080b0c9319349", "RepoTags": [ "paketo-buildpacks/cnb:latest" ], "RepoDigests": [ "paketo-buildpacks/cnb@sha256:915802bb193b66e3fc1a5a8f5584c6a1b6db05425e573887673bddcf426f1b90" ], "Parent": "", "Comment": "", "Created": "2019-10-30T19:34:56.296666503Z", "Container": "84597380a7968131ab47dd1b8183a96dcfe9e1e4acff1efe5824dcd762184a67", "ContainerConfig": { "Hostname": "84597380a796", "Domainname": "", "User": "vcap", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=2000", "CNB_GROUP_ID=2000", "CNB_STACK_ID=org.cloudfoundry.stacks.cflinuxfs3" ], "Cmd": [ "/bin/sh", "-c", "#(nop) ", "LABEL io.buildpacks.stack.id=org.cloudfoundry.stacks.cflinuxfs3" ], "Image": "sha256:523c8ade6e06f814469b2cf04c8045a74becee17088955f2657958476d3fba1f", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.stack.id": "org.cloudfoundry.stacks.cflinuxfs3" } }, "DockerVersion": "18.09.6", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "vcap", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=2000", "CNB_GROUP_ID=2000", "CNB_STACK_ID=org.cloudfoundry.stacks.cflinuxfs3" ], "Cmd": null, "Image": "sha256:523c8ade6e06f814469b2cf04c8045a74becee17088955f2657958476d3fba1f", "Volumes": null, "WorkingDir": "/layers", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.builder.metadata": "{\"description\":\"cflinuxfs3 base image with buildpacks for Java, .NET, NodeJS, Python, Golang, PHP, HTTPD and NGINX\",\"buildpacks\":[{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.0.40\",\"latest\":true},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.0.114\",\"latest\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.0.94\",\"latest\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.0.100\",\"latest\":true},{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.87\",\"latest\":true},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.0.72\",\"latest\":true},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.0.92\",\"latest\":true},{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.1\",\"latest\":true},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.0.37\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.2\",\"latest\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.0.94\",\"latest\":true},{\"id\":\"org.cloudfoundry.php\",\"version\":\"v0.0.0-RC1\",\"latest\":true},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.1.9\",\"latest\":true},{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v0.0.3\",\"latest\":true},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.0.94\",\"latest\":true},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.0.97\",\"latest\":true},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.0.89\",\"latest\":true},{\"id\":\"org.cloudfoundry.python\",\"version\":\"v0.0.1\",\"latest\":true},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.51\",\"latest\":true},{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.48\",\"latest\":true},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.44\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.18\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.57\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.66\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.55\",\"latest\":true},{\"id\":\"org.cloudfoundry.httpd\",\"version\":\"0.0.21\",\"latest\":true},{\"id\":\"org.cloudfoundry.nginx\",\"version\":\"0.0.25\",\"latest\":true},{\"id\":\"org.cloudfoundry.php-composer\",\"version\":\"0.0.16\",\"latest\":true},{\"id\":\"org.cloudfoundry.php-dist\",\"version\":\"0.0.30\",\"latest\":true},{\"id\":\"org.cloudfoundry.php-web\",\"version\":\"0.0.24\",\"latest\":true},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.85\",\"latest\":true},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.yarn\",\"version\":\"0.0.58\",\"latest\":true},{\"id\":\"org.cloudfoundry.conda\",\"version\":\"0.0.37\",\"latest\":true},{\"id\":\"org.cloudfoundry.pip\",\"version\":\"0.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.pipenv\",\"version\":\"0.0.38\",\"latest\":true},{\"id\":\"org.cloudfoundry.python-runtime\",\"version\":\"0.0.57\",\"latest\":true}],\"groups\":[{\"buildpacks\":[{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.87\",\"optional\":true},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.0.53\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.0.114\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.0.72\"},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.1.9\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.0.97\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.0.89\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.0.37\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.0.94\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.0.92\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.0.40\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.0.94\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.0.94\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.0.100\",\"optional\":true}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v0.0.3\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.python\",\"version\":\"v0.0.1\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.1\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.2\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.php\",\"version\":\"v0.0.0-RC1\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.httpd\",\"version\":\"0.0.21\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.nginx\",\"version\":\"0.0.25\"}]}],\"stack\":{\"runImage\":{\"image\":\"cloudfoundry/run:full-cnb\",\"mirrors\":null}},\"lifecycle\":{\"version\":\"0.5.0\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.1\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.5.0 (git sha: c9cfac75b49609524e1ea33f809c12071406547c)\"}}", "io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.87\":{\"layerDiffID\":\"sha256:391d950d763a33d8ae0373f218aa59907599f51e42cd864129591887e1291034\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.0.94\":{\"layerDiffID\":\"sha256:3544ba1fa82d1e89619ed04c2485fab3445b1603959d224792d1183dd658033d\"}},\"org.cloudfoundry.buildsystem\":{\"v1.0.114\":{\"layerDiffID\":\"sha256:0c6ddab305e5452850f3c09fe15310dff8dc7221702d736dc7705882c1df9658\"}},\"org.cloudfoundry.conda\":{\"0.0.37\":{\"layerDiffID\":\"sha256:0943c634f5c24311ebdeca6fef5682a4a374c89a831700d188bff7f987470004\"}},\"org.cloudfoundry.debug\":{\"v1.0.92\":{\"layerDiffID\":\"sha256:ef935546e2c99da3e8962f2eb3cd6813e9e9a8b19bc8d15b56d1cac37f0342d5\"}},\"org.cloudfoundry.dep\":{\"0.0.51\":{\"layerDiffID\":\"sha256:996aee90e29ed78d80a5a0c0e50d60a732a18fddae06f87b68bef183beddd2c4\"}},\"org.cloudfoundry.distzip\":{\"v1.0.89\":{\"layerDiffID\":\"sha256:e5df92d3db931488225ca9f7290de0334225d4bd7c48fc2dcd380d0921bb6680\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.2\":{\"layerDiffID\":\"sha256:96c7f369c29bbf11b971e4dbb6473e8991b666f9de046a414317634eb0a25d2a\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.66\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.53\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.55\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.18\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.57\"}]}]}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.53\":{\"layerDiffID\":\"sha256:ab9aaff2160873663388faea6d987cd8f2b5935137b81c64fde145bf2a330d54\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.18\":{\"layerDiffID\":\"sha256:e1f3ab860045b96235cbc1b89a3e73add955a303eb42905b570b6012b73b9184\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.57\":{\"layerDiffID\":\"sha256:0b260d90d097379d4351132b45110d013b98f4a335795baeb95788fcebcb7f3c\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.66\":{\"layerDiffID\":\"sha256:f0f5ecd72b4e0a38d3ad73b5756d8f209955932e9615715502a61dffe56f401a\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.55\":{\"layerDiffID\":\"sha256:b4cd790490e41c808e8d65f9ac8f2e58c79bc1a9919a713c4519e77b26dc2053\"}},\"org.cloudfoundry.go\":{\"v0.0.1\":{\"layerDiffID\":\"sha256:6d644992d62bd09a2bbf490b7fe3aa1e35e6d0d2479583c2decec7092f193310\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.48\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.44\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.48\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.51\"}]}]}},\"org.cloudfoundry.go-compiler\":{\"0.0.48\":{\"layerDiffID\":\"sha256:30f6a316d4da01d694d8c17aa84b37f468cccc7184248e255486eb3095ebb87c\"}},\"org.cloudfoundry.go-mod\":{\"0.0.44\":{\"layerDiffID\":\"sha256:c694476a7241ba4e4a0663606d4d6eec7ed8624252c010fbef2713968e8f9436\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.0.40\":{\"layerDiffID\":\"sha256:8debe4b6b4290dbbfecea9edea61c22fb455e69e3cbc7d63b17f8e1ab8ea669b\"}},\"org.cloudfoundry.httpd\":{\"0.0.21\":{\"layerDiffID\":\"sha256:16b88c0e7f950c32c7496117d1efad90a8557a2badcb267d99a19676b1f0b76a\"}},\"org.cloudfoundry.jdbc\":{\"v1.0.94\":{\"layerDiffID\":\"sha256:a9527973bb5d7ccdf88b5be8eb81e024094be1709df659af3127865463c1c188\"}},\"org.cloudfoundry.jmx\":{\"v1.0.94\":{\"layerDiffID\":\"sha256:52845fb94361dad36cc4136e49b92c79ca59c16c579e2f51df0c58ba355c4367\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.0.72\":{\"layerDiffID\":\"sha256:5b3ec0a6ed9e3de93bb082151f56b1cde5d7e31f2809039a1b5b55a5052fe873\"}},\"org.cloudfoundry.nginx\":{\"0.0.25\":{\"layerDiffID\":\"sha256:49d36ba00b17fb605f374ca7877ae129678de925d10fd1955f07c2b6f74dd1c9\"}},\"org.cloudfoundry.node-engine\":{\"0.0.85\":{\"layerDiffID\":\"sha256:3d12e651068a0ff19afdd568b5d14ee5292f849542b31d6c9b099a09344e1f4d\"}},\"org.cloudfoundry.nodejs\":{\"v0.0.3\":{\"layerDiffID\":\"sha256:27ad0fc48c381eb77f69b4e80edccb4d8a2399f5cebd5a8c5a3e1c32313343a6\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.85\"},{\"id\":\"org.cloudfoundry.yarn\",\"version\":\"0.0.58\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.85\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.0.53\"}]}]}},\"org.cloudfoundry.npm\":{\"0.0.53\":{\"layerDiffID\":\"sha256:f01e41975a9335f5983021b081bc700e46b85efb262670223c4db61eea0a3ebd\"}},\"org.cloudfoundry.openjdk\":{\"v1.0.53\":{\"layerDiffID\":\"sha256:59d817c36a25078c8ec1f6de0d8336aec598037f89708ed13dbf661557a25084\"}},\"org.cloudfoundry.php\":{\"v0.0.0-RC1\":{\"layerDiffID\":\"sha256:12d99406b52b526af152628cd72ba6eacf5d18484dc79cfdacd4b38a21620a2b\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.php-dist\",\"version\":\"0.0.30\"},{\"id\":\"org.cloudfoundry.php-composer\",\"version\":\"0.0.16\",\"optional\":true},{\"id\":\"org.cloudfoundry.httpd\",\"version\":\"0.0.21\",\"optional\":true},{\"id\":\"org.cloudfoundry.php-web\",\"version\":\"0.0.24\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.php-dist\",\"version\":\"0.0.30\"},{\"id\":\"org.cloudfoundry.php-composer\",\"version\":\"0.0.16\",\"optional\":true},{\"id\":\"org.cloudfoundry.nginx\",\"version\":\"0.0.25\",\"optional\":true},{\"id\":\"org.cloudfoundry.php-web\",\"version\":\"0.0.24\"}]}]}},\"org.cloudfoundry.php-composer\":{\"0.0.16\":{\"layerDiffID\":\"sha256:b31d189a88ca43fee6077c25bcb623582d569193ed6ac11b4e5623558911e3de\"}},\"org.cloudfoundry.php-dist\":{\"0.0.30\":{\"layerDiffID\":\"sha256:3ecfd2822cf64c609c9c8489e2accfbc0b1de0f2a3637ff1b5d30768fb34b40c\"}},\"org.cloudfoundry.php-web\":{\"0.0.24\":{\"layerDiffID\":\"sha256:a7f09c3e09b29c5503962a068f29e8726cb91d1dbce2fab688aee0a98189b2be\"}},\"org.cloudfoundry.pip\":{\"0.0.53\":{\"layerDiffID\":\"sha256:9a183e56c86d376b408bdf922746d0a657f62b0e18c7c8f82a496b87710c576f\"}},\"org.cloudfoundry.pipenv\":{\"0.0.38\":{\"layerDiffID\":\"sha256:d919f3c2f534ddbb0b6057f82bca36051ce80a2a9cd3016c320ae276884311f5\"}},\"org.cloudfoundry.procfile\":{\"v1.0.37\":{\"layerDiffID\":\"sha256:6636ce01d12372e56a89ec77ea8d9ed510f8c701df1220750add4613764c05a4\"}},\"org.cloudfoundry.python\":{\"v0.0.1\":{\"layerDiffID\":\"sha256:290ac64fbae3288821551371c8dda38fcf5dfa063a54cb270dcc395a090f5173\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.python-runtime\",\"version\":\"0.0.57\"},{\"id\":\"org.cloudfoundry.pipenv\",\"version\":\"0.0.38\",\"optional\":true},{\"id\":\"org.cloudfoundry.pip\",\"version\":\"0.0.53\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.conda\",\"version\":\"0.0.37\"}]}]}},\"org.cloudfoundry.python-runtime\":{\"0.0.57\":{\"layerDiffID\":\"sha256:108a3eb288f8094aab6ffd822c593902e48e85c8a37b7da2bd21b15f785d92c5\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.0.100\":{\"layerDiffID\":\"sha256:480cd420e43c6895240c87c88969b87417549c02393cde1b6f71a3a3d5a2a620\"}},\"org.cloudfoundry.springboot\":{\"v1.0.97\":{\"layerDiffID\":\"sha256:7bf3a57229276fb913155b077d00a18ec6cba92c7f062728ca1c3bc3503c0b55\"}},\"org.cloudfoundry.tomcat\":{\"v1.1.9\":{\"layerDiffID\":\"sha256:3b3cda9eceb0fca56c274e3be93daf53f59501e6b3628fabbaea8ea416eb757a\"}},\"org.cloudfoundry.yarn\":{\"0.0.58\":{\"layerDiffID\":\"sha256:2b1b655bb8752f631e786c4c55670315d8569acccfe26402942977c216f2803a\"}}}", "io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.archiveexpanding\",\"optional\":true},{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.python\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.php\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.httpd\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.nginx\"}]}]", "io.buildpacks.stack.id": "org.cloudfoundry.stacks.cflinuxfs3" } }, "Os": "linux", "Architecture": "arm64", "Variant": "v1", "Size": 1559461360, "VirtualSize": 1559461360, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/58e30cd9f3a4da4e0d30f20c3b50de7655e261fb3d32f04818f1bd960c1e8b6c/diff:/var/lib/docker/overlay2/ad95d738069aa405ff17a9ebb1fdc32f8490b0dd885c3ba3a28e2c3b25d64641/diff:/var/lib/docker/overlay2/74d2896cfe9efc6945ff18870a7213583b987ecf4306e189ff6b793f77af5dcd/diff:/var/lib/docker/overlay2/1052615e5c240724e10928048f735cc9e7a7676a9af5f173b895df57c6921a40/diff:/var/lib/docker/overlay2/b5a62216c4282e7568e84427073f096551977c8c6f80d3a04ebb04c25730edde/diff:/var/lib/docker/overlay2/016a36bf7d7d7258eca08da62c01e47bf8e531531f914dde7cae33e191ab2218/diff:/var/lib/docker/overlay2/a585012bf1cf9da0472b2bbe86c4919355593e1a02cf399a9b012928eb816bcd/diff:/var/lib/docker/overlay2/b4aa8b70bd59d7b7dc6d6fb2e655c2334dc8360c764232f83d036d1f241e3298/diff:/var/lib/docker/overlay2/5f4cab16092522163e2dba6587b48d53ee3b09c8778b0736999bc120dd3753b1/diff:/var/lib/docker/overlay2/90e60622603d230f238976f4d9f65797fc9f070df62b1d2ccad0cefe4e205b43/diff:/var/lib/docker/overlay2/c43877934a580e47cc477ed46e71246468d7b6d7151abc5f1a97bb1e8c8104cf/diff:/var/lib/docker/overlay2/8734b165cabb3ff234a08d488f622135aeae9b7347cf41273445ff7d07aa4565/diff:/var/lib/docker/overlay2/2743cd9d4b7da84925b1b530732dad97108fe77e75865de580255579ba2cdb92/diff:/var/lib/docker/overlay2/68308d057b24bbcde7a4880f5db0e653743debdcc0ff3e736d1776296c4168a1/diff:/var/lib/docker/overlay2/7a4411dc4ac1ed7a1da9aabf088985b8b131e0db047e513f9890eb9c001c1895/diff:/var/lib/docker/overlay2/7f7c262fea8dea5ec86507188848ea391354a76468b09ec93523920e18a400ea/diff:/var/lib/docker/overlay2/8b3bfa567fb956204ad866e49489dacd2fdf5fbfa4f9b05ed3668e1106a5383b/diff:/var/lib/docker/overlay2/31bbc4f1616a35b7ce157266e44513963502e30d836a8fd7b7ee18436a8c46cf/diff:/var/lib/docker/overlay2/149b8e9f1142cdf6dcdfe17ea286ec17197f1a329cf23d5c82958a2032facf54/diff:/var/lib/docker/overlay2/92fb1e680083eb8314c5310bf10ced63ec2b0a98afbf84cc5175a98b3d44507a/diff:/var/lib/docker/overlay2/175a35b6f7af6eb91ca500dbd3d7e798f6d174cf8549881ffe5eed8e92a70b9f/diff:/var/lib/docker/overlay2/48ca54bbd27f7df19acf2b6cc719d05dd3b63f8133038a55d216a4498d4dc913/diff:/var/lib/docker/overlay2/ffe3cc3b93c9030f9dcb0e64c258d1e554f1f0cf27a0f8d4e98bb7ece5ffe882/diff:/var/lib/docker/overlay2/1fb2d962bb27e95c40a9a2c1aa910ca847d186d04e3d7dcdf93967101cc30dde/diff:/var/lib/docker/overlay2/10b34138f9e9e8d70c684d0a564452b1309363441b9d7e048f75e0e1179411dc/diff:/var/lib/docker/overlay2/1d888c7e9c62c22ccda6478f03f3df4b43d43fa3b32a2c2fdc9345fdc7193cd9/diff:/var/lib/docker/overlay2/649fc275c002d7336b277365636e1c8e5651bb3ed1557806d26dd6dfa1d9119a/diff:/var/lib/docker/overlay2/4484c2c0ee4a20aa17017c8cd54c842c876fea32afb297e88614d759ec5410dc/diff:/var/lib/docker/overlay2/bd5f374e0ea6749c90535d778f2689c076b7290ad9d3f050af0a40c9626fdea4/diff:/var/lib/docker/overlay2/c6ba97531b15be65bccaf7ebc866d8bc0b88ce838b224aceb196a55824b289a5/diff:/var/lib/docker/overlay2/6c65fab249fe652cd20a6391b2e0786379b6d2c7d4fde02914dfb4fac84035bd/diff:/var/lib/docker/overlay2/f391b54493024e0183331b8ec7835107bc1b84b8a6e77d852f5357724eb940ff/diff:/var/lib/docker/overlay2/8044f9e3ceb529c80531fa2fe52ad550286f788e69843f235e7d756b90c213b8/diff:/var/lib/docker/overlay2/7d3b5539c46c9f0e7c4f6f733f435d1bf6428a8ca81ba71f4da1031cef58aa6c/diff:/var/lib/docker/overlay2/b8080b36b0ddec4e4d738571ddf9d89815f6a95a555d282cfebb73519b4835a0/diff:/var/lib/docker/overlay2/8a737007d5862aa43119254122eb7050c8bd110a3b653c8d6afca23e76fc4042/diff:/var/lib/docker/overlay2/3bb8f3670831e2031be2173381caf02874ad72e664716a990a330bcc3454f4a2/diff:/var/lib/docker/overlay2/cbd675efde19ccac72d3566404e5df8b152a9063c1668d8154711c7db398f852/diff:/var/lib/docker/overlay2/84fb9095136cb645f7f15aeeeba1db6fae3999cb48a559daf8dd46bf3befbeba/diff:/var/lib/docker/overlay2/cbc51912822c4a3fb8624e0cf678e5dedeb76dc2fa0e5bc56f3cbfbfefb26d68/diff:/var/lib/docker/overlay2/d08d5bdcf39aaf46bdf1e0f4576bb64931af646213ff350065b4d306e00f7e28/diff:/var/lib/docker/overlay2/cf180c218fe181bdf836065c5e85103816ea9e8dbb8ab54fb311209c33455eb2/diff:/var/lib/docker/overlay2/b0aef801fd38973eaf116001e05e7c3f8e2eb58ccc7ed37a4bd8d4fcc2ad172b/diff:/var/lib/docker/overlay2/f73c585ae34bd962e1fee2c3e2d95d47b9daf68b23cf469fb13bc3282cf77238/diff:/var/lib/docker/overlay2/c071c8471b26e55a90b6573a21c581dec43b6c7683a3fe87cb33a0734c83342a/diff", "MergedDir": "/var/lib/docker/overlay2/41ced64ea40f3382f7a475030a5bc89b9c86e2a03d43031c5eba3c5c72616c2b/merged", "UpperDir": "/var/lib/docker/overlay2/41ced64ea40f3382f7a475030a5bc89b9c86e2a03d43031c5eba3c5c72616c2b/diff", "WorkDir": "/var/lib/docker/overlay2/41ced64ea40f3382f7a475030a5bc89b9c86e2a03d43031c5eba3c5c72616c2b/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:733a8e5ce32984099ef675fce04730f6e2a6dcfdf5bd292fea01a8f936265342", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image.json ================================================ { "Id": "sha256:9b450bffdb05bcf660d464d0bfdf344ee6ca38e9b8de4f408c8080b0c9319349", "Descriptor": { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96", "size": 424, "urls": [ "https://example.com" ], "annotations": { "com.docker.official-images.bashbrew.arch": "amd64", "org.opencontainers.image.version": "24.04" }, "data": null, "platform": { "architecture": "arm", "os": "windows", "os.version": "10.0.19041.1165", "os.features": [ ], "variant": "v7" }, "artifactType": null }, "RepoTags": [ "paketo-buildpacks/cnb:latest" ], "RepoDigests": [ "paketo-buildpacks/cnb@sha256:915802bb193b66e3fc1a5a8f5584c6a1b6db05425e573887673bddcf426f1b90" ], "Parent": "", "Comment": "", "Created": "2019-10-30T19:34:56.296666503Z", "Container": "84597380a7968131ab47dd1b8183a96dcfe9e1e4acff1efe5824dcd762184a67", "ContainerConfig": { "Hostname": "84597380a796", "Domainname": "", "User": "vcap", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=2000", "CNB_GROUP_ID=2000", "CNB_STACK_ID=org.cloudfoundry.stacks.cflinuxfs3" ], "Cmd": [ "/bin/sh", "-c", "#(nop) ", "LABEL io.buildpacks.stack.id=org.cloudfoundry.stacks.cflinuxfs3" ], "Image": "sha256:523c8ade6e06f814469b2cf04c8045a74becee17088955f2657958476d3fba1f", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.stack.id": "org.cloudfoundry.stacks.cflinuxfs3" } }, "DockerVersion": "18.09.6", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "vcap", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CNB_USER_ID=2000", "CNB_GROUP_ID=2000", "CNB_STACK_ID=org.cloudfoundry.stacks.cflinuxfs3" ], "Cmd": null, "Image": "sha256:523c8ade6e06f814469b2cf04c8045a74becee17088955f2657958476d3fba1f", "Volumes": null, "WorkingDir": "/layers", "Entrypoint": null, "OnBuild": null, "Labels": { "io.buildpacks.builder.metadata": "{\"description\":\"cflinuxfs3 base image with buildpacks for Java, .NET, NodeJS, Python, Golang, PHP, HTTPD and NGINX\",\"buildpacks\":[{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.0.40\",\"latest\":true},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.0.114\",\"latest\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.0.94\",\"latest\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.0.100\",\"latest\":true},{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.87\",\"latest\":true},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.0.72\",\"latest\":true},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.0.92\",\"latest\":true},{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.1\",\"latest\":true},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.0.37\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.2\",\"latest\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.0.94\",\"latest\":true},{\"id\":\"org.cloudfoundry.php\",\"version\":\"v0.0.0-RC1\",\"latest\":true},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.1.9\",\"latest\":true},{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v0.0.3\",\"latest\":true},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.0.94\",\"latest\":true},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.0.97\",\"latest\":true},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.0.89\",\"latest\":true},{\"id\":\"org.cloudfoundry.python\",\"version\":\"v0.0.1\",\"latest\":true},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.51\",\"latest\":true},{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.48\",\"latest\":true},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.44\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.18\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.57\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.66\",\"latest\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.55\",\"latest\":true},{\"id\":\"org.cloudfoundry.httpd\",\"version\":\"0.0.21\",\"latest\":true},{\"id\":\"org.cloudfoundry.nginx\",\"version\":\"0.0.25\",\"latest\":true},{\"id\":\"org.cloudfoundry.php-composer\",\"version\":\"0.0.16\",\"latest\":true},{\"id\":\"org.cloudfoundry.php-dist\",\"version\":\"0.0.30\",\"latest\":true},{\"id\":\"org.cloudfoundry.php-web\",\"version\":\"0.0.24\",\"latest\":true},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.85\",\"latest\":true},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.yarn\",\"version\":\"0.0.58\",\"latest\":true},{\"id\":\"org.cloudfoundry.conda\",\"version\":\"0.0.37\",\"latest\":true},{\"id\":\"org.cloudfoundry.pip\",\"version\":\"0.0.53\",\"latest\":true},{\"id\":\"org.cloudfoundry.pipenv\",\"version\":\"0.0.38\",\"latest\":true},{\"id\":\"org.cloudfoundry.python-runtime\",\"version\":\"0.0.57\",\"latest\":true}],\"groups\":[{\"buildpacks\":[{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.87\",\"optional\":true},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.0.53\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.0.114\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.0.72\"},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.1.9\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.0.97\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.0.89\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.0.37\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.0.94\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.0.92\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.0.40\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.0.94\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.0.94\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.0.100\",\"optional\":true}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v0.0.3\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.python\",\"version\":\"v0.0.1\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.1\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.2\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.php\",\"version\":\"v0.0.0-RC1\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.httpd\",\"version\":\"0.0.21\"}]},{\"buildpacks\":[{\"id\":\"org.cloudfoundry.nginx\",\"version\":\"0.0.25\"}]}],\"stack\":{\"runImage\":{\"image\":\"cloudfoundry/run:full-cnb\",\"mirrors\":null}},\"lifecycle\":{\"version\":\"0.5.0\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.1\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.5.0 (git sha: c9cfac75b49609524e1ea33f809c12071406547c)\"}}", "io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.87\":{\"layerDiffID\":\"sha256:391d950d763a33d8ae0373f218aa59907599f51e42cd864129591887e1291034\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.0.94\":{\"layerDiffID\":\"sha256:3544ba1fa82d1e89619ed04c2485fab3445b1603959d224792d1183dd658033d\"}},\"org.cloudfoundry.buildsystem\":{\"v1.0.114\":{\"layerDiffID\":\"sha256:0c6ddab305e5452850f3c09fe15310dff8dc7221702d736dc7705882c1df9658\"}},\"org.cloudfoundry.conda\":{\"0.0.37\":{\"layerDiffID\":\"sha256:0943c634f5c24311ebdeca6fef5682a4a374c89a831700d188bff7f987470004\"}},\"org.cloudfoundry.debug\":{\"v1.0.92\":{\"layerDiffID\":\"sha256:ef935546e2c99da3e8962f2eb3cd6813e9e9a8b19bc8d15b56d1cac37f0342d5\"}},\"org.cloudfoundry.dep\":{\"0.0.51\":{\"layerDiffID\":\"sha256:996aee90e29ed78d80a5a0c0e50d60a732a18fddae06f87b68bef183beddd2c4\"}},\"org.cloudfoundry.distzip\":{\"v1.0.89\":{\"layerDiffID\":\"sha256:e5df92d3db931488225ca9f7290de0334225d4bd7c48fc2dcd380d0921bb6680\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.2\":{\"layerDiffID\":\"sha256:96c7f369c29bbf11b971e4dbb6473e8991b666f9de046a414317634eb0a25d2a\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.66\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.53\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.55\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.18\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.57\"}]}]}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.53\":{\"layerDiffID\":\"sha256:ab9aaff2160873663388faea6d987cd8f2b5935137b81c64fde145bf2a330d54\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.18\":{\"layerDiffID\":\"sha256:e1f3ab860045b96235cbc1b89a3e73add955a303eb42905b570b6012b73b9184\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.57\":{\"layerDiffID\":\"sha256:0b260d90d097379d4351132b45110d013b98f4a335795baeb95788fcebcb7f3c\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.66\":{\"layerDiffID\":\"sha256:f0f5ecd72b4e0a38d3ad73b5756d8f209955932e9615715502a61dffe56f401a\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.55\":{\"layerDiffID\":\"sha256:b4cd790490e41c808e8d65f9ac8f2e58c79bc1a9919a713c4519e77b26dc2053\"}},\"org.cloudfoundry.go\":{\"v0.0.1\":{\"layerDiffID\":\"sha256:6d644992d62bd09a2bbf490b7fe3aa1e35e6d0d2479583c2decec7092f193310\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.48\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.44\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.48\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.51\"}]}]}},\"org.cloudfoundry.go-compiler\":{\"0.0.48\":{\"layerDiffID\":\"sha256:30f6a316d4da01d694d8c17aa84b37f468cccc7184248e255486eb3095ebb87c\"}},\"org.cloudfoundry.go-mod\":{\"0.0.44\":{\"layerDiffID\":\"sha256:c694476a7241ba4e4a0663606d4d6eec7ed8624252c010fbef2713968e8f9436\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.0.40\":{\"layerDiffID\":\"sha256:8debe4b6b4290dbbfecea9edea61c22fb455e69e3cbc7d63b17f8e1ab8ea669b\"}},\"org.cloudfoundry.httpd\":{\"0.0.21\":{\"layerDiffID\":\"sha256:16b88c0e7f950c32c7496117d1efad90a8557a2badcb267d99a19676b1f0b76a\"}},\"org.cloudfoundry.jdbc\":{\"v1.0.94\":{\"layerDiffID\":\"sha256:a9527973bb5d7ccdf88b5be8eb81e024094be1709df659af3127865463c1c188\"}},\"org.cloudfoundry.jmx\":{\"v1.0.94\":{\"layerDiffID\":\"sha256:52845fb94361dad36cc4136e49b92c79ca59c16c579e2f51df0c58ba355c4367\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.0.72\":{\"layerDiffID\":\"sha256:5b3ec0a6ed9e3de93bb082151f56b1cde5d7e31f2809039a1b5b55a5052fe873\"}},\"org.cloudfoundry.nginx\":{\"0.0.25\":{\"layerDiffID\":\"sha256:49d36ba00b17fb605f374ca7877ae129678de925d10fd1955f07c2b6f74dd1c9\"}},\"org.cloudfoundry.node-engine\":{\"0.0.85\":{\"layerDiffID\":\"sha256:3d12e651068a0ff19afdd568b5d14ee5292f849542b31d6c9b099a09344e1f4d\"}},\"org.cloudfoundry.nodejs\":{\"v0.0.3\":{\"layerDiffID\":\"sha256:27ad0fc48c381eb77f69b4e80edccb4d8a2399f5cebd5a8c5a3e1c32313343a6\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.85\"},{\"id\":\"org.cloudfoundry.yarn\",\"version\":\"0.0.58\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.85\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.0.53\"}]}]}},\"org.cloudfoundry.npm\":{\"0.0.53\":{\"layerDiffID\":\"sha256:f01e41975a9335f5983021b081bc700e46b85efb262670223c4db61eea0a3ebd\"}},\"org.cloudfoundry.openjdk\":{\"v1.0.53\":{\"layerDiffID\":\"sha256:59d817c36a25078c8ec1f6de0d8336aec598037f89708ed13dbf661557a25084\"}},\"org.cloudfoundry.php\":{\"v0.0.0-RC1\":{\"layerDiffID\":\"sha256:12d99406b52b526af152628cd72ba6eacf5d18484dc79cfdacd4b38a21620a2b\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.php-dist\",\"version\":\"0.0.30\"},{\"id\":\"org.cloudfoundry.php-composer\",\"version\":\"0.0.16\",\"optional\":true},{\"id\":\"org.cloudfoundry.httpd\",\"version\":\"0.0.21\",\"optional\":true},{\"id\":\"org.cloudfoundry.php-web\",\"version\":\"0.0.24\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.php-dist\",\"version\":\"0.0.30\"},{\"id\":\"org.cloudfoundry.php-composer\",\"version\":\"0.0.16\",\"optional\":true},{\"id\":\"org.cloudfoundry.nginx\",\"version\":\"0.0.25\",\"optional\":true},{\"id\":\"org.cloudfoundry.php-web\",\"version\":\"0.0.24\"}]}]}},\"org.cloudfoundry.php-composer\":{\"0.0.16\":{\"layerDiffID\":\"sha256:b31d189a88ca43fee6077c25bcb623582d569193ed6ac11b4e5623558911e3de\"}},\"org.cloudfoundry.php-dist\":{\"0.0.30\":{\"layerDiffID\":\"sha256:3ecfd2822cf64c609c9c8489e2accfbc0b1de0f2a3637ff1b5d30768fb34b40c\"}},\"org.cloudfoundry.php-web\":{\"0.0.24\":{\"layerDiffID\":\"sha256:a7f09c3e09b29c5503962a068f29e8726cb91d1dbce2fab688aee0a98189b2be\"}},\"org.cloudfoundry.pip\":{\"0.0.53\":{\"layerDiffID\":\"sha256:9a183e56c86d376b408bdf922746d0a657f62b0e18c7c8f82a496b87710c576f\"}},\"org.cloudfoundry.pipenv\":{\"0.0.38\":{\"layerDiffID\":\"sha256:d919f3c2f534ddbb0b6057f82bca36051ce80a2a9cd3016c320ae276884311f5\"}},\"org.cloudfoundry.procfile\":{\"v1.0.37\":{\"layerDiffID\":\"sha256:6636ce01d12372e56a89ec77ea8d9ed510f8c701df1220750add4613764c05a4\"}},\"org.cloudfoundry.python\":{\"v0.0.1\":{\"layerDiffID\":\"sha256:290ac64fbae3288821551371c8dda38fcf5dfa063a54cb270dcc395a090f5173\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.python-runtime\",\"version\":\"0.0.57\"},{\"id\":\"org.cloudfoundry.pipenv\",\"version\":\"0.0.38\",\"optional\":true},{\"id\":\"org.cloudfoundry.pip\",\"version\":\"0.0.53\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.conda\",\"version\":\"0.0.37\"}]}]}},\"org.cloudfoundry.python-runtime\":{\"0.0.57\":{\"layerDiffID\":\"sha256:108a3eb288f8094aab6ffd822c593902e48e85c8a37b7da2bd21b15f785d92c5\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.0.100\":{\"layerDiffID\":\"sha256:480cd420e43c6895240c87c88969b87417549c02393cde1b6f71a3a3d5a2a620\"}},\"org.cloudfoundry.springboot\":{\"v1.0.97\":{\"layerDiffID\":\"sha256:7bf3a57229276fb913155b077d00a18ec6cba92c7f062728ca1c3bc3503c0b55\"}},\"org.cloudfoundry.tomcat\":{\"v1.1.9\":{\"layerDiffID\":\"sha256:3b3cda9eceb0fca56c274e3be93daf53f59501e6b3628fabbaea8ea416eb757a\"}},\"org.cloudfoundry.yarn\":{\"0.0.58\":{\"layerDiffID\":\"sha256:2b1b655bb8752f631e786c4c55670315d8569acccfe26402942977c216f2803a\"}}}", "io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.archiveexpanding\",\"optional\":true},{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.python\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.php\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.httpd\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.nginx\"}]}]", "io.buildpacks.stack.id": "org.cloudfoundry.stacks.cflinuxfs3" } }, "Os": "linux", "Architecture": "amd64", "Variant": "v1", "Size": 1559461360, "VirtualSize": 1559461360, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/58e30cd9f3a4da4e0d30f20c3b50de7655e261fb3d32f04818f1bd960c1e8b6c/diff:/var/lib/docker/overlay2/ad95d738069aa405ff17a9ebb1fdc32f8490b0dd885c3ba3a28e2c3b25d64641/diff:/var/lib/docker/overlay2/74d2896cfe9efc6945ff18870a7213583b987ecf4306e189ff6b793f77af5dcd/diff:/var/lib/docker/overlay2/1052615e5c240724e10928048f735cc9e7a7676a9af5f173b895df57c6921a40/diff:/var/lib/docker/overlay2/b5a62216c4282e7568e84427073f096551977c8c6f80d3a04ebb04c25730edde/diff:/var/lib/docker/overlay2/016a36bf7d7d7258eca08da62c01e47bf8e531531f914dde7cae33e191ab2218/diff:/var/lib/docker/overlay2/a585012bf1cf9da0472b2bbe86c4919355593e1a02cf399a9b012928eb816bcd/diff:/var/lib/docker/overlay2/b4aa8b70bd59d7b7dc6d6fb2e655c2334dc8360c764232f83d036d1f241e3298/diff:/var/lib/docker/overlay2/5f4cab16092522163e2dba6587b48d53ee3b09c8778b0736999bc120dd3753b1/diff:/var/lib/docker/overlay2/90e60622603d230f238976f4d9f65797fc9f070df62b1d2ccad0cefe4e205b43/diff:/var/lib/docker/overlay2/c43877934a580e47cc477ed46e71246468d7b6d7151abc5f1a97bb1e8c8104cf/diff:/var/lib/docker/overlay2/8734b165cabb3ff234a08d488f622135aeae9b7347cf41273445ff7d07aa4565/diff:/var/lib/docker/overlay2/2743cd9d4b7da84925b1b530732dad97108fe77e75865de580255579ba2cdb92/diff:/var/lib/docker/overlay2/68308d057b24bbcde7a4880f5db0e653743debdcc0ff3e736d1776296c4168a1/diff:/var/lib/docker/overlay2/7a4411dc4ac1ed7a1da9aabf088985b8b131e0db047e513f9890eb9c001c1895/diff:/var/lib/docker/overlay2/7f7c262fea8dea5ec86507188848ea391354a76468b09ec93523920e18a400ea/diff:/var/lib/docker/overlay2/8b3bfa567fb956204ad866e49489dacd2fdf5fbfa4f9b05ed3668e1106a5383b/diff:/var/lib/docker/overlay2/31bbc4f1616a35b7ce157266e44513963502e30d836a8fd7b7ee18436a8c46cf/diff:/var/lib/docker/overlay2/149b8e9f1142cdf6dcdfe17ea286ec17197f1a329cf23d5c82958a2032facf54/diff:/var/lib/docker/overlay2/92fb1e680083eb8314c5310bf10ced63ec2b0a98afbf84cc5175a98b3d44507a/diff:/var/lib/docker/overlay2/175a35b6f7af6eb91ca500dbd3d7e798f6d174cf8549881ffe5eed8e92a70b9f/diff:/var/lib/docker/overlay2/48ca54bbd27f7df19acf2b6cc719d05dd3b63f8133038a55d216a4498d4dc913/diff:/var/lib/docker/overlay2/ffe3cc3b93c9030f9dcb0e64c258d1e554f1f0cf27a0f8d4e98bb7ece5ffe882/diff:/var/lib/docker/overlay2/1fb2d962bb27e95c40a9a2c1aa910ca847d186d04e3d7dcdf93967101cc30dde/diff:/var/lib/docker/overlay2/10b34138f9e9e8d70c684d0a564452b1309363441b9d7e048f75e0e1179411dc/diff:/var/lib/docker/overlay2/1d888c7e9c62c22ccda6478f03f3df4b43d43fa3b32a2c2fdc9345fdc7193cd9/diff:/var/lib/docker/overlay2/649fc275c002d7336b277365636e1c8e5651bb3ed1557806d26dd6dfa1d9119a/diff:/var/lib/docker/overlay2/4484c2c0ee4a20aa17017c8cd54c842c876fea32afb297e88614d759ec5410dc/diff:/var/lib/docker/overlay2/bd5f374e0ea6749c90535d778f2689c076b7290ad9d3f050af0a40c9626fdea4/diff:/var/lib/docker/overlay2/c6ba97531b15be65bccaf7ebc866d8bc0b88ce838b224aceb196a55824b289a5/diff:/var/lib/docker/overlay2/6c65fab249fe652cd20a6391b2e0786379b6d2c7d4fde02914dfb4fac84035bd/diff:/var/lib/docker/overlay2/f391b54493024e0183331b8ec7835107bc1b84b8a6e77d852f5357724eb940ff/diff:/var/lib/docker/overlay2/8044f9e3ceb529c80531fa2fe52ad550286f788e69843f235e7d756b90c213b8/diff:/var/lib/docker/overlay2/7d3b5539c46c9f0e7c4f6f733f435d1bf6428a8ca81ba71f4da1031cef58aa6c/diff:/var/lib/docker/overlay2/b8080b36b0ddec4e4d738571ddf9d89815f6a95a555d282cfebb73519b4835a0/diff:/var/lib/docker/overlay2/8a737007d5862aa43119254122eb7050c8bd110a3b653c8d6afca23e76fc4042/diff:/var/lib/docker/overlay2/3bb8f3670831e2031be2173381caf02874ad72e664716a990a330bcc3454f4a2/diff:/var/lib/docker/overlay2/cbd675efde19ccac72d3566404e5df8b152a9063c1668d8154711c7db398f852/diff:/var/lib/docker/overlay2/84fb9095136cb645f7f15aeeeba1db6fae3999cb48a559daf8dd46bf3befbeba/diff:/var/lib/docker/overlay2/cbc51912822c4a3fb8624e0cf678e5dedeb76dc2fa0e5bc56f3cbfbfefb26d68/diff:/var/lib/docker/overlay2/d08d5bdcf39aaf46bdf1e0f4576bb64931af646213ff350065b4d306e00f7e28/diff:/var/lib/docker/overlay2/cf180c218fe181bdf836065c5e85103816ea9e8dbb8ab54fb311209c33455eb2/diff:/var/lib/docker/overlay2/b0aef801fd38973eaf116001e05e7c3f8e2eb58ccc7ed37a4bd8d4fcc2ad172b/diff:/var/lib/docker/overlay2/f73c585ae34bd962e1fee2c3e2d95d47b9daf68b23cf469fb13bc3282cf77238/diff:/var/lib/docker/overlay2/c071c8471b26e55a90b6573a21c581dec43b6c7683a3fe87cb33a0734c83342a/diff", "MergedDir": "/var/lib/docker/overlay2/41ced64ea40f3382f7a475030a5bc89b9c86e2a03d43031c5eba3c5c72616c2b/merged", "UpperDir": "/var/lib/docker/overlay2/41ced64ea40f3382f7a475030a5bc89b9c86e2a03d43031c5eba3c5c72616c2b/diff", "WorkDir": "/var/lib/docker/overlay2/41ced64ea40f3382f7a475030a5bc89b9c86e2a03d43031c5eba3c5c72616c2b/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:733a8e5ce32984099ef675fce04730f6e2a6dcfdf5bd292fea01a8f936265342", "sha256:7755b972f0b4f49de73ef5114fb3ba9c69d80f217e80da99f56f0d0a5dcb3d70", "sha256:8f0b2d09ab4b38530a1630403967d11a601e56e02e79d3f56370d34fd071fe38", "sha256:8debe4b6b4290dbbfecea9edea61c22fb455e69e3cbc7d63b17f8e1ab8ea669b", "sha256:0c6ddab305e5452850f3c09fe15310dff8dc7221702d736dc7705882c1df9658", "sha256:a9527973bb5d7ccdf88b5be8eb81e024094be1709df659af3127865463c1c188", "sha256:480cd420e43c6895240c87c88969b87417549c02393cde1b6f71a3a3d5a2a620", "sha256:391d950d763a33d8ae0373f218aa59907599f51e42cd864129591887e1291034", "sha256:5b3ec0a6ed9e3de93bb082151f56b1cde5d7e31f2809039a1b5b55a5052fe873", "sha256:ef935546e2c99da3e8962f2eb3cd6813e9e9a8b19bc8d15b56d1cac37f0342d5", "sha256:6d644992d62bd09a2bbf490b7fe3aa1e35e6d0d2479583c2decec7092f193310", "sha256:59d817c36a25078c8ec1f6de0d8336aec598037f89708ed13dbf661557a25084", "sha256:6636ce01d12372e56a89ec77ea8d9ed510f8c701df1220750add4613764c05a4", "sha256:96c7f369c29bbf11b971e4dbb6473e8991b666f9de046a414317634eb0a25d2a", "sha256:3544ba1fa82d1e89619ed04c2485fab3445b1603959d224792d1183dd658033d", "sha256:12d99406b52b526af152628cd72ba6eacf5d18484dc79cfdacd4b38a21620a2b", "sha256:3b3cda9eceb0fca56c274e3be93daf53f59501e6b3628fabbaea8ea416eb757a", "sha256:27ad0fc48c381eb77f69b4e80edccb4d8a2399f5cebd5a8c5a3e1c32313343a6", "sha256:52845fb94361dad36cc4136e49b92c79ca59c16c579e2f51df0c58ba355c4367", "sha256:7bf3a57229276fb913155b077d00a18ec6cba92c7f062728ca1c3bc3503c0b55", "sha256:e5df92d3db931488225ca9f7290de0334225d4bd7c48fc2dcd380d0921bb6680", "sha256:290ac64fbae3288821551371c8dda38fcf5dfa063a54cb270dcc395a090f5173", "sha256:996aee90e29ed78d80a5a0c0e50d60a732a18fddae06f87b68bef183beddd2c4", "sha256:30f6a316d4da01d694d8c17aa84b37f468cccc7184248e255486eb3095ebb87c", "sha256:c694476a7241ba4e4a0663606d4d6eec7ed8624252c010fbef2713968e8f9436", "sha256:ab9aaff2160873663388faea6d987cd8f2b5935137b81c64fde145bf2a330d54", "sha256:e1f3ab860045b96235cbc1b89a3e73add955a303eb42905b570b6012b73b9184", "sha256:0b260d90d097379d4351132b45110d013b98f4a335795baeb95788fcebcb7f3c", "sha256:f0f5ecd72b4e0a38d3ad73b5756d8f209955932e9615715502a61dffe56f401a", "sha256:b4cd790490e41c808e8d65f9ac8f2e58c79bc1a9919a713c4519e77b26dc2053", "sha256:16b88c0e7f950c32c7496117d1efad90a8557a2badcb267d99a19676b1f0b76a", "sha256:49d36ba00b17fb605f374ca7877ae129678de925d10fd1955f07c2b6f74dd1c9", "sha256:b31d189a88ca43fee6077c25bcb623582d569193ed6ac11b4e5623558911e3de", "sha256:3ecfd2822cf64c609c9c8489e2accfbc0b1de0f2a3637ff1b5d30768fb34b40c", "sha256:a7f09c3e09b29c5503962a068f29e8726cb91d1dbce2fab688aee0a98189b2be", "sha256:3d12e651068a0ff19afdd568b5d14ee5292f849542b31d6c9b099a09344e1f4d", "sha256:f01e41975a9335f5983021b081bc700e46b85efb262670223c4db61eea0a3ebd", "sha256:2b1b655bb8752f631e786c4c55670315d8569acccfe26402942977c216f2803a", "sha256:0943c634f5c24311ebdeca6fef5682a4a374c89a831700d188bff7f987470004", "sha256:9a183e56c86d376b408bdf922746d0a657f62b0e18c7c8f82a496b87710c576f", "sha256:d919f3c2f534ddbb0b6057f82bca36051ce80a2a9cd3016c320ae276884311f5", "sha256:108a3eb288f8094aab6ffd822c593902e48e85c8a37b7da2bd21b15f785d92c5", "sha256:f8b5dcfa1d082af23bb2b2c08526131921329d48d1614d9f2f163a997176087a", "sha256:ee13e75c33e0af49fbf6c3aaa5bbd102fc468c2d554c4f94763d35a33964dfe4", "sha256:2571abab1776d4c2e427fba10d61531afff2ab0789f89ef46ce925b6a5d98e0f", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/manifest.json ================================================ [ { "Config": "fdc5f384ea0818dd99462e53bf2088a0fa42ad4de5878fdf078935192604da6d.json", "Layers": [ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "/e39b9186d3d35693645f81db5ec6ced177c4da2d26f71a55de7834fc3b161a60.tar", "/791b31c608b369f0d6e23aaf55dd6bae76ffd92292afd3eb4dd35f8a389636fb.tar", "/66d1ab676a2ecb3852104177d2fd9499d90bbbd97984bccb62180502e15a7086.tar", "/b5787d8d30d02769ebbe6b1ac32d37764feef3cd5cdc68aeffd72bb27d1886e5.tar" ], "RepoTags": [ "pack.local/builder/6b7874626575656b6162:latest" ] } ] ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/minimal-image-config.json ================================================ { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": null, "Image": "", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null } ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/json/stream.json ================================================ { "status": "Pulling from paketo-buildpacks/cnb", "id": "base" } {"status":"Pulling fs layer","progressDetail":{},"id":"5667fdb72017"} {"status":"Pulling fs layer","progressDetail":{},"id":"d83811f270d5"} {"status":"Pulling fs layer","progressDetail":{},"id":"ee671aafb583"} {"status":"Pulling fs layer","progressDetail":{},"id":"7fc152dfb3a6"} {"status":"Pulling fs layer","progressDetail":{},"id":"4ab897fa6fbf"} {"status":"Pulling fs layer","progressDetail":{},"id":"d837a2a1365e"} {"status":"Pulling fs layer","progressDetail":{},"id":"988ae18fe41a"} {"status":"Pulling fs layer","progressDetail":{},"id":"eeb8ef83b565"} {"status":"Pulling fs layer","progressDetail":{},"id":"357fefdf9bc9"} {"status":"Pulling fs layer","progressDetail":{},"id":"45b746196f82"} {"status":"Pulling fs layer","progressDetail":{},"id":"fbf4ce20f8c2"} {"status":"Pulling fs layer","progressDetail":{},"id":"90aca3c647fe"} {"status":"Pulling fs layer","progressDetail":{},"id":"1dd62f37c84c"} {"status":"Pulling fs layer","progressDetail":{},"id":"3192b2fa42db"} {"status":"Pulling fs layer","progressDetail":{},"id":"ae190b8f66a7"} {"status":"Pulling fs layer","progressDetail":{},"id":"97bb6e138460"} {"status":"Waiting","progressDetail":{},"id":"eeb8ef83b565"} {"status":"Pulling fs layer","progressDetail":{},"id":"2edb982d5170"} {"status":"Waiting","progressDetail":{},"id":"357fefdf9bc9"} {"status":"Pulling fs layer","progressDetail":{},"id":"7ddc8e6d6da9"} {"status":"Pulling fs layer","progressDetail":{},"id":"0df6fd234b59"} {"status":"Waiting","progressDetail":{},"id":"45b746196f82"} {"status":"Pulling fs layer","progressDetail":{},"id":"8fc1ba8efe21"} {"status":"Pulling fs layer","progressDetail":{},"id":"1f6f45e783b5"} {"status":"Pulling fs layer","progressDetail":{},"id":"43ea61082f68"} {"status":"Waiting","progressDetail":{},"id":"7fc152dfb3a6"} {"status":"Pulling fs layer","progressDetail":{},"id":"b8cf53bbc6ba"} {"status":"Waiting","progressDetail":{},"id":"fbf4ce20f8c2"} {"status":"Pulling fs layer","progressDetail":{},"id":"25efb07e4521"} {"status":"Waiting","progressDetail":{},"id":"90aca3c647fe"} {"status":"Pulling fs layer","progressDetail":{},"id":"1c3245356213"} {"status":"Waiting","progressDetail":{},"id":"1dd62f37c84c"} {"status":"Pulling fs layer","progressDetail":{},"id":"61ebb123c1eb"} {"status":"Pulling fs layer","progressDetail":{},"id":"0964b769d2c9"} {"status":"Waiting","progressDetail":{},"id":"3192b2fa42db"} {"status":"Pulling fs layer","progressDetail":{},"id":"87f7843f43cd"} {"status":"Pulling fs layer","progressDetail":{},"id":"a89dbf94d794"} {"status":"Waiting","progressDetail":{},"id":"ae190b8f66a7"} {"status":"Pulling fs layer","progressDetail":{},"id":"f0d43ddca77f"} {"status":"Pulling fs layer","progressDetail":{},"id":"7c674f0cb40c"} {"status":"Waiting","progressDetail":{},"id":"97bb6e138460"} {"status":"Pulling fs layer","progressDetail":{},"id":"b48a885b52bc"} {"status":"Pulling fs layer","progressDetail":{},"id":"272cdf839cbb"} {"status":"Pulling fs layer","progressDetail":{},"id":"50d054c97f4f"} {"status":"Pulling fs layer","progressDetail":{},"id":"4c6bbd90b64d"} {"status":"Waiting","progressDetail":{},"id":"2edb982d5170"} {"status":"Pulling fs layer","progressDetail":{},"id":"4f4fb700ef54"} {"status":"Waiting","progressDetail":{},"id":"7ddc8e6d6da9"} {"status":"Waiting","progressDetail":{},"id":"b8cf53bbc6ba"} {"status":"Waiting","progressDetail":{},"id":"25efb07e4521"} {"status":"Waiting","progressDetail":{},"id":"0df6fd234b59"} {"status":"Waiting","progressDetail":{},"id":"1c3245356213"} {"status":"Waiting","progressDetail":{},"id":"61ebb123c1eb"} {"status":"Waiting","progressDetail":{},"id":"8fc1ba8efe21"} {"status":"Waiting","progressDetail":{},"id":"0964b769d2c9"} {"status":"Waiting","progressDetail":{},"id":"87f7843f43cd"} {"status":"Waiting","progressDetail":{},"id":"1f6f45e783b5"} {"status":"Waiting","progressDetail":{},"id":"a89dbf94d794"} {"status":"Waiting","progressDetail":{},"id":"43ea61082f68"} {"status":"Waiting","progressDetail":{},"id":"f0d43ddca77f"} {"status":"Waiting","progressDetail":{},"id":"7c674f0cb40c"} {"status":"Waiting","progressDetail":{},"id":"b48a885b52bc"} {"status":"Waiting","progressDetail":{},"id":"272cdf839cbb"} {"status":"Waiting","progressDetail":{},"id":"50d054c97f4f"} {"status":"Waiting","progressDetail":{},"id":"4c6bbd90b64d"} {"status":"Waiting","progressDetail":{},"id":"4f4fb700ef54"} {"status":"Waiting","progressDetail":{},"id":"4ab897fa6fbf"} {"status":"Waiting","progressDetail":{},"id":"d837a2a1365e"} {"status":"Waiting","progressDetail":{},"id":"988ae18fe41a"} {"status":"Downloading","progressDetail":{"current":487,"total":850},"progress":"[============================\u003e ] 487B/850B","id":"ee671aafb583"} {"status":"Downloading","progressDetail":{"current":485,"total":35355},"progress":"[\u003e ] 485B/35.35kB","id":"d83811f270d5"} {"status":"Downloading","progressDetail":{"current":35355,"total":35355},"progress":"[==================================================\u003e] 35.35kB/35.35kB","id":"d83811f270d5"} {"status":"Verifying Checksum","progressDetail":{},"id":"d83811f270d5"} {"status":"Download complete","progressDetail":{},"id":"d83811f270d5"} {"status":"Downloading","progressDetail":{"current":277600,"total":26683298},"progress":"[\u003e ] 277.6kB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":850,"total":850},"progress":"[==================================================\u003e] 850B/850B","id":"ee671aafb583"} {"status":"Verifying Checksum","progressDetail":{},"id":"ee671aafb583"} {"status":"Download complete","progressDetail":{},"id":"ee671aafb583"} {"status":"Downloading","progressDetail":{"current":2218692,"total":26683298},"progress":"[====\u003e ] 2.219MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":4160196,"total":26683298},"progress":"[=======\u003e ] 4.16MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":6109892,"total":26683298},"progress":"[===========\u003e ] 6.11MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":7772868,"total":26683298},"progress":"[==============\u003e ] 7.773MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":9444036,"total":26683298},"progress":"[=================\u003e ] 9.444MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":163,"total":163},"progress":"[==================================================\u003e] 163B/163B","id":"7fc152dfb3a6"} {"status":"Verifying Checksum","progressDetail":{},"id":"7fc152dfb3a6"} {"status":"Download complete","progressDetail":{},"id":"7fc152dfb3a6"} {"status":"Downloading","progressDetail":{"current":10832580,"total":26683298},"progress":"[====================\u003e ] 10.83MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":531179,"total":88111129},"progress":"[\u003e ] 531.2kB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":11668164,"total":26683298},"progress":"[=====================\u003e ] 11.67MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":1604331,"total":88111129},"progress":"[\u003e ] 1.604MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":12495556,"total":26683298},"progress":"[=======================\u003e ] 12.5MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":3209963,"total":88111129},"progress":"[=\u003e ] 3.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":13331140,"total":26683298},"progress":"[========================\u003e ] 13.33MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":4283115,"total":88111129},"progress":"[==\u003e ] 4.283MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":14166724,"total":26683298},"progress":"[==========================\u003e ] 14.17MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":5888747,"total":88111129},"progress":"[===\u003e ] 5.889MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":15280836,"total":26683298},"progress":"[============================\u003e ] 15.28MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":14318,"total":1391657},"progress":"[\u003e ] 14.32kB/1.392MB","id":"d837a2a1365e"} {"status":"Downloading","progressDetail":{"current":6961899,"total":88111129},"progress":"[===\u003e ] 6.962MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":16116420,"total":26683298},"progress":"[==============================\u003e ] 16.12MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":936688,"total":1391657},"progress":"[=================================\u003e ] 936.7kB/1.392MB","id":"d837a2a1365e"} {"status":"Verifying Checksum","progressDetail":{},"id":"d837a2a1365e"} {"status":"Download complete","progressDetail":{},"id":"d837a2a1365e"} {"status":"Downloading","progressDetail":{"current":8022763,"total":88111129},"progress":"[====\u003e ] 8.023MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":16931524,"total":26683298},"progress":"[===============================\u003e ] 16.93MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":18045636,"total":26683298},"progress":"[=================================\u003e ] 18.05MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":9632491,"total":88111129},"progress":"[=====\u003e ] 9.632MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":10709739,"total":88111129},"progress":"[======\u003e ] 10.71MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":19143364,"total":26683298},"progress":"[===================================\u003e ] 19.14MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":11778795,"total":88111129},"progress":"[======\u003e ] 11.78MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":20249284,"total":26683298},"progress":"[=====================================\u003e ] 20.25MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":12851947,"total":88111129},"progress":"[=======\u003e ] 12.85MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":21072580,"total":26683298},"progress":"[=======================================\u003e ] 21.07MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":14133,"total":1328346},"progress":"[\u003e ] 14.13kB/1.328MB","id":"988ae18fe41a"} {"status":"Downloading","progressDetail":{"current":13933291,"total":88111129},"progress":"[=======\u003e ] 13.93MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":21908164,"total":26683298},"progress":"[=========================================\u003e ] 21.91MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":973511,"total":1328346},"progress":"[====================================\u003e ] 973.5kB/1.328MB","id":"988ae18fe41a"} {"status":"Verifying Checksum","progressDetail":{},"id":"988ae18fe41a"} {"status":"Download complete","progressDetail":{},"id":"988ae18fe41a"} {"status":"Downloading","progressDetail":{"current":15014635,"total":88111129},"progress":"[========\u003e ] 15.01MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":22747844,"total":26683298},"progress":"[==========================================\u003e ] 22.75MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":16075499,"total":88111129},"progress":"[=========\u003e ] 16.08MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":23575236,"total":26683298},"progress":"[============================================\u003e ] 23.58MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":24414916,"total":26683298},"progress":"[=============================================\u003e ] 24.41MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":17132267,"total":88111129},"progress":"[=========\u003e ] 17.13MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":25250500,"total":26683298},"progress":"[===============================================\u003e ] 25.25MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":18213611,"total":88111129},"progress":"[==========\u003e ] 18.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":26073796,"total":26683298},"progress":"[================================================\u003e ] 26.07MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":19286763,"total":88111129},"progress":"[==========\u003e ] 19.29MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":490,"total":4478},"progress":"[=====\u003e ] 490B/4.478kB","id":"eeb8ef83b565"} {"status":"Downloading","progressDetail":{"current":4478,"total":4478},"progress":"[==================================================\u003e] 4.478kB/4.478kB","id":"eeb8ef83b565"} {"status":"Verifying Checksum","progressDetail":{},"id":"eeb8ef83b565"} {"status":"Download complete","progressDetail":{},"id":"eeb8ef83b565"} {"status":"Verifying Checksum","progressDetail":{},"id":"5667fdb72017"} {"status":"Download complete","progressDetail":{},"id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":20892395,"total":88111129},"progress":"[===========\u003e ] 20.89MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":294912,"total":26683298},"progress":"[\u003e ] 294.9kB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":23050987,"total":88111129},"progress":"[=============\u003e ] 23.05MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":2654208,"total":26683298},"progress":"[====\u003e ] 2.654MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":25205483,"total":88111129},"progress":"[==============\u003e ] 25.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":6193152,"total":26683298},"progress":"[===========\u003e ] 6.193MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":27355883,"total":88111129},"progress":"[===============\u003e ] 27.36MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":8552448,"total":26683298},"progress":"[================\u003e ] 8.552MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":197,"total":197},"progress":"[==================================================\u003e] 197B/197B","id":"357fefdf9bc9"} {"status":"Verifying Checksum","progressDetail":{},"id":"357fefdf9bc9"} {"status":"Download complete","progressDetail":{},"id":"357fefdf9bc9"} {"status":"Extracting","progressDetail":{"current":11796480,"total":26683298},"progress":"[======================\u003e ] 11.8MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":29510379,"total":88111129},"progress":"[================\u003e ] 29.51MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":277600,"total":27504647},"progress":"[\u003e ] 277.6kB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":15040512,"total":26683298},"progress":"[============================\u003e ] 15.04MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":1391300,"total":27504647},"progress":"[==\u003e ] 1.391MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":31132395,"total":88111129},"progress":"[=================\u003e ] 31.13MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":17989632,"total":26683298},"progress":"[=================================\u003e ] 17.99MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":32754411,"total":88111129},"progress":"[==================\u003e ] 32.75MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2230980,"total":27504647},"progress":"[====\u003e ] 2.231MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":22118400,"total":26683298},"progress":"[=========================================\u003e ] 22.12MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":33835755,"total":88111129},"progress":"[===================\u003e ] 33.84MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3078852,"total":27504647},"progress":"[=====\u003e ] 3.079MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":24477696,"total":26683298},"progress":"[=============================================\u003e ] 24.48MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":52419,"total":5205016},"progress":"[\u003e ] 52.42kB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":34917099,"total":88111129},"progress":"[===================\u003e ] 34.92MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3922628,"total":27504647},"progress":"[=======\u003e ] 3.923MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":912096,"total":5205016},"progress":"[========\u003e ] 912.1kB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Extracting","progressDetail":{"current":26247168,"total":26683298},"progress":"[=================================================\u003e ] 26.25MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":4487876,"total":27504647},"progress":"[========\u003e ] 4.488MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":35986155,"total":88111129},"progress":"[====================\u003e ] 35.99MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":26683298,"total":26683298},"progress":"[==================================================\u003e] 26.68MB/26.68MB","id":"5667fdb72017"} {"status":"Downloading","progressDetail":{"current":1805024,"total":5205016},"progress":"[=================\u003e ] 1.805MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":5044932,"total":27504647},"progress":"[=========\u003e ] 5.045MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":36522731,"total":88111129},"progress":"[====================\u003e ] 36.52MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2550496,"total":5205016},"progress":"[========================\u003e ] 2.55MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":5601988,"total":27504647},"progress":"[==========\u003e ] 5.602MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":3381984,"total":5205016},"progress":"[================================\u003e ] 3.382MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":37063403,"total":88111129},"progress":"[=====================\u003e ] 37.06MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":6159044,"total":27504647},"progress":"[===========\u003e ] 6.159MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":4152032,"total":5205016},"progress":"[=======================================\u003e ] 4.152MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":37604075,"total":88111129},"progress":"[=====================\u003e ] 37.6MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Pull complete","progressDetail":{},"id":"5667fdb72017"} {"status":"Extracting","progressDetail":{"current":32768,"total":35355},"progress":"[==============================================\u003e ] 32.77kB/35.35kB","id":"d83811f270d5"} {"status":"Extracting","progressDetail":{"current":35355,"total":35355},"progress":"[==================================================\u003e] 35.35kB/35.35kB","id":"d83811f270d5"} {"status":"Downloading","progressDetail":{"current":5004000,"total":5205016},"progress":"[================================================\u003e ] 5.004MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":6716100,"total":27504647},"progress":"[============\u003e ] 6.716MB/27.5MB","id":"45b746196f82"} {"status":"Verifying Checksum","progressDetail":{},"id":"fbf4ce20f8c2"} {"status":"Download complete","progressDetail":{},"id":"fbf4ce20f8c2"} {"status":"Downloading","progressDetail":{"current":38144747,"total":88111129},"progress":"[=====================\u003e ] 38.14MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Pull complete","progressDetail":{},"id":"d83811f270d5"} {"status":"Extracting","progressDetail":{"current":850,"total":850},"progress":"[==================================================\u003e] 850B/850B","id":"ee671aafb583"} {"status":"Extracting","progressDetail":{"current":850,"total":850},"progress":"[==================================================\u003e] 850B/850B","id":"ee671aafb583"} {"status":"Downloading","progressDetail":{"current":7293636,"total":27504647},"progress":"[=============\u003e ] 7.294MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":39213803,"total":88111129},"progress":"[======================\u003e ] 39.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":8129220,"total":27504647},"progress":"[==============\u003e ] 8.129MB/27.5MB","id":"45b746196f82"} {"status":"Pull complete","progressDetail":{},"id":"ee671aafb583"} {"status":"Extracting","progressDetail":{"current":163,"total":163},"progress":"[==================================================\u003e] 163B/163B","id":"7fc152dfb3a6"} {"status":"Extracting","progressDetail":{"current":163,"total":163},"progress":"[==================================================\u003e] 163B/163B","id":"7fc152dfb3a6"} {"status":"Downloading","progressDetail":{"current":40295147,"total":88111129},"progress":"[======================\u003e ] 40.3MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":8964804,"total":27504647},"progress":"[================\u003e ] 8.965MB/27.5MB","id":"45b746196f82"} {"status":"Pull complete","progressDetail":{},"id":"7fc152dfb3a6"} {"status":"Downloading","progressDetail":{"current":9800388,"total":27504647},"progress":"[=================\u003e ] 9.8MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":41368299,"total":88111129},"progress":"[=======================\u003e ] 41.37MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":49680,"total":4964709},"progress":"[\u003e ] 49.68kB/4.965MB","id":"90aca3c647fe"} {"status":"Downloading","progressDetail":{"current":10635972,"total":27504647},"progress":"[===================\u003e ] 10.64MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":908013,"total":4964709},"progress":"[=========\u003e ] 908kB/4.965MB","id":"90aca3c647fe"} {"status":"Downloading","progressDetail":{"current":41908971,"total":88111129},"progress":"[=======================\u003e ] 41.91MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":11193028,"total":27504647},"progress":"[====================\u003e ] 11.19MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":2038509,"total":4964709},"progress":"[====================\u003e ] 2.039MB/4.965MB","id":"90aca3c647fe"} {"status":"Downloading","progressDetail":{"current":42449643,"total":88111129},"progress":"[========================\u003e ] 42.45MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":11750084,"total":27504647},"progress":"[=====================\u003e ] 11.75MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":3316461,"total":4964709},"progress":"[=================================\u003e ] 3.316MB/4.965MB","id":"90aca3c647fe"} {"status":"Downloading","progressDetail":{"current":4791021,"total":4964709},"progress":"[================================================\u003e ] 4.791MB/4.965MB","id":"90aca3c647fe"} {"status":"Verifying Checksum","progressDetail":{},"id":"90aca3c647fe"} {"status":"Download complete","progressDetail":{},"id":"90aca3c647fe"} {"status":"Downloading","progressDetail":{"current":12315332,"total":27504647},"progress":"[======================\u003e ] 12.32MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":42990315,"total":88111129},"progress":"[========================\u003e ] 42.99MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":13155012,"total":27504647},"progress":"[=======================\u003e ] 13.16MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":43530987,"total":88111129},"progress":"[========================\u003e ] 43.53MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":13990596,"total":27504647},"progress":"[=========================\u003e ] 13.99MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":44063467,"total":88111129},"progress":"[=========================\u003e ] 44.06MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":15112900,"total":27504647},"progress":"[===========================\u003e ] 15.11MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":45132523,"total":88111129},"progress":"[=========================\u003e ] 45.13MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":16235204,"total":27504647},"progress":"[=============================\u003e ] 16.24MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":52418,"total":5149051},"progress":"[\u003e ] 52.42kB/5.149MB","id":"1dd62f37c84c"} {"status":"Downloading","progressDetail":{"current":1195147,"total":5149051},"progress":"[===========\u003e ] 1.195MB/5.149MB","id":"1dd62f37c84c"} {"status":"Downloading","progressDetail":{"current":16792260,"total":27504647},"progress":"[==============================\u003e ] 16.79MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":45673195,"total":88111129},"progress":"[=========================\u003e ] 45.67MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2702475,"total":5149051},"progress":"[==========================\u003e ] 2.702MB/5.149MB","id":"1dd62f37c84c"} {"status":"Downloading","progressDetail":{"current":4078320,"total":5149051},"progress":"[=======================================\u003e ] 4.078MB/5.149MB","id":"1dd62f37c84c"} {"status":"Downloading","progressDetail":{"current":17349316,"total":27504647},"progress":"[===============================\u003e ] 17.35MB/27.5MB","id":"45b746196f82"} {"status":"Verifying Checksum","progressDetail":{},"id":"1dd62f37c84c"} {"status":"Download complete","progressDetail":{},"id":"1dd62f37c84c"} {"status":"Downloading","progressDetail":{"current":46213867,"total":88111129},"progress":"[==========================\u003e ] 46.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":17918660,"total":27504647},"progress":"[================================\u003e ] 17.92MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":19040964,"total":27504647},"progress":"[==================================\u003e ] 19.04MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":47295211,"total":88111129},"progress":"[==========================\u003e ] 47.3MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":20183748,"total":27504647},"progress":"[====================================\u003e ] 20.18MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":48368363,"total":88111129},"progress":"[===========================\u003e ] 48.37MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":21301956,"total":27504647},"progress":"[======================================\u003e ] 21.3MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":22432452,"total":27504647},"progress":"[========================================\u003e ] 22.43MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":38884,"total":3855277},"progress":"[\u003e ] 38.88kB/3.855MB","id":"3192b2fa42db"} {"status":"Downloading","progressDetail":{"current":49445611,"total":88111129},"progress":"[============================\u003e ] 49.45MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":977632,"total":3855277},"progress":"[============\u003e ] 977.6kB/3.855MB","id":"3192b2fa42db"} {"status":"Downloading","progressDetail":{"current":23268036,"total":27504647},"progress":"[==========================================\u003e ] 23.27MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":49986283,"total":88111129},"progress":"[============================\u003e ] 49.99MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1895136,"total":3855277},"progress":"[========================\u003e ] 1.895MB/3.855MB","id":"3192b2fa42db"} {"status":"Downloading","progressDetail":{"current":23833284,"total":27504647},"progress":"[===========================================\u003e ] 23.83MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":2939616,"total":3855277},"progress":"[======================================\u003e ] 2.94MB/3.855MB","id":"3192b2fa42db"} {"status":"Downloading","progressDetail":{"current":24390340,"total":27504647},"progress":"[============================================\u003e ] 24.39MB/27.5MB","id":"45b746196f82"} {"status":"Download complete","progressDetail":{},"id":"3192b2fa42db"} {"status":"Downloading","progressDetail":{"current":50518763,"total":88111129},"progress":"[============================\u003e ] 50.52MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":24947396,"total":27504647},"progress":"[=============================================\u003e ] 24.95MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":51059435,"total":88111129},"progress":"[============================\u003e ] 51.06MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":25803460,"total":27504647},"progress":"[==============================================\u003e ] 25.8MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":26942148,"total":27504647},"progress":"[================================================\u003e ] 26.94MB/27.5MB","id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":52140779,"total":88111129},"progress":"[=============================\u003e ] 52.14MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":27504647,"total":27504647},"progress":"[==================================================\u003e] 27.5MB/27.5MB","id":"45b746196f82"} {"status":"Verifying Checksum","progressDetail":{},"id":"45b746196f82"} {"status":"Download complete","progressDetail":{},"id":"45b746196f82"} {"status":"Downloading","progressDetail":{"current":53222123,"total":88111129},"progress":"[==============================\u003e ] 53.22MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":51194,"total":4983195},"progress":"[\u003e ] 51.19kB/4.983MB","id":"ae190b8f66a7"} {"status":"Downloading","progressDetail":{"current":54299371,"total":88111129},"progress":"[==============================\u003e ] 54.3MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1268464,"total":4983195},"progress":"[============\u003e ] 1.268MB/4.983MB","id":"ae190b8f66a7"} {"status":"Downloading","progressDetail":{"current":54827755,"total":88111129},"progress":"[===============================\u003e ] 54.83MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2767600,"total":4983195},"progress":"[===========================\u003e ] 2.768MB/4.983MB","id":"ae190b8f66a7"} {"status":"Downloading","progressDetail":{"current":4528880,"total":4983195},"progress":"[=============================================\u003e ] 4.529MB/4.983MB","id":"ae190b8f66a7"} {"status":"Downloading","progressDetail":{"current":55368427,"total":88111129},"progress":"[===============================\u003e ] 55.37MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Verifying Checksum","progressDetail":{},"id":"ae190b8f66a7"} {"status":"Download complete","progressDetail":{},"id":"ae190b8f66a7"} {"status":"Downloading","progressDetail":{"current":63614,"total":6103207},"progress":"[\u003e ] 63.61kB/6.103MB","id":"97bb6e138460"} {"status":"Downloading","progressDetail":{"current":56449771,"total":88111129},"progress":"[================================\u003e ] 56.45MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1530606,"total":6103207},"progress":"[============\u003e ] 1.531MB/6.103MB","id":"97bb6e138460"} {"status":"Downloading","progressDetail":{"current":3193582,"total":6103207},"progress":"[==========================\u003e ] 3.194MB/6.103MB","id":"97bb6e138460"} {"status":"Downloading","progressDetail":{"current":56990443,"total":88111129},"progress":"[================================\u003e ] 56.99MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":4786926,"total":6103207},"progress":"[=======================================\u003e ] 4.787MB/6.103MB","id":"97bb6e138460"} {"status":"Downloading","progressDetail":{"current":57531115,"total":88111129},"progress":"[================================\u003e ] 57.53MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Download complete","progressDetail":{},"id":"97bb6e138460"} {"status":"Downloading","progressDetail":{"current":489,"total":787},"progress":"[===============================\u003e ] 489B/787B","id":"2edb982d5170"} {"status":"Downloading","progressDetail":{"current":58612459,"total":88111129},"progress":"[=================================\u003e ] 58.61MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":787,"total":787},"progress":"[==================================================\u003e] 787B/787B","id":"2edb982d5170"} {"status":"Verifying Checksum","progressDetail":{},"id":"2edb982d5170"} {"status":"Download complete","progressDetail":{},"id":"2edb982d5170"} {"status":"Downloading","progressDetail":{"current":60213995,"total":88111129},"progress":"[==================================\u003e ] 60.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":61827819,"total":88111129},"progress":"[===================================\u003e ] 61.83MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":63449835,"total":88111129},"progress":"[====================================\u003e ] 63.45MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":65071851,"total":88111129},"progress":"[====================================\u003e ] 65.07MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":49803,"total":4894860},"progress":"[\u003e ] 49.8kB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":49681,"total":4953791},"progress":"[\u003e ] 49.68kB/4.954MB","id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":912099,"total":4894860},"progress":"[=========\u003e ] 912.1kB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":66145003,"total":88111129},"progress":"[=====================================\u003e ] 66.15MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":748270,"total":4953791},"progress":"[=======\u003e ] 748.3kB/4.954MB","id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":1702627,"total":4894860},"progress":"[=================\u003e ] 1.703MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":67205867,"total":88111129},"progress":"[======================================\u003e ] 67.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1678062,"total":4953791},"progress":"[================\u003e ] 1.678MB/4.954MB","id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":2194147,"total":4894860},"progress":"[======================\u003e ] 2.194MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":67746539,"total":88111129},"progress":"[======================================\u003e ] 67.75MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2648814,"total":4953791},"progress":"[==========================\u003e ] 2.649MB/4.954MB","id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":2743011,"total":4894860},"progress":"[============================\u003e ] 2.743MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":68287211,"total":88111129},"progress":"[======================================\u003e ] 68.29MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3697390,"total":4953791},"progress":"[=====================================\u003e ] 3.697MB/4.954MB","id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":3381987,"total":4894860},"progress":"[==================================\u003e ] 3.382MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":4774638,"total":4953791},"progress":"[================================================\u003e ] 4.775MB/4.954MB","id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":68827883,"total":88111129},"progress":"[=======================================\u003e ] 68.83MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":4953791,"total":4953791},"progress":"[==================================================\u003e] 4.954MB/4.954MB","id":"0df6fd234b59"} {"status":"Verifying Checksum","progressDetail":{},"id":"0df6fd234b59"} {"status":"Download complete","progressDetail":{},"id":"0df6fd234b59"} {"status":"Downloading","progressDetail":{"current":4004579,"total":4894860},"progress":"[========================================\u003e ] 4.005MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":4893411,"total":4894860},"progress":"[=================================================\u003e ] 4.893MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Verifying Checksum","progressDetail":{},"id":"7ddc8e6d6da9"} {"status":"Download complete","progressDetail":{},"id":"7ddc8e6d6da9"} {"status":"Downloading","progressDetail":{"current":69909227,"total":88111129},"progress":"[=======================================\u003e ] 69.91MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":71527147,"total":88111129},"progress":"[========================================\u003e ] 71.53MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":73149163,"total":88111129},"progress":"[=========================================\u003e ] 73.15MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":74771179,"total":88111129},"progress":"[==========================================\u003e ] 74.77MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":63573,"total":6137526},"progress":"[\u003e ] 63.57kB/6.138MB","id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":75311851,"total":88111129},"progress":"[==========================================\u003e ] 75.31MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1317559,"total":6137526},"progress":"[==========\u003e ] 1.318MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":2710199,"total":6137526},"progress":"[======================\u003e ] 2.71MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":38729,"total":3854415},"progress":"[\u003e ] 38.73kB/3.854MB","id":"1f6f45e783b5"} {"status":"Downloading","progressDetail":{"current":76368619,"total":88111129},"progress":"[===========================================\u003e ] 76.37MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3783351,"total":6137526},"progress":"[==============================\u003e ] 3.783MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":658157,"total":3854415},"progress":"[========\u003e ] 658.2kB/3.854MB","id":"1f6f45e783b5"} {"status":"Downloading","progressDetail":{"current":4520631,"total":6137526},"progress":"[====================================\u003e ] 4.521MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":1350381,"total":3854415},"progress":"[=================\u003e ] 1.35MB/3.854MB","id":"1f6f45e783b5"} {"status":"Downloading","progressDetail":{"current":5364407,"total":6137526},"progress":"[===========================================\u003e ] 5.364MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":77445867,"total":88111129},"progress":"[===========================================\u003e ] 77.45MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2153197,"total":3854415},"progress":"[===========================\u003e ] 2.153MB/3.854MB","id":"1f6f45e783b5"} {"status":"Verifying Checksum","progressDetail":{},"id":"8fc1ba8efe21"} {"status":"Download complete","progressDetail":{},"id":"8fc1ba8efe21"} {"status":"Downloading","progressDetail":{"current":77986539,"total":88111129},"progress":"[============================================\u003e ] 77.99MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3021549,"total":3854415},"progress":"[=======================================\u003e ] 3.022MB/3.854MB","id":"1f6f45e783b5"} {"status":"Verifying Checksum","progressDetail":{},"id":"1f6f45e783b5"} {"status":"Download complete","progressDetail":{},"id":"1f6f45e783b5"} {"status":"Downloading","progressDetail":{"current":79067883,"total":88111129},"progress":"[============================================\u003e ] 79.07MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":80149227,"total":88111129},"progress":"[=============================================\u003e ] 80.15MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":81767147,"total":88111129},"progress":"[==============================================\u003e ] 81.77MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":52419,"total":5222290},"progress":"[\u003e ] 52.42kB/5.222MB","id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":1055455,"total":5222290},"progress":"[==========\u003e ] 1.055MB/5.222MB","id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":83372779,"total":88111129},"progress":"[===============================================\u003e ] 83.37MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2333407,"total":5222290},"progress":"[======================\u003e ] 2.333MB/5.222MB","id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":35991,"total":3564359},"progress":"[\u003e ] 35.99kB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Downloading","progressDetail":{"current":84454123,"total":88111129},"progress":"[===============================================\u003e ] 84.45MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3300063,"total":5222290},"progress":"[===============================\u003e ] 3.3MB/5.222MB","id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":752366,"total":3564359},"progress":"[==========\u003e ] 752.4kB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Downloading","progressDetail":{"current":3979999,"total":5222290},"progress":"[======================================\u003e ] 3.98MB/5.222MB","id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":1743598,"total":3564359},"progress":"[========================\u003e ] 1.744MB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Downloading","progressDetail":{"current":85527275,"total":88111129},"progress":"[================================================\u003e ] 85.53MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":4537055,"total":5222290},"progress":"[===========================================\u003e ] 4.537MB/5.222MB","id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":2833134,"total":3564359},"progress":"[=======================================\u003e ] 2.833MB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Downloading","progressDetail":{"current":5077727,"total":5222290},"progress":"[================================================\u003e ] 5.078MB/5.222MB","id":"43ea61082f68"} {"status":"Verifying Checksum","progressDetail":{},"id":"b8cf53bbc6ba"} {"status":"Download complete","progressDetail":{},"id":"b8cf53bbc6ba"} {"status":"Verifying Checksum","progressDetail":{},"id":"43ea61082f68"} {"status":"Download complete","progressDetail":{},"id":"43ea61082f68"} {"status":"Downloading","progressDetail":{"current":86067947,"total":88111129},"progress":"[================================================\u003e ] 86.07MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":87132907,"total":88111129},"progress":"[=================================================\u003e ] 87.13MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Verifying Checksum","progressDetail":{},"id":"4ab897fa6fbf"} {"status":"Download complete","progressDetail":{},"id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":557056,"total":88111129},"progress":"[\u003e ] 557.1kB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":52418,"total":5120108},"progress":"[\u003e ] 52.42kB/5.12MB","id":"1c3245356213"} {"status":"Downloading","progressDetail":{"current":489,"total":790},"progress":"[==============================\u003e ] 489B/790B","id":"25efb07e4521"} {"status":"Extracting","progressDetail":{"current":5013504,"total":88111129},"progress":"[==\u003e ] 5.014MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":790,"total":790},"progress":"[==================================================\u003e] 790B/790B","id":"25efb07e4521"} {"status":"Verifying Checksum","progressDetail":{},"id":"25efb07e4521"} {"status":"Download complete","progressDetail":{},"id":"25efb07e4521"} {"status":"Downloading","progressDetail":{"current":1764079,"total":5120108},"progress":"[=================\u003e ] 1.764MB/5.12MB","id":"1c3245356213"} {"status":"Extracting","progressDetail":{"current":8355840,"total":88111129},"progress":"[====\u003e ] 8.356MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3635951,"total":5120108},"progress":"[===================================\u003e ] 3.636MB/5.12MB","id":"1c3245356213"} {"status":"Verifying Checksum","progressDetail":{},"id":"1c3245356213"} {"status":"Download complete","progressDetail":{},"id":"1c3245356213"} {"status":"Extracting","progressDetail":{"current":11141120,"total":88111129},"progress":"[======\u003e ] 11.14MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":52419,"total":5117023},"progress":"[\u003e ] 52.42kB/5.117MB","id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":13369344,"total":88111129},"progress":"[=======\u003e ] 13.37MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1596142,"total":5117023},"progress":"[===============\u003e ] 1.596MB/5.117MB","id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":13926400,"total":88111129},"progress":"[=======\u003e ] 13.93MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3242734,"total":5117023},"progress":"[===============================\u003e ] 3.243MB/5.117MB","id":"61ebb123c1eb"} {"status":"Downloading","progressDetail":{"current":55157,"total":5384215},"progress":"[\u003e ] 55.16kB/5.384MB","id":"0964b769d2c9"} {"status":"Downloading","progressDetail":{"current":4635374,"total":5117023},"progress":"[=============================================\u003e ] 4.635MB/5.117MB","id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":15040512,"total":88111129},"progress":"[========\u003e ] 15.04MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Verifying Checksum","progressDetail":{},"id":"61ebb123c1eb"} {"status":"Download complete","progressDetail":{},"id":"61ebb123c1eb"} {"status":"Downloading","progressDetail":{"current":989937,"total":5384215},"progress":"[=========\u003e ] 989.9kB/5.384MB","id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":15597568,"total":88111129},"progress":"[========\u003e ] 15.6MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2558705,"total":5384215},"progress":"[=======================\u003e ] 2.559MB/5.384MB","id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":18382848,"total":88111129},"progress":"[==========\u003e ] 18.38MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":4311793,"total":5384215},"progress":"[========================================\u003e ] 4.312MB/5.384MB","id":"0964b769d2c9"} {"status":"Downloading","progressDetail":{"current":53788,"total":5252487},"progress":"[\u003e ] 53.79kB/5.252MB","id":"87f7843f43cd"} {"status":"Extracting","progressDetail":{"current":22839296,"total":88111129},"progress":"[============\u003e ] 22.84MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":5212913,"total":5384215},"progress":"[================================================\u003e ] 5.213MB/5.384MB","id":"0964b769d2c9"} {"status":"Downloading","progressDetail":{"current":846577,"total":5252487},"progress":"[========\u003e ] 846.6kB/5.252MB","id":"87f7843f43cd"} {"status":"Verifying Checksum","progressDetail":{},"id":"0964b769d2c9"} {"status":"Download complete","progressDetail":{},"id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":26181632,"total":88111129},"progress":"[==============\u003e ] 26.18MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":2628337,"total":5252487},"progress":"[=========================\u003e ] 2.628MB/5.252MB","id":"87f7843f43cd"} {"status":"Extracting","progressDetail":{"current":30638080,"total":88111129},"progress":"[=================\u003e ] 30.64MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":4340465,"total":5252487},"progress":"[=========================================\u003e ] 4.34MB/5.252MB","id":"87f7843f43cd"} {"status":"Download complete","progressDetail":{},"id":"87f7843f43cd"} {"status":"Extracting","progressDetail":{"current":33423360,"total":88111129},"progress":"[==================\u003e ] 33.42MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":51204,"total":5015856},"progress":"[\u003e ] 51.2kB/5.016MB","id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":36208640,"total":88111129},"progress":"[====================\u003e ] 36.21MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1624816,"total":5015856},"progress":"[================\u003e ] 1.625MB/5.016MB","id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":38436864,"total":88111129},"progress":"[=====================\u003e ] 38.44MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3373808,"total":5015856},"progress":"[=================================\u003e ] 3.374MB/5.016MB","id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":40665088,"total":88111129},"progress":"[=======================\u003e ] 40.67MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":53910,"total":5310566},"progress":"[\u003e ] 53.91kB/5.311MB","id":"f0d43ddca77f"} {"status":"Downloading","progressDetail":{"current":4905712,"total":5015856},"progress":"[================================================\u003e ] 4.906MB/5.016MB","id":"a89dbf94d794"} {"status":"Verifying Checksum","progressDetail":{},"id":"a89dbf94d794"} {"status":"Download complete","progressDetail":{},"id":"a89dbf94d794"} {"status":"Downloading","progressDetail":{"current":1313521,"total":5310566},"progress":"[============\u003e ] 1.314MB/5.311MB","id":"f0d43ddca77f"} {"status":"Extracting","progressDetail":{"current":44007424,"total":88111129},"progress":"[========================\u003e ] 44.01MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":46792704,"total":88111129},"progress":"[==========================\u003e ] 46.79MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3082993,"total":5310566},"progress":"[=============================\u003e ] 3.083MB/5.311MB","id":"f0d43ddca77f"} {"status":"Downloading","progressDetail":{"current":49836,"total":4915049},"progress":"[\u003e ] 49.84kB/4.915MB","id":"7c674f0cb40c"} {"status":"Downloading","progressDetail":{"current":4373233,"total":5310566},"progress":"[=========================================\u003e ] 4.373MB/5.311MB","id":"f0d43ddca77f"} {"status":"Extracting","progressDetail":{"current":48463872,"total":88111129},"progress":"[===========================\u003e ] 48.46MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":711407,"total":4915049},"progress":"[=======\u003e ] 711.4kB/4.915MB","id":"7c674f0cb40c"} {"status":"Verifying Checksum","progressDetail":{},"id":"f0d43ddca77f"} {"status":"Download complete","progressDetail":{},"id":"f0d43ddca77f"} {"status":"Downloading","progressDetail":{"current":1710831,"total":4915049},"progress":"[=================\u003e ] 1.711MB/4.915MB","id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":52363264,"total":88111129},"progress":"[=============================\u003e ] 52.36MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":3504879,"total":4915049},"progress":"[===================================\u003e ] 3.505MB/4.915MB","id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":55705600,"total":88111129},"progress":"[===============================\u003e ] 55.71MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":4905711,"total":4915049},"progress":"[=================================================\u003e ] 4.906MB/4.915MB","id":"7c674f0cb40c"} {"status":"Verifying Checksum","progressDetail":{},"id":"7c674f0cb40c"} {"status":"Download complete","progressDetail":{},"id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":58490880,"total":88111129},"progress":"[=================================\u003e ] 58.49MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":52419,"total":5119213},"progress":"[\u003e ] 52.42kB/5.119MB","id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":61276160,"total":88111129},"progress":"[==================================\u003e ] 61.28MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1333999,"total":5119213},"progress":"[=============\u003e ] 1.334MB/5.119MB","id":"b48a885b52bc"} {"status":"Downloading","progressDetail":{"current":2657007,"total":5119213},"progress":"[=========================\u003e ] 2.657MB/5.119MB","id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":64061440,"total":88111129},"progress":"[====================================\u003e ] 64.06MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Download complete","progressDetail":{},"id":"272cdf839cbb"} {"status":"Downloading","progressDetail":{"current":4344559,"total":5119213},"progress":"[==========================================\u003e ] 4.345MB/5.119MB","id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":66289664,"total":88111129},"progress":"[=====================================\u003e ] 66.29MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Verifying Checksum","progressDetail":{},"id":"b48a885b52bc"} {"status":"Download complete","progressDetail":{},"id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":70746112,"total":88111129},"progress":"[========================================\u003e ] 70.75MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":73531392,"total":88111129},"progress":"[=========================================\u003e ] 73.53MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":77430784,"total":88111129},"progress":"[===========================================\u003e ] 77.43MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Download complete","progressDetail":{},"id":"50d054c97f4f"} {"status":"Downloading","progressDetail":{"current":488,"total":1069},"progress":"[======================\u003e ] 488B/1.069kB","id":"4c6bbd90b64d"} {"status":"Extracting","progressDetail":{"current":80216064,"total":88111129},"progress":"[=============================================\u003e ] 80.22MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Downloading","progressDetail":{"current":1069,"total":1069},"progress":"[==================================================\u003e] 1.069kB/1.069kB","id":"4c6bbd90b64d"} {"status":"Verifying Checksum","progressDetail":{},"id":"4c6bbd90b64d"} {"status":"Download complete","progressDetail":{},"id":"4c6bbd90b64d"} {"status":"Downloading","progressDetail":{"current":32,"total":32},"progress":"[==================================================\u003e] 32B/32B","id":"4f4fb700ef54"} {"status":"Verifying Checksum","progressDetail":{},"id":"4f4fb700ef54"} {"status":"Download complete","progressDetail":{},"id":"4f4fb700ef54"} {"status":"Extracting","progressDetail":{"current":81887232,"total":88111129},"progress":"[==============================================\u003e ] 81.89MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":83558400,"total":88111129},"progress":"[===============================================\u003e ] 83.56MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":85229568,"total":88111129},"progress":"[================================================\u003e ] 85.23MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":86900736,"total":88111129},"progress":"[=================================================\u003e ] 86.9MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":88014848,"total":88111129},"progress":"[=================================================\u003e ] 88.01MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":88111129,"total":88111129},"progress":"[==================================================\u003e] 88.11MB/88.11MB","id":"4ab897fa6fbf"} {"status":"Pull complete","progressDetail":{},"id":"4ab897fa6fbf"} {"status":"Extracting","progressDetail":{"current":32768,"total":1391657},"progress":"[=\u003e ] 32.77kB/1.392MB","id":"d837a2a1365e"} {"status":"Extracting","progressDetail":{"current":327680,"total":1391657},"progress":"[===========\u003e ] 327.7kB/1.392MB","id":"d837a2a1365e"} {"status":"Extracting","progressDetail":{"current":1391657,"total":1391657},"progress":"[==================================================\u003e] 1.392MB/1.392MB","id":"d837a2a1365e"} {"status":"Extracting","progressDetail":{"current":1391657,"total":1391657},"progress":"[==================================================\u003e] 1.392MB/1.392MB","id":"d837a2a1365e"} {"status":"Pull complete","progressDetail":{},"id":"d837a2a1365e"} {"status":"Extracting","progressDetail":{"current":32768,"total":1328346},"progress":"[=\u003e ] 32.77kB/1.328MB","id":"988ae18fe41a"} {"status":"Extracting","progressDetail":{"current":753664,"total":1328346},"progress":"[============================\u003e ] 753.7kB/1.328MB","id":"988ae18fe41a"} {"status":"Extracting","progressDetail":{"current":1328346,"total":1328346},"progress":"[==================================================\u003e] 1.328MB/1.328MB","id":"988ae18fe41a"} {"status":"Extracting","progressDetail":{"current":1328346,"total":1328346},"progress":"[==================================================\u003e] 1.328MB/1.328MB","id":"988ae18fe41a"} {"status":"Pull complete","progressDetail":{},"id":"988ae18fe41a"} {"status":"Extracting","progressDetail":{"current":4478,"total":4478},"progress":"[==================================================\u003e] 4.478kB/4.478kB","id":"eeb8ef83b565"} {"status":"Extracting","progressDetail":{"current":4478,"total":4478},"progress":"[==================================================\u003e] 4.478kB/4.478kB","id":"eeb8ef83b565"} {"status":"Pull complete","progressDetail":{},"id":"eeb8ef83b565"} {"status":"Extracting","progressDetail":{"current":197,"total":197},"progress":"[==================================================\u003e] 197B/197B","id":"357fefdf9bc9"} {"status":"Extracting","progressDetail":{"current":197,"total":197},"progress":"[==================================================\u003e] 197B/197B","id":"357fefdf9bc9"} {"status":"Pull complete","progressDetail":{},"id":"357fefdf9bc9"} {"status":"Extracting","progressDetail":{"current":294912,"total":27504647},"progress":"[\u003e ] 294.9kB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":589824,"total":27504647},"progress":"[=\u003e ] 589.8kB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":5013504,"total":27504647},"progress":"[=========\u003e ] 5.014MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":9142272,"total":27504647},"progress":"[================\u003e ] 9.142MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":13565952,"total":27504647},"progress":"[========================\u003e ] 13.57MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":16515072,"total":27504647},"progress":"[==============================\u003e ] 16.52MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":18579456,"total":27504647},"progress":"[=================================\u003e ] 18.58MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":21528576,"total":27504647},"progress":"[=======================================\u003e ] 21.53MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":25657344,"total":27504647},"progress":"[==============================================\u003e ] 25.66MB/27.5MB","id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":27504647,"total":27504647},"progress":"[==================================================\u003e] 27.5MB/27.5MB","id":"45b746196f82"} {"status":"Pull complete","progressDetail":{},"id":"45b746196f82"} {"status":"Extracting","progressDetail":{"current":65536,"total":5205016},"progress":"[\u003e ] 65.54kB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Extracting","progressDetail":{"current":1048576,"total":5205016},"progress":"[==========\u003e ] 1.049MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Extracting","progressDetail":{"current":5205016,"total":5205016},"progress":"[==================================================\u003e] 5.205MB/5.205MB","id":"fbf4ce20f8c2"} {"status":"Pull complete","progressDetail":{},"id":"fbf4ce20f8c2"} {"status":"Extracting","progressDetail":{"current":65536,"total":4964709},"progress":"[\u003e ] 65.54kB/4.965MB","id":"90aca3c647fe"} {"status":"Extracting","progressDetail":{"current":1245184,"total":4964709},"progress":"[============\u003e ] 1.245MB/4.965MB","id":"90aca3c647fe"} {"status":"Extracting","progressDetail":{"current":4964709,"total":4964709},"progress":"[==================================================\u003e] 4.965MB/4.965MB","id":"90aca3c647fe"} {"status":"Pull complete","progressDetail":{},"id":"90aca3c647fe"} {"status":"Extracting","progressDetail":{"current":65536,"total":5149051},"progress":"[\u003e ] 65.54kB/5.149MB","id":"1dd62f37c84c"} {"status":"Extracting","progressDetail":{"current":393216,"total":5149051},"progress":"[===\u003e ] 393.2kB/5.149MB","id":"1dd62f37c84c"} {"status":"Extracting","progressDetail":{"current":5149051,"total":5149051},"progress":"[==================================================\u003e] 5.149MB/5.149MB","id":"1dd62f37c84c"} {"status":"Pull complete","progressDetail":{},"id":"1dd62f37c84c"} {"status":"Extracting","progressDetail":{"current":65536,"total":3855277},"progress":"[\u003e ] 65.54kB/3.855MB","id":"3192b2fa42db"} {"status":"Extracting","progressDetail":{"current":851968,"total":3855277},"progress":"[===========\u003e ] 852kB/3.855MB","id":"3192b2fa42db"} {"status":"Extracting","progressDetail":{"current":3855277,"total":3855277},"progress":"[==================================================\u003e] 3.855MB/3.855MB","id":"3192b2fa42db"} {"status":"Extracting","progressDetail":{"current":3855277,"total":3855277},"progress":"[==================================================\u003e] 3.855MB/3.855MB","id":"3192b2fa42db"} {"status":"Pull complete","progressDetail":{},"id":"3192b2fa42db"} {"status":"Extracting","progressDetail":{"current":65536,"total":4983195},"progress":"[\u003e ] 65.54kB/4.983MB","id":"ae190b8f66a7"} {"status":"Extracting","progressDetail":{"current":327680,"total":4983195},"progress":"[===\u003e ] 327.7kB/4.983MB","id":"ae190b8f66a7"} {"status":"Extracting","progressDetail":{"current":4980736,"total":4983195},"progress":"[=================================================\u003e ] 4.981MB/4.983MB","id":"ae190b8f66a7"} {"status":"Extracting","progressDetail":{"current":4983195,"total":4983195},"progress":"[==================================================\u003e] 4.983MB/4.983MB","id":"ae190b8f66a7"} {"status":"Pull complete","progressDetail":{},"id":"ae190b8f66a7"} {"status":"Extracting","progressDetail":{"current":65536,"total":6103207},"progress":"[\u003e ] 65.54kB/6.103MB","id":"97bb6e138460"} {"status":"Extracting","progressDetail":{"current":327680,"total":6103207},"progress":"[==\u003e ] 327.7kB/6.103MB","id":"97bb6e138460"} {"status":"Extracting","progressDetail":{"current":3670016,"total":6103207},"progress":"[==============================\u003e ] 3.67MB/6.103MB","id":"97bb6e138460"} {"status":"Extracting","progressDetail":{"current":6103207,"total":6103207},"progress":"[==================================================\u003e] 6.103MB/6.103MB","id":"97bb6e138460"} {"status":"Pull complete","progressDetail":{},"id":"97bb6e138460"} {"status":"Extracting","progressDetail":{"current":787,"total":787},"progress":"[==================================================\u003e] 787B/787B","id":"2edb982d5170"} {"status":"Extracting","progressDetail":{"current":787,"total":787},"progress":"[==================================================\u003e] 787B/787B","id":"2edb982d5170"} {"status":"Pull complete","progressDetail":{},"id":"2edb982d5170"} {"status":"Extracting","progressDetail":{"current":65536,"total":4894860},"progress":"[\u003e ] 65.54kB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Extracting","progressDetail":{"current":327680,"total":4894860},"progress":"[===\u003e ] 327.7kB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Extracting","progressDetail":{"current":3735552,"total":4894860},"progress":"[======================================\u003e ] 3.736MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Extracting","progressDetail":{"current":4894860,"total":4894860},"progress":"[==================================================\u003e] 4.895MB/4.895MB","id":"7ddc8e6d6da9"} {"status":"Pull complete","progressDetail":{},"id":"7ddc8e6d6da9"} {"status":"Extracting","progressDetail":{"current":65536,"total":4953791},"progress":"[\u003e ] 65.54kB/4.954MB","id":"0df6fd234b59"} {"status":"Extracting","progressDetail":{"current":327680,"total":4953791},"progress":"[===\u003e ] 327.7kB/4.954MB","id":"0df6fd234b59"} {"status":"Extracting","progressDetail":{"current":4325376,"total":4953791},"progress":"[===========================================\u003e ] 4.325MB/4.954MB","id":"0df6fd234b59"} {"status":"Extracting","progressDetail":{"current":4953791,"total":4953791},"progress":"[==================================================\u003e] 4.954MB/4.954MB","id":"0df6fd234b59"} {"status":"Pull complete","progressDetail":{},"id":"0df6fd234b59"} {"status":"Extracting","progressDetail":{"current":65536,"total":6137526},"progress":"[\u003e ] 65.54kB/6.138MB","id":"8fc1ba8efe21"} {"status":"Extracting","progressDetail":{"current":327680,"total":6137526},"progress":"[==\u003e ] 327.7kB/6.138MB","id":"8fc1ba8efe21"} {"status":"Extracting","progressDetail":{"current":3801088,"total":6137526},"progress":"[==============================\u003e ] 3.801MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Extracting","progressDetail":{"current":6137526,"total":6137526},"progress":"[==================================================\u003e] 6.138MB/6.138MB","id":"8fc1ba8efe21"} {"status":"Pull complete","progressDetail":{},"id":"8fc1ba8efe21"} {"status":"Extracting","progressDetail":{"current":65536,"total":3854415},"progress":"[\u003e ] 65.54kB/3.854MB","id":"1f6f45e783b5"} {"status":"Extracting","progressDetail":{"current":851968,"total":3854415},"progress":"[===========\u003e ] 852kB/3.854MB","id":"1f6f45e783b5"} {"status":"Extracting","progressDetail":{"current":3854415,"total":3854415},"progress":"[==================================================\u003e] 3.854MB/3.854MB","id":"1f6f45e783b5"} {"status":"Extracting","progressDetail":{"current":3854415,"total":3854415},"progress":"[==================================================\u003e] 3.854MB/3.854MB","id":"1f6f45e783b5"} {"status":"Pull complete","progressDetail":{},"id":"1f6f45e783b5"} {"status":"Extracting","progressDetail":{"current":65536,"total":5222290},"progress":"[\u003e ] 65.54kB/5.222MB","id":"43ea61082f68"} {"status":"Extracting","progressDetail":{"current":458752,"total":5222290},"progress":"[====\u003e ] 458.8kB/5.222MB","id":"43ea61082f68"} {"status":"Extracting","progressDetail":{"current":4849664,"total":5222290},"progress":"[==============================================\u003e ] 4.85MB/5.222MB","id":"43ea61082f68"} {"status":"Extracting","progressDetail":{"current":5222290,"total":5222290},"progress":"[==================================================\u003e] 5.222MB/5.222MB","id":"43ea61082f68"} {"status":"Pull complete","progressDetail":{},"id":"43ea61082f68"} {"status":"Extracting","progressDetail":{"current":65536,"total":3564359},"progress":"[\u003e ] 65.54kB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Extracting","progressDetail":{"current":327680,"total":3564359},"progress":"[====\u003e ] 327.7kB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Extracting","progressDetail":{"current":3564359,"total":3564359},"progress":"[==================================================\u003e] 3.564MB/3.564MB","id":"b8cf53bbc6ba"} {"status":"Pull complete","progressDetail":{},"id":"b8cf53bbc6ba"} {"status":"Extracting","progressDetail":{"current":790,"total":790},"progress":"[==================================================\u003e] 790B/790B","id":"25efb07e4521"} {"status":"Extracting","progressDetail":{"current":790,"total":790},"progress":"[==================================================\u003e] 790B/790B","id":"25efb07e4521"} {"status":"Pull complete","progressDetail":{},"id":"25efb07e4521"} {"status":"Extracting","progressDetail":{"current":65536,"total":5120108},"progress":"[\u003e ] 65.54kB/5.12MB","id":"1c3245356213"} {"status":"Extracting","progressDetail":{"current":327680,"total":5120108},"progress":"[===\u003e ] 327.7kB/5.12MB","id":"1c3245356213"} {"status":"Extracting","progressDetail":{"current":5111808,"total":5120108},"progress":"[=================================================\u003e ] 5.112MB/5.12MB","id":"1c3245356213"} {"status":"Extracting","progressDetail":{"current":5120108,"total":5120108},"progress":"[==================================================\u003e] 5.12MB/5.12MB","id":"1c3245356213"} {"status":"Pull complete","progressDetail":{},"id":"1c3245356213"} {"status":"Extracting","progressDetail":{"current":65536,"total":5117023},"progress":"[\u003e ] 65.54kB/5.117MB","id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":655360,"total":5117023},"progress":"[======\u003e ] 655.4kB/5.117MB","id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":4259840,"total":5117023},"progress":"[=========================================\u003e ] 4.26MB/5.117MB","id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":5117023,"total":5117023},"progress":"[==================================================\u003e] 5.117MB/5.117MB","id":"61ebb123c1eb"} {"status":"Pull complete","progressDetail":{},"id":"61ebb123c1eb"} {"status":"Extracting","progressDetail":{"current":65536,"total":5384215},"progress":"[\u003e ] 65.54kB/5.384MB","id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":327680,"total":5384215},"progress":"[===\u003e ] 327.7kB/5.384MB","id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":5177344,"total":5384215},"progress":"[================================================\u003e ] 5.177MB/5.384MB","id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":5384215,"total":5384215},"progress":"[==================================================\u003e] 5.384MB/5.384MB","id":"0964b769d2c9"} {"status":"Pull complete","progressDetail":{},"id":"0964b769d2c9"} {"status":"Extracting","progressDetail":{"current":65536,"total":5252487},"progress":"[\u003e ] 65.54kB/5.252MB","id":"87f7843f43cd"} {"status":"Extracting","progressDetail":{"current":655360,"total":5252487},"progress":"[======\u003e ] 655.4kB/5.252MB","id":"87f7843f43cd"} {"status":"Extracting","progressDetail":{"current":5252487,"total":5252487},"progress":"[==================================================\u003e] 5.252MB/5.252MB","id":"87f7843f43cd"} {"status":"Pull complete","progressDetail":{},"id":"87f7843f43cd"} {"status":"Extracting","progressDetail":{"current":65536,"total":5015856},"progress":"[\u003e ] 65.54kB/5.016MB","id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":327680,"total":5015856},"progress":"[===\u003e ] 327.7kB/5.016MB","id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":3997696,"total":5015856},"progress":"[=======================================\u003e ] 3.998MB/5.016MB","id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":5015856,"total":5015856},"progress":"[==================================================\u003e] 5.016MB/5.016MB","id":"a89dbf94d794"} {"status":"Pull complete","progressDetail":{},"id":"a89dbf94d794"} {"status":"Extracting","progressDetail":{"current":65536,"total":5310566},"progress":"[\u003e ] 65.54kB/5.311MB","id":"f0d43ddca77f"} {"status":"Extracting","progressDetail":{"current":393216,"total":5310566},"progress":"[===\u003e ] 393.2kB/5.311MB","id":"f0d43ddca77f"} {"status":"Extracting","progressDetail":{"current":3407872,"total":5310566},"progress":"[================================\u003e ] 3.408MB/5.311MB","id":"f0d43ddca77f"} {"status":"Extracting","progressDetail":{"current":5310566,"total":5310566},"progress":"[==================================================\u003e] 5.311MB/5.311MB","id":"f0d43ddca77f"} {"status":"Pull complete","progressDetail":{},"id":"f0d43ddca77f"} {"status":"Extracting","progressDetail":{"current":65536,"total":4915049},"progress":"[\u003e ] 65.54kB/4.915MB","id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":786432,"total":4915049},"progress":"[========\u003e ] 786.4kB/4.915MB","id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":4915049,"total":4915049},"progress":"[==================================================\u003e] 4.915MB/4.915MB","id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":4915049,"total":4915049},"progress":"[==================================================\u003e] 4.915MB/4.915MB","id":"7c674f0cb40c"} {"status":"Pull complete","progressDetail":{},"id":"7c674f0cb40c"} {"status":"Extracting","progressDetail":{"current":65536,"total":5119213},"progress":"[\u003e ] 65.54kB/5.119MB","id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":327680,"total":5119213},"progress":"[===\u003e ] 327.7kB/5.119MB","id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":4390912,"total":5119213},"progress":"[==========================================\u003e ] 4.391MB/5.119MB","id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":5119213,"total":5119213},"progress":"[==================================================\u003e] 5.119MB/5.119MB","id":"b48a885b52bc"} {"status":"Pull complete","progressDetail":{},"id":"b48a885b52bc"} {"status":"Extracting","progressDetail":{"current":395,"total":395},"progress":"[==================================================\u003e] 395B/395B","id":"272cdf839cbb"} {"status":"Extracting","progressDetail":{"current":395,"total":395},"progress":"[==================================================\u003e] 395B/395B","id":"272cdf839cbb"} {"status":"Pull complete","progressDetail":{},"id":"272cdf839cbb"} {"status":"Extracting","progressDetail":{"current":155,"total":155},"progress":"[==================================================\u003e] 155B/155B","id":"50d054c97f4f"} {"status":"Extracting","progressDetail":{"current":155,"total":155},"progress":"[==================================================\u003e] 155B/155B","id":"50d054c97f4f"} {"status":"Pull complete","progressDetail":{},"id":"50d054c97f4f"} {"status":"Extracting","progressDetail":{"current":1069,"total":1069},"progress":"[==================================================\u003e] 1.069kB/1.069kB","id":"4c6bbd90b64d"} {"status":"Extracting","progressDetail":{"current":1069,"total":1069},"progress":"[==================================================\u003e] 1.069kB/1.069kB","id":"4c6bbd90b64d"} {"status":"Pull complete","progressDetail":{},"id":"4c6bbd90b64d"} {"status":"Extracting","progressDetail":{"current":32,"total":32},"progress":"[==================================================\u003e] 32B/32B","id":"4f4fb700ef54"} {"status":"Extracting","progressDetail":{"current":32,"total":32},"progress":"[==================================================\u003e] 32B/32B","id":"4f4fb700ef54"} {"status":"Pull complete","progressDetail":{},"id":"4f4fb700ef54"} {"status":"Digest: sha256:4acb6bfd6c4f0cabaf7f3690e444afe51f1c7de54d51da7e63fac709c56f1c30"} {"status":"Status: Downloaded newer image for paketo-buildpacks/cnb:base"} ================================================ FILE: buildpack/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/json/test-mapped-object.json ================================================ { "string": "stringvalue", "stringarray": [ "a", "b" ], "StartsWithUppercase": "value", "person": { "name": { "title": "dr", "first": "spring", "last": "boot" } } } ================================================ FILE: cli/spring-boot-cli/build.gradle ================================================ /* * 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. */ import org.springframework.boot.build.properties.BuildProperties import org.springframework.boot.build.properties.BuildType plugins { id "java" id "eclipse" id "org.springframework.boot.deployed" id "org.springframework.boot.integration-test" } description = "Spring Boot CLI" sourceSets { main { java { srcDir file("src/json-shade/java") } } } configurations { loader testRepository compileOnlyProject compileClasspath.extendsFrom(compileOnlyProject) } dependencies { compileOnlyProject(project(":core:spring-boot")) implementation(project(":loader:spring-boot-loader-tools")) implementation("jline:jline") implementation("net.sf.jopt-simple:jopt-simple") implementation("org.apache.httpcomponents.client5:httpclient5") implementation("org.slf4j:slf4j-simple") implementation("org.springframework:spring-core") implementation("org.springframework.security:spring-security-crypto") intTestImplementation(project(":test-support:spring-boot-test-support")) loader(project(":loader:spring-boot-loader")) testImplementation(project(":core:spring-boot")) testImplementation(project(":core:spring-boot-test")) testImplementation(project(":test-support:spring-boot-test-support")) } nullability { requireExplicitNullMarking = false } tasks.register("fullJar", Jar) { dependsOn configurations.loader archiveClassifier = "full" entryCompression = "stored" from(configurations.runtimeClasspath) { into "BOOT-INF/lib" } from(sourceSets.main.output) { into "BOOT-INF/classes" } from { zipTree(configurations.loader.singleFile).matching { exclude "META-INF/LICENSE.txt" exclude "META-INF/NOTICE.txt" exclude "META-INF/spring-boot.properties" } } manifest { attributes( "Main-Class": "org.springframework.boot.loader.launch.JarLauncher", "Start-Class": "org.springframework.boot.cli.SpringCli" ) } } def configureArchive(archive) { archive.archiveClassifier = "bin" archive.into "spring-${project.version}" archive.from(fullJar) { rename { it.replace("-full", "") } into "lib/" } archive.from(file("src/main/content")) { dirPermissions { unix(0755) } filePermissions { unix(0644) } } archive.from(file("src/main/executablecontent")) { filePermissions { unix(0755) } } } tasks.register("zip", Zip) { archiveClassifier = "bin" configureArchive it } intTest { dependsOn zip } tasks.register("tar", Tar) { compression = "gzip" archiveExtension = "tar.gz" configureArchive it } if (BuildProperties.get(project).buildType() == BuildType.OPEN_SOURCE) { def homebrewFormula = tasks.register("homebrewFormula", org.springframework.boot.build.cli.HomebrewFormula) { dependsOn tar outputDir = layout.buildDirectory.dir("homebrew") template = file("src/main/homebrew/spring-boot.rb") archive = tar.archiveFile } publishing { publications { getByName("maven") { artifact(homebrewFormula.map { it.outputDir.file("spring-boot.rb") }) { formula -> formula.classifier = "homebrew" formula.extension = "rb" } } } } } publishing { publications { getByName("maven") { artifact fullJar artifact tar artifact zip } } } eclipse.classpath { // https://github.com/eclipse/buildship/issues/939 plusConfigurations += [ configurations.compileOnlyProject ] } tasks.named("compileTestJava") { options.nullability.checking = "tests" } tasks.named("compileIntTestJava") { options.nullability.checking = "tests" } ================================================ FILE: cli/spring-boot-cli/src/intTest/java/org/springframework/boot/cli/CommandLineIT.java ================================================ /* * 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.cli; import java.io.File; import java.io.IOException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.cli.infrastructure.CommandLineInvoker; import org.springframework.boot.cli.infrastructure.CommandLineInvoker.Invocation; import static org.assertj.core.api.Assertions.assertThat; /** * Integration Tests for the command line application. * * @author Andy Wilkinson * @author Phillip Webb */ class CommandLineIT { private CommandLineInvoker cli; @BeforeEach void setup(@TempDir File tempDir) { this.cli = new CommandLineInvoker(tempDir); } @Test void hintProducesListOfValidCommands() throws IOException, InterruptedException { Invocation cli = this.cli.invoke("hint"); assertThat(cli.await()).isEqualTo(0); assertThat(cli.getErrorOutput()).isEmpty(); assertThat(cli.getStandardOutputLines()).hasSize(5); } @Test void invokingWithNoArgumentsDisplaysHelp() throws IOException, InterruptedException { Invocation cli = this.cli.invoke(); assertThat(cli.await()).isEqualTo(1); assertThat(cli.getErrorOutput()).isEmpty(); assertThat(cli.getStandardOutput()).startsWith("usage:"); } @Test void unrecognizedCommandsAreHandledGracefully() throws IOException, InterruptedException { Invocation cli = this.cli.invoke("not-a-real-command"); assertThat(cli.await()).isEqualTo(1); assertThat(cli.getErrorOutput()).contains("'not-a-real-command' is not a valid command"); assertThat(cli.getStandardOutput()).isEmpty(); } @Test void version() throws IOException, InterruptedException { Invocation cli = this.cli.invoke("version"); assertThat(cli.await()).isEqualTo(0); assertThat(cli.getErrorOutput()).isEmpty(); assertThat(cli.getStandardOutput()).startsWith("Spring CLI v"); } @Test void help() throws IOException, InterruptedException { Invocation cli = this.cli.invoke("help"); assertThat(cli.await()).isEqualTo(1); assertThat(cli.getErrorOutput()).isEmpty(); assertThat(cli.getStandardOutput()).startsWith("usage:"); } } ================================================ FILE: cli/spring-boot-cli/src/intTest/java/org/springframework/boot/cli/infrastructure/CommandLineInvoker.java ================================================ /* * 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.cli.infrastructure; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.springframework.boot.testsupport.BuildOutput; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; /** * Utility to invoke the command line in the same way as a user would, i.e. through the * shell script in the package's bin directory. * * @author Andy Wilkinson * @author Phillip Webb */ public final class CommandLineInvoker { private final File workingDirectory; private final File temp; public CommandLineInvoker(File temp) { this(new File("."), temp); } public CommandLineInvoker(File workingDirectory, File temp) { this.workingDirectory = workingDirectory; this.temp = temp; } public Invocation invoke(String... args) throws IOException { return new Invocation(runCliProcess(args)); } private Process runCliProcess(String... args) throws IOException { Path m2 = this.temp.toPath().resolve(".m2"); Files.createDirectories(m2); Files.copy(Paths.get("src", "intTest", "resources", "settings.xml"), m2.resolve("settings.xml"), StandardCopyOption.REPLACE_EXISTING); List command = new ArrayList<>(); command.add(findLaunchScript().getAbsolutePath()); command.addAll(Arrays.asList(args)); ProcessBuilder processBuilder = new ProcessBuilder(command).directory(this.workingDirectory); processBuilder.environment().put("JAVA_OPTS", "-Duser.home=" + this.temp); processBuilder.environment().put("JAVA_HOME", System.getProperty("java.home")); return processBuilder.start(); } private File findLaunchScript() throws IOException { File unpacked = new File(this.temp, "unpacked-cli"); if (!unpacked.isDirectory()) { File zip = new File(new BuildOutput(getClass()).getRootLocation(), "distributions/spring-boot-cli-" + Versions.getBootVersion() + "-bin.zip"); try (ZipInputStream input = new ZipInputStream(new FileInputStream(zip))) { ZipEntry entry; while ((entry = input.getNextEntry()) != null) { File file = new File(unpacked, entry.getName()); if (entry.isDirectory()) { file.mkdirs(); } else { file.getParentFile().mkdirs(); try (FileOutputStream output = new FileOutputStream(file)) { StreamUtils.copy(input, output); if (entry.getName().endsWith("/bin/spring")) { file.setExecutable(true); } } } } } } File bin = new File(unpacked.listFiles()[0], "bin"); File launchScript = new File(bin, isWindows() ? "spring.bat" : "spring"); Assert.state(launchScript.exists() && launchScript.isFile(), () -> "Could not find CLI launch script " + launchScript.getAbsolutePath()); return launchScript; } private boolean isWindows() { return File.separatorChar == '\\'; } /** * An ongoing Process invocation. */ public static final class Invocation { private final StringBuffer err = new StringBuffer(); private final StringBuffer out = new StringBuffer(); private final StringBuffer combined = new StringBuffer(); private final Process process; private final List streamReaders = new ArrayList<>(); public Invocation(Process process) { this.process = process; this.streamReaders .add(new Thread(new StreamReadingRunnable(this.process.getErrorStream(), this.err, this.combined))); this.streamReaders .add(new Thread(new StreamReadingRunnable(this.process.getInputStream(), this.out, this.combined))); for (Thread streamReader : this.streamReaders) { streamReader.start(); } } public String getOutput() { return postProcessLines(getLines(this.combined)); } public String getErrorOutput() { return postProcessLines(getLines(this.err)); } public String getStandardOutput() { return postProcessLines(getStandardOutputLines()); } public List getStandardOutputLines() { return getLines(this.out); } private String postProcessLines(List lines) { StringWriter out = new StringWriter(); PrintWriter printOut = new PrintWriter(out); for (String line : lines) { if (!line.startsWith("Maven settings decryption failed")) { printOut.println(line); } } return out.toString(); } private List getLines(StringBuffer buffer) { BufferedReader reader = new BufferedReader(new StringReader(buffer.toString())); return reader.lines().filter((line) -> !line.startsWith("Picked up ")).toList(); } public int await() throws InterruptedException { for (Thread streamReader : this.streamReaders) { streamReader.join(); } return this.process.waitFor(); } /** * {@link Runnable} to copy stream output. */ private final class StreamReadingRunnable implements Runnable { private final InputStream stream; private final StringBuffer[] outputs; private final byte[] buffer = new byte[4096]; private StreamReadingRunnable(InputStream stream, StringBuffer... outputs) { this.stream = stream; this.outputs = outputs; } @Override public void run() { int read; try { while ((read = this.stream.read(this.buffer)) > 0) { for (StringBuffer output : this.outputs) { output.append(new String(this.buffer, 0, read)); } } } catch (IOException ex) { // Allow thread to die } } } } } ================================================ FILE: cli/spring-boot-cli/src/intTest/java/org/springframework/boot/cli/infrastructure/Versions.java ================================================ /* * 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.cli.infrastructure; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; /** * Provides access to the current Boot version by referring to {@code gradle.properties}. * * @author Andy Wilkinson */ final class Versions { private Versions() { } static String getBootVersion() { Properties gradleProperties = new Properties(); try (FileInputStream input = new FileInputStream("../../gradle.properties")) { gradleProperties.load(input); return gradleProperties.getProperty("version"); } catch (IOException ex) { throw new RuntimeException(ex); } } } ================================================ FILE: cli/spring-boot-cli/src/intTest/resources/settings.xml ================================================ ../../../../build/local-m2-repository cli-test-repo true local.central file:../../../../build/test-repository true true thymeleaf-snapshot https://oss.sonatype.org/content/repositories/snapshots true true ================================================ FILE: cli/spring-boot-cli/src/json-shade/README.adoc ================================================ ## Shaded JSON This source was originally taken from `com.vaadin.external.google:android-json` which provides a clean room re-implementation of the `org.json` APIs and does not include the "Do not use for evil" clause. ================================================ FILE: cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSON.java ================================================ /* * 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.cli.json; class JSON { static double checkDouble(double d) throws JSONException { if (Double.isInfinite(d) || Double.isNaN(d)) { throw new JSONException("Forbidden numeric value: " + d); } return d; } static Boolean toBoolean(Object value) { if (value instanceof Boolean) { return (Boolean) value; } if (value instanceof String stringValue) { if ("true".equalsIgnoreCase(stringValue)) { return true; } if ("false".equalsIgnoreCase(stringValue)) { return false; } } return null; } static Double toDouble(Object value) { if (value instanceof Double) { return (Double) value; } if (value instanceof Number) { return ((Number) value).doubleValue(); } if (value instanceof String) { try { return Double.valueOf((String) value); } catch (NumberFormatException ex) { // Ignore } } return null; } static Integer toInteger(Object value) { if (value instanceof Integer) { return (Integer) value; } if (value instanceof Number) { return ((Number) value).intValue(); } if (value instanceof String) { try { return (int) Double.parseDouble((String) value); } catch (NumberFormatException ex) { // Ignore } } return null; } static Long toLong(Object value) { if (value instanceof Long) { return (Long) value; } if (value instanceof Number) { return ((Number) value).longValue(); } if (value instanceof String) { try { return (long) Double.parseDouble((String) value); } catch (NumberFormatException ex) { // Ignore } } return null; } static String toString(Object value) { if (value instanceof String) { return (String) value; } if (value != null) { return String.valueOf(value); } return null; } public static JSONException typeMismatch(Object indexOrName, Object actual, String requiredType) throws JSONException { if (actual == null) { throw new JSONException("Value at " + indexOrName + " is null."); } throw new JSONException("Value " + actual + " at " + indexOrName + " of type " + actual.getClass().getName() + " cannot be converted to " + requiredType); } public static JSONException typeMismatch(Object actual, String requiredType) throws JSONException { if (actual == null) { throw new JSONException("Value is null."); } throw new JSONException("Value " + actual + " of type " + actual.getClass().getName() + " cannot be converted to " + requiredType); } } ================================================ FILE: cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONArray.java ================================================ /* * 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.cli.json; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; // Note: this class was written without inspecting the non-free org.json source code. /** * A dense indexed sequence of values. Values may be any mix of {@link JSONObject * JSONObjects}, other {@link JSONArray JSONArrays}, Strings, Booleans, Integers, Longs, * Doubles, {@code null} or {@link JSONObject#NULL}. Values may not be * {@link Double#isNaN() NaNs}, {@link Double#isInfinite() infinities}, or of any type not * listed here. *

* {@code JSONArray} has the same type coercion behavior and optional/mandatory accessors * as {@link JSONObject}. See that class' documentation for details. *

* Warning: this class represents null in two incompatible ways: the * standard Java {@code null} reference, and the sentinel value {@link JSONObject#NULL}. * In particular, {@code get} fails if the requested index holds the null reference, but * succeeds if it holds {@code JSONObject.NULL}. *

* Instances of this class are not thread safe. Although this class is nonfinal, it was * not designed for inheritance and should not be subclassed. In particular, self-use by * overridable methods is not specified. See Effective Java Item 17, "Design and * Document or inheritance or else prohibit it" for further information. */ public class JSONArray { private final List values; /** * Creates a {@code JSONArray} with no values. */ public JSONArray() { this.values = new ArrayList<>(); } /** * Creates a new {@code JSONArray} by copying all values from the given collection. * @param copyFrom a collection whose values are of supported types. Unsupported * values are not permitted and will yield an array in an inconsistent state. */ /* Accept a raw type for API compatibility */ @SuppressWarnings("rawtypes") public JSONArray(Collection copyFrom) { this(); if (copyFrom != null) { for (Iterator it = copyFrom.iterator(); it.hasNext();) { put(JSONObject.wrap(it.next())); } } } /** * Creates a new {@code JSONArray} with values from the next array in the tokener. * @param readFrom a tokener whose nextValue() method will yield a {@code JSONArray}. * @throws JSONException if the parse fails or doesn't yield a {@code JSONArray}. * @throws JSONException if processing of json failed */ public JSONArray(JSONTokener readFrom) throws JSONException { /* * Getting the parser to populate this could get tricky. Instead, just parse to * temporary JSONArray and then steal the data from that. */ Object object = readFrom.nextValue(); if (object instanceof JSONArray) { this.values = ((JSONArray) object).values; } else { throw JSON.typeMismatch(object, "JSONArray"); } } /** * Creates a new {@code JSONArray} with values from the JSON string. * @param json a JSON-encoded string containing an array. * @throws JSONException if the parse fails or doesn't yield a {@code * JSONArray}. */ public JSONArray(String json) throws JSONException { this(new JSONTokener(json)); } /** * Creates a new {@code JSONArray} with values from the given primitive array. * @param array a primitive array * @throws JSONException if processing of json failed */ public JSONArray(Object array) throws JSONException { if (!array.getClass().isArray()) { throw new JSONException("Not a primitive array: " + array.getClass()); } final int length = Array.getLength(array); this.values = new ArrayList<>(length); for (int i = 0; i < length; ++i) { put(JSONObject.wrap(Array.get(array, i))); } } /** * Returns the number of values in this array. * @return the length of this array */ public int length() { return this.values.size(); } /** * Appends {@code value} to the end of this array. * @param value the value * @return this array. */ public JSONArray put(boolean value) { this.values.add(value); return this; } /** * Appends {@code value} to the end of this array. * @param value a finite value. May not be {@link Double#isNaN() NaNs} or * {@link Double#isInfinite() infinities}. * @return this array. * @throws JSONException if processing of json failed */ public JSONArray put(double value) throws JSONException { this.values.add(JSON.checkDouble(value)); return this; } /** * Appends {@code value} to the end of this array. * @param value the value * @return this array. */ public JSONArray put(int value) { this.values.add(value); return this; } /** * Appends {@code value} to the end of this array. * @param value the value * @return this array. */ public JSONArray put(long value) { this.values.add(value); return this; } /** * Appends {@code value} to the end of this array. * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, * Long, Double, {@link JSONObject#NULL}, or {@code null}. May not be * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. Unsupported * values are not permitted and will cause the array to be in an inconsistent state. * @return this array. */ public JSONArray put(Object value) { this.values.add(value); return this; } /** * Sets the value at {@code index} to {@code value}, null padding this array to the * required length if necessary. If a value already exists at {@code * index}, it will be replaced. * @param index the index to set the value to * @param value the value * @return this array. * @throws JSONException if processing of json failed */ public JSONArray put(int index, boolean value) throws JSONException { return put(index, (Boolean) value); } /** * Sets the value at {@code index} to {@code value}, null padding this array to the * required length if necessary. If a value already exists at {@code * index}, it will be replaced. * @param index the index to set the value to * @param value a finite value. May not be {@link Double#isNaN() NaNs} or * {@link Double#isInfinite() infinities}. * @return this array. * @throws JSONException if processing of json failed */ public JSONArray put(int index, double value) throws JSONException { return put(index, (Double) value); } /** * Sets the value at {@code index} to {@code value}, null padding this array to the * required length if necessary. If a value already exists at {@code * index}, it will be replaced. * @param index the index to set the value to * @param value the value * @return this array. * @throws JSONException if processing of json failed */ public JSONArray put(int index, int value) throws JSONException { return put(index, (Integer) value); } /** * Sets the value at {@code index} to {@code value}, null padding this array to the * required length if necessary. If a value already exists at {@code * index}, it will be replaced. * @param index the index to set the value to * @param value the value * @return this array. * @throws JSONException if processing of json failed */ public JSONArray put(int index, long value) throws JSONException { return put(index, (Long) value); } /** * Sets the value at {@code index} to {@code value}, null padding this array to the * required length if necessary. If a value already exists at {@code * index}, it will be replaced. * @param index the index to set the value to * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, * Long, Double, {@link JSONObject#NULL}, or {@code null}. May not be * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. * @return this array. * @throws JSONException if processing of json failed */ public JSONArray put(int index, Object value) throws JSONException { if (value instanceof Number) { // deviate from the original by checking all Numbers, not just floats & // doubles JSON.checkDouble(((Number) value).doubleValue()); } while (this.values.size() <= index) { this.values.add(null); } this.values.set(index, value); return this; } /** * Returns true if this array has no value at {@code index}, or if its value is the * {@code null} reference or {@link JSONObject#NULL}. * @param index the index to set the value to * @return true if this array has no value at {@code index} */ public boolean isNull(int index) { Object value = opt(index); return value == null || value == JSONObject.NULL; } /** * Returns the value at {@code index}. * @param index the index to get the value from * @return the value at {@code index}. * @throws JSONException if this array has no value at {@code index}, or if that value * is the {@code null} reference. This method returns normally if the value is * {@code JSONObject#NULL}. */ public Object get(int index) throws JSONException { try { Object value = this.values.get(index); if (value == null) { throw new JSONException("Value at " + index + " is null."); } return value; } catch (IndexOutOfBoundsException e) { throw new JSONException("Index " + index + " out of range [0.." + this.values.size() + ")"); } } /** * Returns the value at {@code index}, or null if the array has no value at * {@code index}. * @param index the index to get the value from * @return the value at {@code index} or {@code null} */ public Object opt(int index) { if (index < 0 || index >= this.values.size()) { return null; } return this.values.get(index); } /** * Removes and returns the value at {@code index}, or null if the array has no value * at {@code index}. * @param index the index of the value to remove * @return the previous value at {@code index} */ public Object remove(int index) { if (index < 0 || index >= this.values.size()) { return null; } return this.values.remove(index); } /** * Returns the value at {@code index} if it exists and is a boolean or can be coerced * to a boolean. * @param index the index to get the value from * @return the value at {@code index} * @throws JSONException if the value at {@code index} doesn't exist or cannot be * coerced to a boolean. */ public boolean getBoolean(int index) throws JSONException { Object object = get(index); Boolean result = JSON.toBoolean(object); if (result == null) { throw JSON.typeMismatch(index, object, "boolean"); } return result; } /** * Returns the value at {@code index} if it exists and is a boolean or can be coerced * to a boolean. Returns false otherwise. * @param index the index to get the value from * @return the {@code value} or {@code false} */ public boolean optBoolean(int index) { return optBoolean(index, false); } /** * Returns the value at {@code index} if it exists and is a boolean or can be coerced * to a boolean. Returns {@code fallback} otherwise. * @param index the index to get the value from * @param fallback the fallback value * @return the value at {@code index} of {@code fallback} */ public boolean optBoolean(int index, boolean fallback) { Object object = opt(index); Boolean result = JSON.toBoolean(object); return result != null ? result : fallback; } /** * Returns the value at {@code index} if it exists and is a double or can be coerced * to a double. * @param index the index to get the value from * @return the {@code value} * @throws JSONException if the value at {@code index} doesn't exist or cannot be * coerced to a double. */ public double getDouble(int index) throws JSONException { Object object = get(index); Double result = JSON.toDouble(object); if (result == null) { throw JSON.typeMismatch(index, object, "double"); } return result; } /** * Returns the value at {@code index} if it exists and is a double or can be coerced * to a double. Returns {@code NaN} otherwise. * @param index the index to get the value from * @return the {@code value} or {@code NaN} */ public double optDouble(int index) { return optDouble(index, Double.NaN); } /** * Returns the value at {@code index} if it exists and is a double or can be coerced * to a double. Returns {@code fallback} otherwise. * @param index the index to get the value from * @param fallback the fallback value * @return the value at {@code index} of {@code fallback} */ public double optDouble(int index, double fallback) { Object object = opt(index); Double result = JSON.toDouble(object); return result != null ? result : fallback; } /** * Returns the value at {@code index} if it exists and is an int or can be coerced to * an int. * @param index the index to get the value from * @return the {@code value} * @throws JSONException if the value at {@code index} doesn't exist or cannot be * coerced to an int. */ public int getInt(int index) throws JSONException { Object object = get(index); Integer result = JSON.toInteger(object); if (result == null) { throw JSON.typeMismatch(index, object, "int"); } return result; } /** * Returns the value at {@code index} if it exists and is an int or can be coerced to * an int. Returns 0 otherwise. * @param index the index to get the value from * @return the {@code value} or {@code 0} */ public int optInt(int index) { return optInt(index, 0); } /** * Returns the value at {@code index} if it exists and is an int or can be coerced to * an int. Returns {@code fallback} otherwise. * @param index the index to get the value from * @param fallback the fallback value * @return the value at {@code index} of {@code fallback} */ public int optInt(int index, int fallback) { Object object = opt(index); Integer result = JSON.toInteger(object); return result != null ? result : fallback; } /** * Returns the value at {@code index} if it exists and is a long or can be coerced to * a long. * @param index the index to get the value from * @return the {@code value} * @throws JSONException if the value at {@code index} doesn't exist or cannot be * coerced to a long. */ public long getLong(int index) throws JSONException { Object object = get(index); Long result = JSON.toLong(object); if (result == null) { throw JSON.typeMismatch(index, object, "long"); } return result; } /** * Returns the value at {@code index} if it exists and is a long or can be coerced to * a long. Returns 0 otherwise. * @param index the index to get the value from * @return the {@code value} or {@code 0} */ public long optLong(int index) { return optLong(index, 0L); } /** * Returns the value at {@code index} if it exists and is a long or can be coerced to * a long. Returns {@code fallback} otherwise. * @param index the index to get the value from * @param fallback the fallback value * @return the value at {@code index} of {@code fallback} */ public long optLong(int index, long fallback) { Object object = opt(index); Long result = JSON.toLong(object); return result != null ? result : fallback; } /** * Returns the value at {@code index} if it exists, coercing it if necessary. * @param index the index to get the value from * @return the {@code value} * @throws JSONException if no such value exists. */ public String getString(int index) throws JSONException { Object object = get(index); String result = JSON.toString(object); if (result == null) { throw JSON.typeMismatch(index, object, "String"); } return result; } /** * Returns the value at {@code index} if it exists, coercing it if necessary. Returns * the empty string if no such value exists. * @param index the index to get the value from * @return the {@code value} or an empty string */ public String optString(int index) { return optString(index, ""); } /** * Returns the value at {@code index} if it exists, coercing it if necessary. Returns * {@code fallback} if no such value exists. * @param index the index to get the value from * @param fallback the fallback value * @return the value at {@code index} of {@code fallback} */ public String optString(int index, String fallback) { Object object = opt(index); String result = JSON.toString(object); return result != null ? result : fallback; } /** * Returns the value at {@code index} if it exists and is a {@code * JSONArray}. * @param index the index to get the value from * @return the array at {@code index} * @throws JSONException if the value doesn't exist or is not a {@code * JSONArray}. */ public JSONArray getJSONArray(int index) throws JSONException { Object object = get(index); if (object instanceof JSONArray) { return (JSONArray) object; } else { throw JSON.typeMismatch(index, object, "JSONArray"); } } /** * Returns the value at {@code index} if it exists and is a {@code * JSONArray}. Returns null otherwise. * @param index the index to get the value from * @return the array at {@code index} or {@code null} */ public JSONArray optJSONArray(int index) { Object object = opt(index); return object instanceof JSONArray ? (JSONArray) object : null; } /** * Returns the value at {@code index} if it exists and is a {@code * JSONObject}. * @param index the index to get the value from * @return the object at {@code index} * @throws JSONException if the value doesn't exist or is not a {@code * JSONObject}. */ public JSONObject getJSONObject(int index) throws JSONException { Object object = get(index); if (object instanceof JSONObject) { return (JSONObject) object; } else { throw JSON.typeMismatch(index, object, "JSONObject"); } } /** * Returns the value at {@code index} if it exists and is a {@code * JSONObject}. Returns null otherwise. * @param index the index to get the value from * @return the object at {@code index} or {@code null} */ public JSONObject optJSONObject(int index) { Object object = opt(index); return object instanceof JSONObject ? (JSONObject) object : null; } /** * Returns a new object whose values are the values in this array, and whose names are * the values in {@code names}. Names and values are paired up by index from 0 through * to the shorter array's length. Names that are not strings will be coerced to * strings. This method returns null if either array is empty. * @param names the property names * @return a json object * @throws JSONException if processing of json failed */ public JSONObject toJSONObject(JSONArray names) throws JSONException { JSONObject result = new JSONObject(); int length = Math.min(names.length(), this.values.size()); if (length == 0) { return null; } for (int i = 0; i < length; i++) { String name = JSON.toString(names.opt(i)); result.put(name, opt(i)); } return result; } /** * Returns a new string by alternating this array's values with {@code * separator}. This array's string values are quoted and have their special characters * escaped. For example, the array containing the strings '12" pizza', 'taco' and * 'soda' joined on '+' returns this:
"12\" pizza"+"taco"+"soda"
* @param separator the separator to use * @return the joined value * @throws JSONException if processing of json failed */ public String join(String separator) throws JSONException { JSONStringer stringer = new JSONStringer(); stringer.open(JSONStringer.Scope.NULL, ""); for (int i = 0, size = this.values.size(); i < size; i++) { if (i > 0) { stringer.out.append(separator); } stringer.value(this.values.get(i)); } stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); return stringer.out.toString(); } /** * Encodes this array as a compact JSON string, such as:
[94043,90210]
* @return a compact JSON string representation of this array */ @Override public String toString() { try { JSONStringer stringer = new JSONStringer(); writeTo(stringer); return stringer.toString(); } catch (JSONException e) { return null; } } /** * Encodes this array as a human-readable JSON string for debugging, such as:
	 * [
	 *     94043,
	 *     90210
	 * ]
* @param indentSpaces the number of spaces to indent for each level of nesting. * @return a human-readable JSON string of this array * @throws JSONException if processing of json failed */ public String toString(int indentSpaces) throws JSONException { JSONStringer stringer = new JSONStringer(indentSpaces); writeTo(stringer); return stringer.toString(); } void writeTo(JSONStringer stringer) throws JSONException { stringer.array(); for (Object value : this.values) { stringer.value(value); } stringer.endArray(); } @Override public boolean equals(Object o) { return o instanceof JSONArray && ((JSONArray) o).values.equals(this.values); } @Override public int hashCode() { // diverge from the original, which doesn't implement hashCode return this.values.hashCode(); } } ================================================ FILE: cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONException.java ================================================ /* * 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.cli.json; // Note: this class was written without inspecting the non-free org.json source code. /** * Thrown to indicate a problem with the JSON API. Such problems include: *
    *
  • Attempts to parse or construct malformed documents *
  • Use of null as a name *
  • Use of numeric types not available to JSON, such as {@link Double#isNaN() NaNs} or * {@link Double#isInfinite() infinities}. *
  • Lookups using an out of range index or nonexistent name *
  • Type mismatches on lookups *
*

* Although this is a checked exception, it is rarely recoverable. Most callers should * simply wrap this exception in an unchecked exception and rethrow:

 *     public JSONArray toJSONObject() {
 *     try {
 *         JSONObject result = new JSONObject();
 *         ...
 *     } catch (JSONException e) {
 *         throw new RuntimeException(e);
 *     }
 * }
*/ public class JSONException extends Exception { public JSONException(String s) { super(s); } } ================================================ FILE: cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONObject.java ================================================ /* * 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.cli.json; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; // Note: this class was written without inspecting the non-free org.json source code. /** * A modifiable set of name/value mappings. Names are unique, non-null strings. Values may * be any mix of {@link JSONObject JSONObjects}, {@link JSONArray JSONArrays}, Strings, * Booleans, Integers, Longs, Doubles or {@link #NULL}. Values may not be {@code null}, * {@link Double#isNaN() NaNs}, {@link Double#isInfinite() infinities}, or of any type not * listed here. *

* This class can coerce values to another type when requested. *

*

* This class can look up both mandatory and optional values: *

    *
  • Use getType() to retrieve a mandatory value. This fails with a * {@code JSONException} if the requested name has no value or if the value cannot be * coerced to the requested type. *
  • Use optType() to retrieve an optional value. This returns a * system- or user-supplied default if the requested name has no value or if the value * cannot be coerced to the requested type. *
*

* Warning: this class represents null in two incompatible ways: the * standard Java {@code null} reference, and the sentinel value {@link JSONObject#NULL}. * In particular, calling {@code put(name, null)} removes the named entry from the object * but {@code put(name, JSONObject.NULL)} stores an entry whose value is * {@code JSONObject.NULL}. *

* Instances of this class are not thread safe. Although this class is nonfinal, it was * not designed for inheritance and should not be subclassed. In particular, self-use by * overrideable methods is not specified. See Effective Java Item 17, "Design and * Document or inheritance or else prohibit it" for further information. */ public class JSONObject { private static final Double NEGATIVE_ZERO = -0d; /** * A sentinel value used to explicitly define a name with no value. Unlike * {@code null}, names with this value: *

    *
  • show up in the {@link #names} array *
  • show up in the {@link #keys} iterator *
  • return {@code true} for {@link #has(String)} *
  • do not throw on {@link #get(String)} *
  • are included in the encoded JSON string. *
*

* This value violates the general contract of {@link Object#equals} by returning true * when compared to {@code null}. Its {@link #toString} method returns "null". */ public static final Object NULL = new Object() { @Override public boolean equals(Object o) { return o == this || o == null; // API specifies this broken equals // implementation } @Override public String toString() { return "null"; } }; private final Map nameValuePairs; /** * Creates a {@code JSONObject} with no name/value mappings. */ public JSONObject() { this.nameValuePairs = new LinkedHashMap<>(); } /** * Creates a new {@code JSONObject} by copying all name/value mappings from the given * map. * @param copyFrom a map whose keys are of type {@link String} and whose values are of * supported types. * @throws NullPointerException if any of the map's keys are null. */ /* (accept a raw type for API compatibility) */ @SuppressWarnings("rawtypes") public JSONObject(Map copyFrom) { this(); Map contentsTyped = copyFrom; for (Map.Entry entry : contentsTyped.entrySet()) { /* * Deviate from the original by checking that keys are non-null and of the * proper type. (We still defer validating the values). */ String key = (String) entry.getKey(); if (key == null) { throw new NullPointerException("key == null"); } this.nameValuePairs.put(key, wrap(entry.getValue())); } } /** * Creates a new {@code JSONObject} with name/value mappings from the next object in * the tokener. * @param readFrom a tokener whose nextValue() method will yield a {@code JSONObject}. * @throws JSONException if the parse fails or doesn't yield a {@code JSONObject}. */ public JSONObject(JSONTokener readFrom) throws JSONException { /* * Getting the parser to populate this could get tricky. Instead, just parse to * temporary JSONObject and then steal the data from that. */ Object object = readFrom.nextValue(); if (object instanceof JSONObject) { this.nameValuePairs = ((JSONObject) object).nameValuePairs; } else { throw JSON.typeMismatch(object, "JSONObject"); } } /** * Creates a new {@code JSONObject} with name/value mappings from the JSON string. * @param json a JSON-encoded string containing an object. * @throws JSONException if the parse fails or doesn't yield a {@code * JSONObject}. */ public JSONObject(String json) throws JSONException { this(new JSONTokener(json)); } /** * Creates a new {@code JSONObject} by copying mappings for the listed names from the * given object. Names that aren't present in {@code copyFrom} will be skipped. * @param copyFrom the source * @param names the property names * @throws JSONException if an error occurs */ public JSONObject(JSONObject copyFrom, String[] names) throws JSONException { this(); for (String name : names) { Object value = copyFrom.opt(name); if (value != null) { this.nameValuePairs.put(name, value); } } } /** * Returns the number of name/value mappings in this object. * @return the number of name/value mappings in this object */ public int length() { return this.nameValuePairs.size(); } /** * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with * the same name. * @param name the name of the property * @param value the value of the property * @return this object. * @throws JSONException if an error occurs */ public JSONObject put(String name, boolean value) throws JSONException { this.nameValuePairs.put(checkName(name), value); return this; } /** * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with * the same name. * @param name the name of the property * @param value a finite value. May not be {@link Double#isNaN() NaNs} or * {@link Double#isInfinite() infinities}. * @return this object. * @throws JSONException if an error occurs */ public JSONObject put(String name, double value) throws JSONException { this.nameValuePairs.put(checkName(name), JSON.checkDouble(value)); return this; } /** * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with * the same name. * @param name the name of the property * @param value the value of the property * @return this object. * @throws JSONException if an error occurs */ public JSONObject put(String name, int value) throws JSONException { this.nameValuePairs.put(checkName(name), value); return this; } /** * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with * the same name. * @param name the name of the property * @param value the value of the property * @return this object. * @throws JSONException if an error occurs */ public JSONObject put(String name, long value) throws JSONException { this.nameValuePairs.put(checkName(name), value); return this; } /** * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with * the same name. If the value is {@code null}, any existing mapping for {@code name} * is removed. * @param name the name of the property * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, * Long, Double, {@link #NULL}, or {@code null}. May not be {@link Double#isNaN() * NaNs} or {@link Double#isInfinite() infinities}. * @return this object. * @throws JSONException if an error occurs */ public JSONObject put(String name, Object value) throws JSONException { if (value == null) { this.nameValuePairs.remove(name); return this; } if (value instanceof Number) { // deviate from the original by checking all Numbers, not just floats & // doubles JSON.checkDouble(((Number) value).doubleValue()); } this.nameValuePairs.put(checkName(name), value); return this; } /** * Equivalent to {@code put(name, value)} when both parameters are non-null; does * nothing otherwise. * @param name the name of the property * @param value the value of the property * @return this object. * @throws JSONException if an error occurs */ public JSONObject putOpt(String name, Object value) throws JSONException { if (name == null || value == null) { return this; } return put(name, value); } /** * Appends {@code value} to the array already mapped to {@code name}. If this object * has no mapping for {@code name}, this inserts a new mapping. If the mapping exists * but its value is not an array, the existing and new values are inserted in order * into a new array which is itself mapped to {@code name}. In aggregate, this allows * values to be added to a mapping one at a time. * @param name the name of the property * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, * Long, Double, {@link #NULL} or null. May not be {@link Double#isNaN() NaNs} or * {@link Double#isInfinite() infinities}. * @return this object. * @throws JSONException if an error occurs */ public JSONObject accumulate(String name, Object value) throws JSONException { Object current = this.nameValuePairs.get(checkName(name)); if (current == null) { return put(name, value); } // check in accumulate, since array.put(Object) doesn't do any checking if (value instanceof Number) { JSON.checkDouble(((Number) value).doubleValue()); } if (current instanceof JSONArray array) { array.put(value); } else { JSONArray array = new JSONArray(); array.put(current); array.put(value); this.nameValuePairs.put(name, array); } return this; } String checkName(String name) throws JSONException { if (name == null) { throw new JSONException("Names must be non-null"); } return name; } /** * Removes the named mapping if it exists; does nothing otherwise. * @param name the name of the property * @return the value previously mapped by {@code name}, or null if there was no such * mapping. */ public Object remove(String name) { return this.nameValuePairs.remove(name); } /** * Returns true if this object has no mapping for {@code name} or if it has a mapping * whose value is {@link #NULL}. * @param name the name of the property * @return true if this object has no mapping for {@code name} */ public boolean isNull(String name) { Object value = this.nameValuePairs.get(name); return value == null || value == NULL; } /** * Returns true if this object has a mapping for {@code name}. The mapping may be * {@link #NULL}. * @param name the name of the property * @return true if this object has a mapping for {@code name} */ public boolean has(String name) { return this.nameValuePairs.containsKey(name); } /** * Returns the value mapped by {@code name}. * @param name the name of the property * @return the value * @throws JSONException if no such mapping exists. */ public Object get(String name) throws JSONException { Object result = this.nameValuePairs.get(name); if (result == null) { throw new JSONException("No value for " + name); } return result; } /** * Returns the value mapped by {@code name}, or null if no such mapping exists. * @param name the name of the property * @return the value or {@code null} */ public Object opt(String name) { return this.nameValuePairs.get(name); } /** * Returns the value mapped by {@code name} if it exists and is a boolean or can be * coerced to a boolean. * @param name the name of the property * @return the value * @throws JSONException if the mapping doesn't exist or cannot be coerced to a * boolean. */ public boolean getBoolean(String name) throws JSONException { Object object = get(name); Boolean result = JSON.toBoolean(object); if (result == null) { throw JSON.typeMismatch(name, object, "boolean"); } return result; } /** * Returns the value mapped by {@code name} if it exists and is a boolean or can be * coerced to a boolean. Returns false otherwise. * @param name the name of the property * @return the value or {@code null} */ public boolean optBoolean(String name) { return optBoolean(name, false); } /** * Returns the value mapped by {@code name} if it exists and is a boolean or can be * coerced to a boolean. Returns {@code fallback} otherwise. * @param name the name of the property * @param fallback a fallback value * @return the value or {@code fallback} */ public boolean optBoolean(String name, boolean fallback) { Object object = opt(name); Boolean result = JSON.toBoolean(object); return result != null ? result : fallback; } /** * Returns the value mapped by {@code name} if it exists and is a double or can be * coerced to a double. * @param name the name of the property * @return the value * @throws JSONException if the mapping doesn't exist or cannot be coerced to a * double. */ public double getDouble(String name) throws JSONException { Object object = get(name); Double result = JSON.toDouble(object); if (result == null) { throw JSON.typeMismatch(name, object, "double"); } return result; } /** * Returns the value mapped by {@code name} if it exists and is a double or can be * coerced to a double. Returns {@code NaN} otherwise. * @param name the name of the property * @return the value or {@code NaN} */ public double optDouble(String name) { return optDouble(name, Double.NaN); } /** * Returns the value mapped by {@code name} if it exists and is a double or can be * coerced to a double. Returns {@code fallback} otherwise. * @param name the name of the property * @param fallback a fallback value * @return the value or {@code fallback} */ public double optDouble(String name, double fallback) { Object object = opt(name); Double result = JSON.toDouble(object); return result != null ? result : fallback; } /** * Returns the value mapped by {@code name} if it exists and is an int or can be * coerced to an int. * @param name the name of the property * @return the value * @throws JSONException if the mapping doesn't exist or cannot be coerced to an int. */ public int getInt(String name) throws JSONException { Object object = get(name); Integer result = JSON.toInteger(object); if (result == null) { throw JSON.typeMismatch(name, object, "int"); } return result; } /** * Returns the value mapped by {@code name} if it exists and is an int or can be * coerced to an int. Returns 0 otherwise. * @param name the name of the property * @return the value of {@code 0} */ public int optInt(String name) { return optInt(name, 0); } /** * Returns the value mapped by {@code name} if it exists and is an int or can be * coerced to an int. Returns {@code fallback} otherwise. * @param name the name of the property * @param fallback a fallback value * @return the value or {@code fallback} */ public int optInt(String name, int fallback) { Object object = opt(name); Integer result = JSON.toInteger(object); return result != null ? result : fallback; } /** * Returns the value mapped by {@code name} if it exists and is a long or can be * coerced to a long. Note that JSON represents numbers as doubles, so this is * lossy; use strings to transfer numbers over JSON. * @param name the name of the property * @return the value * @throws JSONException if the mapping doesn't exist or cannot be coerced to a long. */ public long getLong(String name) throws JSONException { Object object = get(name); Long result = JSON.toLong(object); if (result == null) { throw JSON.typeMismatch(name, object, "long"); } return result; } /** * Returns the value mapped by {@code name} if it exists and is a long or can be * coerced to a long. Returns 0 otherwise. Note that JSON represents numbers as * doubles, so this is lossy; use strings to transfer numbers via * JSON. * @param name the name of the property * @return the value or {@code 0L} */ public long optLong(String name) { return optLong(name, 0L); } /** * Returns the value mapped by {@code name} if it exists and is a long or can be * coerced to a long. Returns {@code fallback} otherwise. Note that JSON represents * numbers as doubles, so this is lossy; use strings to transfer * numbers over JSON. * @param name the name of the property * @param fallback a fallback value * @return the value or {@code fallback} */ public long optLong(String name, long fallback) { Object object = opt(name); Long result = JSON.toLong(object); return result != null ? result : fallback; } /** * Returns the value mapped by {@code name} if it exists, coercing it if necessary. * @param name the name of the property * @return the value * @throws JSONException if no such mapping exists. */ public String getString(String name) throws JSONException { Object object = get(name); String result = JSON.toString(object); if (result == null) { throw JSON.typeMismatch(name, object, "String"); } return result; } /** * Returns the value mapped by {@code name} if it exists, coercing it if necessary. * Returns the empty string if no such mapping exists. * @param name the name of the property * @return the value or an empty string */ public String optString(String name) { return optString(name, ""); } /** * Returns the value mapped by {@code name} if it exists, coercing it if necessary. * Returns {@code fallback} if no such mapping exists. * @param name the name of the property * @param fallback a fallback value * @return the value or {@code fallback} */ public String optString(String name, String fallback) { Object object = opt(name); String result = JSON.toString(object); return result != null ? result : fallback; } /** * Returns the value mapped by {@code name} if it exists and is a {@code * JSONArray}. * @param name the name of the property * @return the value * @throws JSONException if the mapping doesn't exist or is not a {@code * JSONArray}. */ public JSONArray getJSONArray(String name) throws JSONException { Object object = get(name); if (object instanceof JSONArray) { return (JSONArray) object; } else { throw JSON.typeMismatch(name, object, "JSONArray"); } } /** * Returns the value mapped by {@code name} if it exists and is a {@code * JSONArray}. Returns null otherwise. * @param name the name of the property * @return the value or {@code null} */ public JSONArray optJSONArray(String name) { Object object = opt(name); return object instanceof JSONArray ? (JSONArray) object : null; } /** * Returns the value mapped by {@code name} if it exists and is a {@code * JSONObject}. * @param name the name of the property * @return the value * @throws JSONException if the mapping doesn't exist or is not a {@code * JSONObject}. */ public JSONObject getJSONObject(String name) throws JSONException { Object object = get(name); if (object instanceof JSONObject) { return (JSONObject) object; } else { throw JSON.typeMismatch(name, object, "JSONObject"); } } /** * Returns the value mapped by {@code name} if it exists and is a {@code * JSONObject}. Returns null otherwise. * @param name the name of the property * @return the value or {@code null} */ public JSONObject optJSONObject(String name) { Object object = opt(name); return object instanceof JSONObject ? (JSONObject) object : null; } /** * Returns an array with the values corresponding to {@code names}. The array contains * null for names that aren't mapped. This method returns null if {@code names} is * either null or empty. * @param names the names of the properties * @return the array */ public JSONArray toJSONArray(JSONArray names) { JSONArray result = new JSONArray(); if (names == null) { return null; } int length = names.length(); if (length == 0) { return null; } for (int i = 0; i < length; i++) { String name = JSON.toString(names.opt(i)); result.put(opt(name)); } return result; } /** * Returns an iterator of the {@code String} names in this object. The returned * iterator supports {@link Iterator#remove() remove}, which will remove the * corresponding mapping from this object. If this object is modified after the * iterator is returned, the iterator's behavior is undefined. The order of the keys * is undefined. * @return the keys */ /* Return a raw type for API compatibility */ @SuppressWarnings("rawtypes") public Iterator keys() { return this.nameValuePairs.keySet().iterator(); } /** * Returns an array containing the string names in this object. This method returns * null if this object contains no mappings. * @return the array */ public JSONArray names() { return this.nameValuePairs.isEmpty() ? null : new JSONArray(new ArrayList<>(this.nameValuePairs.keySet())); } /** * Encodes this object as a compact JSON string, such as: *

{"query":"Pizza","locations":[94043,90210]}
* @return a string representation of the object. */ @Override public String toString() { try { JSONStringer stringer = new JSONStringer(); writeTo(stringer); return stringer.toString(); } catch (JSONException e) { return null; } } /** * Encodes this object as a human-readable JSON string for debugging, such as:
	 * {
	 *     "query": "Pizza",
	 *     "locations": [
	 *         94043,
	 *         90210
	 *     ]
	 * }
* @param indentSpaces the number of spaces to indent for each level of nesting. * @return a string representation of the object. * @throws JSONException if an error occurs */ public String toString(int indentSpaces) throws JSONException { JSONStringer stringer = new JSONStringer(indentSpaces); writeTo(stringer); return stringer.toString(); } void writeTo(JSONStringer stringer) throws JSONException { stringer.object(); for (Map.Entry entry : this.nameValuePairs.entrySet()) { stringer.key(entry.getKey()).value(entry.getValue()); } stringer.endObject(); } /** * Encodes the number as a JSON string. * @param number a finite value. May not be {@link Double#isNaN() NaNs} or * {@link Double#isInfinite() infinities}. * @return the encoded value * @throws JSONException if an error occurs */ public static String numberToString(Number number) throws JSONException { if (number == null) { throw new JSONException("Number must be non-null"); } double doubleValue = number.doubleValue(); JSON.checkDouble(doubleValue); // the original returns "-0" instead of "-0.0" for negative zero if (number.equals(NEGATIVE_ZERO)) { return "-0"; } long longValue = number.longValue(); if (doubleValue == longValue) { return Long.toString(longValue); } return number.toString(); } /** * Encodes {@code data} as a JSON string. This applies quotes and any necessary * character escaping. * @param data the string to encode. Null will be interpreted as an empty string. * @return the quoted value */ public static String quote(String data) { if (data == null) { return "\"\""; } try { JSONStringer stringer = new JSONStringer(); stringer.open(JSONStringer.Scope.NULL, ""); stringer.value(data); stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); return stringer.toString(); } catch (JSONException e) { throw new AssertionError(); } } /** * Wraps the given object if necessary. *

* If the object is null or, returns {@link #NULL}. If the object is a * {@code JSONArray} or {@code JSONObject}, no wrapping is necessary. If the object is * {@code NULL}, no wrapping is necessary. If the object is an array or * {@code Collection}, returns an equivalent {@code JSONArray}. If the object is a * {@code Map}, returns an equivalent {@code JSONObject}. If the object is a primitive * wrapper type or {@code String}, returns the object. Otherwise if the object is from * a {@code java} package, returns the result of {@code toString}. If wrapping fails, * returns null. * @param o the object to wrap * @return the wrapped object */ @SuppressWarnings("rawtypes") public static Object wrap(Object o) { if (o == null) { return NULL; } if (o instanceof JSONArray || o instanceof JSONObject) { return o; } if (o.equals(NULL)) { return o; } try { if (o instanceof Collection) { return new JSONArray((Collection) o); } else if (o.getClass().isArray()) { return new JSONArray(o); } if (o instanceof Map) { return new JSONObject((Map) o); } if (o instanceof Boolean || o instanceof Byte || o instanceof Character || o instanceof Double || o instanceof Float || o instanceof Integer || o instanceof Long || o instanceof Short || o instanceof String) { return o; } if (o.getClass().getPackage().getName().startsWith("java.")) { return o.toString(); } } catch (Exception ex) { // Ignore } return null; } } ================================================ FILE: cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONStringer.java ================================================ /* * 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.cli.json; import java.util.ArrayList; import java.util.Arrays; import java.util.List; // Note: this class was written without inspecting the non-free org.json source code. /** * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most application * developers should use those methods directly and disregard this API. For example:

 * JSONObject object = ...
 * String json = object.toString();
*

* Stringers only encode well-formed JSON strings. In particular: *

    *
  • The stringer must have exactly one top-level array or object. *
  • Lexical scopes must be balanced: every call to {@link #array} must have a matching * call to {@link #endArray} and every call to {@link #object} must have a matching call * to {@link #endObject}. *
  • Arrays may not contain keys (property names). *
  • Objects must alternate keys (property names) and values. *
  • Values are inserted with either literal {@link #value(Object) value} calls, or by * nesting arrays or objects. *
* Calls that would result in a malformed JSON string will fail with a * {@link JSONException}. *

* This class provides no facility for pretty-printing (ie. indenting) output. To encode * indented output, use {@link JSONObject#toString(int)} or * {@link JSONArray#toString(int)}. *

* Some implementations of the API support at most 20 levels of nesting. Attempts to * create more than 20 levels of nesting may fail with a {@link JSONException}. *

* Each stringer may be used to encode a single top level value. Instances of this class * are not thread safe. Although this class is nonfinal, it was not designed for * inheritance and should not be subclassed. In particular, self-use by overrideable * methods is not specified. See Effective Java Item 17, "Design and Document or * inheritance or else prohibit it" for further information. */ public class JSONStringer { /** * The output data, containing at most one top-level array or object. */ final StringBuilder out = new StringBuilder(); /** * Lexical scoping elements within this stringer, necessary to insert the appropriate * separator characters (i.e. commas and colons) and to detect nesting errors. */ enum Scope { /** * An array with no elements requires no separators or newlines before it is * closed. */ EMPTY_ARRAY, /** * An array with at least one value requires a comma and newline before the next * element. */ NONEMPTY_ARRAY, /** * An object with no keys or values requires no separators or newlines before it * is closed. */ EMPTY_OBJECT, /** * An object whose most recent element is a key. The next element must be a value. */ DANGLING_KEY, /** * An object with at least one name/value pair requires a comma and newline before * the next element. */ NONEMPTY_OBJECT, /** * A special bracketless array needed by JSONStringer.join() and * JSONObject.quote() only. Not used for JSON encoding. */ NULL } /** * Unlike the original implementation, this stack isn't limited to 20 levels of * nesting. */ private final List stack = new ArrayList<>(); /** * A string containing a full set of spaces for a single level of indentation, or null * for no pretty printing. */ private final String indent; public JSONStringer() { this.indent = null; } JSONStringer(int indentSpaces) { char[] indentChars = new char[indentSpaces]; Arrays.fill(indentChars, ' '); this.indent = new String(indentChars); } /** * Begins encoding a new array. Each call to this method must be paired with a call to * {@link #endArray}. * @return this stringer. * @throws JSONException if processing of json failed */ public JSONStringer array() throws JSONException { return open(Scope.EMPTY_ARRAY, "["); } /** * Ends encoding the current array. * @return this stringer. * @throws JSONException if processing of json failed */ public JSONStringer endArray() throws JSONException { return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]"); } /** * Begins encoding a new object. Each call to this method must be paired with a call * to {@link #endObject}. * @return this stringer. * @throws JSONException if processing of json failed */ public JSONStringer object() throws JSONException { return open(Scope.EMPTY_OBJECT, "{"); } /** * Ends encoding the current object. * @return this stringer. * @throws JSONException if processing of json failed */ public JSONStringer endObject() throws JSONException { return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}"); } /** * Enters a new scope by appending any necessary whitespace and the given bracket. * @param empty any necessary whitespace * @param openBracket the open bracket * @return this object * @throws JSONException if processing of json failed */ JSONStringer open(Scope empty, String openBracket) throws JSONException { if (this.stack.isEmpty() && !this.out.isEmpty()) { throw new JSONException("Nesting problem: multiple top-level roots"); } beforeValue(); this.stack.add(empty); this.out.append(openBracket); return this; } /** * Closes the current scope by appending any necessary whitespace and the given * bracket. * @param empty any necessary whitespace * @param nonempty the current scope * @param closeBracket the close bracket * @return the JSON stringer * @throws JSONException if processing of json failed */ JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException { Scope context = peek(); if (context != nonempty && context != empty) { throw new JSONException("Nesting problem"); } this.stack.remove(this.stack.size() - 1); if (context == nonempty) { newline(); } this.out.append(closeBracket); return this; } /** * Returns the value on the top of the stack. * @return the scope * @throws JSONException if processing of json failed */ private Scope peek() throws JSONException { if (this.stack.isEmpty()) { throw new JSONException("Nesting problem"); } return this.stack.get(this.stack.size() - 1); } /** * Replace the value on the top of the stack with the given value. * @param topOfStack the scope at the top of the stack */ private void replaceTop(Scope topOfStack) { this.stack.set(this.stack.size() - 1, topOfStack); } /** * Encodes {@code value}. * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, * Long, Double or null. May not be {@link Double#isNaN() NaNs} or * {@link Double#isInfinite() infinities}. * @return this stringer. * @throws JSONException if processing of json failed */ public JSONStringer value(Object value) throws JSONException { if (this.stack.isEmpty()) { throw new JSONException("Nesting problem"); } if (value instanceof JSONArray) { ((JSONArray) value).writeTo(this); return this; } else if (value instanceof JSONObject) { ((JSONObject) value).writeTo(this); return this; } beforeValue(); if (value == null || value instanceof Boolean || value == JSONObject.NULL) { this.out.append(value); } else if (value instanceof Number) { this.out.append(JSONObject.numberToString((Number) value)); } else { string(value.toString()); } return this; } /** * Encodes {@code value} to this stringer. * @param value the value to encode * @return this stringer. * @throws JSONException if processing of json failed */ public JSONStringer value(boolean value) throws JSONException { if (this.stack.isEmpty()) { throw new JSONException("Nesting problem"); } beforeValue(); this.out.append(value); return this; } /** * Encodes {@code value} to this stringer. * @param value a finite value. May not be {@link Double#isNaN() NaNs} or * {@link Double#isInfinite() infinities}. * @return this stringer. * @throws JSONException if processing of json failed */ public JSONStringer value(double value) throws JSONException { if (this.stack.isEmpty()) { throw new JSONException("Nesting problem"); } beforeValue(); this.out.append(JSONObject.numberToString(value)); return this; } /** * Encodes {@code value} to this stringer. * @param value the value to encode * @return this stringer. * @throws JSONException if processing of json failed */ public JSONStringer value(long value) throws JSONException { if (this.stack.isEmpty()) { throw new JSONException("Nesting problem"); } beforeValue(); this.out.append(value); return this; } private void string(String value) { this.out.append("\""); for (int i = 0, length = value.length(); i < length; i++) { char c = value.charAt(i); /* * From RFC 4627, "All Unicode characters may be placed within the quotation * marks except for the characters that must be escaped: quotation mark, * reverse solidus, and the control characters (U+0000 through U+001F)." */ switch (c) { case '"', '\\', '/' -> this.out.append('\\').append(c); case '\t' -> this.out.append("\\t"); case '\b' -> this.out.append("\\b"); case '\n' -> this.out.append("\\n"); case '\r' -> this.out.append("\\r"); case '\f' -> this.out.append("\\f"); default -> { if (c <= 0x1F) { this.out.append(String.format("\\u%04x", (int) c)); } else { this.out.append(c); } } } } this.out.append("\""); } private void newline() { if (this.indent == null) { return; } this.out.append("\n"); this.out.append(this.indent.repeat(this.stack.size())); } /** * Encodes the key (property name) to this stringer. * @param name the name of the forthcoming value. May not be null. * @return this stringer. * @throws JSONException if processing of json failed */ public JSONStringer key(String name) throws JSONException { if (name == null) { throw new JSONException("Names must be non-null"); } beforeKey(); string(name); return this; } /** * Inserts any necessary separators and whitespace before a name. Also adjusts the * stack to expect the key's value. * @throws JSONException if processing of json failed */ private void beforeKey() throws JSONException { Scope context = peek(); if (context == Scope.NONEMPTY_OBJECT) { // first in object this.out.append(','); } else if (context != Scope.EMPTY_OBJECT) { // not in an object! throw new JSONException("Nesting problem"); } newline(); replaceTop(Scope.DANGLING_KEY); } /** * Inserts any necessary separators and whitespace before a literal value, inline * array, or inline object. Also adjusts the stack to expect either a closing bracket * or another element. * @throws JSONException if processing of json failed */ private void beforeValue() throws JSONException { if (this.stack.isEmpty()) { return; } Scope context = peek(); if (context == Scope.EMPTY_ARRAY) { // first in array replaceTop(Scope.NONEMPTY_ARRAY); newline(); } else if (context == Scope.NONEMPTY_ARRAY) { // another in array this.out.append(','); newline(); } else if (context == Scope.DANGLING_KEY) { // value for key this.out.append(this.indent == null ? ":" : ": "); replaceTop(Scope.NONEMPTY_OBJECT); } else if (context != Scope.NULL) { throw new JSONException("Nesting problem"); } } /** * Returns the encoded JSON string. *

* If invoked with unterminated arrays or unclosed objects, this method's return value * is undefined. *

* Warning: although it contradicts the general contract of * {@link Object#toString}, this method returns null if the stringer contains no data. * @return the encoded JSON string. */ @Override public String toString() { return this.out.isEmpty() ? null : this.out.toString(); } } ================================================ FILE: cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONTokener.java ================================================ /* * 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.cli.json; // Note: this class was written without inspecting the non-free org.json source code. /** * Parses a JSON (RFC 4627) encoded * string into the corresponding object. Most clients of this class will use only need the * {@link #JSONTokener(String) constructor} and {@link #nextValue} method. Example usage: *

 * String json = "{"
 *         + "  \"query\": \"Pizza\", "
 *         + "  \"locations\": [ 94043, 90210 ] "
 *         + "}";
 *
 * JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
 * String query = object.getString("query");
 * JSONArray locations = object.getJSONArray("locations");
*

* For best interoperability and performance use JSON that complies with RFC 4627, such as * that generated by {@link JSONStringer}. For legacy reasons this parser is lenient, so a * successful parse does not indicate that the input string was valid JSON. All the * following syntax errors will be ignored: *

    *
  • End of line comments starting with {@code //} or {@code #} and ending with a * newline character. *
  • C-style comments starting with {@code /*} and ending with {@code *}{@code /}. Such * comments may not be nested. *
  • Strings that are unquoted or {@code 'single quoted'}. *
  • Hexadecimal integers prefixed with {@code 0x} or {@code 0X}. *
  • Octal integers prefixed with {@code 0}. *
  • Array elements separated by {@code ;}. *
  • Unnecessary array separators. These are interpreted as if null was the omitted * value. *
  • Key-value pairs separated by {@code =} or {@code =>}. *
  • Key-value pairs separated by {@code ;}. *
*

* Each tokener may be used to parse a single JSON string. Instances of this class are not * thread safe. Although this class is nonfinal, it was not designed for inheritance and * should not be subclassed. In particular, self-use by overrideable methods is not * specified. See Effective Java Item 17, "Design and Document or inheritance or * else prohibit it" for further information. */ public class JSONTokener { /** * The input JSON. */ private final String in; /** * The index of the next character to be returned by {@link #next}. When the input is * exhausted, this equals the input's length. */ private int pos; /** * @param in JSON encoded string. Null is not permitted and will yield a tokener that * throws {@code NullPointerExceptions} when methods are called. */ public JSONTokener(String in) { // consume an optional byte order mark (BOM) if it exists if (in != null && in.startsWith("\ufeff")) { in = in.substring(1); } this.in = in; } /** * Returns the next value from the input. * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, Long, * Double or {@link JSONObject#NULL}. * @throws JSONException if the input is malformed. */ public Object nextValue() throws JSONException { int c = nextCleanInternal(); switch (c) { case -1: throw syntaxError("End of input"); case '{': return readObject(); case '[': return readArray(); case '\'', '"': return nextString((char) c); default: this.pos--; return readLiteral(); } } private int nextCleanInternal() throws JSONException { while (this.pos < this.in.length()) { int c = this.in.charAt(this.pos++); switch (c) { case '\t', ' ', '\n', '\r': continue; case '/': if (this.pos == this.in.length()) { return c; } char peek = this.in.charAt(this.pos); switch (peek) { case '*': // skip a /* c-style comment */ this.pos++; int commentEnd = this.in.indexOf("*/", this.pos); if (commentEnd == -1) { throw syntaxError("Unterminated comment"); } this.pos = commentEnd + 2; continue; case '/': // skip a // end-of-line comment this.pos++; skipToEndOfLine(); continue; default: return c; } case '#': /* * Skip a # hash end-of-line comment. The JSON RFC doesn't specify * this behavior, but it's required to parse existing documents. See * https://b/2571423. */ skipToEndOfLine(); continue; default: return c; } } return -1; } /** * Advances the position until after the next newline character. If the line is * terminated by "\r\n", the '\n' must be consumed as whitespace by the caller. */ private void skipToEndOfLine() { for (; this.pos < this.in.length(); this.pos++) { char c = this.in.charAt(this.pos); if (c == '\r' || c == '\n') { this.pos++; break; } } } /** * Returns the string up to but not including {@code quote}, unescaping any character * escape sequences encountered along the way. The opening quote should have already * been read. This consumes the closing quote, but does not include it in the returned * string. * @param quote either ' or ". * @return the string up to but not including {@code quote} * @throws NumberFormatException if any unicode escape sequences are malformed. * @throws JSONException if processing of json failed */ public String nextString(char quote) throws JSONException { /* * For strings that are free of escape sequences, we can just extract the result * as a substring of the input. But if we encounter an escape sequence, we need to * use a StringBuilder to compose the result. */ StringBuilder builder = null; /* the index of the first character not yet appended to the builder. */ int start = this.pos; while (this.pos < this.in.length()) { int c = this.in.charAt(this.pos++); if (c == quote) { if (builder == null) { // a new string avoids leaking memory return new String(this.in.substring(start, this.pos - 1)); } else { builder.append(this.in, start, this.pos - 1); return builder.toString(); } } if (c == '\\') { if (this.pos == this.in.length()) { throw syntaxError("Unterminated escape sequence"); } if (builder == null) { builder = new StringBuilder(); } builder.append(this.in, start, this.pos - 1); builder.append(readEscapeCharacter()); start = this.pos; } } throw syntaxError("Unterminated string"); } /** * Unescapes the character identified by the character or characters that immediately * follow a backslash. The backslash '\' should have already been read. This supports * both unicode escapes "u000A" and two-character escapes "\n". * @return the unescaped char * @throws NumberFormatException if any unicode escape sequences are malformed. * @throws JSONException if processing of json failed */ private char readEscapeCharacter() throws JSONException { char escaped = this.in.charAt(this.pos++); switch (escaped) { case 'u': if (this.pos + 4 > this.in.length()) { throw syntaxError("Unterminated escape sequence"); } String hex = this.in.substring(this.pos, this.pos + 4); this.pos += 4; return (char) Integer.parseInt(hex, 16); case 't': return '\t'; case 'b': return '\b'; case 'n': return '\n'; case 'r': return '\r'; case 'f': return '\f'; case '\'', '"', '\\': default: return escaped; } } /** * Reads a null, boolean, numeric or unquoted string literal value. Numeric values * will be returned as an Integer, Long, or Double, in that order of preference. * @return a literal value * @throws JSONException if processing of json failed */ private Object readLiteral() throws JSONException { String literal = nextToInternal("{}[]/\\:,=;# \t\f"); if (literal.isEmpty()) { throw syntaxError("Expected literal value"); } else if ("null".equalsIgnoreCase(literal)) { return JSONObject.NULL; } else if ("true".equalsIgnoreCase(literal)) { return Boolean.TRUE; } else if ("false".equalsIgnoreCase(literal)) { return Boolean.FALSE; } /* try to parse as an integral type... */ if (literal.indexOf('.') == -1) { int base = 10; String number = literal; if (number.startsWith("0x") || number.startsWith("0X")) { number = number.substring(2); base = 16; } else if (number.startsWith("0") && number.length() > 1) { number = number.substring(1); base = 8; } try { long longValue = Long.parseLong(number, base); if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) { return (int) longValue; } else { return longValue; } } catch (NumberFormatException e) { /* * This only happens for integral numbers greater than Long.MAX_VALUE, * numbers in exponential form (5e-10) and unquoted strings. Fall through * to try floating point. */ } } /* ...next try to parse as a floating point... */ try { return Double.valueOf(literal); } catch (NumberFormatException ex) { // Ignore } /* ... finally give up. We have an unquoted string */ return new String(literal); // a new string avoids leaking memory } /** * Returns the string up to but not including any of the given characters or a newline * character. This does not consume the excluded character. * @return the string up to but not including any of the given characters or a newline * character */ private String nextToInternal(String excluded) { int start = this.pos; for (; this.pos < this.in.length(); this.pos++) { char c = this.in.charAt(this.pos); if (c == '\r' || c == '\n' || excluded.indexOf(c) != -1) { return this.in.substring(start, this.pos); } } return this.in.substring(start); } /** * Reads a sequence of key/value pairs and the trailing closing brace '}' of an * object. The opening brace '{' should have already been read. * @return an object * @throws JSONException if processing of json failed */ private JSONObject readObject() throws JSONException { JSONObject result = new JSONObject(); /* Peek to see if this is the empty object. */ int first = nextCleanInternal(); if (first == '}') { return result; } else if (first != -1) { this.pos--; } while (true) { Object name = nextValue(); if (!(name instanceof String)) { if (name == null) { throw syntaxError("Names cannot be null"); } else { throw syntaxError( "Names must be strings, but " + name + " is of type " + name.getClass().getName()); } } /* * Expect the name/value separator to be either a colon ':', an equals sign * '=', or an arrow "=>". The last two are bogus but we include them because * that's what the original implementation did. */ int separator = nextCleanInternal(); if (separator != ':' && separator != '=') { throw syntaxError("Expected ':' after " + name); } if (this.pos < this.in.length() && this.in.charAt(this.pos) == '>') { this.pos++; } result.put((String) name, nextValue()); switch (nextCleanInternal()) { case '}': return result; case ';', ',': continue; default: throw syntaxError("Unterminated object"); } } } /** * Reads a sequence of values and the trailing closing brace ']' of an array. The * opening brace '[' should have already been read. Note that "[]" yields an empty * array, but "[,]" returns a two-element array equivalent to "[null,null]". * @return an array * @throws JSONException if processing of json failed */ private JSONArray readArray() throws JSONException { JSONArray result = new JSONArray(); /* to cover input that ends with ",]". */ boolean hasTrailingSeparator = false; while (true) { switch (nextCleanInternal()) { case -1: throw syntaxError("Unterminated array"); case ']': if (hasTrailingSeparator) { result.put(null); } return result; case ',', ';': /* A separator without a value first means "null". */ result.put(null); hasTrailingSeparator = true; continue; default: this.pos--; } result.put(nextValue()); switch (nextCleanInternal()) { case ']': return result; case ',', ';': hasTrailingSeparator = true; continue; default: throw syntaxError("Unterminated array"); } } } /** * Returns an exception containing the given message plus the current position and the * entire input string. * @param message the message * @return an exception */ public JSONException syntaxError(String message) { return new JSONException(message + this); } /** * Returns the current position and the entire input string. * @return the current position and the entire input string. */ @Override public String toString() { // consistent with the original implementation return " at character " + this.pos + " of " + this.in; } /* * Legacy APIs. * * None of the methods below are on the critical path of parsing JSON documents. They * exist only because they were exposed by the original implementation and may be used * by some clients. */ public boolean more() { return this.pos < this.in.length(); } public char next() { return this.pos < this.in.length() ? this.in.charAt(this.pos++) : '\0'; } public char next(char c) throws JSONException { char result = next(); if (result != c) { throw syntaxError("Expected " + c + " but was " + result); } return result; } public char nextClean() throws JSONException { int nextCleanInt = nextCleanInternal(); return nextCleanInt == -1 ? '\0' : (char) nextCleanInt; } public String next(int length) throws JSONException { if (this.pos + length > this.in.length()) { throw syntaxError(length + " is out of bounds"); } String result = this.in.substring(this.pos, this.pos + length); this.pos += length; return result; } public String nextTo(String excluded) { if (excluded == null) { throw new NullPointerException("excluded == null"); } return nextToInternal(excluded).trim(); } public String nextTo(char excluded) { return nextToInternal(String.valueOf(excluded)).trim(); } public void skipPast(String thru) { int thruStart = this.in.indexOf(thru, this.pos); this.pos = thruStart == -1 ? this.in.length() : (thruStart + thru.length()); } public char skipTo(char to) { int index = this.in.indexOf(to, this.pos); if (index != -1) { this.pos = index; return to; } else { return '\0'; } } public void back() { if (--this.pos == -1) { this.pos = 0; } } public static int dehexchar(char hex) { if (hex >= '0' && hex <= '9') { return hex - '0'; } else if (hex >= 'A' && hex <= 'F') { return hex - 'A' + 10; } else if (hex >= 'a' && hex <= 'f') { return hex - 'a' + 10; } else { return -1; } } } ================================================ FILE: cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/package-info.java ================================================ /* * 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. */ @NullUnmarked package org.springframework.boot.cli.json; import org.jspecify.annotations.NullUnmarked; ================================================ FILE: cli/spring-boot-cli/src/main/content/INSTALL.txt ================================================ SPRING BOOT CLI - INSTALLATION ============================== Thank you for downloading the Spring Boot CLI tool. Please follow these instructions in order to complete your installation. Prerequisites ------------- Spring Boot CLI requires Java JDK 17 or above in order to run. The CLI will use whatever JDK it finds on your path, to check that you have an appropriate version you should run: java -version Alternatively, you can set the JAVA_HOME environment variable to point a suitable JDK. Environment Variables --------------------- No specific environment variables are required to run the CLI, however, you may want to set SPRING_HOME to point to a specific installation. You should also add SPRING_HOME/bin to your PATH environment variable. Shell Completion ---------------- Shell auto-completion scripts are provided for BASH and ZSH. Add symlinks to the appropriate location for your environment. For example, something like: ln -s ./shell-completion/bash/spring /etc/bash_completion.d/spring ln -s ./shell-completion/zsh/_spring /usr/local/share/zsh/site-functions/_spring Checking Your Installation -------------------------- To test if you have successfully installed the CLI you can run the following command: spring --version ================================================ FILE: cli/spring-boot-cli/src/main/content/LICENCE.txt ================================================ Copyright 2012-2013 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. ================================================ FILE: cli/spring-boot-cli/src/main/content/bin/spring.bat ================================================ @if "%DEBUG%" == "" @echo off @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal :setSpringHome @rem Setup SPRING_HOME if not already defined if defined SPRING_HOME goto setJavaHome set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set SPRING_HOME=%DIRNAME%\.. :setJavaHome @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto runSpring echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto runSpring echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :runSpring @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%SPRING_HOME%\lib\* "%JAVA_EXE%" %JAVA_OPTS% -cp "%CLASSPATH%" org.springframework.boot.loader.launch.JarLauncher %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable SPRING_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%SPRING_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal ================================================ FILE: cli/spring-boot-cli/src/main/content/legal/open_source_licenses.txt ================================================ open_source_licenses.txt Spring Boot CLI ================================================================== VMware makes available all content in this download ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the Apache License 2.0 (the "License"). A copy of the license is available in the file called LICENSE.txt or you may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 The following copyright statements and licenses apply to various open source software packages (or portions thereof) that are distributed with this content. ================================================================= TABLE OF CONTENTS ================================================================= The following is a listing of the open source components detailed in this document. This list is provided for your convenience; please read further if you wish to review the copyright notice(s) and the full text of the license associated with each component. SECTION 1: BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES >>> JLine (jline:jline) >>> JOpt Simple (net.sf.jopt-simple:jopt-simple) >>> ASM 4.0 (org.ow2.asm:asm) SECTION 2: Apache License, V2.0 >>> JSON library from Android SDK (com.vaadin.external.google:android-json) >>> Apache Commons Codec (commons-codec:commons-codec) >>> Apache HttpClient (org.apache.httpcomponents:httpclient) >>> Apache HttpCore (org.apache.httpcomponents:httpcore) >>> Plexus Cipher: encryption/decryption Component (org.sonatype.plexus:plexus-cipher) >>> Plexus Security Dispatcher Component (org.sonatype.plexus:plexus-sec-dispatcher) >>> Apache Commons Logging (commons-logging:commons-logging) >>> Apache Groovy (org.apache.groovy:groovy) >>> Maven Aether Provider (org.apache.maven:maven-aether-provider) >>> Maven Model (org.apache.maven:maven-model) >>> Maven Model Builder (org.apache.maven:maven-model-builder) >>> Maven Repository Metadata Model (org.apache.maven:maven-repository-metadata) >>> Maven Settings (org.apache.maven:maven-settings) >>> Maven Settings Builder (org.apache.maven:maven-settings-builder) >>> Plexus :: Component Annotations (org.codehaus.plexus:plexus-component-annotations) >>> Plexus Common Utilities (org.codehaus.plexus:plexus-utils) >>> Plexus Component API (org.codehaus.plexus:plexus-component-api) >>> Plexus Interpolation API (org.codehaus.plexus:plexus-interpolation) SECTION 3: Eclipse Public License, Version 1.0 >>> Aether API (org.eclipse.aether:aether-api) >>> Aether Connector Basic (org.eclipse.aether:aether-connector-basic) >>> Aether Implementation (org.eclipse.aether:aether-impl) >>> Aether SPI (org.eclipse.aether:aether-spi) >>> Aether Transport File (org.eclipse.aether:aether-transport-file) >>> Aether Transport HTTP (org.eclipse.aether:aether-transport-http) >>> Aether Utilities (org.eclipse.aether:aether-util) --------------- SECTION 1: BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES ---------- BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES are applicable to the following component(s). >>> JLine (jline:jline) Copyright (c) 2002-2006, Marc Prud'hommeaux All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of JLine nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >>> net.sf.jopt-simple:jopt-simple:4.5 The MIT License (MIT) Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. >>> org.ow2.asm:asm Copyright (c) 2000-2011 INRIA, France Telecom All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------- SECTION 2: Apache License, V2.0 ---------- Apache License, V2.0 is applicable to the following component(s). >>> org.apache.httpcomponents:httpclient >>> org.apache.httpcomponents:httpcore >>> org.sonatype.plexus:plexus-cipher >>> org.sonatype.plexus:plexus-sec-dispatcher >>> commons-logging:commons-logging >>> org.apache.groovy:groovy >>> org.apache.maven:maven-aether-provider >>> org.apache.maven:maven-model >>> org.apache.maven:maven-model-builder >>> org.apache.maven:maven-repository-metadata >>> org.apache.maven:maven-settings >>> org.apache.maven:maven-settings-builder >>> org.codehaus.plexus:plexus-component-annotations >>> org.codehaus.plexus:plexus-utils >>> org.codehaus.plexus:plexus-component-api >>> org.codehaus.plexus:plexus-interpolation 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 >>> CGLIB 3.0 (cglib:cglib:3.0): Per the LICENSE file in the CGLIB JAR distribution downloaded from https://sourceforge.net/projects/cglib/files/cglib3/3.0/cglib-3.0.jar/download, CGLIB 3.0 is licensed under the Apache License, version 2.0, the text of which is included above. --------------- SECTION 3: Eclipse Public License, Version 1.0 ---------- Eclipse Public License, Version 1.0 is applicable to the following component(s). >>> org.eclipse.aether:aether-api >>> org.eclipse.aether:aether-connector-basic >>> org.eclipse.aether:aether-impl >>> org.eclipse.aether:aether-spi >>> org.eclipse.aether:aether-transport-file >>> org.eclipse.aether:aether-transport-http >>> org.eclipse.aether:aether-util The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available at https://www.eclipse.org/legal/epl-v10.html. For purposes of the EPL, "Program" will mean the Content. If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at https://www.eclipse.org/ =========================================================================== To the extent any open source subcomponents are licensed under the EPL and/or other similar licenses that require the source code and/or modifications to source code to be made available (as would be noted above), you may obtain a copy of the source code corresponding to the binaries for such open source components and modifications thereto, if any, (the "Source Files"), by downloading the Source Files from https://github.com/spring-projects/spring-boot, or by sending a request, with your name and address to: VMware, Inc., 875 Howard St, San Francisco, CA 94103 United States of America or email ask@spring.io. All such requests should clearly specify: OPEN SOURCE FILES REQUEST Attention General Counsel ================================================ FILE: cli/spring-boot-cli/src/main/content/shell-completion/bash/spring ================================================ # bash completion for spring # Installation: source this file locally in a terminal or from # ~/.bashrc or put it in /etc/bash_completions.d (debian) _spring() { local cur prev help helps words cword command commands i _get_comp_words_by_ref cur prev words cword COMPREPLY=() while read -r line; do reply=`echo "$line" | awk '{print $1;}'` COMPREPLY+=("$reply") done < <(spring hint ${cword} ${words[*]}) if [ $cword -ne 1 ]; then _filedir fi } && complete -F _spring spring ================================================ FILE: cli/spring-boot-cli/src/main/content/shell-completion/zsh/_spring ================================================ #compdef spring 'spring' #autoload _spring() { local cword let cword=CURRENT-1 local hints hints=() local reply while read -r line; do reply=`echo "$line" | awk '{printf $1 ":"; for (i=2; i \(.*\)$') if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=$(dirname "$PRG")"/$link" fi done SAVED="$(pwd)" cd "$(dirname "${PRG}")/../" > /dev/null || exit 1 SPRING_HOME="$(pwd -P)" export SPRING_HOME cd "$SAVED" > /dev/null || exit 1 fi if [ ! -d "${SPRING_HOME}" ]; then echo "Not a directory: SPRING_HOME=${SPRING_HOME}" echo "Please rectify and restart." exit 2 fi [[ "${cygwin}" == "true" ]] && SPRINGPATH=$(cygpath "${SPRING_HOME}") || SPRINGPATH=$SPRING_HOME CLASSPATH=${SPRINGPATH}/bin if [ -d "${SPRINGPATH}/ext" ]; then CLASSPATH=$CLASSPATH:${SPRINGPATH}/ext fi for f in "${SPRINGPATH}"/lib/*; do [[ "${cygwin}" == "true" ]] && LIBFILE=$(cygpath "$f") || LIBFILE=$f CLASSPATH=$CLASSPATH:$LIBFILE done if $cygwin; then SPRING_HOME=$(cygpath --path --mixed "$SPRING_HOME") CLASSPATH=$(cygpath --path --mixed "$CLASSPATH") fi IFS=" " read -r -a javaOpts <<< "$JAVA_OPTS" exec "${JAVA_HOME}/bin/java" "${javaOpts[@]}" -cp "$CLASSPATH" org.springframework.boot.loader.launch.JarLauncher "$@" ================================================ FILE: cli/spring-boot-cli/src/main/homebrew/spring-boot.rb ================================================ require 'formula' class SpringBoot < Formula homepage 'https://spring.io/projects/spring-boot' url '${repo}/org/springframework/boot/spring-boot-cli/${version}/spring-boot-cli-${version}-bin.tar.gz' version '${version}' sha256 '${hash}' def install libexec.install Dir["./*"] (bin/"spring").write_env_script libexec/"bin/spring", {} bash_comp = libexec/"shell-completion/bash/spring" zsh_comp = libexec/"shell-completion/zsh/_spring" bash_completion.install bash_comp if bash_comp.exist? zsh_completion.install zsh_comp if zsh_comp.exist? end end ================================================ FILE: cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/DefaultCommandFactory.java ================================================ /* * 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.cli; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.springframework.boot.cli.command.Command; import org.springframework.boot.cli.command.CommandFactory; import org.springframework.boot.cli.command.core.VersionCommand; import org.springframework.boot.cli.command.encodepassword.EncodePasswordCommand; import org.springframework.boot.cli.command.init.InitCommand; /** * Default implementation of {@link CommandFactory}. * * @author Dave Syer * @since 1.0.0 */ public class DefaultCommandFactory implements CommandFactory { private static final List DEFAULT_COMMANDS; static { List defaultCommands = new ArrayList<>(); defaultCommands.add(new VersionCommand()); defaultCommands.add(new InitCommand()); defaultCommands.add(new EncodePasswordCommand()); DEFAULT_COMMANDS = Collections.unmodifiableList(defaultCommands); } @Override public Collection getCommands() { return DEFAULT_COMMANDS; } } ================================================ FILE: cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/SpringCli.java ================================================ /* * 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.cli; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; import org.springframework.boot.cli.command.CommandFactory; import org.springframework.boot.cli.command.CommandRunner; import org.springframework.boot.cli.command.core.HelpCommand; import org.springframework.boot.cli.command.core.HintCommand; import org.springframework.boot.cli.command.core.VersionCommand; import org.springframework.boot.cli.command.shell.ShellCommand; import org.springframework.boot.loader.tools.LogbackInitializer; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.SystemPropertyUtils; /** * Spring Command Line Interface. This is the main entry-point for the Spring command line * application. * * @author Phillip Webb * @since 1.0.0 * @see #main(String...) * @see CommandRunner */ public final class SpringCli { private SpringCli() { } public static void main(String... args) { System.setProperty("java.awt.headless", Boolean.toString(true)); LogbackInitializer.initialize(); CommandRunner runner = new CommandRunner("spring"); ClassUtils.overrideThreadContextClassLoader(createExtendedClassLoader(runner)); runner.addCommand(new HelpCommand(runner)); addServiceLoaderCommands(runner); runner.addCommand(new ShellCommand()); runner.addCommand(new HintCommand(runner)); runner.setOptionCommands(HelpCommand.class, VersionCommand.class); runner.setHiddenCommands(HintCommand.class); int exitCode = runner.runAndHandleErrors(args); if (exitCode != 0) { // If successful, leave it to run in case it's a server app System.exit(exitCode); } } private static void addServiceLoaderCommands(CommandRunner runner) { ServiceLoader factories = ServiceLoader.load(CommandFactory.class); for (CommandFactory factory : factories) { runner.addCommands(factory.getCommands()); } } private static URLClassLoader createExtendedClassLoader(CommandRunner runner) { return new URLClassLoader(getExtensionURLs(), runner.getClass().getClassLoader()); } private static URL[] getExtensionURLs() { List urls = new ArrayList<>(); String home = SystemPropertyUtils.resolvePlaceholders("${spring.home:${SPRING_HOME:.}}"); File extDirectory = new File(new File(home, "lib"), "ext"); if (extDirectory.isDirectory()) { File[] files = extDirectory.listFiles(); Assert.state(files != null, "'files' must not be null"); for (File file : files) { if (file.getName().endsWith(".jar")) { try { urls.add(file.toURI().toURL()); } catch (MalformedURLException ex) { throw new IllegalStateException(ex); } } } } return urls.toArray(new URL[0]); } } ================================================ FILE: cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/AbstractCommand.java ================================================ /* * 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.cli.command; import java.util.Collection; import java.util.Collections; import org.jspecify.annotations.Nullable; import org.springframework.boot.cli.command.options.OptionHelp; /** * Abstract {@link Command} implementation. * * @author Phillip Webb * @author Dave Syer * @since 1.0.0 */ public abstract class AbstractCommand implements Command { private final String name; private final String description; /** * Create a new {@link AbstractCommand} instance. * @param name the name of the command * @param description the command description */ protected AbstractCommand(String name, String description) { this.name = name; this.description = description; } @Override public String getName() { return this.name; } @Override public String getDescription() { return this.description; } @Override public @Nullable String getUsageHelp() { return null; } @Override public @Nullable String getHelp() { return null; } @Override public Collection getOptionsHelp() { return Collections.emptyList(); } @Override public @Nullable Collection getExamples() { return null; } } ================================================ FILE: cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/Command.java ================================================ /* * 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.cli.command; import java.util.Collection; import org.jspecify.annotations.Nullable; import org.springframework.boot.cli.command.options.OptionHelp; import org.springframework.boot.cli.command.status.ExitStatus; /** * A single command that can be run from the CLI. * * @author Phillip Webb * @author Dave Syer * @author Stephane Nicoll * @since 1.0.0 * @see #run(String...) */ public interface Command { /** * Returns the name of the command. * @return the command's name */ String getName(); /** * Returns a description of the command. * @return the command's description */ String getDescription(); /** * Returns usage help for the command. This should be a simple one-line string * describing basic usage. e.g. '[options] <file>'. Do not include the name of * the command in this string. * @return the command's usage help */ @Nullable String getUsageHelp(); /** * Gets full help text for the command, e.g. a longer description and one line per * option. * @return the command's help text */ @Nullable String getHelp(); /** * Returns help for each supported option. * @return help for each of the command's options */ Collection getOptionsHelp(); /** * Return some examples for the command. * @return the command's examples */ @Nullable Collection getExamples(); /** * Run the command. * @param args command arguments (this will not include the command itself) * @return the outcome of the command * @throws Exception if the command fails */ ExitStatus run(String... args) throws Exception; } ================================================ FILE: cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandException.java ================================================ /* * 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.cli.command; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.Set; import org.jspecify.annotations.Nullable; /** * Runtime exception wrapper that defines additional {@link Option}s that are understood * by the {@link CommandRunner}. * * @author Phillip Webb * @since 1.0.0 */ public class CommandException extends RuntimeException { private static final long serialVersionUID = 0L; private final EnumSet