diff --git a/build.gradle b/build.gradle index 25fcfdc8..8bb35dc2 100644 --- a/build.gradle +++ b/build.gradle @@ -53,6 +53,9 @@ dependencies { implementation 'com.google.guava:guava' implementation 'com.netflix.nebula:nebula-dependency-recommender' implementation 'com.palantir.gradle.failure-reports:gradle-failure-reports-exceptions' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-guava' + implementation 'one.util:streamex' testImplementation platform('org.junit:junit-bom') testImplementation 'com.netflix.nebula:nebula-test' @@ -113,6 +116,13 @@ gradlePlugin { description = displayName tags.set(['versions']) } + versionsPropsIdeaPlugin { + id = 'com.palantir.version-props-idea' + implementationClass = 'com.palantir.gradle.versions.VersionsPropsIdeaPlugin' + displayName = 'Plugin to collect repositories for use by the IDEA plugin.' + description = displayName + tags.set(['versions']) + } } } diff --git a/changelog/@unreleased/pr-1233.v2.yml b/changelog/@unreleased/pr-1233.v2.yml new file mode 100644 index 00000000..9948d293 --- /dev/null +++ b/changelog/@unreleased/pr-1233.v2.yml @@ -0,0 +1,5 @@ +type: feature +feature: + description: Plugin to create a maven repo list in the .ideas folder + links: + - https://github.com/palantir/gradle-consistent-versions/pull/1233 diff --git a/src/main/java/com/palantir/gradle/versions/ConsistentVersionsPlugin.java b/src/main/java/com/palantir/gradle/versions/ConsistentVersionsPlugin.java index 85d8a3c2..51d7add5 100644 --- a/src/main/java/com/palantir/gradle/versions/ConsistentVersionsPlugin.java +++ b/src/main/java/com/palantir/gradle/versions/ConsistentVersionsPlugin.java @@ -31,6 +31,7 @@ public final void apply(Project project) { project.getPluginManager().apply(VersionsLockPlugin.class); project.getPluginManager().apply(VersionsPropsPlugin.class); project.getPluginManager().apply(GetVersionPlugin.class); + project.getPluginManager().apply(VersionsPropsIdeaPlugin.class); project.allprojects(proj -> { proj.getPluginManager().withPlugin("java", _plugin -> { diff --git a/src/main/java/com/palantir/gradle/versions/GenerateMavenRepositoriesTask.java b/src/main/java/com/palantir/gradle/versions/GenerateMavenRepositoriesTask.java new file mode 100644 index 00000000..095efd01 --- /dev/null +++ b/src/main/java/com/palantir/gradle/versions/GenerateMavenRepositoriesTask.java @@ -0,0 +1,95 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.gradle.versions; + +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.datatype.guava.GuavaModule; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.SetProperty; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import org.immutables.value.Value; + +public abstract class GenerateMavenRepositoriesTask extends DefaultTask { + + private static final ObjectMapper XML_MAPPER = new XmlMapper().registerModule(new GuavaModule()); + + private static final String MAVEN_REPOSITORIES_FILE_NAME = ".idea/gcv-maven-repositories.xml"; + + @Input + public abstract SetProperty getMavenRepositories(); + + @OutputFile + public abstract RegularFileProperty getOutputFile(); + + public GenerateMavenRepositoriesTask() { + getOutputFile().set(getProject().file(MAVEN_REPOSITORIES_FILE_NAME)); + } + + @TaskAction + final void action() { + writeRepositoriesToXml(); + } + + private void writeRepositoriesToXml() { + File file = getOutputFile().get().getAsFile(); + List repositories = getMavenRepositories().get().stream() + .map(ImmutableRepositoryConfig::of) + .collect(Collectors.toList()); + Repositories wrapped = ImmutableRepositories.of(repositories); + + try { + XML_MAPPER.writerWithDefaultPrettyPrinter().writeValue(file, wrapped); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Value.Immutable + @JsonDeserialize(as = ImmutableRepositoryConfig.class) + @JsonSerialize(as = ImmutableRepositoryConfig.class) + interface RepositoryConfig { + + @Value.Parameter + @JacksonXmlProperty(isAttribute = true) + String url(); + } + + @Value.Immutable + @JsonDeserialize(as = ImmutableRepositories.class) + @JsonSerialize(as = ImmutableRepositories.class) + @JsonRootName("repositories") + interface Repositories { + + @Value.Parameter + @JacksonXmlElementWrapper(useWrapping = false) + @JacksonXmlProperty(localName = "repository") + List repositories(); + } +} diff --git a/src/main/java/com/palantir/gradle/versions/VersionsLockPlugin.java b/src/main/java/com/palantir/gradle/versions/VersionsLockPlugin.java index 1d133870..42adac5e 100644 --- a/src/main/java/com/palantir/gradle/versions/VersionsLockPlugin.java +++ b/src/main/java/com/palantir/gradle/versions/VersionsLockPlugin.java @@ -108,7 +108,7 @@ public class VersionsLockPlugin implements Plugin { private static final Logger log = Logging.getLogger(VersionsLockPlugin.class); - static final GradleVersion MINIMUM_GRADLE_VERSION = GradleVersion.version("7.2"); + static final GradleVersion MINIMUM_GRADLE_VERSION = GradleVersion.version("7.6.4"); /** Root project configuration that collects all the dependencies from each project. */ static final String UNIFIED_CLASSPATH_CONFIGURATION_NAME = "unifiedClasspath"; diff --git a/src/main/java/com/palantir/gradle/versions/VersionsPropsIdeaPlugin.java b/src/main/java/com/palantir/gradle/versions/VersionsPropsIdeaPlugin.java new file mode 100644 index 00000000..e956eedc --- /dev/null +++ b/src/main/java/com/palantir/gradle/versions/VersionsPropsIdeaPlugin.java @@ -0,0 +1,54 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.gradle.versions; + +import java.util.List; +import java.util.stream.Collectors; +import org.gradle.StartParameter; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; +import org.gradle.api.tasks.TaskProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class VersionsPropsIdeaPlugin implements Plugin { + private static final Logger log = LoggerFactory.getLogger(VersionsPropsIdeaPlugin.class); + + @Override + public void apply(Project project) { + + if (!Boolean.getBoolean("idea.active")) { + return; + } + + TaskProvider writeMavenRepositories = project.getTasks() + .register("writeMavenRepositories", GenerateMavenRepositoriesTask.class, task -> { + task.getMavenRepositories().set(project.provider(() -> project.getRepositories().stream() + .filter(repo -> repo instanceof MavenArtifactRepository) + .map(repo -> + ((MavenArtifactRepository) repo).getUrl().toString()) + .map(url -> url.endsWith("/") ? url : url + "/") + .collect(Collectors.toSet()))); + }); + + StartParameter startParameter = project.getGradle().getStartParameter(); + List taskNames = startParameter.getTaskNames(); + taskNames.add(String.format(":%s", writeMavenRepositories.getName())); + startParameter.setTaskNames(taskNames); + } +} diff --git a/src/test/groovy/com/palantir/gradle/versions/GradleTestVersions.java b/src/test/groovy/com/palantir/gradle/versions/GradleTestVersions.java index b225e03a..e8393e56 100644 --- a/src/test/groovy/com/palantir/gradle/versions/GradleTestVersions.java +++ b/src/test/groovy/com/palantir/gradle/versions/GradleTestVersions.java @@ -22,5 +22,5 @@ public final class GradleTestVersions { private GradleTestVersions() {} public static final List GRADLE_VERSIONS = - List.of(VersionsLockPlugin.MINIMUM_GRADLE_VERSION.getVersion(), "7.6.4", "8.8"); + List.of(VersionsLockPlugin.MINIMUM_GRADLE_VERSION.getVersion(), "8.8"); } diff --git a/src/test/groovy/com/palantir/gradle/versions/VersionPropsIdeaPluginIntegrationSpec.groovy b/src/test/groovy/com/palantir/gradle/versions/VersionPropsIdeaPluginIntegrationSpec.groovy new file mode 100644 index 00000000..d0e5da09 --- /dev/null +++ b/src/test/groovy/com/palantir/gradle/versions/VersionPropsIdeaPluginIntegrationSpec.groovy @@ -0,0 +1,81 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.gradle.versions + +import com.fasterxml.jackson.dataformat.xml.XmlMapper +import com.fasterxml.jackson.datatype.guava.GuavaModule +import nebula.test.IntegrationSpec +import spock.util.environment.RestoreSystemProperties + +import java.util.stream.Collectors + +class VersionPropsIdeaPluginIntegrationSpec extends IntegrationSpec { + + def setup() { + //language=gradle + buildFile << """ + repositories { + maven { + url 'https://test' + } + maven { + url 'https://demo/' + } + mavenCentral() { metadataSources { mavenPom(); ignoreGradleMetadataRedirection() } } + } + + apply plugin: 'com.palantir.version-props-idea' + apply plugin: 'idea' + """.stripIndent(true) + + def ideaDir = new File(projectDir, '.idea') + ideaDir.mkdirs() + } + + def "plugin creates gcv-maven-repositories.xml file in .idea folder"() { + when: 'we run the first time' + runTasksSuccessfully('-Didea.active=true') + + then: 'we generate the correct config' + def repoFile = new File(projectDir, '.idea/gcv-maven-repositories.xml') + repoFile.exists() + + // language=xml + def expectedXml = ''' + + + + + + '''.stripIndent(true).trim() + + def projectNode = new XmlParser().parse(repoFile) + nodeToXmlString(projectNode) == expectedXml + + when: 'we run the second time' + def secondRun = runTasksSuccessfully('-Didea.active=true') + + then: "if nothing has changed, the task is then up-to-date" + secondRun.wasUpToDate(":writeMavenRepositories") + } + + private static String nodeToXmlString(debugRunConf) { + ByteArrayOutputStream baos = new ByteArrayOutputStream() + new XmlNodePrinter(new PrintWriter(baos)).print(debugRunConf) + return baos.toString().trim() + } +} diff --git a/versions.lock b/versions.lock index 0c8c02e2..f4362b2d 100644 --- a/versions.lock +++ b/versions.lock @@ -1,14 +1,21 @@ # Run ./gradlew writeVersionsLocks to regenerate this file +com.fasterxml.jackson.core:jackson-annotations:2.17.2 (2 constraints: fb298814) +com.fasterxml.jackson.core:jackson-core:2.17.2 (3 constraints: 9f40f174) +com.fasterxml.jackson.core:jackson-databind:2.17.2 (3 constraints: 51338be7) +com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.2 (1 constraints: 3e05463b) +com.fasterxml.jackson.datatype:jackson-datatype-guava:2.17.2 (1 constraints: 3e05463b) +com.fasterxml.woodstox:woodstox-core:6.7.0 (1 constraints: 41175526) com.google.code.findbugs:jsr305:3.0.2 (2 constraints: 1d0fb186) com.google.errorprone:error_prone_annotations:2.11.0 (2 constraints: 7b0f13a1) com.google.guava:failureaccess:1.0.1 (1 constraints: 140ae1b4) -com.google.guava:guava:31.1-jre (2 constraints: 791a280b) +com.google.guava:guava:31.1-jre (3 constraints: 29325c0c) com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava (1 constraints: bd17c918) com.google.j2objc:j2objc-annotations:1.3 (1 constraints: b809eda0) com.netflix.nebula:nebula-dependency-recommender:11.0.0 (1 constraints: 34052c3b) com.netflix.nebula:nebula-gradle-interop:1.0.11 (1 constraints: 9214098d) com.palantir.gradle.failure-reports:gradle-failure-reports-exceptions:1.2.0 (1 constraints: 0505f635) javax.inject:javax.inject:1 (1 constraints: 9d0e8743) +one.util:streamex:0.8.3 (1 constraints: 0d050636) org.apache.commons:commons-lang3:3.8.1 (1 constraints: 8d0d772f) org.apache.maven:maven-artifact:3.8.2 (1 constraints: 650ff37f) org.apache.maven:maven-builder-support:3.8.2 (1 constraints: 650ff37f) @@ -17,6 +24,7 @@ org.apache.maven:maven-model-builder:3.8.2 (1 constraints: 6c149a78) org.checkerframework:checker-qual:3.12.0 (1 constraints: 480a3bbf) org.codehaus.plexus:plexus-interpolation:1.25 (1 constraints: 320f8170) org.codehaus.plexus:plexus-utils:3.2.1 (3 constraints: 2d293a2f) +org.codehaus.woodstox:stax2-api:4.2.2 (2 constraints: 6a278bd2) org.eclipse.sisu:org.eclipse.sisu.inject:0.3.4 (1 constraints: 5f0fd77f) org.immutables:value:2.8.8 (1 constraints: 14051536) org.jetbrains:annotations:13.0 (1 constraints: df0e795c) @@ -27,9 +35,6 @@ org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50 (1 constraints: 58113be1) [Test dependencies] cglib:cglib-nodep:3.2.2 (1 constraints: 490ded24) -com.fasterxml.jackson.core:jackson-annotations:2.17.2 (1 constraints: 8c124321) -com.fasterxml.jackson.core:jackson-core:2.17.2 (1 constraints: 8c124321) -com.fasterxml.jackson.core:jackson-databind:2.17.2 (1 constraints: 3e05463b) com.netflix.nebula:nebula-test:10.0.0 (1 constraints: 3305273b) junit:junit:4.13.2 (2 constraints: 6a1ed87b) net.bytebuddy:byte-buddy:1.12.14 (2 constraints: ed164366) diff --git a/versions.props b/versions.props index d775e134..7565f6fc 100644 --- a/versions.props +++ b/versions.props @@ -1,5 +1,7 @@ com.fasterxml.jackson.*:jackson-* = 2.17.2 com.fasterxml.jackson.core:jackson-databind = 2.17.2 +com.fasterxml.jackson.dataformat:jackson-dataformat-xml = 2.17.2 +com.fasterxml.jackson.datatype:jackson-datatype-guava = 2.17.2 com.google.guava:guava = 31.1-jre com.netflix.nebula:nebula-dependency-recommender = 11.0.0 org.assertj:* = 3.23.1 @@ -8,6 +10,7 @@ org.junit:junit-bom = 5.9.1 org.mockito:mockito-core = 4.8.0 com.netflix.nebula:nebula-test = 10.0.0 com.palantir.gradle.failure-reports:* = 1.2.0 +one.util:streamex = 0.8.3 # Unnecessary once we have lock files com.google.code.findbugs:jsr305 = 3.0.2