From ae898ed383f4a6a843a22a79cf70725719c7844c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Thu, 19 Sep 2024 13:46:37 +0200 Subject: [PATCH 001/213] Next development version (v3.3.5-SNAPSHOT) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 5dd8b0065247..fee3f5fc4f87 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=3.3.4-SNAPSHOT +version=3.3.5-SNAPSHOT latestVersion=false spring.build-type=oss From 41bcda84293f94716fef89ee2b152e742a11711c Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 19 Sep 2024 14:23:32 +0100 Subject: [PATCH 002/213] Document support for Java 23 Closes gh-42374 --- .../src/docs/asciidoc/getting-started/system-requirements.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/system-requirements.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/system-requirements.adoc index 0129bb4cfb30..2ed78e42e19f 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/system-requirements.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/system-requirements.adoc @@ -1,6 +1,6 @@ [[getting-started.system-requirements]] == System Requirements -Spring Boot {spring-boot-version} requires https://www.java.com[Java 17] and is compatible up to and including Java 22. +Spring Boot {spring-boot-version} requires at least https://www.java.com[Java 17] and is compatible with versions up to and including Java 23. {spring-framework-docs}/[Spring Framework {spring-framework-version}] or above is also required. Explicit build support is provided for the following build tools: From 0e7bdb3e8b6965b0d94b89305d642f4b17760faf Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 19 Sep 2024 14:20:49 +0100 Subject: [PATCH 003/213] Remove note about graceful shutdown requiring Tomcat 9.0.33 Since the note was added, we've upgraded to and now require Tomcat 10.1 so it no longer applies. Closes gh-42373 --- .../src/docs/asciidoc/web/graceful-shutdown.adoc | 2 -- 1 file changed, 2 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/graceful-shutdown.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/graceful-shutdown.adoc index fce4f166aa48..0a7f18cb3b3b 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/graceful-shutdown.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/graceful-shutdown.adoc @@ -13,8 +13,6 @@ TIP: To learn about more the specific method used with your web server, see the Jetty, Reactor Netty, and Tomcat will stop accepting new requests at the network layer. Undertow will accept new connections but respond immediately with a service unavailable (503) response. -NOTE: Graceful shutdown with Tomcat requires Tomcat 9.0.33 or later. - To enable graceful shutdown, configure the configprop:server.shutdown[] property, as shown in the following example: [source,yaml,indent=0,subs="verbatim",configprops,configblocks] From 87c647cf657d798dc1b1fc595546a7543922ac92 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 19 Sep 2024 15:05:37 +0100 Subject: [PATCH 004/213] Reduce log output of SampleJettyApplicationTests Closes gh-42376 --- .../test/java/smoketest/jetty/SampleJettyApplicationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty/src/test/java/smoketest/jetty/SampleJettyApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty/src/test/java/smoketest/jetty/SampleJettyApplicationTests.java index 0c9a5eef7169..6760e0df983f 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty/src/test/java/smoketest/jetty/SampleJettyApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty/src/test/java/smoketest/jetty/SampleJettyApplicationTests.java @@ -41,7 +41,7 @@ * @author Michael Weidmann * @author Moritz Halbritter */ -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "logging.level.org.eclipse:trace") +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class SampleJettyApplicationTests { @Autowired From 319e33f786ad7a8fd050a5e0540c4a05e25c247b Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 19 Sep 2024 15:23:47 +0100 Subject: [PATCH 005/213] Make a single scrape attempt, protected by Awaitility Using a single scrape attempt that is protected by Awaitility should protect against instability of the OpenTelemetry Collector instance running in the container and will hopefully stabilize the test. This commit has also increased the timeout for a successful response to 30 seconds and removed the configuration of the configuration of the polling delay and interval as the values being set were the same as the defaults. Closes gh-42377 --- ...nectionDetailsFactoryIntegrationTests.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests.java index c48d6a2135ce..50b558711ebc 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests.java @@ -80,24 +80,20 @@ void connectionCanBeMadeToOpenTelemetryCollectorContainer() { Timer.builder("test.timer").register(this.meterRegistry).record(Duration.ofMillis(123)); DistributionSummary.builder("test.distributionsummary").register(this.meterRegistry).record(24); Awaitility.await() - .atMost(Duration.ofSeconds(5)) - .pollDelay(Duration.ofMillis(100)) - .pollInterval(Duration.ofMillis(100)) + .atMost(Duration.ofSeconds(30)) .untilAsserted(() -> whenPrometheusScraped().then() .statusCode(200) .contentType(OPENMETRICS_001) - .body(endsWith("# EOF\n"), containsString("service_name"))); - whenPrometheusScraped().then() - .body(containsString( - "{job=\"test\",service_name=\"test\",telemetry_sdk_language=\"java\",telemetry_sdk_name=\"io.micrometer\""), - matchesPattern("(?s)^.*test_counter\\{.+} 42\\.0\\n.*$"), - matchesPattern("(?s)^.*test_gauge\\{.+} 12\\.0\\n.*$"), - matchesPattern("(?s)^.*test_timer_count\\{.+} 1\\n.*$"), - matchesPattern("(?s)^.*test_timer_sum\\{.+} 123\\.0\\n.*$"), - matchesPattern("(?s)^.*test_timer_bucket\\{.+,le=\"\\+Inf\"} 1\\n.*$"), - matchesPattern("(?s)^.*test_distributionsummary_count\\{.+} 1\\n.*$"), - matchesPattern("(?s)^.*test_distributionsummary_sum\\{.+} 24\\.0\\n.*$"), - matchesPattern("(?s)^.*test_distributionsummary_bucket\\{.+,le=\"\\+Inf\"} 1\\n.*$")); + .body(endsWith("# EOF\n"), containsString( + "{job=\"test\",service_name=\"test\",telemetry_sdk_language=\"java\",telemetry_sdk_name=\"io.micrometer\""), + matchesPattern("(?s)^.*test_counter\\{.+} 42\\.0\\n.*$"), + matchesPattern("(?s)^.*test_gauge\\{.+} 12\\.0\\n.*$"), + matchesPattern("(?s)^.*test_timer_count\\{.+} 1\\n.*$"), + matchesPattern("(?s)^.*test_timer_sum\\{.+} 123\\.0\\n.*$"), + matchesPattern("(?s)^.*test_timer_bucket\\{.+,le=\"\\+Inf\"} 1\\n.*$"), + matchesPattern("(?s)^.*test_distributionsummary_count\\{.+} 1\\n.*$"), + matchesPattern("(?s)^.*test_distributionsummary_sum\\{.+} 24\\.0\\n.*$"), + matchesPattern("(?s)^.*test_distributionsummary_bucket\\{.+,le=\"\\+Inf\"} 1\\n.*$"))); } private Response whenPrometheusScraped() { From 0aeea6f06940f94c5e80ec04bb7e53d881a8c910 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 19 Sep 2024 14:11:09 +0100 Subject: [PATCH 006/213] Increase timeouts in ZipkinWebClientSenderTests This is an attempt to stabilize the tests. They are currently flaky due to a timeout that appears to occur because the mock web server isn't responding quickly enough. A larger timeout will either confirm this if the tests stabilize or indicate that the problem has a different cause if they do not stabilize. Closes gh-42375 --- .../autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java | 2 +- .../tracing/zipkin/ZipkinWebClientSenderTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java index 66903b9f413e..9a58404b0ee1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java @@ -91,7 +91,7 @@ public void onError(Throwable t) { callbackResult.set(new CallbackResult(false, t)); } }); - return Awaitility.await().atMost(Duration.ofSeconds(5)).until(callbackResult::get, Objects::nonNull); + return Awaitility.await().atMost(Duration.ofMinutes(1)).until(callbackResult::get, Objects::nonNull); } protected void makeSyncRequest(List encodedSpans) throws IOException { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSenderTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSenderTests.java index dcfb8616660b..a4ddd69b3bc5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSenderTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSenderTests.java @@ -80,7 +80,7 @@ void beforeEach() { @Override Sender createSender() { - return createSender(Duration.ofSeconds(10)); + return createSender(Duration.ofMinutes(1)); } Sender createSender(Duration timeout) { From 7c8a6740c1ede905811426b3e74834c7e50fff9d Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Fri, 20 Sep 2024 13:09:40 +0800 Subject: [PATCH 007/213] Remove redundant @Test annotation See gh-42393 --- ...cDockerComposeConnectionDetailsFactoryIntegrationTests.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java index 31ae113c413a..398bffbc22a0 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/postgres/PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -16,8 +16,6 @@ package org.springframework.boot.docker.compose.service.connection.postgres; -import org.junit.jupiter.api.Test; - import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; import org.springframework.boot.testsupport.container.TestImage; @@ -39,7 +37,6 @@ void runCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) { assertConnectionDetails(connectionDetails); } - @Test @DockerComposeTest(composeFile = "postgres-bitnami-compose.yaml", image = TestImage.BITNAMI_POSTGRESQL) void runWithBitnamiImageCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) { assertConnectionDetails(connectionDetails); From 267a642e0f77204a1f2df1f8583d4bf17866d06a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 20 Sep 2024 10:23:04 +0100 Subject: [PATCH 008/213] Reduce duplicate binding of meters to user-defined composites Fixes gh-42396 --- .../metrics/MeterRegistryPostProcessor.java | 51 ++++++++--- .../MeterRegistryPostProcessorTests.java | 88 +++++++++++++++---- 2 files changed, 106 insertions(+), 33 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryPostProcessor.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryPostProcessor.java index 06a0789ec6d5..801a65f7e601 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryPostProcessor.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -42,7 +42,7 @@ */ class MeterRegistryPostProcessor implements BeanPostProcessor, SmartInitializingSingleton { - private final boolean hasNoCompositeMeterRegistryBeans; + private final CompositeMeterRegistries compositeMeterRegistries; private final ObjectProvider properties; @@ -59,17 +59,13 @@ class MeterRegistryPostProcessor implements BeanPostProcessor, SmartInitializing MeterRegistryPostProcessor(ApplicationContext applicationContext, ObjectProvider metricsProperties, ObjectProvider> customizers, ObjectProvider filters, ObjectProvider binders) { - this(hasNoCompositeMeterRegistryBeans(applicationContext), metricsProperties, customizers, filters, binders); + this(CompositeMeterRegistries.of(applicationContext), metricsProperties, customizers, filters, binders); } - private static boolean hasNoCompositeMeterRegistryBeans(ApplicationContext applicationContext) { - return applicationContext.getBeanNamesForType(CompositeMeterRegistry.class, false, false).length == 0; - } - - MeterRegistryPostProcessor(boolean hasNoCompositeMeterRegistryBeans, ObjectProvider properties, - ObjectProvider> customizers, ObjectProvider filters, - ObjectProvider binders) { - this.hasNoCompositeMeterRegistryBeans = hasNoCompositeMeterRegistryBeans; + MeterRegistryPostProcessor(CompositeMeterRegistries compositeMeterRegistries, + ObjectProvider properties, ObjectProvider> customizers, + ObjectProvider filters, ObjectProvider binders) { + this.compositeMeterRegistries = compositeMeterRegistries; this.properties = properties; this.customizers = customizers; this.filters = filters; @@ -130,11 +126,21 @@ private boolean isGlobalRegistry(MeterRegistry meterRegistry) { } private boolean isBindable(MeterRegistry meterRegistry) { - return this.hasNoCompositeMeterRegistryBeans || isCompositeMeterRegistry(meterRegistry); + return isAutoConfiguredComposite(meterRegistry) || isCompositeWithOnlyUserDefinedComposites(meterRegistry) + || noCompositeMeterRegistries(); + } + + private boolean isAutoConfiguredComposite(MeterRegistry meterRegistry) { + return meterRegistry instanceof AutoConfiguredCompositeMeterRegistry; + } + + private boolean isCompositeWithOnlyUserDefinedComposites(MeterRegistry meterRegistry) { + return this.compositeMeterRegistries == CompositeMeterRegistries.ONLY_USER_DEFINED + && meterRegistry instanceof CompositeMeterRegistry; } - private boolean isCompositeMeterRegistry(MeterRegistry meterRegistry) { - return meterRegistry instanceof CompositeMeterRegistry; + private boolean noCompositeMeterRegistries() { + return this.compositeMeterRegistries == CompositeMeterRegistries.NONE; } void applyBinders(MeterRegistry meterRegistry) { @@ -149,4 +155,21 @@ void applyBinders(MeterRegistry meterRegistry) { this.binders.orderedStream().forEach((binder) -> binder.bindTo(meterRegistry)); } + enum CompositeMeterRegistries { + + NONE, AUTO_CONFIGURED, ONLY_USER_DEFINED; + + private static CompositeMeterRegistries of(ApplicationContext context) { + if (hasBeansOfType(AutoConfiguredCompositeMeterRegistry.class, context)) { + return AUTO_CONFIGURED; + } + return hasBeansOfType(CompositeMeterRegistry.class, context) ? ONLY_USER_DEFINED : NONE; + } + + private static boolean hasBeansOfType(Class type, ApplicationContext context) { + return context.getBeanNamesForType(type, false, false).length > 0; + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryPostProcessorTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryPostProcessorTests.java index 54d3bbcc4167..4621577027a2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryPostProcessorTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * 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. @@ -17,8 +17,10 @@ package org.springframework.boot.actuate.autoconfigure.metrics; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry.Config; import io.micrometer.core.instrument.Metrics; @@ -32,6 +34,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryPostProcessor.CompositeMeterRegistries; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -76,21 +79,34 @@ class MeterRegistryPostProcessorTests { } @Test - void postProcessAndInitializeWhenCompositeAppliesCustomizer() { + void postProcessAndInitializeWhenUserDefinedCompositeAppliesCustomizer() { this.customizers.add(this.mockCustomizer); - MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(false, - createObjectProvider(this.properties), createObjectProvider(this.customizers), - createObjectProvider(this.filters), createObjectProvider(this.binders)); + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor( + CompositeMeterRegistries.ONLY_USER_DEFINED, createObjectProvider(this.properties), + createObjectProvider(this.customizers), createObjectProvider(this.filters), + createObjectProvider(this.binders)); CompositeMeterRegistry composite = new CompositeMeterRegistry(); postProcessAndInitialize(processor, composite); then(this.mockCustomizer).should().customize(composite); } + @Test + void postProcessAndInitializeWhenAutoConfiguredCompositeAppliesCustomizer() { + this.customizers.add(this.mockCustomizer); + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, + createObjectProvider(this.properties), createObjectProvider(this.customizers), null, + createObjectProvider(this.binders)); + AutoConfiguredCompositeMeterRegistry composite = new AutoConfiguredCompositeMeterRegistry(Clock.SYSTEM, + Collections.emptyList()); + postProcessAndInitialize(processor, composite); + then(this.mockCustomizer).should().customize(composite); + } + @Test void postProcessAndInitializeAppliesCustomizer() { given(this.mockRegistry.config()).willReturn(this.mockConfig); this.customizers.add(this.mockCustomizer); - MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(true, + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.filters), createObjectProvider(this.binders)); postProcessAndInitialize(processor, this.mockRegistry); @@ -101,7 +117,7 @@ void postProcessAndInitializeAppliesCustomizer() { void postProcessAndInitializeAppliesFilter() { given(this.mockRegistry.config()).willReturn(this.mockConfig); this.filters.add(this.mockFilter); - MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(true, + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.filters), createObjectProvider(this.binders)); postProcessAndInitialize(processor, this.mockRegistry); @@ -112,7 +128,7 @@ void postProcessAndInitializeAppliesFilter() { void postProcessAndInitializeBindsTo() { given(this.mockRegistry.config()).willReturn(this.mockConfig); this.binders.add(this.mockBinder); - MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(true, + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.filters), createObjectProvider(this.binders)); postProcessAndInitialize(processor, this.mockRegistry); @@ -120,20 +136,54 @@ void postProcessAndInitializeBindsTo() { } @Test - void postProcessAndInitializeWhenCompositeBindsTo() { + void whenUserDefinedCompositeThenPostProcessAndInitializeCompositeBindsTo() { this.binders.add(this.mockBinder); - MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(false, - createObjectProvider(this.properties), createObjectProvider(this.customizers), - createObjectProvider(this.filters), createObjectProvider(this.binders)); + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor( + CompositeMeterRegistries.ONLY_USER_DEFINED, createObjectProvider(this.properties), + createObjectProvider(this.customizers), createObjectProvider(this.filters), + createObjectProvider(this.binders)); CompositeMeterRegistry composite = new CompositeMeterRegistry(); postProcessAndInitialize(processor, composite); then(this.mockBinder).should().bindTo(composite); } @Test - void postProcessAndInitializeWhenCompositeExistsDoesNotBindTo() { + void whenUserDefinedCompositeThenPostProcessAndInitializeStandardRegistryDoesNotBindTo() { + given(this.mockRegistry.config()).willReturn(this.mockConfig); + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor( + CompositeMeterRegistries.ONLY_USER_DEFINED, createObjectProvider(this.properties), + createObjectProvider(this.customizers), createObjectProvider(this.filters), null); + postProcessAndInitialize(processor, this.mockRegistry); + then(this.mockBinder).shouldHaveNoInteractions(); + } + + @Test + void whenAutoConfiguredCompositeThenPostProcessAndInitializeAutoConfiguredCompositeBindsTo() { + this.binders.add(this.mockBinder); + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, + createObjectProvider(this.properties), createObjectProvider(this.customizers), null, + createObjectProvider(this.binders)); + AutoConfiguredCompositeMeterRegistry composite = new AutoConfiguredCompositeMeterRegistry(Clock.SYSTEM, + Collections.emptyList()); + postProcessAndInitialize(processor, composite); + then(this.mockBinder).should().bindTo(composite); + } + + @Test + void whenAutoConfiguredCompositeThenPostProcessAndInitializeCompositeDoesNotBindTo() { + this.binders.add(this.mockBinder); + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, + createObjectProvider(this.properties), createObjectProvider(this.customizers), + createObjectProvider(this.filters), null); + CompositeMeterRegistry composite = new CompositeMeterRegistry(); + postProcessAndInitialize(processor, composite); + then(this.mockBinder).shouldHaveNoInteractions(); + } + + @Test + void whenAutoConfiguredCompositeThenPostProcessAndInitializeStandardRegistryDoesNotBindTo() { given(this.mockRegistry.config()).willReturn(this.mockConfig); - MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(false, + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.filters), null); postProcessAndInitialize(processor, this.mockRegistry); @@ -141,12 +191,12 @@ void postProcessAndInitializeWhenCompositeExistsDoesNotBindTo() { } @Test - void postProcessAndInitializeBeOrderedCustomizerThenFilterThenBindTo() { + void postProcessAndInitializeIsOrderedCustomizerThenFilterThenBindTo() { given(this.mockRegistry.config()).willReturn(this.mockConfig); this.customizers.add(this.mockCustomizer); this.filters.add(this.mockFilter); this.binders.add(this.mockBinder); - MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(true, + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.filters), createObjectProvider(this.binders)); postProcessAndInitialize(processor, this.mockRegistry); @@ -160,7 +210,7 @@ void postProcessAndInitializeBeOrderedCustomizerThenFilterThenBindTo() { void postProcessAndInitializeWhenUseGlobalRegistryTrueAddsToGlobalRegistry() { given(this.mockRegistry.config()).willReturn(this.mockConfig); this.properties.setUseGlobalRegistry(true); - MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(true, + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.filters), createObjectProvider(this.binders)); try { @@ -175,7 +225,7 @@ void postProcessAndInitializeWhenUseGlobalRegistryTrueAddsToGlobalRegistry() { @Test void postProcessAndInitializeWhenUseGlobalRegistryFalseDoesNotAddToGlobalRegistry() { given(this.mockRegistry.config()).willReturn(this.mockConfig); - MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(true, + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.filters), createObjectProvider(this.binders)); postProcessAndInitialize(processor, this.mockRegistry); @@ -186,7 +236,7 @@ void postProcessAndInitializeWhenUseGlobalRegistryFalseDoesNotAddToGlobalRegistr void postProcessDoesNotBindToUntilSingletonsInitialized() { given(this.mockRegistry.config()).willReturn(this.mockConfig); this.binders.add(this.mockBinder); - MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(true, + MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.filters), createObjectProvider(this.binders)); processor.postProcessAfterInitialization(this.mockRegistry, "meterRegistry"); From b4702612de5ff51c39d7b8bce241037b21252460 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 20 Sep 2024 11:06:18 +0100 Subject: [PATCH 009/213] Revert "Increase timeouts in ZipkinWebClientSenderTests" This reverts commit 0aeea6f06940f94c5e80ec04bb7e53d881a8c910. See gh-42375 --- .../autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java | 2 +- .../tracing/zipkin/ZipkinWebClientSenderTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java index 9a58404b0ee1..66903b9f413e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpSenderTests.java @@ -91,7 +91,7 @@ public void onError(Throwable t) { callbackResult.set(new CallbackResult(false, t)); } }); - return Awaitility.await().atMost(Duration.ofMinutes(1)).until(callbackResult::get, Objects::nonNull); + return Awaitility.await().atMost(Duration.ofSeconds(5)).until(callbackResult::get, Objects::nonNull); } protected void makeSyncRequest(List encodedSpans) throws IOException { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSenderTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSenderTests.java index a4ddd69b3bc5..dcfb8616660b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSenderTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSenderTests.java @@ -80,7 +80,7 @@ void beforeEach() { @Override Sender createSender() { - return createSender(Duration.ofMinutes(1)); + return createSender(Duration.ofSeconds(10)); } Sender createSender(Duration timeout) { From 27eb9838870d5bd94f5bedfe54a080ae94c9dc72 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 20 Sep 2024 15:41:43 +0100 Subject: [PATCH 010/213] Trigger a docs build when doing a release Closes gh-42378 --- .github/workflows/release.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 058c1492ff9a..45efd8fdee4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -120,6 +120,19 @@ jobs: with: spring-boot-version: ${{ needs.build-and-stage-release.outputs.version }} token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} + trigger-docs-build: + name: Trigger Docs Build + runs-on: ubuntu-latest + needs: + - build-and-stage-release + - sync-to-maven-central + permissions: + actions: write + steps: + - name: Run Deploy Docs Workflow + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh workflow run deploy-docs.yml --repo spring-projects/spring-boot -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: @@ -127,6 +140,7 @@ jobs: - promote-release - publish-gradle-plugin - publish-to-sdkman + - trigger-docs-build - update-homebrew-tap runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: From 80b1944878747aa1295667541dcc1a0e1fc5514c Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 20 Sep 2024 12:29:33 -0700 Subject: [PATCH 011/213] Update changelog-generator for commercial builds See gh-42333 --- .github/actions/create-github-release/changelog-generator.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/actions/create-github-release/changelog-generator.yml b/.github/actions/create-github-release/changelog-generator.yml index 67db9ca4f9d7..267dda904da7 100644 --- a/.github/actions/create-github-release/changelog-generator.yml +++ b/.github/actions/create-github-release/changelog-generator.yml @@ -1,5 +1,5 @@ changelog: - repository: spring-projects/spring-boot + repository: ${{ github.repository }} sections: - title: ":star: New Features" labels: @@ -16,6 +16,7 @@ changelog: labels: - "type: dependency-upgrade" issues: + generate_links: ${{ !vars.COMMERCIAL }} ports: - label: "status: forward-port" bodyExpression: 'Forward port of issue #(\d+).*' From 19d9e3b6cc1be969882b3808804b6171acde903b Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 20 Sep 2024 16:50:36 -0700 Subject: [PATCH 012/213] Update BOM plugin to support commercial releases See gh-42333 --- .../boot/build/bom/BomExtension.java | 72 +++++++----- .../boot/build/bom/BomPlugin.java | 5 +- .../bomr/MavenMetadataVersionResolver.java | 45 ++++---- .../boot/build/bom/bomr/MoveToSnapshots.java | 57 ++++++---- .../boot/build/bom/bomr/UpgradeBom.java | 28 ++++- .../build/bom/bomr/UpgradeDependencies.java | 34 ++++-- .../build/properties/BuildProperties.java | 88 +++++++++++++++ .../boot/build/properties/BuildType.java | 36 ++++++ .../boot/build/repository/RepositoryUrl.java | 37 ++++++ .../build/repository/SpringRepository.java | 105 ++++++++++++++++++ 10 files changed, 419 insertions(+), 88 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/properties/BuildProperties.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/properties/BuildType.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/repository/RepositoryUrl.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/repository/SpringRepository.java diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java index 6a0ec63ec0d9..9cf76901db78 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java @@ -65,6 +65,7 @@ import org.springframework.boot.build.bom.Library.VersionAlignment; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.boot.build.mavenplugin.MavenExec; +import org.springframework.boot.build.properties.BuildProperties; import org.springframework.util.FileCopyUtils; /** @@ -74,22 +75,19 @@ */ public class BomExtension { + 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<>(); - private final UpgradeHandler upgradeHandler; - - private final DependencyHandler dependencyHandler; - - private final Project project; - - public BomExtension(DependencyHandler dependencyHandler, Project project) { - this.dependencyHandler = dependencyHandler; - this.upgradeHandler = project.getObjects().newInstance(UpgradeHandler.class); + public BomExtension(Project project) { this.project = project; + this.upgradeHandler = project.getObjects().newInstance(UpgradeHandler.class, project); } public List getLibraries() { @@ -101,8 +99,9 @@ public void upgrade(Action action) { } public Upgrade getUpgrade() { - return new Upgrade(this.upgradeHandler.upgradePolicy, new GitHub(this.upgradeHandler.gitHub.organization, - this.upgradeHandler.gitHub.repository, this.upgradeHandler.gitHub.issueLabels)); + 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) { @@ -191,6 +190,7 @@ private void putArtifactVersionProperty(String groupId, String artifactId, Strin } private void addLibrary(Library library) { + DependencyHandler dependencies = this.project.getDependencies(); this.libraries.add(library); String versionProperty = library.getVersionProperty(); if (versionProperty != null) { @@ -198,23 +198,30 @@ private void addLibrary(Library library) { } for (Group group : library.getGroups()) { for (Module module : group.getModules()) { - putArtifactVersionProperty(group.getId(), module.getName(), module.getClassifier(), versionProperty); - this.dependencyHandler.getConstraints() - .add(JavaPlatformPlugin.API_CONFIGURATION_NAME, createDependencyNotation(group.getId(), - module.getName(), library.getVersion().getVersion())); + addModule(library, dependencies, versionProperty, group, module); } for (String bomImport : group.getBoms()) { - putArtifactVersionProperty(group.getId(), bomImport, versionProperty); - String bomDependency = createDependencyNotation(group.getId(), bomImport, - library.getVersion().getVersion()); - this.dependencyHandler.add(JavaPlatformPlugin.API_CONFIGURATION_NAME, - this.dependencyHandler.platform(bomDependency)); - this.dependencyHandler.add(BomPlugin.API_ENFORCED_CONFIGURATION_NAME, - this.dependencyHandler.enforcedPlatform(bomDependency)); + addBomImport(library, dependencies, versionProperty, group, bomImport); } } } + 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 List groups = new ArrayList<>(); @@ -421,7 +428,12 @@ public static class UpgradeHandler { private UpgradePolicy upgradePolicy; - private final GitHubHandler gitHub = new GitHubHandler(); + private final GitHubHandler gitHub; + + @Inject + public UpgradeHandler(Project project) { + this.gitHub = new GitHubHandler(project); + } public void setPolicy(UpgradePolicy upgradePolicy) { this.upgradePolicy = upgradePolicy; @@ -456,12 +468,18 @@ public GitHub getGitHub() { public static class GitHubHandler { - private String organization = "spring-projects"; + private String organization; - private String repository = "spring-boot"; + 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; } @@ -478,9 +496,9 @@ public void setIssueLabels(List issueLabels) { public static final class GitHub { - private String organization = "spring-projects"; + private String organization; - private String repository = "spring-boot"; + private String repository; private final List issueLabels; diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java index 6cbd5ae787b5..a02a460fee4a 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -60,8 +60,7 @@ public void apply(Project project) { JavaPlatformExtension javaPlatform = project.getExtensions().getByType(JavaPlatformExtension.class); javaPlatform.allowDependencies(); createApiEnforcedConfiguration(project); - BomExtension bom = project.getExtensions() - .create("bom", BomExtension.class, project.getDependencies(), project); + BomExtension bom = project.getExtensions().create("bom", BomExtension.class, project); CheckBom checkBom = project.getTasks().create("bomrCheck", CheckBom.class, bom); project.getTasks().named("check").configure((check) -> check.dependsOn(checkBom)); project.getTasks().create("bomrUpgrade", UpgradeBom.class, bom); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MavenMetadataVersionResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MavenMetadataVersionResolver.java index fbd12f571f28..2da3daee56d3 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MavenMetadataVersionResolver.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MavenMetadataVersionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -30,11 +30,15 @@ import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; 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; @@ -50,42 +54,37 @@ final class MavenMetadataVersionResolver implements VersionResolver { private final RestTemplate rest; - private final Collection repositoryUrls; + private final Collection repositories; - MavenMetadataVersionResolver(Collection repositoryUrls) { - this(new RestTemplate(Collections.singletonList(new StringHttpMessageConverter())), repositoryUrls); + MavenMetadataVersionResolver(Collection repositories) { + this(new RestTemplate(Collections.singletonList(new StringHttpMessageConverter())), repositories); } - MavenMetadataVersionResolver(RestTemplate restTemplate, Collection repositoryUrls) { + MavenMetadataVersionResolver(RestTemplate restTemplate, Collection repositories) { this.rest = restTemplate; - this.repositoryUrls = normalize(repositoryUrls); - } - - private Collection normalize(Collection uris) { - return uris.stream().map(this::normalize).toList(); - } - - private URI normalize(URI uri) { - if ("/".equals(uri.getPath())) { - return uri; - } - return URI.create(uri + "/"); + this.repositories = repositories; } @Override public SortedSet resolveVersions(String groupId, String artifactId) { Set versions = new HashSet<>(); - for (URI repositoryUrl : this.repositoryUrls) { - versions.addAll(resolveVersions(groupId, artifactId, repositoryUrl)); + 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, URI repositoryUrl) { + private Set resolveVersions(String groupId, String artifactId, MavenArtifactRepository repository) { Set versions = new HashSet<>(); - URI url = repositoryUrl.resolve(groupId.replace('.', '/') + "/" + artifactId + "/maven-metadata.xml"); + URI url = repository.getUrl().resolve(groupId.replace('.', '/') + "/" + artifactId + "/maven-metadata.xml"); try { - String metadata = this.rest.getForObject(url, String.class); + HttpHeaders headers = new HttpHeaders(); + String username = repository.getCredentials().getUsername(); + if (username != null) { + headers.setBasicAuth(username, repository.getCredentials().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))); @@ -104,7 +103,7 @@ private Set resolveVersions(String groupId, String artifactId, URI repos } catch (Exception ex) { System.err.println("Failed to resolve versions for module " + groupId + ":" + artifactId + " in repository " - + repositoryUrl + ": " + ex.getMessage()); + + repository + ": " + ex.getMessage()); } return versions; } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MoveToSnapshots.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MoveToSnapshots.java index b47545839378..e1b170accabc 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MoveToSnapshots.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MoveToSnapshots.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 the original author or authors. + * Copyright 2021-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. @@ -16,8 +16,8 @@ package org.springframework.boot.build.bom.bomr; -import java.net.URI; import java.time.OffsetDateTime; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.function.BiPredicate; @@ -34,6 +34,10 @@ 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; +import org.springframework.boot.build.repository.SpringRepository; +import org.springframework.boot.build.repository.SpringRepository.RepositoryType; /** * A {@link Task} to move to snapshot dependencies. @@ -44,12 +48,20 @@ public abstract class MoveToSnapshots extends UpgradeDependencies { private static final Logger log = LoggerFactory.getLogger(MoveToSnapshots.class); - private final URI REPOSITORY_URI = URI.create("https://repo.spring.io/snapshot/"); - @Inject public MoveToSnapshots(BomExtension bom) { super(bom, true); - getRepositoryUris().add(this.REPOSITORY_URI); + BuildType buildType = BuildProperties.get(this).buildType(); + getRepositoryNames().addAll(getSnapshotRepositoryNames(buildType)); + } + + public static List getSnapshotRepositoryNames(BuildType buildType) { + return Arrays.stream(SpringRepository.values()) + .filter((repository) -> repository.getRepositoryType() == RepositoryType.SNAPSHOT) + .filter((repository) -> repository.getBuildType() == BuildType.OPEN_SOURCE + || repository.getBuildType() == buildType) + .map(SpringRepository::getName) + .toList(); } @Override @@ -83,26 +95,31 @@ protected boolean eligible(Library library) { @Override protected List> determineUpdatePredicates(Milestone milestone) { - ReleaseSchedule releaseSchedule = new ReleaseSchedule(); - Map> releases = releaseSchedule.releasesBetween(OffsetDateTime.now(), - milestone.getDueOn()); + return switch (BuildProperties.get(this).buildType()) { + case OPEN_SOURCE -> determineOpenSourceUpdatePredicates(milestone); + case COMMERCIAL -> super.determineUpdatePredicates(milestone); + }; + } + + private List> determineOpenSourceUpdatePredicates(Milestone milestone) { + Map> scheduledReleases = getScheduledOpenSourceReleases(milestone); List> predicates = super.determineUpdatePredicates(milestone); predicates.add((library, candidate) -> { - List releasesForLibrary = releases.get(library.getCalendarName()); - if (releasesForLibrary != null) { - for (Release release : releasesForLibrary) { - if (candidate.isSnapshotFor(release.getVersion())) { - return true; - } - } - } - if (log.isInfoEnabled()) { - log.info("Ignoring " + candidate + ". No release of " + library.getName() + " scheduled before " - + milestone.getDueOn()); + List releases = scheduledReleases.get(library.getCalendarName()); + boolean match = (releases != null) + && releases.stream().anyMatch((release) -> candidate.isSnapshotFor(release.getVersion())); + if (log.isInfoEnabled() && !match) { + log.info("Ignoring {}. No release of {} scheduled before {}", candidate, library.getName(), + milestone.getDueOn()); } - return false; + return match; }); return predicates; } + private Map> getScheduledOpenSourceReleases(Milestone milestone) { + ReleaseSchedule releaseSchedule = new ReleaseSchedule(); + return releaseSchedule.releasesBetween(OffsetDateTime.now(), milestone.getDueOn()); + } + } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java index 16e4dbdc0687..b71fe2614f8b 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -16,14 +16,15 @@ package org.springframework.boot.build.bom.bomr; -import java.net.URI; - import javax.inject.Inject; import org.gradle.api.Task; +import org.gradle.api.artifacts.ArtifactRepositoryContainer; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.springframework.boot.build.bom.BomExtension; +import org.springframework.boot.build.properties.BuildProperties; +import org.springframework.boot.build.repository.SpringRepository; /** * {@link Task} to upgrade the libraries managed by a bom. @@ -36,14 +37,29 @@ public abstract class UpgradeBom extends UpgradeDependencies { @Inject public UpgradeBom(BomExtension bom) { super(bom); + switch (BuildProperties.get(this).buildType()) { + case OPEN_SOURCE -> addOpenSourceRepositories(); + case COMMERCIAL -> addCommercialRepositories(); + } + } + + private void addOpenSourceRepositories() { getProject().getRepositories().withType(MavenArtifactRepository.class, (repository) -> { - URI repositoryUrl = repository.getUrl(); - if (!repositoryUrl.toString().endsWith("snapshot")) { - getRepositoryUris().add(repositoryUrl); + if (!isSnaphotRepository(repository)) { + getRepositoryNames().add(repository.getName()); } }); } + private void addCommercialRepositories() { + getRepositoryNames().addAll(ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME, + SpringRepository.COMMERCIAL_RELEASE.getName()); + } + + private boolean isSnaphotRepository(MavenArtifactRepository repository) { + return repository.getUrl().toString().endsWith("snapshot"); + } + @Override protected String issueTitle(Upgrade upgrade) { return "Upgrade to " + upgrade.getLibrary().getName() + " " + upgrade.getVersion(); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeDependencies.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeDependencies.java index 04934445cd87..2886e78d7115 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeDependencies.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeDependencies.java @@ -20,10 +20,10 @@ import java.io.FileReader; import java.io.IOException; import java.io.Reader; -import java.net.URI; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Properties; import java.util.Set; @@ -35,6 +35,7 @@ import org.gradle.api.DefaultTask; import org.gradle.api.InvalidUserDataException; +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; @@ -54,7 +55,7 @@ import org.springframework.util.StringUtils; /** - * Base class for tasks that upgrade dependencies in a bom. + * Base class for tasks that upgrade dependencies in a BOM. * * @author Andy Wilkinson * @author Moritz Halbritter @@ -91,7 +92,7 @@ protected UpgradeDependencies(BomExtension bom, boolean movingToSnapshots) { public abstract Property getLibraries(); @Input - abstract ListProperty getRepositoryUris(); + abstract ListProperty getRepositoryNames(); @TaskAction void upgradeDependencies() { @@ -216,12 +217,27 @@ private Issue findExistingUpgradeIssue(List existingUpgradeIssues, Upgrad @SuppressWarnings("deprecation") private List resolveUpgrades(Milestone milestone) { - List upgrades = new InteractiveUpgradeResolver(getServices().get(UserInputHandler.class), - new MultithreadedLibraryUpdateResolver(getThreads().get(), - new StandardLibraryUpdateResolver(new MavenMetadataVersionResolver(getRepositoryUris().get()), - determineUpdatePredicates(milestone)))) - .resolveUpgrades(matchingLibraries(), this.bom.getLibraries()); - return upgrades; + 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, + determineUpdatePredicates(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(getProject().getRepositories()::getByName) + .map(MavenArtifactRepository.class::cast) + .toList(); } protected List> determineUpdatePredicates(Milestone milestone) { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/properties/BuildProperties.java b/buildSrc/src/main/java/org/springframework/boot/build/properties/BuildProperties.java new file mode 100644 index 000000000000..ed347095e5d0 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/properties/BuildProperties.java @@ -0,0 +1,88 @@ +/* + * Copyright 2024-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. + */ + +package org.springframework.boot.build.properties; + +import org.gradle.api.Project; +import org.gradle.api.Task; + +/** + * 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 Task}. + * @param task the source task + * @return the build properties + */ + public static BuildProperties get(Task task) { + return get(task.getProject()); + } + + /** + * 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"); + + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/properties/BuildType.java b/buildSrc/src/main/java/org/springframework/boot/build/properties/BuildType.java new file mode 100644 index 000000000000..420e6ed6f4c6 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/properties/BuildType.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024-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. + */ + +package org.springframework.boot.build.properties; + +/** + * The type of build being performed. + * + * @author Phillip Webb + */ +public enum BuildType { + + /** + * An open source build. + */ + OPEN_SOURCE, + + /** + * A commercial build. + */ + COMMERCIAL + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/repository/RepositoryUrl.java b/buildSrc/src/main/java/org/springframework/boot/build/repository/RepositoryUrl.java new file mode 100644 index 000000000000..690bd23ec8f0 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/repository/RepositoryUrl.java @@ -0,0 +1,37 @@ +/* + * Copyright 2024-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. + */ + +package org.springframework.boot.build.repository; + +/** + * Utility to build repository URLs. + * + * @author Phillip Webb + */ +final class RepositoryUrl { + + private RepositoryUrl() { + } + + static String openSource(String path) { + return "https://repo.spring.io" + path; + } + + static String commercial(String path) { + return "https://usw1.packages.broadcom.com" + path; + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/repository/SpringRepository.java b/buildSrc/src/main/java/org/springframework/boot/build/repository/SpringRepository.java new file mode 100644 index 000000000000..50e1471258c6 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/repository/SpringRepository.java @@ -0,0 +1,105 @@ +/* + * Copyright 2024-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. + */ + +package org.springframework.boot.build.repository; + +import org.springframework.boot.build.properties.BuildType; + +/** + * Enumeration of repositories defined in the order that they should be used. + * + * @author Phillip Webb + */ +public enum SpringRepository { + + /** + * Repository for commercial releases. + */ + COMMERCIAL_RELEASE("spring-commerical-release", BuildType.COMMERCIAL, RepositoryType.RELEASE, + RepositoryUrl.commercial("/spring-enterprise-maven-prod-local")), + + /** + * Repository for open source milestones. + */ + OSS_MILESTONE("spring-oss-milestone", BuildType.OPEN_SOURCE, RepositoryType.MILESTONE, + RepositoryUrl.openSource("/milestone")), + + /** + * Repository for commercial snapshots. + */ + COMMERCIAL_SNAPSHOT("spring-commerical-snapshot", BuildType.COMMERCIAL, RepositoryType.SNAPSHOT, + RepositoryUrl.commercial("/spring-enterprise-maven-dev-local")), + + /** + * Repository for open source snapshots. + */ + OSS_SNAPSHOT("spring-oss-snapshot", BuildType.OPEN_SOURCE, RepositoryType.SNAPSHOT, + RepositoryUrl.openSource("/snapshot")); + + private final String name; + + private final BuildType buildType; + + private final RepositoryType repositoryType; + + private final String url; + + SpringRepository(String name, BuildType buildType, RepositoryType repositoryType, String url) { + this.name = name; + this.buildType = buildType; + this.repositoryType = repositoryType; + this.url = url; + } + + public String getName() { + return this.name; + } + + public BuildType getBuildType() { + return this.buildType; + } + + public RepositoryType getRepositoryType() { + return this.repositoryType; + } + + public String getUrl() { + return this.url; + } + + /** + * Repository types. + */ + public enum RepositoryType { + + /** + * Repository containing release artifacts. + */ + RELEASE, + + /** + * Repository containing milestone artifacts. + */ + MILESTONE, + + /** + * Repository containing snapshot artifacts. + */ + SNAPSHOT + + } + +} From 0d3fceec5d9409839ea1fca9eda053795fd84754 Mon Sep 17 00:00:00 2001 From: arefbehboudi Date: Sun, 22 Sep 2024 17:00:06 +0330 Subject: [PATCH 013/213] Polish See gh-42413 --- .../boot/build/test/DockerTestPlugin.java | 3 +-- .../boot/loader/jar/JarEntriesStream.java | 3 --- .../loader/net/protocol/jar/JarUrlConnection.java | 12 ++++++------ .../net/protocol/jar/LazyDelegatingInputStream.java | 6 +++--- .../boot/loader/nio/file/NestedFileSystem.java | 8 +++----- .../loader/nio/file/NestedFileSystemProvider.java | 4 ++-- .../boot/loader/nio/file/UriPathEncoder.java | 2 +- 7 files changed, 16 insertions(+), 22 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java index 017ca001a43b..b2970b2ed240 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java @@ -98,7 +98,7 @@ private SourceSet createSourceSet(Project project) { private Provider createTestTask(Project project, SourceSet dockerTestSourceSet, Provider buildService) { - Provider dockerTest = project.getTasks().register(DOCKER_TEST_TASK_NAME, Test.class, (task) -> { + 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."); @@ -106,7 +106,6 @@ private Provider createTestTask(Project project, SourceSet dockerTestSourc task.setClasspath(dockerTestSourceSet.getRuntimeClasspath()); task.shouldRunAfter(JavaPlugin.TEST_TASK_NAME); }); - return dockerTest; } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarEntriesStream.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarEntriesStream.java index 35d9421874b4..3bb8419fb952 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarEntriesStream.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarEntriesStream.java @@ -54,9 +54,6 @@ class JarEntriesStream implements Closeable { JarEntry getNextEntry() throws IOException { this.entry = this.in.getNextJarEntry(); - if (this.entry != null) { - this.entry.getSize(); - } this.inflater.reset(); return this.entry; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/net/protocol/jar/JarUrlConnection.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/net/protocol/jar/JarUrlConnection.java index b95965447e62..1f0d069ebb9d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/net/protocol/jar/JarUrlConnection.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/net/protocol/jar/JarUrlConnection.java @@ -211,9 +211,9 @@ public boolean getAllowUserInteraction() { } @Override - public void setAllowUserInteraction(boolean allowuserinteraction) { + public void setAllowUserInteraction(boolean allowUserInteraction) { if (this.jarFileConnection != null) { - this.jarFileConnection.setAllowUserInteraction(allowuserinteraction); + this.jarFileConnection.setAllowUserInteraction(allowUserInteraction); } } @@ -223,9 +223,9 @@ public boolean getUseCaches() { } @Override - public void setUseCaches(boolean usecaches) { + public void setUseCaches(boolean useCaches) { if (this.jarFileConnection != null) { - this.jarFileConnection.setUseCaches(usecaches); + this.jarFileConnection.setUseCaches(useCaches); } } @@ -235,9 +235,9 @@ public boolean getDefaultUseCaches() { } @Override - public void setDefaultUseCaches(boolean defaultusecaches) { + public void setDefaultUseCaches(boolean defaultUseCaches) { if (this.jarFileConnection != null) { - this.jarFileConnection.setDefaultUseCaches(defaultusecaches); + this.jarFileConnection.setDefaultUseCaches(defaultUseCaches); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/net/protocol/jar/LazyDelegatingInputStream.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/net/protocol/jar/LazyDelegatingInputStream.java index 95e5cc3c14a7..ff47aa2debd6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/net/protocol/jar/LazyDelegatingInputStream.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/net/protocol/jar/LazyDelegatingInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -64,9 +64,9 @@ public boolean markSupported() { } @Override - public synchronized void mark(int readlimit) { + public synchronized void mark(int readLimit) { try { - in().mark(readlimit); + in().mark(readLimit); } catch (IOException ex) { // Ignore diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/nio/file/NestedFileSystem.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/nio/file/NestedFileSystem.java index 9b770cbd2122..462a494bbb34 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/nio/file/NestedFileSystem.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/nio/file/NestedFileSystem.java @@ -98,11 +98,9 @@ private boolean hasFileSystem(URI uri) { private boolean isCreatingNewFileSystem() { StackTraceElement[] stack = Thread.currentThread().getStackTrace(); - if (stack != null) { - for (StackTraceElement element : stack) { - if (FILE_SYSTEMS_CLASS_NAME.equals(element.getClassName())) { - return "newFileSystem".equals(element.getMethodName()); - } + for (StackTraceElement element : stack) { + if (FILE_SYSTEMS_CLASS_NAME.equals(element.getClassName())) { + return "newFileSystem".equals(element.getMethodName()); } } return false; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/nio/file/NestedFileSystemProvider.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/nio/file/NestedFileSystemProvider.java index ca136748df8c..fce7e8c56580 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/nio/file/NestedFileSystemProvider.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/nio/file/NestedFileSystemProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -50,7 +50,7 @@ */ public class NestedFileSystemProvider extends FileSystemProvider { - private Map fileSystems = new HashMap<>(); + private final Map fileSystems = new HashMap<>(); @Override public String getScheme() { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/nio/file/UriPathEncoder.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/nio/file/UriPathEncoder.java index a58f252a6f42..9b96ef3c93e2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/nio/file/UriPathEncoder.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/nio/file/UriPathEncoder.java @@ -28,7 +28,7 @@ final class UriPathEncoder { // Based on org.springframework.web.util.UriUtils - private static char[] ALLOWED = "/:@-._~!$&\'()*+,;=".toCharArray(); + private static final char[] ALLOWED = "/:@-._~!$&\'()*+,;=".toCharArray(); private UriPathEncoder() { } From a0dc929e6c6bba70ffa4387bc416a438212c569d Mon Sep 17 00:00:00 2001 From: Johnny Lim Date: Sat, 21 Sep 2024 14:33:36 +0900 Subject: [PATCH 014/213] Add Javadoc `@Since` for new `PrometheusScrapeEndpoint` constructor See gh-42406 --- .../metrics/export/prometheus/PrometheusScrapeEndpoint.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpoint.java index 151a00fb50cb..2e795433b5b6 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpoint.java @@ -69,6 +69,7 @@ public PrometheusScrapeEndpoint(PrometheusRegistry prometheusRegistry) { * @param prometheusRegistry the Prometheus registry to use * @param exporterProperties the properties used to configure Prometheus' * {@link ExpositionFormats} + * @since 3.3.1 */ public PrometheusScrapeEndpoint(PrometheusRegistry prometheusRegistry, Properties exporterProperties) { this.prometheusRegistry = prometheusRegistry; From 2aea7caf36b34f4ba3b5ed5c783ac0b912775f84 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 23 Sep 2024 14:08:35 +0100 Subject: [PATCH 015/213] Remove use of reflection in Artemis connection factory creation Fixes gh-42414 --- ...ArtemisConnectionFactoryConfiguration.java | 6 ++-- .../ArtemisConnectionFactoryFactory.java | 35 +++++++++---------- ...temisXAConnectionFactoryConfiguration.java | 6 ++-- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java index 98a305be8f38..a6391df2bb9d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -56,7 +56,7 @@ ActiveMQConnectionFactory jmsConnectionFactory(ArtemisProperties properties, Lis private static ActiveMQConnectionFactory createJmsConnectionFactory(ArtemisProperties properties, ListableBeanFactory beanFactory) { return new ArtemisConnectionFactoryFactory(beanFactory, properties) - .createConnectionFactory(ActiveMQConnectionFactory.class); + .createConnectionFactory(ActiveMQConnectionFactory::new, ActiveMQConnectionFactory::new); } @Configuration(proxyBeanMethods = false) @@ -89,7 +89,7 @@ static class PooledConnectionFactoryConfiguration { @Bean(destroyMethod = "stop") JmsPoolConnectionFactory jmsConnectionFactory(ListableBeanFactory beanFactory, ArtemisProperties properties) { ActiveMQConnectionFactory connectionFactory = new ArtemisConnectionFactoryFactory(beanFactory, properties) - .createConnectionFactory(ActiveMQConnectionFactory.class); + .createConnectionFactory(ActiveMQConnectionFactory::new, ActiveMQConnectionFactory::new); return new JmsPoolConnectionFactoryFactory(properties.getPool()) .createPooledConnectionFactory(connectionFactory); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java index 344f8ace981c..5221b37be53e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * 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. @@ -16,7 +16,7 @@ package org.springframework.boot.autoconfigure.jms.artemis; -import java.lang.reflect.Constructor; +import java.util.function.Function; import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.api.core.client.ActiveMQClient; @@ -56,10 +56,11 @@ class ArtemisConnectionFactoryFactory { this.properties = properties; } - T createConnectionFactory(Class factoryClass) { + T createConnectionFactory(Function nativeFactoryCreator, + Function embeddedFactoryCreator) { try { startEmbeddedJms(); - return doCreateConnectionFactory(factoryClass); + return doCreateConnectionFactory(nativeFactoryCreator, embeddedFactoryCreator); } catch (Exception ex) { throw new IllegalStateException("Unable to create ActiveMQConnectionFactory", ex); @@ -79,15 +80,16 @@ private void startEmbeddedJms() { } } - private T doCreateConnectionFactory(Class factoryClass) throws Exception { + private T doCreateConnectionFactory(Function nativeFactoryCreator, + Function embeddedFactoryCreator) throws Exception { ArtemisMode mode = this.properties.getMode(); if (mode == null) { mode = deduceMode(); } if (mode == ArtemisMode.EMBEDDED) { - return createEmbeddedConnectionFactory(factoryClass); + return createEmbeddedConnectionFactory(embeddedFactoryCreator); } - return createNativeConnectionFactory(factoryClass); + return createNativeConnectionFactory(nativeFactoryCreator); } /** @@ -110,13 +112,13 @@ private boolean isEmbeddedJmsClassPresent() { return false; } - private T createEmbeddedConnectionFactory(Class factoryClass) - throws Exception { + private T createEmbeddedConnectionFactory( + Function factoryCreator) throws Exception { try { TransportConfiguration transportConfiguration = new TransportConfiguration( InVMConnectorFactory.class.getName(), this.properties.getEmbedded().generateTransportParameters()); - ServerLocator serviceLocator = ActiveMQClient.createServerLocatorWithoutHA(transportConfiguration); - return factoryClass.getConstructor(ServerLocator.class).newInstance(serviceLocator); + ServerLocator serverLocator = ActiveMQClient.createServerLocatorWithoutHA(transportConfiguration); + return factoryCreator.apply(serverLocator); } catch (NoClassDefFoundError ex) { throw new IllegalStateException("Unable to create InVM " @@ -124,9 +126,8 @@ private T createEmbeddedConnectionFactory( } } - private T createNativeConnectionFactory(Class factoryClass) - throws Exception { - T connectionFactory = newNativeConnectionFactory(factoryClass); + private T createNativeConnectionFactory(Function factoryCreator) { + T connectionFactory = newNativeConnectionFactory(factoryCreator); String user = this.properties.getUser(); if (StringUtils.hasText(user)) { connectionFactory.setUser(user); @@ -135,12 +136,10 @@ private T createNativeConnectionFactory(Cl return connectionFactory; } - private T newNativeConnectionFactory(Class factoryClass) throws Exception { + private T newNativeConnectionFactory(Function factoryCreator) { String brokerUrl = StringUtils.hasText(this.properties.getBrokerUrl()) ? this.properties.getBrokerUrl() : DEFAULT_BROKER_URL; - Constructor constructor = factoryClass.getConstructor(String.class); - return constructor.newInstance(brokerUrl); - + return factoryCreator.apply(brokerUrl); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java index b296ae14b892..28d8792d1368 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -46,14 +46,14 @@ class ArtemisXAConnectionFactoryConfiguration { ConnectionFactory jmsConnectionFactory(ListableBeanFactory beanFactory, ArtemisProperties properties, XAConnectionFactoryWrapper wrapper) throws Exception { return wrapper.wrapConnectionFactory(new ArtemisConnectionFactoryFactory(beanFactory, properties) - .createConnectionFactory(ActiveMQXAConnectionFactory.class)); + .createConnectionFactory(ActiveMQXAConnectionFactory::new, ActiveMQXAConnectionFactory::new)); } @Bean ActiveMQXAConnectionFactory nonXaJmsConnectionFactory(ListableBeanFactory beanFactory, ArtemisProperties properties) { return new ArtemisConnectionFactoryFactory(beanFactory, properties) - .createConnectionFactory(ActiveMQXAConnectionFactory.class); + .createConnectionFactory(ActiveMQXAConnectionFactory::new, ActiveMQXAConnectionFactory::new); } } From 23ddad1ebd16488b4785fdbe1abf5faa939e3bf4 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 23 Sep 2024 21:55:56 -0700 Subject: [PATCH 016/213] Propagate `gradle.properties` to `buildSrc` Update `buildSrc` so that properties frok the root `gradle.properties` file are available. See gh-42333 --- buildSrc/build.gradle | 33 +++++++++++---------------------- buildSrc/gradle.properties | 1 - buildSrc/settings.gradle | 8 ++++++++ gradle.properties | 2 ++ 4 files changed, 21 insertions(+), 23 deletions(-) delete mode 100644 buildSrc/gradle.properties diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index d8b44f640c68..2d1755589c92 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -13,18 +13,7 @@ repositories { sourceCompatibility = 17 targetCompatibility = 17 -def versions = [:] -new File(projectDir.parentFile, "gradle.properties").withInputStream { - def properties = new Properties() - properties.load(it) - ["assertj", "commonsCodec", "hamcrest", "jackson", "junitJupiter", - "kotlin", "maven"].each { - versions[it] = properties[it + "Version"] - } -} -versions["springFramework"] = "6.0.12" -ext.set("versions", versions) -if (versions.springFramework.contains("-")) { +if ("${springFrameworkVersion}".contains("-")) { repositories { maven { url "https://repo.spring.io/milestone" } maven { url "https://repo.spring.io/snapshot" } @@ -32,32 +21,32 @@ if (versions.springFramework.contains("-")) { } checkstyle { - toolVersion = "10.12.4" + toolVersion = "{checkstyleToolVersion}" } dependencies { checkstyle("com.puppycrawl.tools:checkstyle:${checkstyle.toolVersion}") checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}") - implementation(platform("org.springframework:spring-framework-bom:${versions.springFramework}")) - implementation("com.fasterxml.jackson.core:jackson-databind:${versions.jackson}") + implementation(platform("org.springframework:spring-framework-bom:${springFrameworkVersion}")) + implementation("com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}") implementation("com.gradle:develocity-gradle-plugin:3.17.2") implementation("com.tngtech.archunit:archunit:1.3.0") - implementation("commons-codec:commons-codec:${versions.commonsCodec}") + implementation("commons-codec:commons-codec:${commonsCodecVersion}") implementation("de.undercouch.download:de.undercouch.download.gradle.plugin:5.5.0") implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}") - implementation("org.apache.maven:maven-embedder:${versions.maven}") + implementation("org.apache.maven:maven-embedder:${mavenVersion}") implementation("org.asciidoctor:asciidoctor-gradle-jvm:4.0.2") - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}") - implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${versions.kotlin}") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") + implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}") implementation("org.springframework:spring-context") implementation("org.springframework:spring-core") implementation("org.springframework:spring-web") implementation("io.spring.nohttp:nohttp-gradle:0.0.11") - testImplementation("org.assertj:assertj-core:${versions.assertj}") - testImplementation("org.hamcrest:hamcrest:${versions.hamcrest}") - testImplementation("org.junit.jupiter:junit-jupiter:${versions.junitJupiter}") + testImplementation("org.assertj:assertj-core:${assertjVersion}") + testImplementation("org.hamcrest:hamcrest:${hamcrestVersion}") + testImplementation("org.junit.jupiter:junit-jupiter:${junitJupiterVersion}") testImplementation("org.springframework:spring-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") diff --git a/buildSrc/gradle.properties b/buildSrc/gradle.properties deleted file mode 100644 index 2a3dd72a1830..000000000000 --- a/buildSrc/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -javaFormatVersion=0.0.43 diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle index 4cdfcbf3bd5a..a61376cc7dda 100644 --- a/buildSrc/settings.gradle +++ b/buildSrc/settings.gradle @@ -4,3 +4,11 @@ pluginManagement { gradlePluginPortal() } } + +gradle.rootProject((project) -> { + new File(rootDir.parentFile, "gradle.properties").withInputStream { + def properties = new Properties() + properties.load(it) + properties.forEach(project.ext::set) + } +}); diff --git a/gradle.properties b/gradle.properties index 679d01153780..4bb39ef5c38d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,10 +6,12 @@ org.gradle.parallel=true org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 assertjVersion=3.24.2 +checkstyleToolVersion=10.12.4 commonsCodecVersion=1.16.1 commonsCompressVersion=1.21 hamcrestVersion=2.2 jacksonVersion=2.15.4 +javaFormatVersion=0.0.43 junitJupiterVersion=5.10.3 kotlinVersion=1.9.25 mavenVersion=3.9.4 From eafe61c4ca40117cd7c88f696c5e8fb03976c0aa Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 24 Sep 2024 11:30:14 -0700 Subject: [PATCH 017/213] Backport upgrade to Gradle 8.10.1 Cherry-pick commits d756bf4e86, 083ac67d13 and 162c929a80 to upgrade to Gradle 8.10.1. Closes gh-42433 --- .github/workflows/ci.yml | 4 +- buildSrc/build.gradle | 8 ++- .../boot/build/KotlinConventions.java | 34 ++++++++++-- .../bom/bomr/InteractiveUpgradeResolver.java | 20 ++++--- .../boot/build/test/DockerTestPlugin.java | 2 + .../build/test/IntegrationTestPlugin.java | 4 +- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 ++- gradlew.bat | 2 + .../build.gradle | 2 + .../spring-boot-docs/build.gradle | 3 +- .../spring-boot-test/build.gradle | 1 + .../spring-boot-cli/build.gradle | 6 +-- .../plugin/ApplicationPluginAction.java | 32 +++--------- .../tasks/bundling/BootArchiveSupport.java | 49 +++++++++--------- .../tasks/bundling/BootZipCopyAction.java | 18 +++---- .../gradle/junit/GradleProjectBuilder.java | 17 +----- .../KotlinPluginActionIntegrationTests.java | 2 +- .../tasks/buildinfo/BuildInfoTests.java | 2 +- .../gradle/testkit/GradleVersions.java | 12 +++-- spring-boot-project/spring-boot/build.gradle | 1 + .../spring-boot-smoke-test-cache/build.gradle | 5 ++ 23 files changed, 129 insertions(+), 104 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19b437bf72ae..84730adbfb23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,9 +22,9 @@ jobs: - version: 17 toolchain: false - version: 21 - toolchain: true + toolchain: false - version: 22 - toolchain: true + toolchain: false - version: 23 toolchain: true exclude: diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 2d1755589c92..2ed0333fae7d 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -10,8 +10,11 @@ repositories { gradlePluginPortal() } -sourceCompatibility = 17 -targetCompatibility = 17 +java { + sourceCompatibility = 17 + targetCompatibility = 17 +} + if ("${springFrameworkVersion}".contains("-")) { repositories { @@ -34,6 +37,7 @@ dependencies { implementation("com.tngtech.archunit:archunit:1.3.0") implementation("commons-codec:commons-codec:${commonsCodecVersion}") implementation("de.undercouch.download:de.undercouch.download.gradle.plugin:5.5.0") + implementation("dev.adamko.dokkatoo:dokkatoo-plugin:2.3.1") implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}") implementation("org.apache.maven:maven-embedder:${mavenVersion}") implementation("org.asciidoctor:asciidoctor-gradle-jvm:4.0.2") diff --git a/buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java index c5e896160b47..1547d7015ee0 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java @@ -16,10 +16,15 @@ package org.springframework.boot.build; +import java.net.URI; import java.util.ArrayList; import java.util.List; +import dev.adamko.dokkatoo.DokkatooExtension; +import dev.adamko.dokkatoo.formats.DokkatooHtmlPlugin; import org.gradle.api.Project; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions; import org.jetbrains.kotlin.gradle.tasks.KotlinCompile; @@ -44,9 +49,10 @@ class KotlinConventions { void apply(Project project) { - project.getPlugins() - .withId("org.jetbrains.kotlin.jvm", - (plugin) -> project.getTasks().withType(KotlinCompile.class, this::configure)); + project.getPlugins().withId("org.jetbrains.kotlin.jvm", (plugin) -> { + project.getTasks().withType(KotlinCompile.class, this::configure); + project.getPlugins().withType(DokkatooHtmlPlugin.class, (dokkatooPlugin) -> configureDokkatoo(project)); + }); } private void configure(KotlinCompile compile) { @@ -60,4 +66,26 @@ private void configure(KotlinCompile compile) { compile.getKotlinOptions().setFreeCompilerArgs(freeCompilerArgs); } + private void configureDokkatoo(Project project) { + DokkatooExtension dokkatoo = project.getExtensions().getByType(DokkatooExtension.class); + dokkatoo.getDokkatooSourceSets().named(SourceSet.MAIN_SOURCE_SET_NAME).configure((sourceSet) -> { + 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")); + }); + }); + } + } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java index 840665d78d45..3778b177a638 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -16,6 +16,7 @@ package org.springframework.boot.build.bom.bomr; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -58,12 +59,17 @@ private Upgrade resolveUpgrade(LibraryWithVersionOptions libraryWithVersionOptio if (libraryWithVersionOptions.getVersionOptions().isEmpty()) { return null; } - VersionOption current = new VersionOption(libraryWithVersionOptions.getLibrary().getVersion().getVersion()); - VersionOption selected = this.userInputHandler.selectOption( - libraryWithVersionOptions.getLibrary().getName() + " " - + libraryWithVersionOptions.getLibrary().getVersion().getVersion(), - libraryWithVersionOptions.getVersionOptions(), current); - return (selected.equals(current)) ? null + VersionOption defaultOption = new VersionOption( + libraryWithVersionOptions.getLibrary().getVersion().getVersion()); + VersionOption selected = this.userInputHandler.askUser((questions) -> { + String question = libraryWithVersionOptions.getLibrary().getName() + " " + + libraryWithVersionOptions.getLibrary().getVersion().getVersion(); + List options = new ArrayList<>(); + options.add(defaultOption); + options.addAll(libraryWithVersionOptions.getVersionOptions()); + return questions.selectOption(question, options, defaultOption); + }).get(); + return (selected.equals(defaultOption)) ? null : new Upgrade(libraryWithVersionOptions.getLibrary(), selected.getVersion()); } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java index b2970b2ed240..6c33d0fb9a82 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/test/DockerTestPlugin.java @@ -71,6 +71,8 @@ private void configureDockerTesting(Project project) { .add(project.getConfigurations() .getByName(dockerTestSourceSet.getRuntimeClasspathConfigurationName()))); }); + project.getDependencies() + .add(dockerTestSourceSet.getRuntimeOnlyConfigurationName(), "org.junit.platform:junit-platform-launcher"); } private SourceSet createSourceSet(Project project) { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/test/IntegrationTestPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/test/IntegrationTestPlugin.java index 6286df7601cf..34ff445b8899 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/test/IntegrationTestPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/test/IntegrationTestPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * 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. @@ -58,6 +58,8 @@ private void configureIntegrationTesting(Project project) { 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) { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 34592 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 delta 34555 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3e593191a337..0aaefbcaf0f1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a426907..f5feea6d6b11 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30dbdeee..9d21a21834d5 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index 775bb2faa3e2..322f803967c6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -235,6 +235,8 @@ tasks.named("test") { } def documentationTest = tasks.register("documentationTest", Test) { + testClassesDirs = testing.suites.test.sources.output.classesDirs + classpath = testing.suites.test.sources.runtimeClasspath filter { includeTestsMatching("org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation.*") } diff --git a/spring-boot-project/spring-boot-docs/build.gradle b/spring-boot-project/spring-boot-docs/build.gradle index 1715916ef0a3..c58c4339b4cd 100644 --- a/spring-boot-project/spring-boot-docs/build.gradle +++ b/spring-boot-project/spring-boot-docs/build.gradle @@ -1,4 +1,5 @@ plugins { + id "dev.adamko.dokkatoo-html" id "java" id "org.asciidoctor.jvm.convert" id "org.springframework.boot.conventions" @@ -47,7 +48,7 @@ sourcesJar { } plugins.withType(EclipsePlugin) { - extensions.getByType(org.gradle.plugins.ide.eclipse.model.EclipseModel).classpath { classpath -> + eclipse.classpath { classpath -> classpath.plusConfigurations.add(configurations.getByName(sourceSets.main.runtimeClasspathConfigurationName)) } } diff --git a/spring-boot-project/spring-boot-test/build.gradle b/spring-boot-project/spring-boot-test/build.gradle index 3094feadbde3..9ed2542ab352 100644 --- a/spring-boot-project/spring-boot-test/build.gradle +++ b/spring-boot-project/spring-boot-test/build.gradle @@ -1,4 +1,5 @@ plugins { + id "dev.adamko.dokkatoo-html" id "java-library" id "org.jetbrains.kotlin.jvm" id "org.springframework.boot.conventions" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-cli/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-cli/build.gradle index bf42c6816213..90d2420d6c1f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-cli/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-cli/build.gradle @@ -82,11 +82,11 @@ def configureArchive(archive) { into "lib/" } archive.from(file("src/main/content")) { - dirMode = 0755 - fileMode = 0644 + dirPermissions { unix(0755) } + filePermissions { unix(0644) } } archive.from(file("src/main/executablecontent")) { - fileMode = 0755 + filePermissions { unix(0755) } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java index 62874380d3e5..b767735ed3be 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java @@ -19,10 +19,8 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.StringWriter; -import java.lang.reflect.Method; import java.util.concurrent.Callable; -import org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; @@ -128,32 +126,16 @@ private String loadResource(String name) { private void configureFilePermissions(CopySpec copySpec, int mode) { if (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) { - try { - Method filePermissions = copySpec.getClass().getMethod("filePermissions", Action.class); - filePermissions.invoke(copySpec, new Action() { - - @Override - public void execute(Object filePermissions) { - String unixPermissions = Integer.toString(mode, 8); - try { - Method unix = filePermissions.getClass().getMethod("unix", String.class); - unix.invoke(filePermissions, unixPermissions); - } - catch (Exception ex) { - throw new GradleException("Failed to set file permissions to '" + unixPermissions + "'", - ex); - } - } - - }); - } - catch (Exception ex) { - throw new GradleException("Failed to set file permissions", ex); - } + copySpec.filePermissions((filePermissions) -> filePermissions.unix(Integer.toString(mode, 8))); } else { - copySpec.setFileMode(mode); + configureFileMode(copySpec, mode); } } + @SuppressWarnings("deprecation") + private void configureFileMode(CopySpec copySpec, int mode) { + copySpec.setFileMode(mode); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java index e90a6335b522..57da07239506 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java @@ -26,9 +26,8 @@ import java.util.Set; import java.util.TreeMap; import java.util.function.Function; -import java.util.function.Supplier; -import org.gradle.api.GradleException; +import org.gradle.api.file.ConfigurableFilePermissions; import org.gradle.api.file.CopySpec; import org.gradle.api.file.FileCopyDetails; import org.gradle.api.file.FileTreeElement; @@ -133,8 +132,8 @@ CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, File output = jar.getArchiveFile().get().getAsFile(); Manifest manifest = jar.getManifest(); boolean preserveFileTimestamps = jar.isPreserveFileTimestamps(); - Integer dirMode = getDirMode(jar); - Integer fileMode = getFileMode(jar); + Integer dirPermissions = getUnixNumericDirPermissions(jar); + Integer filePermissions = getUnixNumericFilePermissions(jar); boolean includeDefaultLoader = isUsingDefaultLoader(jar); Spec requiresUnpack = this.requiresUnpack.getAsSpec(); Spec exclusions = this.exclusions.getAsExcludeSpec(); @@ -142,35 +141,35 @@ CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, Spec librarySpec = this.librarySpec; Function compressionResolver = this.compressionResolver; String encoding = jar.getMetadataCharset(); - CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirMode, fileMode, - includeDefaultLoader, layerToolsLocation, requiresUnpack, exclusions, launchScript, librarySpec, - compressionResolver, encoding, resolvedDependencies, supportsSignatureFile, layerResolver, + CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirPermissions, + filePermissions, includeDefaultLoader, layerToolsLocation, requiresUnpack, exclusions, launchScript, + librarySpec, compressionResolver, encoding, resolvedDependencies, supportsSignatureFile, layerResolver, loaderImplementation); return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action; } - private Integer getDirMode(CopySpec copySpec) { - return getMode(copySpec, "getDirPermissions", () -> copySpec.getDirMode()); + private Integer getUnixNumericDirPermissions(CopySpec copySpec) { + return (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) + ? asUnixNumeric(copySpec.getDirPermissions()) : getDirMode(copySpec); } - private Integer getFileMode(CopySpec copySpec) { - return getMode(copySpec, "getFilePermissions", () -> copySpec.getFileMode()); + private Integer getUnixNumericFilePermissions(CopySpec copySpec) { + return (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) + ? asUnixNumeric(copySpec.getFilePermissions()) : getFileMode(copySpec); } - @SuppressWarnings("unchecked") - private Integer getMode(CopySpec copySpec, String methodName, Supplier fallback) { - if (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) { - try { - Object filePermissions = ((Property) copySpec.getClass().getMethod(methodName).invoke(copySpec)) - .getOrNull(); - return (filePermissions != null) - ? (int) filePermissions.getClass().getMethod("toUnixNumeric").invoke(filePermissions) : null; - } - catch (Exception ex) { - throw new GradleException("Failed to get permissions", ex); - } - } - return fallback.get(); + private Integer asUnixNumeric(Property permissions) { + return permissions.isPresent() ? permissions.get().toUnixNumeric() : null; + } + + @SuppressWarnings("deprecation") + private Integer getDirMode(CopySpec copySpec) { + return copySpec.getDirMode(); + } + + @SuppressWarnings("deprecation") + private Integer getFileMode(CopySpec copySpec) { + return copySpec.getFileMode(); } private boolean isUsingDefaultLoader(Jar jar) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java index 780944145dc2..ce8cf3616090 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java @@ -22,7 +22,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; -import java.lang.reflect.Method; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.Collection; @@ -488,17 +487,12 @@ private int getFileMode(FileCopyDetails details) { } private int getPermissions(FileCopyDetails details) { - if (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) { - try { - Method getPermissionsMethod = details.getClass().getMethod("getPermissions"); - getPermissionsMethod.setAccessible(true); - Object permissions = getPermissionsMethod.invoke(details); - return (int) permissions.getClass().getMethod("toUnixNumeric").invoke(permissions); - } - catch (Exception ex) { - throw new GradleException("Failed to get permissions", ex); - } - } + return (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) + ? details.getPermissions().toUnixNumeric() : getMode(details); + } + + @SuppressWarnings("deprecation") + private int getMode(FileCopyDetails details) { return details.getMode(); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleProjectBuilder.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleProjectBuilder.java index 9f7a01053abf..1f63d8677122 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleProjectBuilder.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleProjectBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * 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. @@ -18,22 +18,17 @@ import java.io.File; -import org.gradle.api.JavaVersion; import org.gradle.api.Project; -import org.gradle.internal.nativeintegration.services.NativeServices; import org.gradle.testfixtures.ProjectBuilder; -import org.gradle.testfixtures.internal.ProjectBuilderImpl; 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} in order to work around an issue - * on JDK 17 and 18. + * functionality of Gradle's own {@link ProjectBuilder}. * * @author Christoph Dreis - * @see Gradle Support JDK 17 */ public final class GradleProjectBuilder { @@ -67,14 +62,6 @@ public Project build() { if (StringUtils.hasText(this.name)) { builder.withName(this.name); } - if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) { - NativeServices.initializeOnClient(userHome); - try { - ProjectBuilderImpl.getGlobalServices(); - } - catch (Throwable ignore) { - } - } return builder.build(); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java index b3978b47f705..0c62564ce8e7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java @@ -83,7 +83,7 @@ void taskConfigurationIsAvoided() throws IOException { configured.add(line.substring("Configuring :".length())); } } - assertThat(configured).containsExactlyInAnyOrder("help", "clean"); + assertThat(configured).containsExactlyInAnyOrder("help", "compileJava", "clean"); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoTests.java index 538c385418c2..d5143117e7e0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoTests.java @@ -176,7 +176,7 @@ private Project createProject(String projectName) { Project project = GradleProjectBuilder.builder().withProjectDir(projectDir).withName(projectName).build(); ((ProjectInternal) project).getServices() .get(GradlePropertiesController.class) - .loadGradlePropertiesFrom(projectDir); + .loadGradlePropertiesFrom(projectDir, false); return project; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java index c21c84badac7..17998b002806 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java @@ -33,10 +33,16 @@ private GradleVersions() { } public static List allCompatible() { - if (isJavaVersion(JavaVersion.VERSION_20)) { - return Arrays.asList("8.1.1", "8.10"); + if (isJavaVersion(JavaVersion.VERSION_23)) { + return Arrays.asList(GradleVersion.current().getVersion()); } - return Arrays.asList("7.5.1", GradleVersion.current().getVersion(), "8.0.2", "8.10"); + if (isJavaVersion(JavaVersion.VERSION_22)) { + return Arrays.asList("8.8", GradleVersion.current().getVersion()); + } + if (isJavaVersion(JavaVersion.VERSION_21)) { + return Arrays.asList("8.5", GradleVersion.current().getVersion()); + } + return Arrays.asList("7.6.4", "8.3", GradleVersion.current().getVersion()); } public static String minimumCompatible() { diff --git a/spring-boot-project/spring-boot/build.gradle b/spring-boot-project/spring-boot/build.gradle index 0e2f235e55b7..416a0f2d817f 100644 --- a/spring-boot-project/spring-boot/build.gradle +++ b/spring-boot-project/spring-boot/build.gradle @@ -1,4 +1,5 @@ plugins { + id "dev.adamko.dokkatoo-html" id "java-library" id "org.jetbrains.kotlin.jvm" id "org.springframework.boot.conventions" diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/build.gradle index db06fa7c815c..8d77282a0be0 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/build.gradle @@ -67,28 +67,33 @@ dependencies { def testCaffeine = tasks.register("testCaffeine", Test) { description = "Runs the tests against Caffeine" classpath = sourceSets.test.runtimeClasspath + configurations.caffeine + testClassesDirs = testing.suites.test.sources.output.classesDirs } def testCouchbase = tasks.register("testCouchbase", Test) { description = "Runs the tests against Couchbase" classpath = sourceSets.test.runtimeClasspath + configurations.couchbase + testClassesDirs = testing.suites.test.sources.output.classesDirs } def testEhcache = tasks.register("testEhcache", Test) { description = "Runs the tests against Ehcache" classpath = sourceSets.test.runtimeClasspath + configurations.ehcache + testClassesDirs = testing.suites.test.sources.output.classesDirs systemProperties = ["spring.cache.jcache.config" : "classpath:ehcache3.xml"] } def testHazelcast = tasks.register("testHazelcast", Test) { description = "Runs the tests against Hazelcast" classpath = sourceSets.test.runtimeClasspath + configurations.hazelcast + testClassesDirs = testing.suites.test.sources.output.classesDirs } def testInfinispan = tasks.register("testInfinispan", Test) { enabled = (toolchain.javaVersion == null || toolchain.javaVersion.asInt() < 23) description = "Runs the tests against Infinispan" classpath = sourceSets.test.runtimeClasspath + configurations.infinispan + testClassesDirs = testing.suites.test.sources.output.classesDirs systemProperties = ["spring.cache.jcache.config" : "classpath:infinispan.xml"] } From 54df1edf8cfd669336d7f6d2579d16fba4461e50 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 24 Sep 2024 13:22:44 -0700 Subject: [PATCH 018/213] Upgrade to Gradle 8.10.2 Closes gh-42435 --- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 188 +++++++++++------------ 2 files changed, 95 insertions(+), 95 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0aaefbcaf0f1..df97d72b8b91 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 9d21a21834d5..9b42019c7915 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,94 +1,94 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle 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=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle 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=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From e210444c688ddccbd9c31d24055b402fa654e017 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 24 Sep 2024 13:25:09 -0700 Subject: [PATCH 019/213] Fix `KotlinConventions` merge error See gh-42433 --- .../boot/build/KotlinConventions.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java index 38485832189f..f87f576c68b2 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/KotlinConventions.java @@ -21,6 +21,7 @@ import java.util.List; import dev.adamko.dokkatoo.DokkatooExtension; +import dev.adamko.dokkatoo.formats.DokkatooHtmlPlugin; import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; @@ -28,8 +29,8 @@ 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: + * Conventions that are applied in the presence of the {@code org.jetbrains.kotlin.jvm} + * plugin. When the plugin is applied: * *