Skip to content

feat(manifest): attribute Gradle direct dependencies to their declaring build script - #1593

Merged
Jeppe Fredsgaard Blaabjerg (jfblaa) merged 3 commits into
v1.xfrom
jfblaa/facts-definition-sites
Oct 8, 2026
Merged

Jeppe Fredsgaard Blaabjerg (jfblaa) merged 3 commits into
v1.xfrom
jfblaa/facts-definition-sites

Conversation

@jfblaa

@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

LLM Description written by Claude Code:claude-opus-5-5

A Gradle subproject configured entirely from the root build script (via subprojects {} or project(':x') {}) has no build file of its own. Its direct dependencies therefore carried only the facts file in manifestFiles, which made them indistinguishable from transitive ones and left nothing to route by subproject. Gradle direct dependencies now always carry a build-file mark.

  • A direct dependency is marked with the in-build script that declared it: the root build script for dependencies added from the root, a script plugin for apply from, and the subproject's own script otherwise. The init script records the script running when each dependency is added. Groovy stack frames carry its path. For Kotlin DSL, the path comes from the script class loader's internal scope id, which needs Java 9+.
  • If no declaring script is known (e.g. a dependency added by a plugin or lazily at resolution), the mark is the subproject's configured buildFile. That file may not exist on disk; it is still emitted because the mark's presence is the signal that the dependency is direct for this subproject.
  • Gradle projects[].manifestFiles is the project's own build file, else the scripts declaring its dependencies (typically the root build script), else the configured build file.
  • Maven and sbt are unchanged.

Known limitations

  • depscan registers a mark for a missing file as a manifest of size 0 (REA-900).
  • A dependency declared in a script outside every project directory (e.g. gradle/deps.gradle) is marked with that script. Coana's Gradle fix then also targets the root project. This is accepted as known behaviour.

Coana
Coana confirmed it doesn't infer directness from build-file marks, ignores marks for files it wasn't given, and reads projects[].manifestFiles only for Maven. No coordinated release needed.

Testing

  • Unit tests cover declaring-script marks, the configured-build-file fallback and projects[].manifestFiles.
  • Real runs on Gradle 5.6 (Groovy), 6.9 and 7.6 (Kotlin) and 9.2 (both): dependencies from root subprojects {}, root project(':x') {}, apply from script plugins, the subproject's own script, and buildSrc convention plugins (falling back to the build file). End-to-end through socket manifest gradle --facts on a Kotlin DSL build.

🤖 Generated with Claude Code


Note

Medium Risk
Changes Gradle facts emission and SBOM manifestFiles semantics used for routing direct deps; Maven is untouched but downstream tools may treat new or missing-file marks differently.

Overview
Gradle direct dependencies now get manifestFiles that name the build script that declared them (root build.gradle, apply from plugins, or the subproject script), instead of only listing every subproject build file that pulled them in directly.

The Gradle init script records declarations via declared line-protocol records (stack walk for Groovy; Kotlin DSL resolves .kts paths from the script class loader on Java 9+). The assembler prefers those scripts per group:name; when none is known it falls back to the subproject’s configured buildFile, including files not on disk (projectBuild + missing).

projects[].manifestFiles for Gradle now uses the module’s own build file when present, otherwise the scripts that declare its dependencies, otherwise the configured (possibly missing) build file. Maven/sbt assembly behavior is unchanged aside from type/docs updates.

Reviewed by Cursor Bugbot for commit 2d0110c. Configure here.

… them

A direct dependency's build-file mark is now the in-build script that
declared it: the root build script for one added from subprojects {} or
project(':x') {}, a script plugin for apply from. A subproject configured
entirely from the root therefore gets the root script, both on its direct
dependencies and in projects[].manifestFiles, instead of no build file.
A dependency no build script declared, such as one added by a plugin,
keeps the project's own build file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uild file

A direct dependency no build script is known to declare now falls back to
the subproject's configured build file even when it is absent on disk, so
every Gradle direct dependency names the subproject pulling it in. The
same configured file is the last fallback for projects[].manifestFiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue. You can view the agent here.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2d0110c. Configure here.

Comment thread src/commands/manifest/scripts/socket-facts.init.gradle
@jfblaa
Jeppe Fredsgaard Blaabjerg (jfblaa) merged commit 22a924e into v1.x Oct 8, 2026
7 checks passed
@jfblaa
Jeppe Fredsgaard Blaabjerg (jfblaa) deleted the jfblaa/facts-definition-sites branch October 8, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants