Skip to content

feat(java): resolve Gradle artifact names from settings.gradle#174

Merged
anderruiz merged 19 commits into
mainfrom
ander/gradle-settings-artifact-id
Jun 19, 2026
Merged

feat(java): resolve Gradle artifact names from settings.gradle#174
anderruiz merged 19 commits into
mainfrom
ander/gradle-settings-artifact-id

Conversation

@anderruiz

Copy link
Copy Markdown
Contributor

Summary

  • Parse settings.gradle / settings.gradle.kts to resolve canonical project names in Gradle GetArtifact() methods
  • Support rootProject.name, include declarations, and project(':x').name overrides (both Groovy and Kotlin DSL)
  • Integrate into both GradleLockExtractor and GradleVerificationMetadataExtractor, with fallback to directory basename when no settings file exists

Problem

The Gradle extractors derive artifact IDs using directory basenames (e.g., filepath.Base(dir)), but Gradle projects define their canonical names in settings.gradle. This produces incorrect artifact IDs when rootProject.name or project(':x').name overrides differ from the directory structure -- which is common in multi-module Gradle builds.

Changes

New file: pkg/extractor/java/parse-gradle-settings.go

  • parseGradleSettingsProjectName(rootDir, projectDir) reads settings.gradle/.kts and returns the canonical name
  • Three regex patterns: rootProject.name, include declarations, project(':x').name overrides
  • Handles nested subproject paths (:sub:module -> sub/module)
  • Falls back to "" (caller uses directory basename) when settings file is absent or project not declared

Modified: parse-gradle-lock.go and parse-gradle-verification-metadata.go

  • Both GetArtifact() methods now call parseGradleSettingsProjectName before falling back to filepath.Base()

Test plan

  • 11 unit tests for parseGradleSettingsProjectName covering: root project (single/double quotes), subproject from include, name overrides, Kotlin DSL, multiple includes, nested subprojects, no settings file, project not in settings, empty rootDir
  • 2 integration tests for GradleLockExtractor.GetArtifact (root project name, subproject override)
  • 1 integration test for GradleVerificationMetadataExtractor.GetArtifact (root project name)
  • All existing tests pass (fallback behavior preserved)
  • Lints pass

🤖 Generated with Claude Code

- Add parseGradleSettingsProjectName function that reads settings.gradle/kts
  from the root directory and resolves the canonical project name
- Parse rootProject.name for root projects, include declarations for
  subprojects, and project(':x').name for name overrides
- Support both Groovy DSL (single quotes) and Kotlin DSL (double quotes)
- Handle nested subproject paths (e.g. :sub:module -> sub/module)
- Fall back to empty string when no settings file or project not declared
- Add 11 unit tests covering all parsing scenarios and edge cases

Rationale: Gradle projects define their canonical names in settings.gradle,
not by directory basename. The existing GetArtifact logic used directory names,
producing incorrect artifact IDs when rootProject.name or project overrides
differ from the directory structure. This parser enables correct name resolution.

This commit made by [/dd:git:commit:atomic](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/atomic.md)
- Update GradleLockExtractor.GetArtifact to resolve project name from
  settings.gradle via parseGradleSettingsProjectName, with directory
  basename fallback
- Update GradleVerificationMetadataExtractor.GetArtifact with the same
  settings.gradle resolution logic
- Add integration tests for both extractors verifying settings.gradle
  name resolution (root project and subproject with name override)
- Existing tests continue to pass, confirming fallback behavior when
  no settings.gradle is present

Rationale: With the settings.gradle parser in place, the Gradle extractors
can now derive correct artifact IDs from the authoritative source instead
of relying on directory basenames, which are often different from the
project's declared name in multi-module Gradle builds.

This commit made by [/dd:git:commit:atomic](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/atomic.md)
@anderruiz
anderruiz requested a review from a team as a code owner June 17, 2026 16:44
@datadog-official

datadog-official Bot commented Jun 17, 2026

Copy link
Copy Markdown

🎯 Code Coverage (details)
Patch Coverage: 84.71%
Overall Coverage: 84.51% (+0.20%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: d7d2b46 | Docs | Datadog PR Page | Give us feedback!

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d00b9e6a00

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkg/extractor/java/gradle_settings.go
Subprojects commonly inherit group from the root via allprojects { group = '...' }
without redeclaring it in their own build.gradle. GetArtifact now falls back to
extractGroupFromRootBuildFile(ctx.RootDir) when the subproject's own build file
has no group declaration, so artifact.Name is populated for all subprojects.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4fc4a2b3a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkg/extractor/java/parse-gradle-settings.go Outdated
…deRefRe

- Restrict includeRefRe to only match Gradle project paths (leading ':'),
  avoiding false positives from inline comments or other quoted strings
- Add MultilineInclude_SingleItem and MultilineInclude_MultiItem_NotSupported
  tests to document actual regex behaviour around multiline include statements
- Add InheritsGroupFromRootBuildFile integration test for
  GradleVerificationMetadataExtractor, mirroring the equivalent lock extractor test

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0dacafb47b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkg/extractor/java/gradle_settings.go
anderruiz and others added 2 commits June 17, 2026 22:14
- Normalize rootDir with filepath.Abs in parseGradleSettingsProjectName
  so filepath.Rel works when the scanner is invoked with a relative path
  (e.g. 'datadog-sbom-generator scan .'), where ctx.RootDir is '.' but
  DepFile.Path() is absolute.

- Restrict extractGroupFromRootBuildFile to allprojects/subprojects blocks
  only. A top-level 'group = x' in the root build file applies only to the
  root project in Gradle and must not be used as a fallback for subprojects.
  Introduces inheritedGroupRe with (?s) flag to span the block body.

- Update tests to document the new behaviour: top-level group is not
  inherited; allprojects/subprojects block group is.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
The file is a utility module (no extractor registered), not a parser
implementation. Keeping the parse- prefix caused TestExtractDeps_FindsExpectedExtractor
to count it as an extractor via the */parse-*.go glob and fail with an
off-by-one mismatch.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 389c4a5efd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkg/extractor/java/gradle_settings.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 315c8aa01f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkg/extractor/java/gradle_settings.go Outdated
Replace `(?s).*?` with `[^{]*` so the pattern stops at the first nested
block opening. This prevents a task's `group = "verification"` inside an
allprojects block from being mistakenly captured as the project group when
it appears before the real `group = 'com.example'` assignment.
@anderruiz
anderruiz force-pushed the ander/gradle-settings-artifact-id branch from 315c8aa to 947c91d Compare June 18, 2026 08:16

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 947c91d6ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkg/extractor/java/gradle_settings.go Outdated
Comment thread pkg/extractor/java/parse-gradle-lock.go Outdated
…fallback

1. Replace inheritedGroupRe (unbounded (?s).*? regex) with extractBlockGroup,
   a brace-counting function that stays strictly within the allprojects/subprojects
   block body. This prevents a top-level group = that appears after the block's
   closing } from being incorrectly captured as an inherited group.

2. Guard the extractGroupFromRootBuildFile fallback in both GetArtifact
   implementations: only call it when projectDir != ctx.RootDir. The root
   project is excluded from subprojects {} blocks in Gradle, so a root build
   file that contains only subprojects { group = 'x' } must not assign that
   group to the root artifact itself.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2dd76e332c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

projectDir := filepath.Dir(filepath.Dir(f.Path()))
group := extractTopLevelGroup(content)
if group == "" {
if ctx.RootDir != "" && projectDir != ctx.RootDir {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow allprojects groups on root artifacts

When verification-metadata.xml belongs to the root project and the root build file declares allprojects { group = 'com.datadoghq' }, projectDir equals ctx.RootDir, so this guard skips the only fallback that can read that group and leaves artifact.Name empty. Gradle allprojects applies to the root project too; only subprojects should be excluded, and the added TestGradleVerificationMetadataExtractor_GetArtifact_InheritsGroupFromRootBuildFile covers this exact scenario and would fail under normal test execution.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Added extractAllProjectsGroupFromRootBuildFile that searches only allprojects { } blocks (not subprojects { }), and updated the root-project path in both parse-gradle-verification-metadata.go and parse-gradle-lock.go to call it when projectDir == absRootDir.

Comment thread pkg/extractor/java/parse-gradle-lock.go Outdated
// Only fall back to root build file for subprojects: the root project is
// excluded from subprojects { } blocks, so a subprojects-only group in the
// root build file must not be assigned to the root artifact itself.
if ctx.RootDir != "" && projectDir != ctx.RootDir {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize the root before the subproject guard

Fresh evidence: scanDir still passes the raw scan argument as ScanContext.RootDir, while DepFile.Path() is absolute, so a common scan . invocation makes the root lockfile's projectDir != ctx.RootDir comparison true. In a root build that only has subprojects { group = ... }, this then assigns the subproject-only group to the root artifact, which can make findArtifact classify unrelated external coordinates as internal; normalize ctx.RootDir before this comparison as is already done for filepath.Rel.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Added filepath.Abs normalization of ctx.RootDir before the root-vs-subproject comparison in parse-gradle-lock.go, matching the pattern already used in parseGradleSettingsProjectName.

Comment thread pkg/extractor/java/gradle_settings.go Outdated
}

blockBody := src[absOpen+1 : blockEnd]
if m := groupAssignRe.FindSubmatch(blockBody); m != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Ignore nested Gradle task groups

Fresh evidence: although extractBlockGroup now bounds the search to the outer allprojects/subprojects block, this FindSubmatch(blockBody) still scans nested blocks. For allprojects { tasks.register("check") { group = "verification" } group = "com.acme" }, it returns the task group before the project group, so subprojects get coordinates like verification:api and findArtifact can miss or misclassify internal Gradle dependencies.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Added findGroupAtTopLevel which blanks out nested block content (via depth tracking) before applying groupAssignRe, so a task group = "verification" inside a nested block is never captured instead of the real project group.

anderruiz and others added 2 commits June 18, 2026 16:30
…fallback

- Add allProjectsBlockHeaderRe and extractAllProjectsGroupFromRootBuildFile to
  restrict the inherited-group fallback for root artifacts to allprojects { }
  only (Gradle allprojects applies to root; subprojects does not)
- Normalize ctx.RootDir with filepath.Abs before root-vs-subproject comparison
  in both GetArtifact implementations so "scan ." (relative path) works correctly
- Add findGroupAtTopLevel to skip group = assignments inside nested blocks
  (e.g. task registrations) when searching for inherited group in extractBlockGroup

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d62f538dfc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkg/extractor/java/gradle_settings.go Outdated
Comment on lines +159 to +160
if g := findGroupAtTopLevel(blockBody); g != "" {
return g

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use later Gradle group overrides

When a subproject has no local group and the root build file configures both blocks, e.g. allprojects { group = 'com.root' } followed by subprojects { group = 'com.sub' }, the later assignment is the effective group for subprojects. This returns as soon as it finds the first block group, so GetArtifact emits com.root:api instead of com.sub:api, which can make findArtifact miss or misclassify internal Gradle project dependencies; keep scanning and use the last applicable assignment in file order.

Useful? React with 👍 / 👎.

…erride order

When a root build file declares both allprojects { group = 'com.root' } and
subprojects { group = 'com.sub' }, Gradle applies both in order so the later
assignment wins for subprojects. extractBlockGroup was returning on the first
match, producing com.root:api instead of com.sub:api. Now scans all matching
blocks and returns the last group found.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f1f937b28

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

func extractBlockGroup(src []byte, findHeader func([]byte) []int) string {
last := ""
for searchFrom := 0; searchFrom < len(src); {
loc := findHeader(src[searchFrom:])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip commented Gradle blocks when inheriting groups

When a root build file has an old allprojects/subprojects block commented out after the real one, this scans the raw bytes for block headers and treats the commented block as a later override. For example, a trailing // subprojects { ... group = 'old' ... } makes subprojects without a local group get old:<name>, so findArtifact can miss or misclassify internal Gradle dependencies; strip/ignore comments before looking for inherited group blocks.

Useful? React with 👍 / 👎.

…blocks

Commented-out allprojects/subprojects blocks (e.g. // subprojects { group = 'old' })
were treated as live overrides by extractBlockGroup, causing later commented blocks
to override real ones. Add stripGradleComments to blank out // and /* */ comment
content (preserving newlines and byte positions) before block scanning.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2fc0a78316

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkg/extractor/java/parse-gradle-lock.go Outdated
Comment on lines +148 to +152
group = extractGroupFromRootBuildFile(ctx.RootDir)
}
}
if group != "" {
projectName := parseGradleSettingsProjectName(ctx.RootDir, projectDir)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve Gradle metadata from the project root

When the CLI scans a repository root that contains a Gradle build in a subdirectory, ctx.RootDir is the scan root, not the nearest Gradle root. For a layout like repo/service/settings.gradle plus a lockfile under repo/service/api, this reads repo/build.gradle for the inherited group and then looks for repo/settings.gradle, so the service's inherited group and settings name overrides are missed; the artifact name is left empty or falls back to the directory basename, which prevents findArtifact from linking internal Gradle dependencies. Resolve the Gradle root from projectDir/buildFilePath before calling these helpers.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Valid concern, but out of scope for this PR. Resolving the Gradle root from projectDir requires walking up the directory tree to find the nearest settings.gradle, which is a separate discovery feature. This PR is scoped to improving artifact naming when ctx.RootDir is already the Gradle root (the typical CLI usage).

Comment thread pkg/extractor/java/gradle_settings.go Outdated
Comment on lines +131 to +133
if i+1 < len(out) && out[i] == '/' && out[i+1] == '/' {
// Single-line comment: blank everything up to (but not including) the newline.
for i < len(out) && out[i] != '\n' {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve URL strings while stripping comments

When an inherited group appears after an inline repository declaration such as repositories { maven { url = uri("https://repo") } }, this treats the // inside the URL string as a comment and blanks the rest of that line, including the closing braces. The brace counter in extractBlockGroup then never reaches the real end of the allprojects/subprojects block, so extractGroupFromRootBuildFile returns empty and subprojects that rely on that group lose their artifact.Name, breaking internal Gradle dependency matching.

Useful? React with 👍 / 👎.

@piloulacdog piloulacdog Jun 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is a legit feedback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 01a533cstripGradleComments now skips over string literals (single and double quoted) before looking for // or /*, so url = uri("https://repo") is left untouched. Added TestStripGradleComments_PreservesURLsInStrings.

Comment thread pkg/extractor/java/gradle_settings.go Outdated

// Matches Gradle project path references like ':subA' or ":sub:module" within an
// include argument list. Requiring the leading ':' avoids false positives from
// inline comments or other quoted strings in the same line.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could we have some reg101 links here to easy test/confirm matches?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added regex101 links to all regex declarations in d7d2b46.

Comment thread pkg/extractor/java/gradle_settings.go Outdated
// Matches Gradle project path references like ':subA' or ":sub:module" within an
// include argument list. Requiring the leading ':' avoids false positives from
// inline comments or other quoted strings in the same line.
includeRefRe = cachedregexp.MustCompile(`['"](:(?:[^'"]+))['"]`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

apparently the refs can be prefixed with AND without a column, and seem to be valid in both case.

// settings.gradle

// Form 1: with leading colon (handled by PR)
include ':app'

// Form 2: WITHOUT leading colon (silently ignored by PR regex)
include 'communication'

// Form 2 + name override (override still works via separate regex)
include 'lib-core'
project(':lib-core').name = 'comms-core'

see how we have both type of include ':app' and communication, and when I ran the tree, it was correctly parsed:

$ gradle projects

Projects:

------------------------------------------------------------
Root project 'my-renamed-root'
------------------------------------------------------------

Location: ....
Project hierarchy:

Root project 'my-renamed-root'
+--- Project ':app'
+--- Project ':comms-core'
\--- Project ':communication'

so we should likely adjust it for:

Suggested change
includeRefRe = cachedregexp.MustCompile(`['"](:(?:[^'"]+))['"]`)
includeRefRe = cachedregexp.MustCompile(`['"]:?([^'"]+)['"]`)

and then in parseGradleSettingsProjectName, drop the leading : from gradlePath and normalize the override regex's capture the same way

// Build the Gradle project path WITHOUT the leading ':' so it matches the
// normalized captures from includeRefRe / the trimmed projectNameRe key.
gradlePath := strings.ReplaceAll(filepath.ToSlash(relPath), "/", ":")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in cc1f37d. Changed includeRefRe to make the leading : optional (consumed outside the capture group). The captured bare path is then normalized with a : prefix before comparing with gradlePath. This handles both include ':app' and include 'app' forms. Added test TestParseGradleSettingsProjectName_IncludeWithoutLeadingColon covering both the colon-less include and the name override case.

Comment thread pkg/extractor/java/gradle_settings.go Outdated

// include ':subA', ':subB' or include(':subA', ':subB')
// Captures the full argument string after include.
includeRe = cachedregexp.MustCompile(`(?m)^\s*include\s*\(?\s*([^)\n]+?)\s*\)?\s*$`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it seems that includeRe matches includeBuild (no word boundary after include), so composite-build paths can be treated as subprojects.

include ':app'                  // i assume it should match
include(':app')                // i assume it should match
includeBuild '../lib'          // i assume it should NOT match
includeBuild('../lib')        // i assume it should NOT match
includeFlat 'sibling'        // i assume it should NOT match

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in cc1f37d — added \b word boundary after include in includeRe so includeBuild, includeFlat, etc. no longer match as subproject declarations. Added test TestParseGradleSettingsProjectName_IncludeBuildNotMatchedAsSubproject.

rootDir = abs
}

content := readSettingsFile(rootDir)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we might have to add something here to strip comments:

content = stripGradleComments(content)

because i'm not fully sure we support cases like those:

Don't we report old-name here?

// settings.gradle

// rootProject.name = 'old-name'
rootProject.name = 'new-name'

Or those, projectNameRe matches inside the block comment. Result: :app resolves to deprecated-name even though the override is commented out.

// settings.gradle

include ':app'
/*
project(':app').name = 'deprecated-name'
*/

or also, block-commented include:

// settings.gradle

/*
include ':removed-module'
*/
include ':app'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in two commits: 01a533c makes stripGradleComments string-aware (skips string literals so // inside URLs is not treated as a comment delimiter), and 9472cd6 applies the strip at the start of parseGradleSettingsProjectName. Added tests for all three cases: line-commented name, block-commented include, and block-commented name override.

Comment thread pkg/extractor/java/gradle_settings.go Outdated
Comment on lines +131 to +133
if i+1 < len(out) && out[i] == '/' && out[i+1] == '/' {
// Single-line comment: blank everything up to (but not including) the newline.
for i < len(out) && out[i] != '\n' {

@piloulacdog piloulacdog Jun 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is a legit feedback

// - Root project: inherits from allprojects { } only (subprojects { } does not apply).
// - Subproject: inherits from allprojects { } or subprojects { }.
// Name: prefer settings.gradle canonical name; fall back to directory basename.
projectDir := filepath.Dir(f.Path())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this logic seems to be duplicated in parse-gradle-verification-metadata.go should we extract it to a resolveGradleArtifactName(ctx, projectDir, ownGroup) helper

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Extracted to resolveGradleArtifactName(ctx, projectDir, buildFileContent) in 8e7aad9. Both GradleLockExtractor.GetArtifact and GradleVerificationMetadataExtractor.GetArtifact now delegate to it.

Comment thread pkg/extractor/java/parse-gradle-lock.go Outdated
}
if projectDir == absRootDir {
// Root project: only allprojects { } applies.
group = extractAllProjectsGroupFromRootBuildFile(ctx.RootDir)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we pass the normalized path? absRootDir

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f7163e5extractAllProjectsGroupFromRootBuildFile and extractGroupFromRootBuildFile now receive absRootDir instead of ctx.RootDir. This is also preserved in the extracted resolveGradleArtifactName helper.

anderruiz and others added 4 commits June 19, 2026 14:36
…ubproject include

The includeRe regex lacked a word boundary after 'include', causing
includeBuild, includeFlat, etc. to be parsed as subproject declarations.
Added \b word boundary to restrict matches to the bare 'include' keyword.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…gradle

Gradle allows both include ':app' and include 'app' (without leading
colon) as valid project declarations. The previous includeRefRe required
the colon, silently ignoring the colon-less form. Now the colon is
optional and the captured path is normalized before comparison.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
The comment stripper was treating '//' inside quoted strings (e.g.
url = uri("https://repo")) as a comment delimiter, blanking the rest
of the line including closing braces. This broke brace-counting in
extractBlockGroup, causing inherited group values to be missed.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
parseGradleSettingsProjectName was applying regexes to raw file content,
so commented-out rootProject.name, include, and project().name statements
were incorrectly matched. Strip comments first so only live declarations
affect project name resolution.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
anderruiz and others added 3 commits June 19, 2026 14:41
…lpers

absRootDir was computed for the root-vs-subproject comparison but the
actual calls to extractGroupFromRootBuildFile and
extractAllProjectsGroupFromRootBuildFile still used the original
ctx.RootDir. Pass the normalized absolute path to both calls for
consistency.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
… duplication

Both GradleLockExtractor.GetArtifact and
GradleVerificationMetadataExtractor.GetArtifact contained identical
group/name resolution logic. Extract to resolveGradleArtifactName in
gradle_settings.go where the helpers it calls already live.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Add regex101 playground links next to each compiled regex so reviewers
and future contributors can quickly verify and experiment with the
patterns without setting up a local environment.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d7d2b4668e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +288 to +289
if m := groupAssignRe.FindSubmatch(flat); m != nil {
return string(m[1])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use the last top-level group assignment

When an allprojects/subprojects block assigns group more than once at top level, Gradle leaves the project with the later value, but this FindSubmatch returns the first one from the flattened block. For example, allprojects { group = 'old'; group = 'new' } makes subprojects use new, while the scanner records old:<project>, so internal Gradle dependencies can be missed or matched to the wrong artifact.

Useful? React with 👍 / 👎.

@anderruiz
anderruiz requested a review from piloulacdog June 19, 2026 13:43

@piloulacdog piloulacdog left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

would be interesting to find real life usecase, and compare what we output when scanning those to the other tools: trivy/snyk/GHAS

@anderruiz
anderruiz merged commit c5d0f09 into main Jun 19, 2026
11 checks passed
@anderruiz
anderruiz deleted the ander/gradle-settings-artifact-id branch June 19, 2026 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants