From c8c04b4652b41df38e2f8abab9c37a8327a60a10 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 2 Jul 2024 10:06:59 +0100 Subject: [PATCH] Reinstate Maven support Closes gh-81 --- README.md | 27 +- .../build.gradle | 91 +++++ .../core}/BuildCacheConventions.java | 24 +- .../core}/BuildScanConventions.java | 62 ++-- .../core/ConfigurableBuildCache.java | 76 +++++ .../core/ConfigurableBuildScan.java | 96 ++++++ .../core/ConfigurableDevelocity.java | 38 +++ .../core}/ContinuousIntegration.java | 4 +- .../ge/conventions/core}/ProcessRunner.java | 14 +- .../ge/conventions/core/package-info.java | 20 ++ .../core/BuildCacheConventionsTests.java | 171 ++++++++++ .../core}/BuildScanConventionsTests.java | 216 ++++++++---- .../conventions/core}/TestProcessRunner.java | 4 +- .../build.gradle | 10 +- ...nymousPublicationBuildScanConventions.java | 19 +- .../gradle/GradleConfigurableBuildCache.java | 93 +++++ .../gradle/GradleConfigurableBuildScan.java | 98 ++++++ .../gradle/GradleConfigurableDevelocity.java | 45 +++ .../GradleEnterpriseConventionsPlugin.java | 14 +- .../ProcessOperationsProcessRunner.java | 4 +- .../WorkingDirectoryProcessOperations.java | 2 +- .../ge/conventions/gradle/package-info.java | 4 +- ...sPublicationBuildScanConventionsTests.java | 6 +- .../GradleConfigurableBuildCacheTests.java | 124 +++++++ .../GradleConfigurableBuildScanTests.java | 93 +++++ ...riseConventionsPluginIntegrationTests.java | 11 +- .../gradle/TestBuildScanConfiguration.java | 4 +- .../gradle/TestDevelocityConfiguration.java | 2 +- .../conventions/gradle/TestProcessRunner.java | 68 ++++ .../ge/conventions/gradle/TestProperty.java | 2 +- .../build.gradle | 105 ++++++ .../maven/ConventionsDevelocityListener.java | 43 +++ .../maven/MavenConfigurableBuildCache.java | 87 +++++ .../maven/MavenConfigurableBuildScan.java | 84 +++++ .../maven/MavenConfigurableDevelocity.java | 45 +++ .../maven/ProcessBuilderProcessRunner.java | 83 +++++ .../ge/conventions/maven/package-info.java | 20 ++ .../resources/META-INF/plexus/components.xml | 10 + .../MavenConfigurableBuildCacheTests.java | 246 ++++++++++++++ .../MavenConfigurableBuildScanTests.java | 319 ++++++++++++++++++ settings.gradle | 6 +- .../gradle/BuildCacheConventionsTests.java | 184 ---------- 42 files changed, 2323 insertions(+), 351 deletions(-) create mode 100644 gradle-enterprise-conventions-core/build.gradle rename {src/main/java/io/spring/ge/conventions/gradle => gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core}/BuildCacheConventions.java (72%) rename {src/main/java/io/spring/ge/conventions/gradle => gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core}/BuildScanConventions.java (69%) create mode 100644 gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ConfigurableBuildCache.java create mode 100644 gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ConfigurableBuildScan.java create mode 100644 gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ConfigurableDevelocity.java rename {src/main/java/io/spring/ge/conventions/gradle => gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core}/ContinuousIntegration.java (95%) rename {src/main/java/io/spring/ge/conventions/gradle => gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core}/ProcessRunner.java (90%) create mode 100644 gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/package-info.java create mode 100644 gradle-enterprise-conventions-core/src/test/java/io/spring/ge/conventions/core/BuildCacheConventionsTests.java rename {src/test/java/io/spring/ge/conventions/gradle => gradle-enterprise-conventions-core/src/test/java/io/spring/ge/conventions/core}/BuildScanConventionsTests.java (59%) rename {src/test/java/io/spring/ge/conventions/gradle => gradle-enterprise-conventions-core/src/test/java/io/spring/ge/conventions/core}/TestProcessRunner.java (95%) rename build.gradle => gradle-enterprise-conventions-gradle-plugin/build.gradle (93%) rename {src => gradle-enterprise-conventions-gradle-plugin/src}/main/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventions.java (57%) create mode 100644 gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildCache.java create mode 100644 gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildScan.java create mode 100644 gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleConfigurableDevelocity.java rename {src => gradle-enterprise-conventions-gradle-plugin/src}/main/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPlugin.java (85%) rename {src => gradle-enterprise-conventions-gradle-plugin/src}/main/java/io/spring/ge/conventions/gradle/ProcessOperationsProcessRunner.java (92%) rename {src => gradle-enterprise-conventions-gradle-plugin/src}/main/java/io/spring/ge/conventions/gradle/WorkingDirectoryProcessOperations.java (96%) rename {src => gradle-enterprise-conventions-gradle-plugin/src}/main/java/io/spring/ge/conventions/gradle/package-info.java (82%) rename {src => gradle-enterprise-conventions-gradle-plugin/src}/test/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventionsTests.java (79%) create mode 100644 gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildCacheTests.java create mode 100644 gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildScanTests.java rename {src => gradle-enterprise-conventions-gradle-plugin/src}/test/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPluginIntegrationTests.java (94%) rename {src => gradle-enterprise-conventions-gradle-plugin/src}/test/java/io/spring/ge/conventions/gradle/TestBuildScanConfiguration.java (99%) rename {src => gradle-enterprise-conventions-gradle-plugin/src}/test/java/io/spring/ge/conventions/gradle/TestDevelocityConfiguration.java (97%) create mode 100644 gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestProcessRunner.java rename {src => gradle-enterprise-conventions-gradle-plugin/src}/test/java/io/spring/ge/conventions/gradle/TestProperty.java (97%) create mode 100644 gradle-enterprise-conventions-maven-extension/build.gradle create mode 100644 gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/ConventionsDevelocityListener.java create mode 100644 gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/MavenConfigurableBuildCache.java create mode 100644 gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/MavenConfigurableBuildScan.java create mode 100644 gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/MavenConfigurableDevelocity.java create mode 100644 gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/ProcessBuilderProcessRunner.java create mode 100644 gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/package-info.java create mode 100644 gradle-enterprise-conventions-maven-extension/src/main/resources/META-INF/plexus/components.xml create mode 100644 gradle-enterprise-conventions-maven-extension/src/test/java/io/spring/ge/conventions/maven/MavenConfigurableBuildCacheTests.java create mode 100644 gradle-enterprise-conventions-maven-extension/src/test/java/io/spring/ge/conventions/maven/MavenConfigurableBuildScanTests.java delete mode 100644 src/test/java/io/spring/ge/conventions/gradle/BuildCacheConventionsTests.java diff --git a/README.md b/README.md index f27dab2..f510fa8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Gradle Enterprise Conventions -Conventions for Gradle projects that use the Gradle Enterprise instance hosted at [ge.spring.io](https://ge.spring.io). +Conventions for Maven and Gradle projects that use the Gradle Enterprise instance hosted at [ge.spring.io](https://ge.spring.io). ## Build cache conventions @@ -26,7 +26,9 @@ When applied alongside the [Develocity Plugin](https://plugins.gradle.org/plugin The build scans will be customized to: - Add tags: - - `JDK-`, where `` is the value of the `toolchainVersion` project property or, when not set, the specification version of the JDK running the build. + - `JDK-`. + When using Maven, `` is the specification version of the JDK running the build. + When using Gradle, `` is the value of the `toolchainVersion` project property or, when not set, it's the specification version of the JDK running the build. - `CI` or `Local` depending on where the build is executing. - `dirty` if the git working copy is dirty. - Name of the git branch being built. @@ -96,6 +98,8 @@ Jenkins is detected by looking for an environment variable named `JENKINS_URL`. Releases of the conventions are published to Maven Central. Snapshots are published to https://repo.spring.io/snapshot. +### Gradle + The first step in using the conventions is to make the necessary repository available for plugin resolution. This is done by configuring a plugin management repository in `settings.gradle`, as shown in the following example: @@ -120,3 +124,22 @@ plugins { // … } ``` + +### Maven + +To use the conventions, create a `.mvn/extensions.xml` file in the root of the project: + +```xml + + + + io.spring.ge.conventions + gradle-enterprise-conventions-maven-extension + <> + + +``` + +Any existing `.mvn/gradle-enterprise.xml` file should be deleted in favor of the configuration that's provided by the conventions. +Lastly, add `.mvn/.develocity/` to the project's `.gitignore` file. +The conventions are ready to use. diff --git a/gradle-enterprise-conventions-core/build.gradle b/gradle-enterprise-conventions-core/build.gradle new file mode 100644 index 0000000..264c0b9 --- /dev/null +++ b/gradle-enterprise-conventions-core/build.gradle @@ -0,0 +1,91 @@ +plugins { + id "checkstyle" + id "io.spring.javaformat" version "$javaFormatVersion" + id "java" + id "maven-publish" +} + +description = "Gradle Enterprise Conventions Core" +group = 'io.spring.ge.conventions' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}") + + testImplementation("org.assertj:assertj-core:3.24.2") + testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") + testImplementation("org.mockito:mockito-core:4.11.0") +} + +test { + useJUnitPlatform() +} + +java { + withJavadocJar() + withSourcesJar() +} + +checkstyle { + def archive = configurations.checkstyle.filter { it.name.startsWith("spring-javaformat-checkstyle")} + config = resources.text.fromArchiveEntry(archive, "io/spring/javaformat/checkstyle/checkstyle.xml") + toolVersion = 9.3 +} + +tasks.withType(GenerateModuleMetadata).all { + enabled = false +} + +if (project.hasProperty("distributionRepository")) { + publishing { + repositories { + maven { + url = "${distributionRepository}" + name = "deployment" + } + } + } +} + +publishing { + publications { + maven(MavenPublication) { + from components.java + } + withType(MavenPublication) { mavenPublication -> + pom { + name = project.description + description = project.description + url = 'https://github.com/spring-io/gradle-enterprise-conventions' + organization { + name = 'Pivotal Software, Inc.' + url = 'https://spring.io' + } + licenses { + license { + name = 'The Apache Software License, Version 2.0' + url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + scm { + url = 'https://github.com/spring-io/gradle-enterprise-conventions' + connection = 'scm:git:https://github.com/spring-io/gradle-enterprise-conventions' + } + developers { + developer { + id = 'wilkinsona' + name = 'Andy Wilkinson' + email = 'awilkinson@pivotal.io' + roles = ["Project lead"] + } + } + } + } + } +} diff --git a/src/main/java/io/spring/ge/conventions/gradle/BuildCacheConventions.java b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/BuildCacheConventions.java similarity index 72% rename from src/main/java/io/spring/ge/conventions/gradle/BuildCacheConventions.java rename to gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/BuildCacheConventions.java index 46fbc65..379a147 100644 --- a/src/main/java/io/spring/ge/conventions/gradle/BuildCacheConventions.java +++ b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/BuildCacheConventions.java @@ -14,13 +14,10 @@ * limitations under the License. */ -package io.spring.ge.conventions.gradle; +package io.spring.ge.conventions.core; import java.util.Map; -import com.gradle.develocity.agent.gradle.buildcache.DevelocityBuildCache; -import org.gradle.caching.configuration.BuildCacheConfiguration; - /** * Conventions that are applied to the build cache. * @@ -30,25 +27,22 @@ public class BuildCacheConventions { private final Map env; - private final Class buildCacheType; - - public BuildCacheConventions(Class buildCache) { - this(buildCache, System.getenv()); + public BuildCacheConventions() { + this(System.getenv()); } - BuildCacheConventions(Class buildCacheType, Map env) { + BuildCacheConventions(Map env) { this.env = env; - this.buildCacheType = buildCacheType; } /** * Applies the conventions to the given {@code buildCache}. * @param buildCache build cache to be configured */ - public void execute(BuildCacheConfiguration buildCache) { - buildCache.local((local) -> local.setEnabled(true)); - buildCache.remote(this.buildCacheType, (remote) -> { - remote.setEnabled(true); + public void execute(ConfigurableBuildCache buildCache) { + buildCache.local((local) -> local.enable()); + buildCache.remote((remote) -> { + remote.enable(); String cacheServer = this.env.get("DEVELOCITY_CACHE_SERVER"); if (cacheServer == null) { cacheServer = serverOfCacheUrl(this.env.get("GRADLE_ENTERPRISE_CACHE_URL")); @@ -62,7 +56,7 @@ public void execute(BuildCacheConfiguration buildCache) { accessKey = this.env.get("GRADLE_ENTERPRISE_ACCESS_KEY"); } if (hasText(accessKey) && ContinuousIntegration.detect(this.env) != null) { - remote.setPush(true); + remote.enablePush(); } }); } diff --git a/src/main/java/io/spring/ge/conventions/gradle/BuildScanConventions.java b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/BuildScanConventions.java similarity index 69% rename from src/main/java/io/spring/ge/conventions/gradle/BuildScanConventions.java rename to gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/BuildScanConventions.java index dc80cfa..dcff6d4 100644 --- a/src/main/java/io/spring/ge/conventions/gradle/BuildScanConventions.java +++ b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/BuildScanConventions.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.spring.ge.conventions.gradle; +package io.spring.ge.conventions.core; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; @@ -23,76 +23,72 @@ import java.util.function.Consumer; import java.util.stream.Collectors; -import com.gradle.develocity.agent.gradle.DevelocityConfiguration; -import com.gradle.develocity.agent.gradle.scan.BuildScanConfiguration; -import io.spring.ge.conventions.gradle.ProcessRunner.RunFailedException; -import org.gradle.api.Action; +import io.spring.ge.conventions.core.ProcessRunner.RunFailedException; /** - * {@link Action} that configures the {@link BuildScanConfiguration build scan} with - * Spring conventions. + * Conventions that are applied to build scans for Maven and Gradle builds. Spring + * conventions. * * @author Andy Wilkinson */ -class BuildScanConventions implements Action { - - private final DevelocityConfiguration develocity; +public class BuildScanConventions { private final ProcessRunner processRunner; private final Map env; - BuildScanConventions(DevelocityConfiguration develocity, ProcessRunner processRunner) { - this(develocity, processRunner, System.getenv()); + public BuildScanConventions(ProcessRunner processRunner) { + this(processRunner, System.getenv()); } - BuildScanConventions(DevelocityConfiguration develocity, ProcessRunner processRunner, Map env) { - this.develocity = develocity; + protected BuildScanConventions(ProcessRunner processRunner, Map env) { this.processRunner = processRunner; this.env = env; } /** - * Applies the conventions to the given {@code buildScan}. + * Applies the conventions to the given {@code develocity} and {@code buildScan}. + * @param develocity develocity to be configured * @param buildScan build scan to be configured */ - @Override - public void execute(BuildScanConfiguration buildScan) { + public void execute(ConfigurableDevelocity develocity, ConfigurableBuildScan buildScan) { buildScan.obfuscation((obfuscation) -> obfuscation .ipAddresses((addresses) -> addresses.stream().map((address) -> "0.0.0.0").collect(Collectors.toList()))); - configurePublishing(buildScan); + configurePublishing(develocity, buildScan); ContinuousIntegration ci = ContinuousIntegration.detect(this.env); tagBuildScan(buildScan, ci); - buildScan.background(this::addGitMetadata); + buildScan.background((backgrounded) -> addGitMetadata(develocity, backgrounded)); buildScan.background(this::addDockerMetadata); buildScan.background(this::addDockerComposeMetadata); addCiMetadata(buildScan, ci); - buildScan.getUploadInBackground().set(ci == null); - buildScan.capture((settings) -> settings.getFileFingerprints().set(true)); + buildScan.uploadInBackground(ci == null); + buildScan.captureInputFiles(true); } /** * Configures publishing of the build scan. The default implementation always * publishes scans when authenticated and publishes them to * {@code https://ge.spring.io}. + * @param develocity develocity to configure * @param buildScan build scan to configure + * */ - protected void configurePublishing(BuildScanConfiguration buildScan) { - buildScan.publishing((publishing) -> publishing.onlyIf((context) -> context.isAuthenticated())); - this.develocity.getServer().set("https://ge.spring.io"); + protected void configurePublishing(ConfigurableDevelocity develocity, ConfigurableBuildScan buildScan) { + buildScan.publishIfAuthenticated(); + develocity.setServer("https://ge.spring.io"); } - private void tagBuildScan(BuildScanConfiguration buildScan, ContinuousIntegration ci) { + private void tagBuildScan(ConfigurableBuildScan buildScan, ContinuousIntegration ci) { tagCiOrLocal(buildScan, ci); tagJdk(buildScan); tagOperatingSystem(buildScan); } - private void tagCiOrLocal(BuildScanConfiguration buildScan, ContinuousIntegration ci) { + private void tagCiOrLocal(ConfigurableBuildScan buildScan, ContinuousIntegration ci) { buildScan.tag((ci != null) ? "CI" : "Local"); } - private void tagJdk(BuildScanConfiguration buildScan) { + private void tagJdk(ConfigurableBuildScan buildScan) { buildScan.tag("JDK-" + getJdkVersion()); } @@ -100,16 +96,16 @@ protected String getJdkVersion() { return System.getProperty("java.specification.version"); } - private void tagOperatingSystem(BuildScanConfiguration buildScan) { + private void tagOperatingSystem(ConfigurableBuildScan buildScan) { buildScan.tag(System.getProperty("os.name")); } - private void addGitMetadata(BuildScanConfiguration buildScan) { + private void addGitMetadata(ConfigurableDevelocity develocity, ConfigurableBuildScan buildScan) { run("git", "rev-parse", "--short=8", "--verify", "HEAD").standardOut((gitCommitId) -> { String commitIdLabel = "Git commit"; buildScan.value(commitIdLabel, gitCommitId); - String server = this.develocity.getServer().getOrNull(); + String server = develocity.getServer(); if (server != null) { buildScan.link("Git commit build scans", server + createSearchUrl(commitIdLabel, gitCommitId)); } @@ -124,16 +120,16 @@ private void addGitMetadata(BuildScanConfiguration buildScan) { }); } - private void addDockerMetadata(BuildScanConfiguration buildScan) { + private void addDockerMetadata(ConfigurableBuildScan buildScan) { run("docker", "--version").standardOut((dockerVersion) -> buildScan.value("Docker", dockerVersion)); } - private void addDockerComposeMetadata(BuildScanConfiguration buildScan) { + private void addDockerComposeMetadata(ConfigurableBuildScan buildScan) { run("docker", "compose", "version") .standardOut((dockerComposeVersion) -> buildScan.value("Docker Compose", dockerComposeVersion)); } - private void addCiMetadata(BuildScanConfiguration buildScan, ContinuousIntegration ci) { + private void addCiMetadata(ConfigurableBuildScan buildScan, ContinuousIntegration ci) { if (ci == null) { return; } diff --git a/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ConfigurableBuildCache.java b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ConfigurableBuildCache.java new file mode 100644 index 0000000..409c0c9 --- /dev/null +++ b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ConfigurableBuildCache.java @@ -0,0 +1,76 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.core; + +import java.util.function.Consumer; + +/** + * A build cache that can be configured. Provides a build-system agnostic API that can be + * used with both Gradle and Maven builds. + * + * @author Andy Wilkinson + */ +public interface ConfigurableBuildCache { + + /** + * Configures the local build cache. + * @param local a consumer that is called to configure the {@link LocalBuildCache} + */ + void local(Consumer local); + + /** + * Configures the remote build cache. + * @param remote a consumer that is called to configure the {@link RemoteBuildCache} + */ + void remote(Consumer remote); + + /** + * Configuration for the local build cache. + */ + interface LocalBuildCache { + + /** + * Enables the local build cache. + */ + void enable(); + + } + + /** + * Configuration for the remote build cache. + */ + interface RemoteBuildCache { + + /** + * Enables the remote build cache. + */ + void enable(); + + /** + * Enables the pushing of entries to the remote build cache. + */ + void enablePush(); + + /** + * Sets the server that's hosting the remote build cache. + * @param server remote cache's server + */ + void setServer(String server); + + } + +} diff --git a/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ConfigurableBuildScan.java b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ConfigurableBuildScan.java new file mode 100644 index 0000000..c65da26 --- /dev/null +++ b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ConfigurableBuildScan.java @@ -0,0 +1,96 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.core; + +import java.net.InetAddress; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * A build scan that can be configured. Provides a build-system agnostic API that can be + * used with both Gradle and Maven builds. + * + * @author Andy Wilkinson + */ +public interface ConfigurableBuildScan { + + /** + * Configures whether to capture input files. + * @param capture {@code true} if input files for Gradle tasks or Maven goals should + * be captured, otherwise {@code false}. + */ + void captureInputFiles(boolean capture); + + /** + * Configures obfuscation of data in the build scan. + * @param configurer called to configure the obfuscation + */ + void obfuscation(Consumer configurer); + + /** + * Configures the build scan to only be published when authenticated. + */ + void publishIfAuthenticated(); + + /** + * Configures whether to upload the build scan in the background. + * @param enabled {@code true} to use background uploads, otherwise {@code false}. + */ + void uploadInBackground(boolean enabled); + + /** + * Adds a link with the given {@code name} and {@code url} to the build scan. + * @param name the name of the link + * @param url the URL of the link + */ + void link(String name, String url); + + /** + * Adds a tag to the build scan. + * @param tag the tag + */ + void tag(String tag); + + /** + * Adds a name-value pair to the build scan. + * @param name the name + * @param value the value + */ + void value(String name, String value); + + /** + * Configures the build scan in the background. + * @param backgroundConfigurer called in the background to configure the build scan + */ + void background(Consumer backgroundConfigurer); + + /** + * Configures the obfuscation of data in the build scan. + */ + interface ObfuscationConfigurer { + + /** + * Obfuscates IP addresses in the build scan by applying the given + * {@code obfuscator} function to them. + * @param obfuscator function to obfuscate IP addresses + */ + void ipAddresses(Function, ? extends List> obfuscator); + + } + +} diff --git a/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ConfigurableDevelocity.java b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ConfigurableDevelocity.java new file mode 100644 index 0000000..6d4acf1 --- /dev/null +++ b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ConfigurableDevelocity.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.core; + +/** + * Configuration for Develocity. + * + * @author Andy Wilkinson + */ +public interface ConfigurableDevelocity { + + /** + * Returns the Develocity server. + * @return the server + */ + String getServer(); + + /** + * Sets the Develocity server. + * @param server the server + */ + void setServer(String server); + +} diff --git a/src/main/java/io/spring/ge/conventions/gradle/ContinuousIntegration.java b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ContinuousIntegration.java similarity index 95% rename from src/main/java/io/spring/ge/conventions/gradle/ContinuousIntegration.java rename to gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ContinuousIntegration.java index a5409b5..7fce2cd 100644 --- a/src/main/java/io/spring/ge/conventions/gradle/ContinuousIntegration.java +++ b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ContinuousIntegration.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2020-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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.spring.ge.conventions.gradle; +package io.spring.ge.conventions.core; import java.util.Map; import java.util.function.Function; diff --git a/src/main/java/io/spring/ge/conventions/gradle/ProcessRunner.java b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ProcessRunner.java similarity index 90% rename from src/main/java/io/spring/ge/conventions/gradle/ProcessRunner.java rename to gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ProcessRunner.java index 06b861d..314f054 100644 --- a/src/main/java/io/spring/ge/conventions/gradle/ProcessRunner.java +++ b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/ProcessRunner.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.spring.ge.conventions.gradle; +package io.spring.ge.conventions.core; import java.io.OutputStream; import java.util.function.Consumer; @@ -23,7 +23,6 @@ * Minimal API for running a process. * * @author Andy Wilkinson - * @see ProcessOperationsProcessRunner */ public interface ProcessRunner { @@ -53,13 +52,14 @@ interface ProcessSpec { } + /** + * Exception indicating a run failure. + * + * @see ProcessRunner#run + */ class RunFailedException extends RuntimeException { - RunFailedException() { - - } - - RunFailedException(Exception cause) { + public RunFailedException(Exception cause) { super(cause); } diff --git a/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/package-info.java b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/package-info.java new file mode 100644 index 0000000..c9ce56a --- /dev/null +++ b/gradle-enterprise-conventions-core/src/main/java/io/spring/ge/conventions/core/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020-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. + */ + +/** + * Core classes for the Gradle Enterprise conventions for Spring projects. + */ +package io.spring.ge.conventions.core; diff --git a/gradle-enterprise-conventions-core/src/test/java/io/spring/ge/conventions/core/BuildCacheConventionsTests.java b/gradle-enterprise-conventions-core/src/test/java/io/spring/ge/conventions/core/BuildCacheConventionsTests.java new file mode 100644 index 0000000..12c375b --- /dev/null +++ b/gradle-enterprise-conventions-core/src/test/java/io/spring/ge/conventions/core/BuildCacheConventionsTests.java @@ -0,0 +1,171 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.core; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link BuildCacheConventions}. + * + * @author Andy Wilkinson + */ +class BuildCacheConventionsTests { + + private final TestConfigurableBuildCache buildCache = new TestConfigurableBuildCache(); + + @Test + void localCacheIsEnabled() { + new BuildCacheConventions().execute(this.buildCache); + assertThat(this.buildCache.local.enabled).isTrue(); + } + + @Test + void remoteCacheIsEnabled() { + new BuildCacheConventions().execute(this.buildCache); + assertThat(this.buildCache.remote.enabled).isTrue(); + assertThat(this.buildCache.remote.server).isEqualTo("https://ge.spring.io"); + assertThat(this.buildCache.remote.push).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "https://ge.example.com/cache/", "https://ge.example.com/cache" }) + void remoteCacheUrlCanBeConfigured(String cacheUrl) { + Map env = new HashMap<>(); + env.put("GRADLE_ENTERPRISE_CACHE_URL", cacheUrl); + new BuildCacheConventions(env).execute(this.buildCache); + assertThat(this.buildCache.remote.enabled).isTrue(); + assertThat(this.buildCache.remote.server).isEqualTo("https://ge.example.com"); + assertThat(this.buildCache.remote.push).isFalse(); + } + + @Test + void remoteCacheServerCanBeConfigured() { + Map env = new HashMap<>(); + env.put("DEVELOCITY_CACHE_SERVER", "https://ge.example.com"); + new BuildCacheConventions(env).execute(this.buildCache); + assertThat(this.buildCache.remote.enabled).isTrue(); + assertThat(this.buildCache.remote.server).isEqualTo("https://ge.example.com"); + assertThat(this.buildCache.remote.push).isFalse(); + } + + @Test + void remoteCacheServerHasPrecedenceOverRemoteCacheUrl() { + Map env = new HashMap<>(); + env.put("GRADLE_ENTERPRISE_CACHE_URL", "https://ge-cache.example.com/cache/"); + env.put("DEVELOCITY_CACHE_SERVER", "https://ge.example.com"); + new BuildCacheConventions(env).execute(this.buildCache); + assertThat(this.buildCache.remote.enabled).isTrue(); + assertThat(this.buildCache.remote.server).isEqualTo("https://ge.example.com"); + assertThat(this.buildCache.remote.push).isFalse(); + } + + @Test + void whenAccessTokenIsProvidedInALocalEnvironmentThenPushingToTheRemoteCacheIsNotEnabled() { + new BuildCacheConventions(Collections.singletonMap("DEVELOCITY_ACCESS_KEY", "ge.example.com=a1b2c3d4")) + .execute(this.buildCache); + assertThat(this.buildCache.remote.push).isFalse(); + } + + @Test + void whenAccessTokenIsProvidedInACiEnvironmentThenPushingToTheRemoteCacheIsEnabled() { + Map env = new HashMap<>(); + env.put("DEVELOCITY_ACCESS_KEY", "ge.example.com=a1b2c3d4"); + env.put("CI", "true"); + new BuildCacheConventions(env).execute(this.buildCache); + assertThat(this.buildCache.remote.push).isTrue(); + } + + @Test + void whenLegacyAccessTokenIsProvidedInALocalEnvironmentThenPushingToTheRemoteCacheIsNotEnabled() { + new BuildCacheConventions(Collections.singletonMap("GRADLE_ENTERPRISE_ACCESS_KEY", "ge.example.com=a1b2c3d4")) + .execute(this.buildCache); + assertThat(this.buildCache.remote.push).isFalse(); + } + + @Test + void whenLegacyAccessTokenIsProvidedInACiEnvironmentThenPushingToTheRemoteCacheIsEnabled() { + Map env = new HashMap<>(); + env.put("GRADLE_ENTERPRISE_ACCESS_KEY", "ge.example.com=a1b2c3d4"); + env.put("CI", "true"); + new BuildCacheConventions(env).execute(this.buildCache); + assertThat(this.buildCache.remote.push).isTrue(); + } + + private static final class TestConfigurableBuildCache implements ConfigurableBuildCache { + + private final TestLocalBuildCache local = new TestLocalBuildCache(); + + private final TestRemoteBuildCache remote = new TestRemoteBuildCache(); + + @Override + public void local(Consumer local) { + local.accept(this.local); + } + + @Override + public void remote(Consumer remote) { + remote.accept(this.remote); + } + + private static final class TestLocalBuildCache implements LocalBuildCache { + + private boolean enabled = false; + + @Override + public void enable() { + this.enabled = true; + } + + } + + private static final class TestRemoteBuildCache implements RemoteBuildCache { + + private boolean enabled = false; + + private boolean push = false; + + private String server = null; + + @Override + public void enable() { + this.enabled = true; + } + + @Override + public void enablePush() { + this.push = true; + } + + @Override + public void setServer(String server) { + this.server = server; + } + + } + + } + +} diff --git a/src/test/java/io/spring/ge/conventions/gradle/BuildScanConventionsTests.java b/gradle-enterprise-conventions-core/src/test/java/io/spring/ge/conventions/core/BuildScanConventionsTests.java similarity index 59% rename from src/test/java/io/spring/ge/conventions/gradle/BuildScanConventionsTests.java rename to gradle-enterprise-conventions-core/src/test/java/io/spring/ge/conventions/core/BuildScanConventionsTests.java index b50d3d2..bffff53 100644 --- a/src/test/java/io/spring/ge/conventions/gradle/BuildScanConventionsTests.java +++ b/gradle-enterprise-conventions-core/src/test/java/io/spring/ge/conventions/core/BuildScanConventionsTests.java @@ -14,23 +14,24 @@ * limitations under the License. */ -package io.spring.ge.conventions.gradle; +package io.spring.ge.conventions.core; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; -import com.gradle.develocity.agent.gradle.scan.BuildScanPublishingConfiguration.PublishingContext; +import io.spring.ge.conventions.core.ConfigurableBuildScan.ObfuscationConfigurer; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; /** * Tests for {@link BuildScanConventions}. @@ -41,19 +42,19 @@ class BuildScanConventionsTests { private final TestProcessRunner processRunner = new TestProcessRunner(); - private final TestDevelocityConfiguration develocity = new TestDevelocityConfiguration(); + private final TestConfigurableBuildScan buildScan = new TestConfigurableBuildScan(); - private final TestBuildScanConfiguration buildScan = new TestBuildScanConfiguration(); + private final TestConfigurableDevelocity develocity = new TestConfigurableDevelocity(); @Test void capturingOfFileFingerprintsIsEnabled() { - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); - assertThat(this.buildScan.captureSettings.getFileFingerprints().get()).isTrue(); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); + assertThat(this.buildScan.captureTaskInputFiles).isTrue(); } @Test void ipAddressesAreObfuscated() throws UnknownHostException { - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); assertThat(this.buildScan.obfuscation.ipAddressesObfuscator).isNotNull(); List obfuscatedAddresses = this.buildScan.obfuscation.ipAddressesObfuscator .apply(Arrays.asList(InetAddress.getByName("10.0.0.1"), InetAddress.getByName("10.0.0.2"))); @@ -62,73 +63,69 @@ void ipAddressesAreObfuscated() throws UnknownHostException { @Test void buildScansAreConfiguredToAlwaysPublishWhenAuthenticated() { - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); - PublishingContext context = mock(PublishingContext.class); - given(context.isAuthenticated()).willReturn(true); - assertThat(this.buildScan.publishing.predicate.isSatisfiedBy(context)).isTrue(); - given(context.isAuthenticated()).willReturn(false); - assertThat(this.buildScan.publishing.predicate.isSatisfiedBy(context)).isFalse(); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); + assertThat(this.buildScan.publishIfAuthenticated).isTrue(); } @Test void buildScansAreConfiguredToPublishToGeSpringIo() { - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); - assertThat(this.develocity.getServer().get()).isEqualTo("https://ge.spring.io"); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); + assertThat(this.develocity.getServer()).isEqualTo("https://ge.spring.io"); } @Test void whenBambooResultEnvVarIsPresentThenBuildScanIsTaggedWithCiNotLocal() { - new BuildScanConventions(this.develocity, this.processRunner, + new BuildScanConventions(this.processRunner, Collections.singletonMap("bamboo_resultsUrl", "https://bamboo.exampl.com")) - .execute(this.buildScan); + .execute(this.develocity, this.buildScan); assertThat(this.buildScan.tags).contains("CI").doesNotContain("Local"); } @Test void whenBambooResultEnvVarIsPresentThenBuildScanHasACiBuildLinkToIt() { - new BuildScanConventions(this.develocity, this.processRunner, + new BuildScanConventions(this.processRunner, Collections.singletonMap("bamboo_resultsUrl", "https://bamboo.example.com")) - .execute(this.buildScan); + .execute(this.develocity, this.buildScan); assertThat(this.buildScan.links).containsEntry("CI build", "https://bamboo.example.com"); } @Test void whenBambooResultEnvVarIsPresentThenBuildScanHasBambooAsTheCiProviderValue() { - new BuildScanConventions(this.develocity, this.processRunner, + new BuildScanConventions(this.processRunner, Collections.singletonMap("bamboo_resultsUrl", "https://bamboo.example.com")) - .execute(this.buildScan); + .execute(this.develocity, this.buildScan); assertThat(this.buildScan.values).containsEntry("CI provider", "Bamboo"); } @Test void whenCircleBuildUrlEnvVarIsPresentThenBuildScanIsTaggedWithCiNotLocal() { - new BuildScanConventions(this.develocity, this.processRunner, + new BuildScanConventions(this.processRunner, Collections.singletonMap("CIRCLE_BUILD_URL", "https://circleci.example.com/gh/org/project/123")) - .execute(this.buildScan); + .execute(this.develocity, this.buildScan); assertThat(this.buildScan.tags).contains("CI").doesNotContain("Local"); } @Test void whenCircleBuildUrlEnvVarIsPresentThenBuildScanHasACiBuildLinkToIt() { - new BuildScanConventions(this.develocity, this.processRunner, + new BuildScanConventions(this.processRunner, Collections.singletonMap("CIRCLE_BUILD_URL", "https://circleci.example.com/gh/org/project/123")) - .execute(this.buildScan); + .execute(this.develocity, this.buildScan); assertThat(this.buildScan.links).containsEntry("CI build", "https://circleci.example.com/gh/org/project/123"); } @Test void whenCircleBuildUrlEnvVarIsPresentThenBuildScanHasCircleCiAsTheCiProviderValue() { - new BuildScanConventions(this.develocity, this.processRunner, + new BuildScanConventions(this.processRunner, Collections.singletonMap("CIRCLE_BUILD_URL", "https://circleci.example.com/gh/org/project/123")) - .execute(this.buildScan); + .execute(this.develocity, this.buildScan); assertThat(this.buildScan.values).containsEntry("CI provider", "CircleCI"); } @Test void whenJenkinsUrlEnvVarIsPresentThenBuildScanIsTaggedWithCiNotLocal() { - new BuildScanConventions(this.develocity, this.processRunner, + new BuildScanConventions(this.processRunner, Collections.singletonMap("JENKINS_URL", "https://jenkins.example.com")) - .execute(this.buildScan); + .execute(this.develocity, this.buildScan); assertThat(this.buildScan.tags).contains("CI").doesNotContain("Local"); } @@ -137,37 +134,36 @@ void whenJenkinsUrlAndBuildUrlEnvVarsArePresentThenBuildScanHasACiBuildLinkToBui Map env = new HashMap<>(); env.put("JENKINS_URL", "https://jenkins.example.com"); env.put("BUILD_URL", "https://jenkins.example.com/builds/123"); - new BuildScanConventions(this.develocity, this.processRunner, env).execute(this.buildScan); + new BuildScanConventions(this.processRunner, env).execute(this.develocity, this.buildScan); assertThat(this.buildScan.links).containsEntry("CI build", "https://jenkins.example.com/builds/123"); } @Test void whenJenkinsUrlEnvVarIsPresentThenBuildScanHasJenkinsAsTheCiProviderValue() { - new BuildScanConventions(this.develocity, this.processRunner, + new BuildScanConventions(this.processRunner, Collections.singletonMap("JENKINS_URL", "https://jenkins.example.com")) - .execute(this.buildScan); + .execute(this.develocity, this.buildScan); assertThat(this.buildScan.values).containsEntry("CI provider", "Jenkins"); } @Test void whenCiEnvVarIsPresentThenBuildScanIsTaggedWithCiNotLocal() { - new BuildScanConventions(this.develocity, this.processRunner, Collections.singletonMap("CI", null)) - .execute(this.buildScan); + new BuildScanConventions(this.processRunner, Collections.singletonMap("CI", null)).execute(this.develocity, + this.buildScan); assertThat(this.buildScan.tags).contains("CI").doesNotContain("Local"); } @Test void whenCiEnvVarIsPresentThenBuildScanHasConcourseAsTheCiProviderValue() { - new BuildScanConventions(this.develocity, this.processRunner, Collections.singletonMap("CI", null)) - .execute(this.buildScan); + new BuildScanConventions(this.processRunner, Collections.singletonMap("CI", null)).execute(this.develocity, + this.buildScan); assertThat(this.buildScan.values).containsEntry("CI provider", "Concourse"); } @Test void whenGitHubActionsEnvVarIsPresentThenBuildScanIsTaggedWithCiNotLocal() { - new BuildScanConventions(this.develocity, this.processRunner, - Collections.singletonMap("GITHUB_ACTIONS", "true")) - .execute(this.buildScan); + new BuildScanConventions(this.processRunner, Collections.singletonMap("GITHUB_ACTIONS", "true")) + .execute(this.develocity, this.buildScan); assertThat(this.buildScan.tags).contains("CI").doesNotContain("Local"); } @@ -178,53 +174,52 @@ void whenGitHubActionsEnvVarsArePresentThenBuildScanHasACiBuildLinkToIt() { env.put("GITHUB_SERVER_URL", "https://github.com"); env.put("GITHUB_REPOSITORY", "spring-projects/spring-boot"); env.put("GITHUB_RUN_ID", "1234567890"); - new BuildScanConventions(this.develocity, this.processRunner, env).execute(this.buildScan); + new BuildScanConventions(this.processRunner, env).execute(this.develocity, this.buildScan); assertThat(this.buildScan.links).containsEntry("CI build", "https://github.com/spring-projects/spring-boot/actions/runs/1234567890"); } @Test void whenGitHubActionsEnvVarIsPresentThenBuildScanHasGitHubActionsAsTheCiProviderValue() { - new BuildScanConventions(this.develocity, this.processRunner, - Collections.singletonMap("GITHUB_ACTIONS", "true")) - .execute(this.buildScan); + new BuildScanConventions(this.processRunner, Collections.singletonMap("GITHUB_ACTIONS", "true")) + .execute(this.develocity, this.buildScan); assertThat(this.buildScan.values).containsEntry("CI provider", "GitHub Actions"); } @Test void whenNoCiIndicatorsArePresentThenBuildScanIsTaggedWithLocalNotCi() { - new BuildScanConventions(this.develocity, this.processRunner, Collections.emptyMap()).execute(this.buildScan); + new BuildScanConventions(this.processRunner, Collections.emptyMap()).execute(this.develocity, this.buildScan); assertThat(this.buildScan.tags).contains("Local").doesNotContain("CI"); } @Test void whenNoCiIndicatorsArePresentThenBuildScanHasNoCiBuildLink() { - new BuildScanConventions(this.develocity, this.processRunner, Collections.emptyMap()).execute(this.buildScan); + new BuildScanConventions(this.processRunner, Collections.emptyMap()).execute(this.develocity, this.buildScan); assertThat(this.buildScan.links).doesNotContainKey("CI build"); } @Test void whenNoCiIndicatorsArePresentThenBuildScanHasNoCiProviderValue() { - new BuildScanConventions(this.develocity, this.processRunner, Collections.emptyMap()).execute(this.buildScan); + new BuildScanConventions(this.processRunner, Collections.emptyMap()).execute(this.develocity, this.buildScan); assertThat(this.buildScan.values).doesNotContainKey("CI provider"); } @Test void buildScanIsTaggedWithJdkVersion() { - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); assertThat(this.buildScan.tags).contains("JDK-" + System.getProperty("java.specification.version")); } @Test void buildScanIsTaggedWithOperatingSystem() { - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); assertThat(this.buildScan.tags).contains(System.getProperty("os.name")); } @Test void whenBranchEnvVarIsPresentThenBuildScanIsTaggedAndConfiguredWithCustomValue() { - new BuildScanConventions(this.develocity, this.processRunner, Collections.singletonMap("BRANCH", "1.1.x")) - .execute(this.buildScan); + new BuildScanConventions(this.processRunner, Collections.singletonMap("BRANCH", "1.1.x")) + .execute(this.develocity, this.buildScan); assertThat(this.buildScan.tags).contains("1.1.x"); assertThat(this.buildScan.values).containsEntry("Git branch", "1.1.x"); } @@ -232,7 +227,7 @@ void whenBranchEnvVarIsPresentThenBuildScanIsTaggedAndConfiguredWithCustomValue( @Test void whenBranchEnvVarIsNotPresentThenBuildScanIsTaggedWithBranchFromGit() { this.processRunner.commandLineOutput.put(Arrays.asList("git", "rev-parse", "--abbrev-ref", "HEAD"), "1.2.x"); - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); assertThat(this.buildScan.tags).contains("1.2.x"); assertThat(this.buildScan.values).containsEntry("Git branch", "1.2.x"); } @@ -241,7 +236,7 @@ void whenBranchEnvVarIsNotPresentThenBuildScanIsTaggedWithBranchFromGit() { void buildScanHasGitCommitIdCustomValueAndLinkToBuildScansForTheSameCommit() { this.processRunner.commandLineOutput.put(Arrays.asList("git", "rev-parse", "--short=8", "--verify", "HEAD"), "79ce52f8"); - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); assertThat(this.buildScan.values).containsEntry("Git commit", "79ce52f8"); assertThat(this.buildScan.links).containsEntry("Git commit build scans", "https://ge.spring.io/scans?search.names=Git+commit&search.values=79ce52f8"); @@ -250,7 +245,7 @@ void buildScanHasGitCommitIdCustomValueAndLinkToBuildScansForTheSameCommit() { @Test void whenGitStatusIsCleanThenBuildScanIsNotTaggedDirtyAndHasNotGitStatusCustomValue() { this.processRunner.commandLineOutput.put(Arrays.asList("git", "status", "--porcelain"), ""); - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); assertThat(this.buildScan.tags).doesNotContain("dirty"); assertThat(this.buildScan.values).doesNotContainKey("Git status"); } @@ -258,7 +253,7 @@ void whenGitStatusIsCleanThenBuildScanIsNotTaggedDirtyAndHasNotGitStatusCustomVa @Test void whenGitStatusIsDirtyThenBuildScanIsTaggedDirtyAndHasGitStatusCustomValue() { this.processRunner.commandLineOutput.put(Arrays.asList("git", "status", "--porcelain"), " M build.gradle"); - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); assertThat(this.buildScan.tags).contains("dirty"); assertThat(this.buildScan.values).containsEntry("Git status", "M build.gradle"); } @@ -268,7 +263,7 @@ void whenGitIsNotAvailableThenConventionsCanBeAppliedWithoutFailure() { this.processRunner.failures.put(Arrays.asList("git", "status", "--porcelain"), new RuntimeException("git is not available")); assertThatNoException() - .isThrownBy(() -> new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan)); + .isThrownBy(() -> new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan)); assertThat(this.buildScan.values).doesNotContainKey("Git status"); } @@ -276,7 +271,7 @@ void whenGitIsNotAvailableThenConventionsCanBeAppliedWithoutFailure() { void buildScanHasDockerCustomValue() { this.processRunner.commandLineOutput.put(Arrays.asList("docker", "--version"), "Docker version 20.10.24, build 297e128"); - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); assertThat(this.buildScan.values).containsEntry("Docker", "Docker version 20.10.24, build 297e128"); } @@ -284,9 +279,9 @@ void buildScanHasDockerCustomValue() { void whenDockerIsNotAvailableThenConventionsCanBeAppliedWithoutFailure() { this.processRunner.failures.put(Arrays.asList("docker", "--version"), new RuntimeException("docker is not available")); - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); assertThatNoException() - .isThrownBy(() -> new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan)); + .isThrownBy(() -> new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan)); assertThat(this.buildScan.values).doesNotContainKey("Docker"); } @@ -294,7 +289,7 @@ void whenDockerIsNotAvailableThenConventionsCanBeAppliedWithoutFailure() { void buildScanHasDockerComposeCustomValue() { this.processRunner.commandLineOutput.put(Arrays.asList("docker", "compose", "version"), "Docker Compose version v2.17.2"); - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); assertThat(this.buildScan.values).containsEntry("Docker Compose", "Docker Compose version v2.17.2"); } @@ -302,23 +297,108 @@ void buildScanHasDockerComposeCustomValue() { void whenDockerComposeIsNotAvailableThenConventionsCanBeAppliedWithoutFailure() { this.processRunner.failures.put(Arrays.asList("docker", "compose", "version"), new RuntimeException("docker compose is not available")); - new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); assertThatNoException() - .isThrownBy(() -> new BuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan)); + .isThrownBy(() -> new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan)); assertThat(this.buildScan.values).doesNotContainKey("Docker Compose"); } @Test void whenBuildingLocallyThenBackgroundUploadIsEnabled() { - new BuildScanConventions(this.develocity, this.processRunner, Collections.emptyMap()).execute(this.buildScan); - assertThat(this.buildScan.uploadInBackground.get()).isTrue(); + new BuildScanConventions(this.processRunner, Collections.emptyMap()).execute(this.develocity, this.buildScan); + assertThat(this.buildScan.uploadInBackground).isTrue(); } @Test void whenBuildingOnCiThenBackgroundUploadIsDisabled() { - new BuildScanConventions(this.develocity, this.processRunner, Collections.singletonMap("CI", null)) - .execute(this.buildScan); - assertThat(this.buildScan.uploadInBackground.get()).isFalse(); + new BuildScanConventions(this.processRunner, Collections.singletonMap("CI", null)).execute(this.develocity, + this.buildScan); + assertThat(this.buildScan.uploadInBackground).isFalse(); + } + + public static final class TestConfigurableBuildScan implements ConfigurableBuildScan { + + private final TestObfuscationConfigurer obfuscation = new TestObfuscationConfigurer(); + + private final List tags = new ArrayList<>(); + + private final Map values = new HashMap<>(); + + private final Map links = new HashMap<>(); + + private boolean captureTaskInputFiles; + + private boolean publishIfAuthenticated; + + private boolean uploadInBackground = true; + + @Override + public void background(Consumer action) { + action.accept(this); + } + + @Override + public void link(String name, String url) { + this.links.put(name, url); + } + + @Override + public void publishIfAuthenticated() { + this.publishIfAuthenticated = true; + } + + @Override + public void captureInputFiles(boolean capture) { + this.captureTaskInputFiles = capture; + } + + @Override + public void tag(String tag) { + this.tags.add(tag); + } + + @Override + public void value(String name, String value) { + this.values.put(name, value); + } + + @Override + public void uploadInBackground(boolean uploadInBackground) { + this.uploadInBackground = uploadInBackground; + } + + @Override + public void obfuscation(Consumer configurer) { + configurer.accept(this.obfuscation); + } + + } + + private static final class TestObfuscationConfigurer implements ObfuscationConfigurer { + + private Function, ? extends List> ipAddressesObfuscator; + + @Override + public void ipAddresses(Function, ? extends List> obfuscator) { + this.ipAddressesObfuscator = obfuscator; + } + + } + + private static final class TestConfigurableDevelocity implements ConfigurableDevelocity { + + private String server; + + @Override + public String getServer() { + return this.server; + } + + @Override + public void setServer(String server) { + this.server = server; + } + } } diff --git a/src/test/java/io/spring/ge/conventions/gradle/TestProcessRunner.java b/gradle-enterprise-conventions-core/src/test/java/io/spring/ge/conventions/core/TestProcessRunner.java similarity index 95% rename from src/test/java/io/spring/ge/conventions/gradle/TestProcessRunner.java rename to gradle-enterprise-conventions-core/src/test/java/io/spring/ge/conventions/core/TestProcessRunner.java index 462b163..392fc02 100644 --- a/src/test/java/io/spring/ge/conventions/gradle/TestProcessRunner.java +++ b/gradle-enterprise-conventions-core/src/test/java/io/spring/ge/conventions/core/TestProcessRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the original author or authors. + * Copyright 2020-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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.spring.ge.conventions.gradle; +package io.spring.ge.conventions.core; import java.io.IOException; import java.io.OutputStream; diff --git a/build.gradle b/gradle-enterprise-conventions-gradle-plugin/build.gradle similarity index 93% rename from build.gradle rename to gradle-enterprise-conventions-gradle-plugin/build.gradle index 432a701..9c83f2a 100644 --- a/build.gradle +++ b/gradle-enterprise-conventions-gradle-plugin/build.gradle @@ -20,6 +20,8 @@ dependencies { checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}") compileOnly("com.gradle:develocity-gradle-plugin:${gradleEnterprisePluginVersion}") + + implementation(project(":gradle-enterprise-conventions-core")) testImplementation("com.gradle:develocity-gradle-plugin:${gradleEnterprisePluginVersion}") testImplementation("org.assertj:assertj-core:3.24.2") @@ -38,14 +40,6 @@ gradlePlugin { } } -tasks.withType(Test) { testTask -> - if (testTask.name.startsWith("compatibilityTest_")) { - testTask.filter { - includeTestsMatching "*IntegrationTests" - } - } -} - test { useJUnitPlatform() } diff --git a/src/main/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventions.java b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventions.java similarity index 57% rename from src/main/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventions.java rename to gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventions.java index 3131146..30f7aae 100644 --- a/src/main/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventions.java +++ b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventions.java @@ -18,8 +18,10 @@ import java.util.Map; -import com.gradle.develocity.agent.gradle.DevelocityConfiguration; -import com.gradle.develocity.agent.gradle.scan.BuildScanConfiguration; +import io.spring.ge.conventions.core.BuildScanConventions; +import io.spring.ge.conventions.core.ConfigurableBuildScan; +import io.spring.ge.conventions.core.ConfigurableDevelocity; +import io.spring.ge.conventions.core.ProcessRunner; /** * Conventions for build scans that are published anonymously to @@ -27,19 +29,18 @@ * * @author Andy Wilkinson */ -public class AnonymousPublicationBuildScanConventions extends BuildScanConventions { +class AnonymousPublicationBuildScanConventions extends BuildScanConventions { - public AnonymousPublicationBuildScanConventions(DevelocityConfiguration develocity, ProcessRunner processRunner, - Map env) { - super(develocity, processRunner, env); + AnonymousPublicationBuildScanConventions(ProcessRunner processRunner) { + super(processRunner); } - public AnonymousPublicationBuildScanConventions(DevelocityConfiguration develocity, ProcessRunner processRunner) { - super(develocity, processRunner); + AnonymousPublicationBuildScanConventions(ProcessRunner processRunner, Map env) { + super(processRunner, env); } @Override - protected void configurePublishing(BuildScanConfiguration buildScan) { + protected void configurePublishing(ConfigurableDevelocity develocity, ConfigurableBuildScan buildScan) { // Use Gradle's defaults } diff --git a/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildCache.java b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildCache.java new file mode 100644 index 0000000..0bc70a5 --- /dev/null +++ b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildCache.java @@ -0,0 +1,93 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.gradle; + +import java.util.function.Consumer; + +import com.gradle.develocity.agent.gradle.buildcache.DevelocityBuildCache; +import io.spring.ge.conventions.core.ConfigurableBuildCache; +import org.gradle.caching.configuration.BuildCacheConfiguration; +import org.gradle.caching.local.DirectoryBuildCache; + +/** + * A {@link ConfigurableBuildCache} for Gradle builds. + * + * @author Andy Wilkinson + */ +class GradleConfigurableBuildCache implements ConfigurableBuildCache { + + private final Class buildCacheType; + + private final BuildCacheConfiguration buildCache; + + GradleConfigurableBuildCache(Class buildCacheType, + BuildCacheConfiguration buildCache) { + this.buildCacheType = buildCacheType; + this.buildCache = buildCache; + } + + @Override + public void local(Consumer local) { + local.accept(new GradleLocalBuildCache(this.buildCache.getLocal())); + } + + @Override + public void remote(Consumer remote) { + remote.accept(new GradleRemoteBuildCache(this.buildCache.remote(this.buildCacheType))); + } + + private static final class GradleLocalBuildCache implements LocalBuildCache { + + private final DirectoryBuildCache localBuildCache; + + private GradleLocalBuildCache(DirectoryBuildCache localBuildCache) { + this.localBuildCache = localBuildCache; + } + + @Override + public void enable() { + this.localBuildCache.setEnabled(true); + } + + } + + private static final class GradleRemoteBuildCache implements RemoteBuildCache { + + private final DevelocityBuildCache remoteBuildCache; + + private GradleRemoteBuildCache(DevelocityBuildCache buildCache) { + this.remoteBuildCache = buildCache; + } + + @Override + public void enable() { + this.remoteBuildCache.setEnabled(true); + } + + @Override + public void enablePush() { + this.remoteBuildCache.setPush(true); + } + + @Override + public void setServer(String server) { + this.remoteBuildCache.setServer(server); + } + + } + +} diff --git a/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildScan.java b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildScan.java new file mode 100644 index 0000000..8ebf575 --- /dev/null +++ b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildScan.java @@ -0,0 +1,98 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.gradle; + +import java.net.InetAddress; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +import com.gradle.develocity.agent.gradle.scan.BuildScanConfiguration; +import com.gradle.develocity.agent.gradle.scan.BuildScanDataObfuscationConfiguration; +import com.gradle.develocity.agent.gradle.scan.BuildScanPublishingConfiguration.PublishingContext; +import io.spring.ge.conventions.core.ConfigurableBuildScan; + +/** + * A {@link ConfigurableBuildScan} for Gradle builds. + * + * @author Andy Wilkinson + */ +class GradleConfigurableBuildScan implements ConfigurableBuildScan { + + private final BuildScanConfiguration buildScan; + + GradleConfigurableBuildScan(BuildScanConfiguration buildScan) { + this.buildScan = buildScan; + } + + @Override + public void captureInputFiles(boolean capture) { + this.buildScan.capture((settings) -> settings.getFileFingerprints().set(capture)); + } + + @Override + public void obfuscation(Consumer configurer) { + configurer.accept(new GradleObfuscationConfigurer(this.buildScan.getObfuscation())); + } + + @Override + public void publishIfAuthenticated() { + this.buildScan.publishing((publishing) -> publishing.onlyIf(PublishingContext::isAuthenticated)); + } + + @Override + public void uploadInBackground(boolean enabled) { + this.buildScan.getUploadInBackground().set(enabled); + } + + @Override + public void link(String name, String url) { + this.buildScan.link(name, url); + } + + @Override + public void tag(String tag) { + this.buildScan.tag(tag); + } + + @Override + public void value(String name, String value) { + this.buildScan.value(name, value); + } + + @Override + public void background(Consumer backgroundConfigurer) { + this.buildScan + .background((buildScan) -> backgroundConfigurer.accept(new GradleConfigurableBuildScan(buildScan))); + } + + private static final class GradleObfuscationConfigurer implements ObfuscationConfigurer { + + private final BuildScanDataObfuscationConfiguration obfuscation; + + private GradleObfuscationConfigurer(BuildScanDataObfuscationConfiguration obfuscation) { + this.obfuscation = obfuscation; + } + + @Override + public void ipAddresses(Function, ? extends List> obfuscator) { + this.obfuscation.ipAddresses(obfuscator); + } + + } + +} diff --git a/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleConfigurableDevelocity.java b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleConfigurableDevelocity.java new file mode 100644 index 0000000..ac07029 --- /dev/null +++ b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleConfigurableDevelocity.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.gradle; + +import com.gradle.develocity.agent.gradle.DevelocityConfiguration; +import io.spring.ge.conventions.core.ConfigurableDevelocity; + +/** + * {@link ConfigurableDevelocity} implementation for Gradle builds. + * + * @author Andy Wilkinson + */ +class GradleConfigurableDevelocity implements ConfigurableDevelocity { + + private final DevelocityConfiguration develocity; + + GradleConfigurableDevelocity(DevelocityConfiguration develocity) { + this.develocity = develocity; + } + + @Override + public String getServer() { + return this.develocity.getServer().getOrNull(); + } + + @Override + public void setServer(String server) { + this.develocity.getServer().set(server); + } + +} diff --git a/src/main/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPlugin.java b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPlugin.java similarity index 85% rename from src/main/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPlugin.java rename to gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPlugin.java index 8243a12..894e301 100644 --- a/src/main/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPlugin.java +++ b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPlugin.java @@ -23,6 +23,8 @@ import com.gradle.develocity.agent.gradle.DevelocityConfiguration; import com.gradle.develocity.agent.gradle.DevelocityPlugin; import com.gradle.develocity.agent.gradle.scan.BuildScanConfiguration; +import io.spring.ge.conventions.core.BuildCacheConventions; +import io.spring.ge.conventions.core.BuildScanConventions; import org.gradle.StartParameter; import org.gradle.api.Plugin; import org.gradle.api.initialization.Settings; @@ -50,8 +52,8 @@ public void apply(Settings settings) { .withType(DevelocityPlugin.class, (plugin) -> configureBuildScanConventions(extension, extension.getBuildScan(), settings.getStartParameter(), settings.getRootDir())); if (settings.getStartParameter().isBuildCacheEnabled()) { - settings.buildCache((buildCacheConfiguration) -> new BuildCacheConventions(extension.getBuildCache()) - .execute(buildCacheConfiguration)); + settings.buildCache((buildCacheConfiguration) -> new BuildCacheConventions() + .execute(new GradleConfigurableBuildCache(extension.getBuildCache(), buildCacheConfiguration))); } } @@ -63,7 +65,7 @@ private void configureBuildScanConventions(DevelocityConfiguration develocity, B ProcessOperationsProcessRunner processRunner = new ProcessOperationsProcessRunner( new WorkingDirectoryProcessOperations(this.processOperations, rootDir)); if (startParameter.isBuildScan()) { - new AnonymousPublicationBuildScanConventions(develocity, processRunner) { + new AnonymousPublicationBuildScanConventions(processRunner) { @Override protected String getJdkVersion() { @@ -71,10 +73,10 @@ protected String getJdkVersion() { return (toolchainVersion != null) ? toolchainVersion : super.getJdkVersion(); } - }.execute(buildScan); + }.execute(new GradleConfigurableDevelocity(develocity), new GradleConfigurableBuildScan(buildScan)); } else { - new BuildScanConventions(develocity, processRunner) { + new BuildScanConventions(processRunner) { @Override protected String getJdkVersion() { @@ -82,7 +84,7 @@ protected String getJdkVersion() { return (toolchainVersion != null) ? toolchainVersion : super.getJdkVersion(); } - }.execute(buildScan); + }.execute(new GradleConfigurableDevelocity(develocity), new GradleConfigurableBuildScan(buildScan)); } } diff --git a/src/main/java/io/spring/ge/conventions/gradle/ProcessOperationsProcessRunner.java b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/ProcessOperationsProcessRunner.java similarity index 92% rename from src/main/java/io/spring/ge/conventions/gradle/ProcessOperationsProcessRunner.java rename to gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/ProcessOperationsProcessRunner.java index b3dd3e3..d817151 100644 --- a/src/main/java/io/spring/ge/conventions/gradle/ProcessOperationsProcessRunner.java +++ b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/ProcessOperationsProcessRunner.java @@ -19,8 +19,8 @@ import java.io.OutputStream; import java.util.function.Consumer; +import io.spring.ge.conventions.core.ProcessRunner; import org.gradle.api.internal.ProcessOperations; -import org.gradle.process.ExecResult; import org.gradle.process.ExecSpec; /** @@ -39,7 +39,7 @@ class ProcessOperationsProcessRunner implements ProcessRunner { @Override public void run(Consumer configurer) { try { - ExecResult exec = this.processOperations.exec((spec) -> configurer.accept(new ExecSpecProcessSpec(spec))); + this.processOperations.exec((spec) -> configurer.accept(new ExecSpecProcessSpec(spec))); } catch (Exception ex) { throw new RunFailedException(ex); diff --git a/src/main/java/io/spring/ge/conventions/gradle/WorkingDirectoryProcessOperations.java b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/WorkingDirectoryProcessOperations.java similarity index 96% rename from src/main/java/io/spring/ge/conventions/gradle/WorkingDirectoryProcessOperations.java rename to gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/WorkingDirectoryProcessOperations.java index d9d1003..3791e72 100644 --- a/src/main/java/io/spring/ge/conventions/gradle/WorkingDirectoryProcessOperations.java +++ b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/WorkingDirectoryProcessOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the original author or authors. + * Copyright 2020-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. diff --git a/src/main/java/io/spring/ge/conventions/gradle/package-info.java b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/package-info.java similarity index 82% rename from src/main/java/io/spring/ge/conventions/gradle/package-info.java rename to gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/package-info.java index 2a7cc22..e5d5ed6 100644 --- a/src/main/java/io/spring/ge/conventions/gradle/package-info.java +++ b/gradle-enterprise-conventions-gradle-plugin/src/main/java/io/spring/ge/conventions/gradle/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-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. @@ -15,6 +15,6 @@ */ /** - * Gradle Enterprise conventions for Spring projects. + * Gradle Enterprise conventions for Spring projects built with Gradle. */ package io.spring.ge.conventions.gradle; diff --git a/src/test/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventionsTests.java b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventionsTests.java similarity index 79% rename from src/test/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventionsTests.java rename to gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventionsTests.java index c869a13..b85f0b9 100644 --- a/src/test/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventionsTests.java +++ b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/AnonymousPublicationBuildScanConventionsTests.java @@ -35,13 +35,15 @@ class AnonymousPublicationBuildScanConventionsTests { @Test void buildScansAreConfiguredToUseDefaultPublicationBehaviour() { - new AnonymousPublicationBuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new AnonymousPublicationBuildScanConventions(this.processRunner).execute( + new GradleConfigurableDevelocity(this.develocity), new GradleConfigurableBuildScan(this.buildScan)); assertThat(this.buildScan.publishing.predicate).isNull(); } @Test void buildScansAreConfiguredToPublishToDefaultServer() { - new AnonymousPublicationBuildScanConventions(this.develocity, this.processRunner).execute(this.buildScan); + new AnonymousPublicationBuildScanConventions(this.processRunner).execute( + new GradleConfigurableDevelocity(this.develocity), new GradleConfigurableBuildScan(this.buildScan)); assertThat(this.develocity.getServer().getOrNull()).isNull(); } diff --git a/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildCacheTests.java b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildCacheTests.java new file mode 100644 index 0000000..179ba82 --- /dev/null +++ b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildCacheTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.gradle; + +import com.gradle.develocity.agent.gradle.buildcache.DevelocityBuildCache; +import org.gradle.api.Action; +import org.gradle.caching.BuildCacheServiceFactory; +import org.gradle.caching.configuration.BuildCache; +import org.gradle.caching.configuration.BuildCacheConfiguration; +import org.gradle.caching.local.DirectoryBuildCache; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link GradleConfigurableBuildCache}. + * + * @author Andy Wilkinson + */ +class GradleConfigurableBuildCacheTests { + + private final TestBuildCacheConfiguration buildCache = new TestBuildCacheConfiguration(); + + @Test + void localCacheCanBeEnabled() { + new GradleConfigurableBuildCache(DevelocityBuildCache.class, this.buildCache).local((local) -> local.enable()); + assertThat(this.buildCache.local.isEnabled()).isTrue(); + } + + @Test + void remoteCacheCanBeEnabled() { + new GradleConfigurableBuildCache(DevelocityBuildCache.class, this.buildCache) + .remote((remote) -> remote.enable()); + } + + @Test + void pushToRemoteCacheCanBeEnabled() { + new GradleConfigurableBuildCache(DevelocityBuildCache.class, this.buildCache) + .remote((remote) -> remote.enablePush()); + assertThat(this.buildCache.remote.isPush()).isTrue(); + } + + @Test + void remoteServerCanBeConfigured() { + new GradleConfigurableBuildCache(DevelocityBuildCache.class, this.buildCache) + .remote((remote) -> remote.setServer("https://ge.spring.io")); + assertThat(this.buildCache.remote.getServer()).isEqualTo("https://ge.spring.io"); + } + + private static final class TestBuildCacheConfiguration implements BuildCacheConfiguration { + + private final DirectoryBuildCache local = new DirectoryBuildCache(); + + private final DevelocityBuildCache remote = new DevelocityBuildCache() { + }; + + @Override + public DirectoryBuildCache getLocal() { + return this.local; + } + + @Override + public BuildCache getRemote() { + throw new UnsupportedOperationException(); + } + + @Override + @Deprecated + public T local(Class cacheType) { + throw new UnsupportedOperationException(); + } + + @Override + public void local(Action action) { + action.execute(this.local); + } + + @Override + @Deprecated + public T local(Class cacheType, Action action) { + throw new UnsupportedOperationException(); + } + + @Override + public void registerBuildCacheService(Class cacheType, + Class> factory) { + throw new UnsupportedOperationException(); + } + + @Override + @SuppressWarnings("unchecked") + public T remote(Class cacheType) { + return (T) this.remote; + } + + @Override + public void remote(Action action) { + throw new UnsupportedOperationException(); + } + + @Override + public T remote(Class type, Action action) { + T cache = remote(type); + action.execute(cache); + return cache; + } + + } + +} diff --git a/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildScanTests.java b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildScanTests.java new file mode 100644 index 0000000..3ea41e3 --- /dev/null +++ b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/GradleConfigurableBuildScanTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.gradle; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import com.gradle.develocity.agent.gradle.scan.BuildScanPublishingConfiguration.PublishingContext; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link GradleConfigurableBuildScan}. + * + * @author Andy Wilkinson + */ +class GradleConfigurableBuildScanTests { + + private final TestBuildScanConfiguration buildScan = new TestBuildScanConfiguration(); + + @Test + void captureOfInputFilesCanBeEnabled() { + new GradleConfigurableBuildScan(this.buildScan).captureInputFiles(true); + assertThat(this.buildScan.captureSettings.getFileFingerprints().get()).isTrue(); + } + + @Test + void ipAddressesCanBeObfuscated() throws UnknownHostException { + new GradleConfigurableBuildScan(this.buildScan).obfuscation((obfuscation) -> obfuscation.ipAddresses( + (inetAddresses) -> inetAddresses.stream().map((address) -> "127.0.0.1").collect(Collectors.toList()))); + assertThat(this.buildScan.obfuscation.ipAddressesObfuscator).isNotNull(); + List obfuscatedAddresses = this.buildScan.obfuscation.ipAddressesObfuscator + .apply(Arrays.asList(InetAddress.getByName("10.0.0.1"), InetAddress.getByName("10.0.0.2"))); + assertThat(obfuscatedAddresses).containsExactly("127.0.0.1", "127.0.0.1"); + } + + @Test + void buildScansCanBeConfiguredToPublishIfAuthenticated() { + new GradleConfigurableBuildScan(this.buildScan).publishIfAuthenticated(); + PublishingContext context = mock(PublishingContext.class); + given(context.isAuthenticated()).willReturn(true); + assertThat(this.buildScan.publishing.predicate.isSatisfiedBy(context)).isTrue(); + given(context.isAuthenticated()).willReturn(false); + assertThat(this.buildScan.publishing.predicate.isSatisfiedBy(context)).isFalse(); + } + + @Test + void whenTagsAreAddedThenBuildScanHasTags() { + GradleConfigurableBuildScan configurableBuildScan = new GradleConfigurableBuildScan(this.buildScan); + configurableBuildScan.tag("some-tag"); + configurableBuildScan.tag("another-tag"); + assertThat(this.buildScan.tags).containsExactly("some-tag", "another-tag"); + } + + @Test + void whenLinkIsAddedThenBuildScanHasLink() { + new GradleConfigurableBuildScan(this.buildScan).link("CI Server", "ci.example.com"); + assertThat(this.buildScan.links).containsEntry("CI Server", "ci.example.com"); + } + + @Test + void whenValueIsAddedThenBuildScanHasValue() { + new GradleConfigurableBuildScan(this.buildScan).value("Branch", "1.2.x"); + assertThat(this.buildScan.values).containsEntry("Branch", "1.2.x"); + } + + @Test + void whenBuildingLocallyThenBackgroundUploadIsEnabled() { + new GradleConfigurableBuildScan(this.buildScan).uploadInBackground(true); + assertThat(this.buildScan.uploadInBackground.get()).isTrue(); + } + +} diff --git a/src/test/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPluginIntegrationTests.java b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPluginIntegrationTests.java similarity index 94% rename from src/test/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPluginIntegrationTests.java rename to gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPluginIntegrationTests.java index 09b543d..ccdb4e4 100644 --- a/src/test/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPluginIntegrationTests.java +++ b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPluginIntegrationTests.java @@ -22,12 +22,14 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.function.Consumer; import com.gradle.develocity.agent.gradle.DevelocityPlugin; +import io.spring.ge.conventions.core.ConfigurableBuildScan; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; import org.junit.jupiter.api.Test; @@ -161,7 +163,7 @@ private void write(File file, Consumer consumer) { private String version() { Properties properties = new Properties(); - try (Reader reader = new FileReader("gradle.properties")) { + try (Reader reader = new FileReader("../gradle.properties")) { properties.load(reader); return properties.getProperty("version"); } @@ -173,12 +175,15 @@ private String version() { private BuildResult build(File projectDir, String gradleVersion, String... arguments) { List classpath = Arrays.asList(new File("bin/main"), new File("build/classes/java/main"), new File("build/resources/main"), - new File(DevelocityPlugin.class.getProtectionDomain().getCodeSource().getLocation().getFile())); + new File(DevelocityPlugin.class.getProtectionDomain().getCodeSource().getLocation().getFile()), + new File(ConfigurableBuildScan.class.getProtectionDomain().getCodeSource().getLocation().getFile())); + List augmentedArguments = new ArrayList<>(Arrays.asList(arguments)); + augmentedArguments.add("--stacktrace"); return GradleRunner.create() .withGradleVersion(gradleVersion) .withProjectDir(projectDir) .withPluginClasspath(classpath) - .withArguments(arguments) + .withArguments(augmentedArguments) .build(); } diff --git a/src/test/java/io/spring/ge/conventions/gradle/TestBuildScanConfiguration.java b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestBuildScanConfiguration.java similarity index 99% rename from src/test/java/io/spring/ge/conventions/gradle/TestBuildScanConfiguration.java rename to gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestBuildScanConfiguration.java index 3ee1115..e448d9c 100644 --- a/src/test/java/io/spring/ge/conventions/gradle/TestBuildScanConfiguration.java +++ b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestBuildScanConfiguration.java @@ -96,7 +96,7 @@ public BuildScanCaptureConfiguration getCapture() { @Override public BuildScanDataObfuscationConfiguration getObfuscation() { - throw new UnsupportedOperationException(); + return this.obfuscation; } @Override @@ -121,7 +121,7 @@ public Property getUploadInBackground() { @Override public void obfuscation(Action action) { - action.execute(this.obfuscation); + throw new UnsupportedOperationException(); } @Override diff --git a/src/test/java/io/spring/ge/conventions/gradle/TestDevelocityConfiguration.java b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestDevelocityConfiguration.java similarity index 97% rename from src/test/java/io/spring/ge/conventions/gradle/TestDevelocityConfiguration.java rename to gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestDevelocityConfiguration.java index de5e2a7..2d668ec 100644 --- a/src/test/java/io/spring/ge/conventions/gradle/TestDevelocityConfiguration.java +++ b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestDevelocityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2020-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. diff --git a/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestProcessRunner.java b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestProcessRunner.java new file mode 100644 index 0000000..1d6fbda --- /dev/null +++ b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestProcessRunner.java @@ -0,0 +1,68 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.gradle; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import io.spring.ge.conventions.core.ProcessRunner; +import org.mockito.ArgumentCaptor; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * A {@link ProcessRunner} for unit testing. + * + * @author Andy Wilkinson + */ +final class TestProcessRunner implements ProcessRunner { + + final Map, String> commandLineOutput = new HashMap<>(); + + final Map, RuntimeException> failures = new HashMap<>(); + + @Override + public void run(Consumer configurer) { + ProcessSpec processSpec = mock(ProcessSpec.class); + configurer.accept(processSpec); + ArgumentCaptor commandLineCaptor = ArgumentCaptor.forClass(Object.class); + verify(processSpec).commandLine(commandLineCaptor.capture()); + ArgumentCaptor standardOut = ArgumentCaptor.forClass(OutputStream.class); + verify(processSpec).standardOutput(standardOut.capture()); + List commandLine = commandLineCaptor.getAllValues(); + RuntimeException failure = this.failures.get(commandLine); + if (failure != null) { + failure.fillInStackTrace(); + throw new RunFailedException(failure); + } + String output = this.commandLineOutput.get(commandLine); + if (output != null) { + try { + standardOut.getValue().write(output.getBytes()); + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + } + +} diff --git a/src/test/java/io/spring/ge/conventions/gradle/TestProperty.java b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestProperty.java similarity index 97% rename from src/test/java/io/spring/ge/conventions/gradle/TestProperty.java rename to gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestProperty.java index 44df004..82ee40e 100644 --- a/src/test/java/io/spring/ge/conventions/gradle/TestProperty.java +++ b/gradle-enterprise-conventions-gradle-plugin/src/test/java/io/spring/ge/conventions/gradle/TestProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2020-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. diff --git a/gradle-enterprise-conventions-maven-extension/build.gradle b/gradle-enterprise-conventions-maven-extension/build.gradle new file mode 100644 index 0000000..93331ed --- /dev/null +++ b/gradle-enterprise-conventions-maven-extension/build.gradle @@ -0,0 +1,105 @@ +plugins { + id "checkstyle" + id "io.spring.javaformat" version "$javaFormatVersion" + id "java" + id "maven-publish" +} + +description = "Gradle Enterprise Conventions Maven extension" +group = 'io.spring.ge.conventions' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}") + + compileOnly("org.apache.maven:maven-core:3.6.3") + compileOnly("org.codehaus.plexus:plexus-component-annotations:1.7.1") + + implementation("com.gradle:develocity-maven-extension:1.21.5") + implementation(project(":gradle-enterprise-conventions-core")) + + testImplementation("org.assertj:assertj-core:3.24.2") + testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") + testImplementation("org.mockito:mockito-core:4.11.0") +} + +tasks.withType(Test) { testTask -> + if (testTask.name.startsWith("compatibilityTest_")) { + testTask.filter { + includeTestsMatching "*IntegrationTests" + } + } +} + +test { + useJUnitPlatform() +} + +java { + withJavadocJar() + withSourcesJar() +} + +checkstyle { + def archive = configurations.checkstyle.filter { it.name.startsWith("spring-javaformat-checkstyle")} + config = resources.text.fromArchiveEntry(archive, "io/spring/javaformat/checkstyle/checkstyle.xml") + toolVersion = 9.3 +} + +tasks.withType(GenerateModuleMetadata).all { + enabled = false +} + +if (project.hasProperty("distributionRepository")) { + publishing { + repositories { + maven { + url = "${distributionRepository}" + name = "deployment" + } + } + } +} + +publishing { + publications { + maven(MavenPublication) { + from components.java + } + withType(MavenPublication) { mavenPublication -> + pom { + name = project.description + description = project.description + url = 'https://github.com/spring-io/gradle-enterprise-conventions' + organization { + name = 'Pivotal Software, Inc.' + url = 'https://spring.io' + } + licenses { + license { + name = 'The Apache Software License, Version 2.0' + url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + scm { + url = 'https://github.com/spring-io/gradle-enterprise-conventions' + connection = 'scm:git:https://github.com/spring-io/gradle-enterprise-conventions' + } + developers { + developer { + id = 'wilkinsona' + name = 'Andy Wilkinson' + email = 'awilkinson@pivotal.io' + roles = ["Project lead"] + } + } + } + } + } +} diff --git a/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/ConventionsDevelocityListener.java b/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/ConventionsDevelocityListener.java new file mode 100644 index 0000000..36a1cc3 --- /dev/null +++ b/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/ConventionsDevelocityListener.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.maven; + +import com.gradle.develocity.agent.maven.api.DevelocityApi; +import com.gradle.develocity.agent.maven.api.DevelocityListener; +import io.spring.ge.conventions.core.BuildCacheConventions; +import io.spring.ge.conventions.core.BuildScanConventions; +import org.apache.maven.execution.MavenSession; +import org.codehaus.plexus.component.annotations.Component; + +/** + * {@link DevelocityListener} for configuring the use of Develocity hosted at + * ge.spring.io. + * + * @author Andy Wilkinson + */ +@Component(role = DevelocityListener.class, hint = "convention-develocity-maven-extension", + description = "Develocity conventions Maven extension") +public final class ConventionsDevelocityListener implements DevelocityListener { + + @Override + public void configure(DevelocityApi develocity, MavenSession MavenSession) throws Exception { + new BuildScanConventions(new ProcessBuilderProcessRunner()).execute(new MavenConfigurableDevelocity(develocity), + new MavenConfigurableBuildScan(develocity.getBuildScan())); + new BuildCacheConventions().execute(new MavenConfigurableBuildCache(develocity.getBuildCache())); + } + +} diff --git a/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/MavenConfigurableBuildCache.java b/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/MavenConfigurableBuildCache.java new file mode 100644 index 0000000..6794371 --- /dev/null +++ b/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/MavenConfigurableBuildCache.java @@ -0,0 +1,87 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.maven; + +import java.util.function.Consumer; + +import com.gradle.develocity.agent.maven.api.cache.BuildCacheApi; +import io.spring.ge.conventions.core.ConfigurableBuildCache; + +/** + * {@link ConfigurableBuildCache} for Maven builds. + * + * @author Andy Wilkinson + */ +class MavenConfigurableBuildCache implements ConfigurableBuildCache { + + private final BuildCacheApi buildCache; + + MavenConfigurableBuildCache(BuildCacheApi buildCache) { + this.buildCache = buildCache; + } + + @Override + public void local(Consumer local) { + local.accept(new MavenLocalBuildCache(this.buildCache.getLocal())); + } + + @Override + public void remote(Consumer remote) { + remote.accept(new MavenRemoteBuildCache(this.buildCache.getRemote())); + } + + private static final class MavenLocalBuildCache implements LocalBuildCache { + + private final com.gradle.develocity.agent.maven.api.cache.LocalBuildCache localBuildCache; + + private MavenLocalBuildCache(com.gradle.develocity.agent.maven.api.cache.LocalBuildCache localBuildCache) { + this.localBuildCache = localBuildCache; + } + + @Override + public void enable() { + this.localBuildCache.setEnabled(true); + } + + } + + private static final class MavenRemoteBuildCache implements RemoteBuildCache { + + private final com.gradle.develocity.agent.maven.api.cache.RemoteBuildCache remoteBuildCache; + + private MavenRemoteBuildCache(com.gradle.develocity.agent.maven.api.cache.RemoteBuildCache remoteBuildCache) { + this.remoteBuildCache = remoteBuildCache; + } + + @Override + public void enable() { + this.remoteBuildCache.setEnabled(true); + } + + @Override + public void enablePush() { + this.remoteBuildCache.setStoreEnabled(true); + } + + @Override + public void setServer(String server) { + this.remoteBuildCache.getServer().setUrl(server); + } + + } + +} diff --git a/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/MavenConfigurableBuildScan.java b/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/MavenConfigurableBuildScan.java new file mode 100644 index 0000000..7526b47 --- /dev/null +++ b/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/MavenConfigurableBuildScan.java @@ -0,0 +1,84 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.maven; + +import java.net.InetAddress; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +import com.gradle.develocity.agent.maven.api.scan.BuildScanApi; +import com.gradle.develocity.agent.maven.api.scan.BuildScanPublishing.PublishingContext; +import io.spring.ge.conventions.core.ConfigurableBuildScan; + +class MavenConfigurableBuildScan implements ConfigurableBuildScan { + + private final BuildScanApi buildScan; + + MavenConfigurableBuildScan(BuildScanApi buildScan) { + this.buildScan = buildScan; + } + + @Override + public void captureInputFiles(boolean capture) { + this.buildScan.getCapture().setFileFingerprints(capture); + } + + @Override + public void obfuscation(Consumer configurer) { + configurer.accept(new ObfuscationConfigurer() { + + @Override + public void ipAddresses(Function, ? extends List> obfuscator) { + MavenConfigurableBuildScan.this.buildScan.getObfuscation().ipAddresses(obfuscator); + } + + }); + } + + @Override + public void publishIfAuthenticated() { + this.buildScan.getPublishing().onlyIf(PublishingContext::isAuthenticated); + } + + @Override + public void uploadInBackground(boolean enabled) { + this.buildScan.setUploadInBackground(enabled); + } + + @Override + public void link(String name, String url) { + this.buildScan.link(name, url); + } + + @Override + public void tag(String tag) { + this.buildScan.tag(tag); + } + + @Override + public void value(String name, String value) { + this.buildScan.value(name, value); + } + + @Override + public void background(Consumer backgroundConfigurer) { + this.buildScan + .background((backgrounded) -> backgroundConfigurer.accept(new MavenConfigurableBuildScan(backgrounded))); + } + +} diff --git a/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/MavenConfigurableDevelocity.java b/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/MavenConfigurableDevelocity.java new file mode 100644 index 0000000..d07a7e1 --- /dev/null +++ b/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/MavenConfigurableDevelocity.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.maven; + +import com.gradle.develocity.agent.maven.api.DevelocityApi; +import io.spring.ge.conventions.core.ConfigurableDevelocity; + +/** + * {@link ConfigurableDevelocity} for Maven builds. + * + * @author Andy Wilkinson + */ +class MavenConfigurableDevelocity implements ConfigurableDevelocity { + + private final DevelocityApi develocity; + + MavenConfigurableDevelocity(DevelocityApi develocity) { + this.develocity = develocity; + } + + @Override + public String getServer() { + return this.develocity.getServer(); + } + + @Override + public void setServer(String server) { + this.develocity.setServer(server); + } + +} diff --git a/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/ProcessBuilderProcessRunner.java b/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/ProcessBuilderProcessRunner.java new file mode 100644 index 0000000..6c13a1d --- /dev/null +++ b/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/ProcessBuilderProcessRunner.java @@ -0,0 +1,83 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.maven; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.spring.ge.conventions.core.ProcessRunner; + +/** + * {@link ProcessRunner} implementation that uses {@link ProcessBuilder}. + * + * @author Andy Wilkinson + */ +class ProcessBuilderProcessRunner implements ProcessRunner { + + @Override + public void run(Consumer configurer) { + ProcessBuilder processBuilder = new ProcessBuilder(); + ProcessBuilderProcessSpec spec = new ProcessBuilderProcessSpec(processBuilder); + configurer.accept(spec); + try { + processBuilder.start().waitFor(); + Files.copy(spec.output, spec.outputStream); + } + catch (Exception ex) { + throw new RuntimeException("Process failed", ex); + } + } + + private static final class ProcessBuilderProcessSpec implements ProcessSpec { + + private final ProcessBuilder processBuilder; + + private final Path output; + + private OutputStream outputStream; + + private ProcessBuilderProcessSpec(ProcessBuilder processBuilder) { + this.processBuilder = processBuilder; + try { + this.output = Files.createTempFile("output", "txt"); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public void commandLine(Object... commandLine) { + List command = Stream.of(commandLine).map(Object::toString).collect(Collectors.toList()); + this.processBuilder.command(command); + } + + @Override + public void standardOutput(OutputStream standardOutput) { + this.processBuilder.redirectOutput(this.output.toFile()); + this.outputStream = standardOutput; + } + + } + +} diff --git a/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/package-info.java b/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/package-info.java new file mode 100644 index 0000000..4961acb --- /dev/null +++ b/gradle-enterprise-conventions-maven-extension/src/main/java/io/spring/ge/conventions/maven/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020-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. + */ + +/** + * Gradle Enterprise conventions for Spring projects built with Maven. + */ +package io.spring.ge.conventions.maven; diff --git a/gradle-enterprise-conventions-maven-extension/src/main/resources/META-INF/plexus/components.xml b/gradle-enterprise-conventions-maven-extension/src/main/resources/META-INF/plexus/components.xml new file mode 100644 index 0000000..7949754 --- /dev/null +++ b/gradle-enterprise-conventions-maven-extension/src/main/resources/META-INF/plexus/components.xml @@ -0,0 +1,10 @@ + + + + + com.gradle.develocity.agent.maven.api.DevelocityListener + conventions-develocity-listener + io.spring.ge.conventions.maven.ConventionsDevelocityListener + + + \ No newline at end of file diff --git a/gradle-enterprise-conventions-maven-extension/src/test/java/io/spring/ge/conventions/maven/MavenConfigurableBuildCacheTests.java b/gradle-enterprise-conventions-maven-extension/src/test/java/io/spring/ge/conventions/maven/MavenConfigurableBuildCacheTests.java new file mode 100644 index 0000000..be1392c --- /dev/null +++ b/gradle-enterprise-conventions-maven-extension/src/test/java/io/spring/ge/conventions/maven/MavenConfigurableBuildCacheTests.java @@ -0,0 +1,246 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.maven; + +import java.io.File; +import java.net.URI; + +import com.gradle.develocity.agent.maven.api.cache.BuildCacheApi; +import com.gradle.develocity.agent.maven.api.cache.CleanupPolicy; +import com.gradle.develocity.agent.maven.api.cache.Credentials; +import com.gradle.develocity.agent.maven.api.cache.LocalBuildCache; +import com.gradle.develocity.agent.maven.api.cache.MojoMetadataProvider; +import com.gradle.develocity.agent.maven.api.cache.NormalizationProvider; +import com.gradle.develocity.agent.maven.api.cache.RemoteBuildCache; +import com.gradle.develocity.agent.maven.api.cache.Server; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MavenConfigurableBuildCache}. + * + * @author Andy Wilkinson + */ +class MavenConfigurableBuildCacheTests { + + private final BuildCacheApi buildCacheApi = new TestBuildCacheApi(); + + private final MavenConfigurableBuildCache buildCache = new MavenConfigurableBuildCache(this.buildCacheApi); + + @Test + void localBuildCacheCanBeEnabled() { + this.buildCache.local((local) -> local.enable()); + assertThat(this.buildCacheApi.getLocal().isEnabled()).isTrue(); + } + + @Test + void remoteBuildCacheCanBeEnabled() { + this.buildCache.remote((remote) -> remote.enable()); + assertThat(this.buildCacheApi.getRemote().isEnabled()).isTrue(); + } + + @Test + void remoteBuildCacheCanHavePushEnabled() { + this.buildCache.remote((remote) -> remote.enablePush()); + assertThat(this.buildCacheApi.getRemote().isStoreEnabled()).isTrue(); + } + + @Test + void remoteBuildCacheCanHaveItsServerConfigured() { + this.buildCache.remote((remote) -> remote.setServer("https://ge.spring.io")); + assertThat(this.buildCacheApi.getRemote().getServer().getUrl()).isEqualTo(URI.create("https://ge.spring.io")); + } + + private static final class TestBuildCacheApi implements BuildCacheApi { + + private final LocalBuildCache local = new TestLocalBuildCache(); + + private final RemoteBuildCache remote = new TestRemoteBuildCache(); + + @Override + public LocalBuildCache getLocal() { + return this.local; + } + + @Override + public RemoteBuildCache getRemote() { + return this.remote; + } + + @Override + public boolean isRequireClean() { + throw new UnsupportedOperationException(); + } + + @Override + public void registerMojoMetadataProvider(MojoMetadataProvider metadataProvider) { + throw new UnsupportedOperationException(); + } + + @Override + public void registerNormalizationProvider(NormalizationProvider normalizationProvider) { + throw new UnsupportedOperationException(); + } + + @Override + public void setRequireClean(boolean requireClean) { + throw new UnsupportedOperationException(); + } + + private static final class TestLocalBuildCache implements LocalBuildCache { + + private boolean enabled; + + private boolean storeEnabled; + + @Override + public CleanupPolicy getCleanupPolicy() { + throw new UnsupportedOperationException(); + } + + @Override + public File getDirectory() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEnabled() { + return this.enabled; + } + + @Override + public boolean isStoreEnabled() { + return this.storeEnabled; + } + + @Override + public void setDirectory(File directory) { + throw new UnsupportedOperationException(); + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public void setStoreEnabled(boolean storeEnabled) { + this.storeEnabled = storeEnabled; + } + + } + + private static final class TestRemoteBuildCache implements RemoteBuildCache { + + private final Server server = new TestServer(); + + private boolean enabled; + + private boolean storeEnabled; + + @Override + public Server getServer() { + return this.server; + } + + @Override + public boolean isEnabled() { + return this.enabled; + } + + @Override + public boolean isStoreEnabled() { + return this.storeEnabled; + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public void setStoreEnabled(boolean storeEnabled) { + this.storeEnabled = storeEnabled; + } + + private static final class TestServer implements Server { + + private URI url; + + @Override + public Credentials getCredentials() { + throw new UnsupportedOperationException(); + } + + @Override + public String getServerId() { + throw new UnsupportedOperationException(); + } + + @Override + public URI getUrl() { + return this.url; + } + + @Override + public boolean isAllowInsecureProtocol() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isAllowUntrusted() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isUseExpectContinue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setAllowInsecureProtocol(boolean allowInsecureProtocol) { + throw new UnsupportedOperationException(); + } + + @Override + public void setAllowUntrusted(boolean allowUntrusted) { + throw new UnsupportedOperationException(); + } + + @Override + public void setServerId(String serverId) { + throw new UnsupportedOperationException(); + } + + @Override + public void setUrl(URI url) { + this.url = url; + } + + @Override + public void setUseExpectContinue(boolean useExpectContinue) { + throw new UnsupportedOperationException(); + } + + } + + } + + } + +} diff --git a/gradle-enterprise-conventions-maven-extension/src/test/java/io/spring/ge/conventions/maven/MavenConfigurableBuildScanTests.java b/gradle-enterprise-conventions-maven-extension/src/test/java/io/spring/ge/conventions/maven/MavenConfigurableBuildScanTests.java new file mode 100644 index 0000000..64fba75 --- /dev/null +++ b/gradle-enterprise-conventions-maven-extension/src/test/java/io/spring/ge/conventions/maven/MavenConfigurableBuildScanTests.java @@ -0,0 +1,319 @@ +/* + * Copyright 2020-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 io.spring.ge.conventions.maven; + +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import com.gradle.develocity.agent.maven.api.scan.BuildResult; +import com.gradle.develocity.agent.maven.api.scan.BuildScanApi; +import com.gradle.develocity.agent.maven.api.scan.BuildScanCaptureSettings; +import com.gradle.develocity.agent.maven.api.scan.BuildScanDataObfuscation; +import com.gradle.develocity.agent.maven.api.scan.BuildScanPublishing; +import com.gradle.develocity.agent.maven.api.scan.BuildScanPublishing.PublishingContext; +import com.gradle.develocity.agent.maven.api.scan.PublishedBuildScan; +import io.spring.ge.conventions.core.ConfigurableBuildScan; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link MavenConfigurableBuildScan}. + * + * @author Andy Wilkinson + */ +public class MavenConfigurableBuildScanTests { + + private final TestBuildScanApi buildScanApi = new TestBuildScanApi(); + + private final MavenConfigurableBuildScan buildScan = new MavenConfigurableBuildScan(this.buildScanApi); + + @Test + void captureOfInputFilesCanBeEnabled() { + this.buildScan.captureInputFiles(true); + assertThat(this.buildScanApi.capture.isFileFingerprints()).isTrue(); + } + + @Test + void ipAddressesCanBeObfuscated() throws UnknownHostException { + this.buildScan.obfuscation((obfuscation) -> obfuscation.ipAddresses( + (inetAddresses) -> inetAddresses.stream().map((address) -> "127.0.0.1").collect(Collectors.toList()))); + assertThat(this.buildScanApi.obfuscation.ipAddressesObfuscator).isNotNull(); + List obfuscatedAddresses = this.buildScanApi.obfuscation.ipAddressesObfuscator + .apply(Arrays.asList(InetAddress.getByName("10.0.0.1"), InetAddress.getByName("10.0.0.2"))); + assertThat(obfuscatedAddresses).containsExactly("127.0.0.1", "127.0.0.1"); + } + + @Test + void buildScansCanBeConfiguredToPublishIfAuthenticated() { + this.buildScan.publishIfAuthenticated(); + PublishingContext context = mock(PublishingContext.class); + given(context.isAuthenticated()).willReturn(true); + assertThat(this.buildScanApi.publishing.onlyIf.test(context)).isTrue(); + given(context.isAuthenticated()).willReturn(false); + assertThat(this.buildScanApi.publishing.onlyIf.test(context)).isFalse(); + } + + @Test + void whenTagsAreAddedThenBuildScanHasTags() { + this.buildScan.tag("some-tag"); + this.buildScan.tag("another-tag"); + assertThat(this.buildScanApi.tags).containsExactly("some-tag", "another-tag"); + } + + @Test + void whenLinkIsAddedThenBuildScanHasLink() { + this.buildScan.link("CI Server", "ci.example.com"); + assertThat(this.buildScanApi.links).containsEntry("CI Server", "ci.example.com"); + } + + @Test + void whenValueIsAddedThenBuildScanHasValue() { + this.buildScan.value("Branch", "1.2.x"); + assertThat(this.buildScanApi.values).containsEntry("Branch", "1.2.x"); + } + + @Test + void whenBuildingLocallyThenBackgroundUploadIsEnabled() { + this.buildScan.uploadInBackground(true); + assertThat(this.buildScanApi.uploadInBackground).isTrue(); + } + + @Test + void backgroundTasksAreInvoked() { + AtomicBoolean invoked = new AtomicBoolean(); + Consumer consumer = (buildScan) -> invoked.set(true); + this.buildScan.background(consumer); + assertThat(invoked.get()).isTrue(); + } + + private static final class TestBuildScanApi implements BuildScanApi { + + private final TestBuildScanCaptureSettings capture = new TestBuildScanCaptureSettings(); + + private final TestBuildScanDataObfuscation obfuscation = new TestBuildScanDataObfuscation(); + + private final TestBuildScanPublishing publishing = new TestBuildScanPublishing(); + + private final List tags = new ArrayList<>(); + + private final Map links = new HashMap<>(); + + private final Map values = new HashMap<>(); + + private boolean uploadInBackground; + + @Override + public void background(Consumer action) { + action.accept(this); + } + + @Override + public void buildFinished(Consumer action) { + throw new UnsupportedOperationException(); + } + + @Override + public void buildScanPublished(Consumer action) { + throw new UnsupportedOperationException(); + } + + @Override + public void capture(Consumer action) { + throw new UnsupportedOperationException(); + } + + @Override + public void executeOnce(String identifier, Consumer action) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean getAllowUntrustedServer() { + throw new UnsupportedOperationException(); + } + + @Override + public BuildScanCaptureSettings getCapture() { + return this.capture; + } + + @Override + public BuildScanDataObfuscation getObfuscation() { + return this.obfuscation; + } + + @Override + public BuildScanPublishing getPublishing() { + return this.publishing; + } + + @Override + public String getServer() { + throw new UnsupportedOperationException(); + } + + @Override + public String getTermsOfUseAgree() { + throw new UnsupportedOperationException(); + } + + @Override + public String getTermsOfUseUrl() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isUploadInBackground() { + throw new UnsupportedOperationException(); + } + + @Override + public void link(String name, String url) { + this.links.put(name, url); + } + + @Override + public void obfuscation(Consumer action) { + throw new UnsupportedOperationException(); + } + + @Override + public void publishing(Consumer action) { + throw new UnsupportedOperationException(); + } + + @Override + public void setAllowUntrustedServer(boolean allow) { + throw new UnsupportedOperationException(); + } + + @Override + public void setServer(URI url) { + throw new UnsupportedOperationException(); + } + + @Override + public void setTermsOfUseAgree(String agree) { + throw new UnsupportedOperationException(); + } + + @Override + public void setTermsOfUseUrl(String termsOfUseUrl) { + throw new UnsupportedOperationException(); + } + + @Override + public void setUploadInBackground(boolean uploadInBackground) { + this.uploadInBackground = uploadInBackground; + } + + @Override + public void tag(String tag) { + this.tags.add(tag); + } + + @Override + public void value(String name, String value) { + this.values.put(name, value); + } + + static class TestBuildScanCaptureSettings implements BuildScanCaptureSettings { + + private boolean fileFingerprints; + + @Override + public boolean isBuildLogging() { + throw new UnsupportedOperationException(); + } + + @Override + public void setBuildLogging(boolean capture) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isFileFingerprints() { + return this.fileFingerprints; + } + + @Override + public boolean isTestLogging() { + throw new UnsupportedOperationException(); + } + + @Override + public void setTestLogging(boolean capture) { + throw new UnsupportedOperationException(); + } + + @Override + public void setFileFingerprints(boolean capture) { + this.fileFingerprints = capture; + } + + } + + static final class TestBuildScanDataObfuscation implements BuildScanDataObfuscation { + + Function, ? extends List> ipAddressesObfuscator; + + @Override + public void hostname(Function obfuscator) { + throw new UnsupportedOperationException(); + } + + @Override + public void ipAddresses(Function, ? extends List> obfuscator) { + this.ipAddressesObfuscator = obfuscator; + } + + @Override + public void username(Function obfuscator) { + throw new UnsupportedOperationException(); + } + + } + + static final class TestBuildScanPublishing implements BuildScanPublishing { + + private Predicate onlyIf; + + @Override + public BuildScanPublishing onlyIf(Predicate onlyIf) { + this.onlyIf = onlyIf; + return this; + } + + } + + } + +} diff --git a/settings.gradle b/settings.gradle index fc40443..b200346 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,4 +12,8 @@ pluginManagement { } } -rootProject.name = 'gradle-enterprise-conventions-gradle-plugin' +rootProject.name = 'gradle-enterprise-conventions' + +include("gradle-enterprise-conventions-core") +include("gradle-enterprise-conventions-gradle-plugin") +include("gradle-enterprise-conventions-maven-extension") diff --git a/src/test/java/io/spring/ge/conventions/gradle/BuildCacheConventionsTests.java b/src/test/java/io/spring/ge/conventions/gradle/BuildCacheConventionsTests.java deleted file mode 100644 index 1b68402..0000000 --- a/src/test/java/io/spring/ge/conventions/gradle/BuildCacheConventionsTests.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2020-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 io.spring.ge.conventions.gradle; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import com.gradle.develocity.agent.gradle.buildcache.DevelocityBuildCache; -import org.gradle.api.Action; -import org.gradle.caching.BuildCacheServiceFactory; -import org.gradle.caching.configuration.BuildCache; -import org.gradle.caching.configuration.BuildCacheConfiguration; -import org.gradle.caching.local.DirectoryBuildCache; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link BuildCacheConventions}. - * - * @author Andy Wilkinson - */ -class BuildCacheConventionsTests { - - private final TestBuildCacheConfiguration buildCache = new TestBuildCacheConfiguration(); - - @Test - void localCacheIsEnabled() { - new BuildCacheConventions(DevelocityBuildCache.class).execute(this.buildCache); - assertThat(this.buildCache.local.isEnabled()).isTrue(); - } - - @Test - void remoteCacheIsEnabled() { - new BuildCacheConventions(DevelocityBuildCache.class).execute(this.buildCache); - assertThat(this.buildCache.remote.isEnabled()).isTrue(); - assertThat(this.buildCache.remote.getServer()).isEqualTo("https://ge.spring.io"); - assertThat(this.buildCache.remote.isPush()).isFalse(); - } - - @ParameterizedTest - @ValueSource(strings = { "https://ge.example.com/cache/", "https://ge.example.com/cache" }) - void remoteCacheUrlCanBeConfigured(String cacheUrl) { - Map env = new HashMap<>(); - env.put("GRADLE_ENTERPRISE_CACHE_URL", cacheUrl); - new BuildCacheConventions(DevelocityBuildCache.class, env).execute(this.buildCache); - assertThat(this.buildCache.remote.isEnabled()).isTrue(); - assertThat(this.buildCache.remote.getServer()).isEqualTo("https://ge.example.com"); - assertThat(this.buildCache.remote.isPush()).isFalse(); - } - - @Test - void remoteCacheServerCanBeConfigured() { - Map env = new HashMap<>(); - env.put("DEVELOCITY_CACHE_SERVER", "https://ge.example.com"); - new BuildCacheConventions(DevelocityBuildCache.class, env).execute(this.buildCache); - assertThat(this.buildCache.remote.isEnabled()).isTrue(); - assertThat(this.buildCache.remote.getServer()).isEqualTo("https://ge.example.com"); - assertThat(this.buildCache.remote.isPush()).isFalse(); - } - - @Test - void remoteCacheServerHasPrecedenceOverRemoteCacheUrl() { - Map env = new HashMap<>(); - env.put("GRADLE_ENTERPRISE_CACHE_URL", "https://ge-cache.example.com/cache/"); - env.put("DEVELOCITY_CACHE_SERVER", "https://ge.example.com"); - new BuildCacheConventions(DevelocityBuildCache.class, env).execute(this.buildCache); - assertThat(this.buildCache.remote.isEnabled()).isTrue(); - assertThat(this.buildCache.remote.getServer()).isEqualTo("https://ge.example.com"); - assertThat(this.buildCache.remote.isPush()).isFalse(); - } - - @Test - void whenAccessTokenIsProvidedInALocalEnvironmentThenPushingToTheRemoteCacheIsNotEnabled() { - new BuildCacheConventions(DevelocityBuildCache.class, - Collections.singletonMap("DEVELOCITY_ACCESS_KEY", "ge.example.com=a1b2c3d4")) - .execute(this.buildCache); - assertThat(this.buildCache.remote.isPush()).isFalse(); - } - - @Test - void whenAccessTokenIsProvidedInACiEnvironmentThenPushingToTheRemoteCacheIsEnabled() { - Map env = new HashMap<>(); - env.put("DEVELOCITY_ACCESS_KEY", "ge.example.com=a1b2c3d4"); - env.put("CI", "true"); - new BuildCacheConventions(DevelocityBuildCache.class, env).execute(this.buildCache); - assertThat(this.buildCache.remote.isPush()).isTrue(); - } - - @Test - void whenLegacyAccessTokenIsProvidedInALocalEnvironmentThenPushingToTheRemoteCacheIsNotEnabled() { - new BuildCacheConventions(DevelocityBuildCache.class, - Collections.singletonMap("GRADLE_ENTERPRISE_ACCESS_KEY", "ge.example.com=a1b2c3d4")) - .execute(this.buildCache); - assertThat(this.buildCache.remote.isPush()).isFalse(); - } - - @Test - void whenLegacyAccessTokenIsProvidedInACiEnvironmentThenPushingToTheRemoteCacheIsEnabled() { - Map env = new HashMap<>(); - env.put("GRADLE_ENTERPRISE_ACCESS_KEY", "ge.example.com=a1b2c3d4"); - env.put("CI", "true"); - new BuildCacheConventions(DevelocityBuildCache.class, env).execute(this.buildCache); - assertThat(this.buildCache.remote.isPush()).isTrue(); - } - - private static final class TestBuildCacheConfiguration implements BuildCacheConfiguration { - - private final DirectoryBuildCache local = new DirectoryBuildCache(); - - private final DevelocityBuildCache remote = new DevelocityBuildCache() { - }; - - @Override - public DirectoryBuildCache getLocal() { - return this.local; - } - - @Override - public BuildCache getRemote() { - throw new UnsupportedOperationException(); - } - - @Override - @Deprecated - public T local(Class cacheType) { - throw new UnsupportedOperationException(); - } - - @Override - public void local(Action action) { - action.execute(this.local); - } - - @Override - @Deprecated - public T local(Class cacheType, Action action) { - throw new UnsupportedOperationException(); - } - - @Override - public void registerBuildCacheService(Class cacheType, - Class> factory) { - throw new UnsupportedOperationException(); - } - - @Override - @SuppressWarnings("unchecked") - public T remote(Class cacheType) { - return (T) this.remote; - } - - @Override - public void remote(Action action) { - throw new UnsupportedOperationException(); - } - - @Override - public T remote(Class type, Action action) { - T cache = remote(type); - action.execute(cache); - return cache; - } - - } - -}