toolbox icon indicating copy to clipboard operation
toolbox copied to clipboard

Published pom leaks gradle-api dependency

Open MarkRx opened this issue 2 years ago • 3 comments

The generated pom & module files leak the gradle-api jar as a dependency when used in a compileOnly configuration. These should not be exported. This behavior differs from the java-gradle-plugin plugin.

Gradle: 6.9.2 Plugin: 1.6.8

build.gradle using dev.gradleplugins.java-gradle-plugin

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'dev.gradleplugins.java-gradle-plugin:dev.gradleplugins.java-gradle-plugin.gradle.plugin:1.6.8'
    }
}

group = 'com.myplugin'
version = '1.0.0'

apply plugin: 'dev.gradleplugins.java-gradle-plugin'
apply plugin: 'java'
apply plugin: 'maven-publish'


gradlePlugin {
    plugins {
        myPlugin {
            id = 'com.myplugin'
            implementationClass = 'com.myplugin.MyPlugin'
        }
    }
    compatibility {
        minimumGradleVersion = '4.10.3'
    }
}

dependencies {
    compileOnly gradleApi('4.10.3')
}

pom.xml using dev.gradleplugins.java-gradle-plugin

Observe how gradle-api is exported as a compile dependency. It should not be.

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <!-- This module was also published with a richer model, Gradle metadata,  -->
  <!-- which should be used instead. Do not delete the following line which  -->
  <!-- is to indicate to Gradle or any Gradle module metadata file consumer  -->
  <!-- that they should prefer consuming it instead. -->
  <!-- do_not_remove: published-with-gradle-metadata -->
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.myplugin</groupId>
  <artifactId>gradle_testplugin</artifactId>
  <version>1.0.0</version>
  <dependencies>
    <dependency>
      <groupId>dev.gradleplugins</groupId>
      <artifactId>gradle-api</artifactId>
      <version>4.10.3</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
</project>

build.gradle using java-gradle-plugin

group = 'com.myplugin'
version = '1.0.0'

apply plugin: 'java-gradle-plugin'
apply plugin: 'java'
apply plugin: 'maven-publish'


gradlePlugin {
    plugins {
        myPlugin {
            id = 'com.myplugin'
            implementationClass = 'com.myplugin.MyPlugin'
        }
    }
}

dependencies {
    compileOnly gradleApi()
}

pom.xml using java-gradle-plugin

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <!-- This module was also published with a richer model, Gradle metadata,  -->
  <!-- which should be used instead. Do not delete the following line which  -->
  <!-- is to indicate to Gradle or any Gradle module metadata file consumer  -->
  <!-- that they should prefer consuming it instead. -->
  <!-- do_not_remove: published-with-gradle-metadata -->
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.myplugin</groupId>
  <artifactId>gradle_testplugin</artifactId>
  <version>1.0.0</version>
</project>

MarkRx avatar Oct 13 '22 21:10 MarkRx

This seems to be because the plugin adds the gradle-api dependency to the compileOnlyApi when using Gradle 6.7+ (see AddGradleApiDependencyToCompileOnlyApiConfiguration).

The compileOnlyApi configuration exports compile dependencies. The compileOnly configuration does not.

MarkRx avatar Oct 13 '22 21:10 MarkRx

I remember changing to compileOnlyApi because of the following logic, if someone depends on a plugin and builds against it, it should also use the same-ish Gradle API, given the plugin is highly opinionated to Gradle runtime. However, we could argue that this use case should be strictly confined to "plugin libraries", which is not a concept the Gradle team modelled. In theory, we depend on plugins for their API, which in turn uses Gradle APIs.

However, I think I see the problem. When the plugin is consumed, it will fetch dependencies of the compile scope from the plugin marker POM because that is how Gradle does it. Instead, we should keep the dependency on the module metadata (so straight consumers are happy) but remove the dependency from the plugin marker as it should be provided. Does that make sense?

I will add some tests to resolve only using the plugin marker and validate that we are pulling the Gradle API. Then, I will add configuration to the plugin marker.

One thing to note, there is no need to add compileOnly gradleApi(<minimum-gradle-version>). The plugin already adds the information under compileOnlyApi based on the minimumGradleVersion. You can force a specific API version by using gradleApiVersion (which is derived by default from minimumGradleVersion).

lacasseio avatar Nov 28 '22 07:11 lacasseio

Here is a workaround:

publishing {
    publications {
        pluginMaven(MavenPublication) {
            // Workaround for https://github.com/gradle-plugins/toolbox/issues/89
            pom.withXml {
                def parent = asElement().getElementsByTagName("dependencies").item(0)
                def deps = parent.getElementsByTagName("dependency")
                def gradleApi = deps.find {
                    def groupId = it.getElementsByTagName("groupId").item(0).firstChild.nodeValue
                    def artifactId = it.getElementsByTagName("artifactId").item(0).firstChild.nodeValue
                    groupId.equals('dev.gradleplugins') && artifactId.equals('gradle-api')
                }
                parent.removeChild(gradleApi)
            }
        }
    }
}

MarkRx avatar Jan 31 '23 16:01 MarkRx