Skip to content

🚧 Draft PR: Prototype for Gradle Native Helper Integration #12471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

kbukum1
Copy link
Contributor

@kbukum1 kbukum1 commented Jun 17, 2025

What are you trying to accomplish?

This PR introduces a prototype for a native Gradle helper using the Gradle Tooling API to improve Dependabot's support for Gradle projects.


✅ What’s included

  • A standalone Kotlin-based CLI tool under gradle/helpers/
  • A GradleHelper class using the Tooling API to extract dependency metadata
  • A CLI entrypoint that accepts JSON over stdin and returns JSON over stdout
  • Gradle wrapper setup (./gradlew) and shadowJar packaging
  • Sample Gradle project structure under spec/fixtures/projects/ for manual testing
  • A comprehensive README.md explaining structure, usage, and roadmap

🤔 Why

Dependabot currently handles some Gradle functionality, including recent support for gradle.lockfile. However, several challenges remain:

  • No native resolution of transitive dependencies using Gradle’s real logic
  • Lack of visibility into custom plugins, configuration blocks, and settings
  • Limited support for resolving dynamically versioned or project-specific dependencies
  • Fragmented logic between Ruby and manually-parsed Gradle files

This helper is a prototype to validate the feasibility of embedding a native JVM-based interface, bringing us closer to parity with how we handle ecosystems like Maven or pip.


🔍 What it does

Currently, the CLI supports:

{
  "function": "list_dependencies",
  "args": {
    "projectDir": "/path/to/gradle/project"
  }
}

It will return a flat list of dependencies based on the IdeaProject model.


🧪 How to test

Build the helper:

cd gradle/helpers
./gradlew shadowJar

Run it:

echo '{"function":"list_dependencies","args":{"projectDir":"spec/fixtures/projects/sample-gradle-project"}}' \
  | java -jar build/libs/gradle-helper.jar

You can add more example projects under spec/fixtures/projects/ for local experimentation.


📦 Integration Plan

The Ruby side of Dependabot can:

  • Extract a real project into a temp directory
  • Invoke this CLI via SharedHelpers.run_helper_subprocess
  • Parse and use the JSON output

We expect to add new commands over time and use this structure to unify and simplify Gradle metadata handling.


🔮 Follow-ups (not in this PR)

  • Investigate if further enhancement to gradle.lockfile support is needed (e.g., diffing, native parsing)

  • Add commands to:

    • Resolve dynamic versions to fixed versions
    • Generate or validate lockfiles
    • Detect applied plugins and repositories
  • Support for multi-project and included builds

  • Support for dependency graph submission with Gradle's full resolution model


📌 Notes for Reviewers

  • This PR is a minimal prototype, intended to validate architecture and tooling

  • Feedback welcome on:

    • CLI contract design (stdin JSON / stdout JSON)
    • Integration via SharedHelpers
    • Tooling API model selection and extensibility
  • We're prioritizing correctness and extensibility over performance at this stage


✅ Checklist

  • CLI accepts structured input and returns structured output
  • Uses Gradle Tooling API instead of manual file parsing
  • Can be invoked by Ruby using SharedHelpers
  • README documents architecture and usage
  • Project builds and runs via ./gradlew shadowJar

@github-actions github-actions bot added the L: java:gradle Maven packages via Gradle label Jun 17, 2025
@kbukum1
Copy link
Contributor Author

kbukum1 commented Jun 18, 2025

Example calling dependency list command

echo '{"function":"list_dependencies","args":{"projectDir":"spec/fixtures/projects/sample_project"}}' \          
  | java -jar build/libs/gradle-helper.jar

Response:

{
  "result": [
    "IdeaLibraryDependency{file=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/32.1.2-jre/5e64ec7e056456bef3a4bc4c6fdaef71e8ab6318/guava-32.1.2-jre.jar, source=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/32.1.2-jre/e1911d4544f426600132fbd450a7ccab8a3ce8dc/guava-32.1.2-jre-sources.jar, javadoc=null, exported=false, scope='IdeaDependencyScope{scope='COMPILE'}', id='GradleModuleVersion{group='com.google.guava', name='guava', version='32.1.2-jre'}'}",
    "IdeaLibraryDependency{file=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/1dcf1de382a0bf95a3d8b0849546c88bac1292c9/failureaccess-1.0.1.jar, source=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/1d064e61aad6c51cc77f9b59dc2cccc78e792f5a/failureaccess-1.0.1-sources.jar, javadoc=null, exported=false, scope='IdeaDependencyScope{scope='COMPILE'}', id='GradleModuleVersion{group='com.google.guava', name='failureaccess', version='1.0.1'}'}",
    "IdeaLibraryDependency{file=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/com.google.guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/b421526c5f297295adef1c886e5246c39d4ac629/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar, source=null, javadoc=null, exported=false, scope='IdeaDependencyScope{scope='COMPILE'}', id='GradleModuleVersion{group='com.google.guava', name='listenablefuture', version='9999.0-empty-to-avoid-conflict-with-guava'}'}",
    "IdeaLibraryDependency{file=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/25ea2e8b0c338a877313bd4672d3fe056ea78f0d/jsr305-3.0.2.jar, source=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/b19b5927c2c25b6c70f093767041e641ae0b1b35/jsr305-3.0.2-sources.jar, javadoc=null, exported=false, scope='IdeaDependencyScope{scope='COMPILE'}', id='GradleModuleVersion{group='com.google.code.findbugs', name='jsr305', version='3.0.2'}'}",
    "IdeaLibraryDependency{file=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/org.checkerframework/checker-qual/3.33.0/de2b60b62da487644fc11f734e73c8b0b431238f/checker-qual-3.33.0.jar, source=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/org.checkerframework/checker-qual/3.33.0/ee4ecf6c55e3f2b05b839aa05c70f1298437ebe9/checker-qual-3.33.0-sources.jar, javadoc=null, exported=false, scope='IdeaDependencyScope{scope='COMPILE'}', id='GradleModuleVersion{group='org.checkerframework', name='checker-qual', version='3.33.0'}'}",
    "IdeaLibraryDependency{file=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/com.google.errorprone/error_prone_annotations/2.18.0/89b684257096f548fa39a7df9fdaa409d4d4df91/error_prone_annotations-2.18.0.jar, source=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/com.google.errorprone/error_prone_annotations/2.18.0/8781f46ef842929cf22e83273ef0d2091b96845f/error_prone_annotations-2.18.0-sources.jar, javadoc=null, exported=false, scope='IdeaDependencyScope{scope='COMPILE'}', id='GradleModuleVersion{group='com.google.errorprone', name='error_prone_annotations', version='2.18.0'}'}",
    "IdeaLibraryDependency{file=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/2.8/c85270e307e7b822f1086b93689124b89768e273/j2objc-annotations-2.8.jar, source=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/2.8/74fb66e276fc42784d884d51f841e35232bc90de/j2objc-annotations-2.8-sources.jar, javadoc=null, exported=false, scope='IdeaDependencyScope{scope='PROVIDED'}', id='GradleModuleVersion{group='com.google.j2objc', name='j2objc-annotations', version='2.8'}'}",
    "IdeaLibraryDependency{file=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/junit/junit/4.13.2/8ac9e16d933b6fb43bc7f576336b8f4d7eb5ba12/junit-4.13.2.jar, source=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/junit/junit/4.13.2/33987872a811fe4d4001ed494b07854822257f42/junit-4.13.2-sources.jar, javadoc=null, exported=false, scope='IdeaDependencyScope{scope='TEST'}', id='GradleModuleVersion{group='junit', name='junit', version='4.13.2'}'}",
    "IdeaLibraryDependency{file=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/42a25dc3219429f0e5d060061f71acb49bf010a0/hamcrest-core-1.3.jar, source=/Users/kbukum1/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/1dc37250fbc78e23a65a67fbbaf71d2e9cbc3c0b/hamcrest-core-1.3-sources.jar, javadoc=null, exported=false, scope='IdeaDependencyScope{scope='TEST'}', id='GradleModuleVersion{group='org.hamcrest', name='hamcrest-core', version='1.3'}'}"
  ]
}

@kbukum1 kbukum1 requested a review from jakecoffman June 18, 2025 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
L: java:gradle Maven packages via Gradle
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant