Use proper Gradle Property injection + extract tasks into classes - #3
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughReplaces mutable DSL fields with Gradle Property-backed extension, registers Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Gradle
participant Plugin as SrcxPlugin
participant ContextTask as srcx-context
participant ReportWriter
participant SymbolExtractor
participant RootProject
participant IncludedBuilds
Note over Plugin,Gradle: Plugin applied — registers ContextTask & CleanTask, wires outputDir via Property conventions
User->>Gradle: run "srcx-context"
Gradle->>ContextTask: schedule & inject outputDir
ContextTask->>RootProject: collect projects & source sets (ProjectScanner)
ContextTask->>SymbolExtractor: extract per-project symbols (parallel)
ContextTask->>ReportWriter: write per-project reports and .gitignore
ContextTask->>IncludedBuilds: iterate & Request included-build summaries (ReportWriter + SymbolExtractor)
ContextTask->>ReportWriter: compute build edges & generate class diagram
ContextTask->>Gradle: emit completion/log
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt (1)
58-68: Avoid duplicate summary extractionLine 59 and Line 67 call
extractProjectSummaryfor the same projects. Reuse summaries from the parallel pass instead of recomputing, to cut runtime and keep per-project reports and dashboard data consistent within one execution.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt` around lines 58 - 68, The code recomputes project summaries: extractProjectSummary is called inside plugin.runParallel and again when building summaries; change the parallel pass to store each project's summary in a temporary concurrent map (e.g., MutableMap<Project, Summary> or ConcurrentHashMap) inside the lambda passed to plugin.runParallel and use plugin.writeProjectReport with that stored summary, then replace the later summaries = projects.map { ... } with a reuse of the map values (projects.map { summariesMap[it]!! }) so the per-project report and the dashboard use the same precomputed summaries; update references to plugin.runParallel, extractProjectSummary, writeProjectReport and the summaries variable accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt`:
- Around line 82-83: The KDoc for the abstract property autoGenerate in Srcx.kt
is stale: update its comment to reflect that when true,
compileKotlin/compileJava tasks will finalize with srcx-context (not
srcx-symbols). Locate the KDoc above the autoGenerate Property<Boolean>
declaration in the Srcx class/object and change the reference from
"srcx-symbols" to "srcx-context", keeping the rest of the description intact.
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt`:
- Around line 24-31: CleanTask (and similarly ContextTask) currently stores live
Gradle model objects rootProject and extension on the task; change the task API
to model only explicit inputs (e.g., add properties like val rootDir:
DirectoryProperty, val outputDir: DirectoryProperty, and Property<T> or `@Nested`
data classes for settings) annotated with `@Input/`@Nested, remove the Project and
extension constructor params, and update any `@TaskAction` to read only those
properties (not Project/extension). During task registration, wire extension
values and project paths into the task properties using set(...) or
convention(...) with Providers (e.g.,
project.layout.projectDirectory.asFileTree/Providers.of(...)) so the task no
longer captures Project or the extension at configuration time. Ensure
ContextTask is updated the same way and that no code inside `@TaskAction`
references rootProject or extension directly.
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 69-71: The current code computes relPath using
build.projectDir.relativeTo(rootProject.projectDir) which throws for included
builds outside the root; change this to use relativeToOrNull and fall back to
the absolute path so the task won't crash: compute relPath with
build.projectDir.relativeToOrNull(rootProject.projectDir)?.path ?:
build.projectDir.path and pass that into
DashboardRenderer.IncludedBuildRef(build.name, relPath) to preserve behavior for
both inside- and outside-root included builds.
---
Nitpick comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 58-68: The code recomputes project summaries:
extractProjectSummary is called inside plugin.runParallel and again when
building summaries; change the parallel pass to store each project's summary in
a temporary concurrent map (e.g., MutableMap<Project, Summary> or
ConcurrentHashMap) inside the lambda passed to plugin.runParallel and use
plugin.writeProjectReport with that stored summary, then replace the later
summaries = projects.map { ... } with a reuse of the map values (projects.map {
summariesMap[it]!! }) so the per-project report and the dashboard use the same
precomputed summaries; update references to plugin.runParallel,
extractProjectSummary, writeProjectReport and the summaries variable
accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e7938b4b-02a4-4d9d-be27-7eaeb3ef7e72
📒 Files selected for processing (9)
src/main/kotlin/zone/clanker/gradle/srcx/Srcx.ktsrc/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.ktsrc/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.ktsrc/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.ktsrc/test/kotlin/zone/clanker/gradle/srcx/BuildEdgesTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt
12e602e to
6853433
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt (1)
65-66:⚠️ Potential issue | 🟠 MajorHandle non-relative included build paths safely.
Line 65 uses
relativeTo(...), which can throw for included builds outside the root directory. UserelativeToOrNull()and fallback to absolute path.Proposed fix
- val relPath = build.projectDir.relativeTo(rootProject.projectDir).path + val relPath = + build.projectDir.relativeToOrNull(rootProject.projectDir)?.path + ?: build.projectDir.absolutePath DashboardRenderer.IncludedBuildRef(build.name, relPath)#!/bin/bash set -euo pipefail # Verify relative path conversion uses throwing API in ContextTask. rg -n -C2 --type kotlin 'relativeTo\(|relativeToOrNull\(' \ src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt` around lines 65 - 66, The code in ContextTask (around DashboardRenderer.IncludedBuildRef) uses build.projectDir.relativeTo(rootProject.projectDir) which throws if the included build is not inside the root; update the computation of relPath to use relativeToOrNull and fall back to the absolute path when null so DashboardRenderer.IncludedBuildRef(build.name, relPath) always receives a safe path (e.g. compute relPath with build.projectDir.relativeToOrNull(rootProject.projectDir)?.path ?: build.projectDir.path).src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt (1)
39-43:⚠️ Potential issue | 🟠 MajorAvoid Gradle model access in
@TaskAction(configuration-cache risk).Line 39 and Line 42 read
project.rootProject/rootProject.gradle.includedBuildsduring execution. Move these to declared task inputs and wire them during registration.Proposed refactor
import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.TaskAction import zone.clanker.gradle.srcx.Srcx import java.io.File @@ abstract class CleanTask : DefaultTask() { + `@get`:InputDirectory + abstract val rootDir: DirectoryProperty + + `@get`:Input + abstract val includedBuildDirs: ListProperty<String> + /** Output directory relative to the root project (e.g. `.srcx`). */ `@get`:Input abstract val outputDir: Property<String> @@ `@TaskAction` fun clean() { - val rootProject = project.rootProject - val dir = File(rootProject.projectDir, outputDir.get()) + val dir = File(rootDir.asFile.get(), outputDir.get()) plugin.cleanOutputDir(dir) - for (build in rootProject.gradle.includedBuilds) { - val buildOutputDir = File(build.projectDir, outputDir.get()) + for (buildDir in includedBuildDirs.get()) { + val buildOutputDir = File(buildDir, outputDir.get()) plugin.cleanOutputDir(buildOutputDir) } } }// In Srcx.SettingsPlugin.registerTasks(...) rootProject.tasks.register(TASK_CLEAN, CleanTask::class.java).configure { it.outputDir.convention(extension.outputDir) it.rootDir.set(rootProject.layout.projectDirectory) it.includedBuildDirs.set( rootProject.provider { rootProject.gradle.includedBuilds.map { b -> b.projectDir.absolutePath } }, ) }#!/bin/bash set -euo pipefail # Verify `@TaskAction` methods that access Gradle model objects at execution time. rg -n -C2 --type kotlin '@TaskAction|project\.rootProject|rootProject\.gradle\.includedBuilds' \ src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt \ src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt` around lines 39 - 43, The CleanTask currently accesses Gradle model objects at execution time (project.rootProject and rootProject.gradle.includedBuilds) which breaks configuration-cache safety; refactor by adding task input properties (e.g., outputDir already exists, add rootDir: RegularFileProperty and includedBuildDirs: ListProperty<String> or SetProperty<String>) on CleanTask and stop calling project/rootProject inside the `@TaskAction`; during plugin/task registration (where TASK_CLEAN is registered) wire those properties from the project model (set rootDir from rootProject.layout.projectDirectory and populate includedBuildDirs via a provider mapping rootProject.gradle.includedBuilds to their projectDir paths) so the `@TaskAction` only reads declared properties and operates on the files passed in.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt`:
- Around line 40-44: Validate and sanitize outputDir before calling
plugin.cleanOutputDir: in CleanTask where you build File(rootProject.projectDir,
outputDir.get()) and File(build.projectDir, outputDir.get()), ensure
outputDir.get() is not an absolute path and does not contain path traversal
(e.g. ".."); resolve and normalize the resulting File.toPath().toRealPath() (or
equivalent) and verify the resolved path startsWith the corresponding
projectDir.toPath().toRealPath(); if validation fails, skip deletion and surface
an error/throw so plugin.cleanOutputDir is never called on paths outside the
project workspace.
---
Duplicate comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt`:
- Around line 39-43: The CleanTask currently accesses Gradle model objects at
execution time (project.rootProject and rootProject.gradle.includedBuilds) which
breaks configuration-cache safety; refactor by adding task input properties
(e.g., outputDir already exists, add rootDir: RegularFileProperty and
includedBuildDirs: ListProperty<String> or SetProperty<String>) on CleanTask and
stop calling project/rootProject inside the `@TaskAction`; during plugin/task
registration (where TASK_CLEAN is registered) wire those properties from the
project model (set rootDir from rootProject.layout.projectDirectory and populate
includedBuildDirs via a provider mapping rootProject.gradle.includedBuilds to
their projectDir paths) so the `@TaskAction` only reads declared properties and
operates on the files passed in.
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 65-66: The code in ContextTask (around
DashboardRenderer.IncludedBuildRef) uses
build.projectDir.relativeTo(rootProject.projectDir) which throws if the included
build is not inside the root; update the computation of relPath to use
relativeToOrNull and fall back to the absolute path when null so
DashboardRenderer.IncludedBuildRef(build.name, relPath) always receives a safe
path (e.g. compute relPath with
build.projectDir.relativeToOrNull(rootProject.projectDir)?.path ?:
build.projectDir.path).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d7b6d0e8-dd21-4f3e-8eaf-1ddebb6ea7cb
📒 Files selected for processing (9)
src/main/kotlin/zone/clanker/gradle/srcx/Srcx.ktsrc/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.ktsrc/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.ktsrc/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.ktsrc/test/kotlin/zone/clanker/gradle/srcx/BuildEdgesTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt
✅ Files skipped from review due to trivial changes (3)
- src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
- src/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.kt
- src/test/kotlin/zone/clanker/gradle/srcx/BuildEdgesTest.kt
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt
- src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt
6853433 to
eee2b59
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt (1)
404-406:⚠️ Potential issue | 🟡 MinorMinor: Duplicate test case for TASK_CONTEXT.
Lines 400-402 and 404-406 both test that
TASK_CONTEXTequals"srcx-context". The second one appears to be a copy-paste oversight.🧹 Proposed fix to remove duplicate
then("TASK_CONTEXT is srcx-context") { Srcx.TASK_CONTEXT shouldBe "srcx-context" } - - then("TASK_CONTEXT is srcx-context") { - Srcx.TASK_CONTEXT shouldBe "srcx-context" - } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt` around lines 404 - 406, Remove the duplicate test assertion that checks Srcx.TASK_CONTEXT equals "srcx-context" in SrcxSettingsPluginTest (the repeated then block around lines 404-406); keep a single assertion for Srcx.TASK_CONTEXT and delete the redundant then("TASK_CONTEXT is srcx-context") { Srcx.TASK_CONTEXT shouldBe "srcx-context" } block to avoid the copy-paste duplicate.
♻️ Duplicate comments (1)
src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt (1)
63-67:⚠️ Potential issue | 🟠 MajorHandle included builds outside the root directory safely.
relativeTo()throwsIllegalArgumentExceptionfor included builds located outside the root directory, which is a valid Gradle composite build configuration. This will crash the task for such setups.Use
relativeToOrNull()with a fallback to the absolute path:🛠️ Proposed fix
val includedBuildRefs = includedBuilds.map { build -> - val relPath = build.projectDir.relativeTo(rootProject.projectDir).path + val relPath = + build.projectDir.relativeToOrNull(rootProject.projectDir)?.path + ?: build.projectDir.absolutePath DashboardRenderer.IncludedBuildRef(build.name, relPath) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt` around lines 63 - 67, The includedBuilds mapping uses projectDir.relativeTo(...) which throws for builds outside the root; update the includedBuildRefs construction in ContextTask to use build.projectDir.relativeToOrNull(rootProject.projectDir)?.path ?: build.projectDir.path (or absolutePath) so it falls back to the absolute path when relativeToOrNull returns null, and continue creating DashboardRenderer.IncludedBuildRef(build.name, relPath) with that safe relPath.
🧹 Nitpick comments (1)
src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt (1)
336-348: Consider extracting sharednewExtension()helper.This helper is duplicated in
SrcxAutoGenerateTest.kt(lines 10-22). Consider extracting to a shared test utility to avoid drift.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt` around lines 336 - 348, The test duplicates the helper that constructs a Srcx.SettingsExtension (the newExtension() function that uses ProjectBuilder.objects to newInstance Srcx.SettingsExtension and sets outputDir.convention(Srcx.OUTPUT_DIR) and autoGenerate.convention(false)); extract that logic into a shared test utility function/class (e.g., TestHelpers.createSrcxSettingsExtension or a common TestExtensions object) and replace the local newExtension() in both SrcxSettingsPluginTest and SrcxAutoGenerateTest with calls to this shared helper to avoid duplication and drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt`:
- Around line 404-406: Remove the duplicate test assertion that checks
Srcx.TASK_CONTEXT equals "srcx-context" in SrcxSettingsPluginTest (the repeated
then block around lines 404-406); keep a single assertion for Srcx.TASK_CONTEXT
and delete the redundant then("TASK_CONTEXT is srcx-context") {
Srcx.TASK_CONTEXT shouldBe "srcx-context" } block to avoid the copy-paste
duplicate.
---
Duplicate comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 63-67: The includedBuilds mapping uses projectDir.relativeTo(...)
which throws for builds outside the root; update the includedBuildRefs
construction in ContextTask to use
build.projectDir.relativeToOrNull(rootProject.projectDir)?.path ?:
build.projectDir.path (or absolutePath) so it falls back to the absolute path
when relativeToOrNull returns null, and continue creating
DashboardRenderer.IncludedBuildRef(build.name, relPath) with that safe relPath.
---
Nitpick comments:
In `@src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt`:
- Around line 336-348: The test duplicates the helper that constructs a
Srcx.SettingsExtension (the newExtension() function that uses
ProjectBuilder.objects to newInstance Srcx.SettingsExtension and sets
outputDir.convention(Srcx.OUTPUT_DIR) and autoGenerate.convention(false));
extract that logic into a shared test utility function/class (e.g.,
TestHelpers.createSrcxSettingsExtension or a common TestExtensions object) and
replace the local newExtension() in both SrcxSettingsPluginTest and
SrcxAutoGenerateTest with calls to this shared helper to avoid duplication and
drift.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 65de2f63-66ce-455d-ba08-ad8c52bb5513
📒 Files selected for processing (9)
src/main/kotlin/zone/clanker/gradle/srcx/Srcx.ktsrc/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.ktsrc/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.ktsrc/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.ktsrc/test/kotlin/zone/clanker/gradle/srcx/BuildEdgesTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt
✅ Files skipped from review due to trivial changes (3)
- src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
- src/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.kt
- src/test/kotlin/zone/clanker/gradle/srcx/BuildEdgesTest.kt
🚧 Files skipped from review as they are similar to previous changes (2)
- src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt
- src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt
eee2b59 to
3d9ecaf
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (4)
src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt (2)
61-64:⚠️ Potential issue | 🟠 MajorDon't let
relativeTo(...)abort composite-build reporting.Line 63 assumes every included build can be relativized against the root project dir. For valid composite builds on a different filesystem root/drive,
relativeTo(...)throws and the whole task fails. UserelativeToOrNull()with a fallback.Suggested fix
val includedBuildRefs = includedBuilds.map { build -> - val relPath = build.projectDir.relativeTo(rootProject.projectDir).path + val relPath = + build.projectDir.relativeToOrNull(rootProject.projectDir)?.path + ?: build.projectDir.path DashboardRenderer.IncludedBuildRef(build.name, relPath) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt` around lines 61 - 64, The code in ContextTask.kt building includedBuildRefs uses build.projectDir.relativeTo(rootProject.projectDir) which can throw for builds on different filesystem roots; change the mapping in the includedBuilds -> includedBuildRefs logic to use relativeToOrNull(rootProject.projectDir) and, if it returns null, fall back to build.projectDir.path (or build.projectDir.absolutePath) so the task won't abort on non-relativizable included builds.
45-84:⚠️ Potential issue | 🟠 MajorThis task still executes against live Gradle model state.
Lines 47-69 call
project.rootProject, iterateincludedBuilds, and passProjectinstances into helpers from inside@TaskAction. That keepssrcx-contextoutside configuration-cache requirements even though the settings values are nowProperty-backed. Move the Gradle-model reads into task registration and keep the action file/provider-based.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt` around lines 45 - 84, The generate() `@TaskAction` in ContextTask reads the live Gradle model (project.rootProject, includedBuilds, project instances) which breaks configuration-cache compatibility; move all Gradle API reads into task registration and expose them as Providers/Files for the action. Specifically, during task registration capture values via providers (e.g., rootProject reference, includedBuilds list, buildPairs, relPath calculations, and summaries) and wire them into task inputs like outputDir and a Provider<List<...>> or RegularFile/DirectoryProperty; then change generate() to only consume those Providers (call get() on them) and call Srcx.collectProjects / Srcx.generateIncludedBuildReports / Srcx.extractProjectSummary / Srcx.computeBuildEdges / Srcx.generateClassDiagram via precomputed provider values or lazy mapped providers rather than directly calling project.rootProject or iterating includedBuilds inside the `@TaskAction`; ensure task inputs are annotated as `@Input/`@InputFiles/@Classpath as appropriate and remove direct Gradle model access from generate().src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt (2)
28-35:⚠️ Potential issue | 🟠 MajorAvoid
projectaccess inside@TaskAction.Line 30 reads
project.rootProjectduring execution. That keepssrcx-cleantied to live Gradle model state, so the task still won't be configuration-cache safe even after the property injection refactor. Model the root dir and included-build dirs as task inputs and wire them inregisterTasks(...)instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt` around lines 28 - 35, The CleanTask.clean method reads project.rootProject and rootProject.gradle.includedBuilds at execution time, breaking configuration-cache safety; change CleanTask to expose task inputs (e.g., a DirectoryProperty for rootProjectDir and a ListProperty/ConfigurableFileCollection for includedBuildDirs) and stop referencing project inside the `@TaskAction`; populate those properties from registerTasks(...) when tasks are registered (wire root dir and included build dirs into the task), update `@TaskAction` clean to iterate those injected properties and call cleanSafe(...) only on them, and add appropriate `@InputFiles/`@PathSensitive annotations so Gradle knows the inputs.
37-42:⚠️ Potential issue | 🔴 CriticalHarden the escape check before deleting anything.
canonicalPath.startsWith(...)is still prefix-based, so a value like../repo2/.srcxcan pass against/tmp/repobecause/tmp/repo2shares the same string prefix. This can delete outside the intended workspace.Suggested hardening
private fun cleanSafe(baseDir: File) { - val dir = File(baseDir, outputDir.get()) - require(dir.canonicalPath.startsWith(baseDir.canonicalPath)) { + require(!File(outputDir.get()).isAbsolute) { + "outputDir must be relative: ${outputDir.get()}" + } + val basePath = baseDir.canonicalFile.toPath() + val dir = File(baseDir, outputDir.get()).canonicalFile + require(dir.toPath().startsWith(basePath)) { "outputDir '${outputDir.get()}' escapes project directory" } Srcx.cleanOutputDir(dir) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt` around lines 37 - 42, The current escape check in cleanSafe uses string prefix matching and can be fooled; update cleanSafe (in CleanTask.kt) to compare Path objects instead: obtain basePath = baseDir.canonicalFile.toPath() and targetPath = File(baseDir, outputDir.get()).canonicalFile.toPath(), normalize them and verify targetPath.startsWith(basePath) (Path.startsWith uses path segments, not string prefixes); if the check fails, throw the same error message and do not call Srcx.cleanOutputDir(dir). This ensures ../ escapes are detected reliably.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt`:
- Around line 282-301: discoverSourceSets() currently detects groovy/scala dirs
but sourceSetDirs() and extractSymbolsFromDirs() only handle kotlin/java,
causing empty summaries; either stop reporting groovy/scala in
discoverSourceSets() or extend downstream logic to fully support them—update
sourceSetDirs() to include groovy/scala source directory names and update
extractSymbolsFromDirs() to scan .groovy and .scala files (and adjust any
symbol-extraction logic to parse or skip these file types safely) so discovered
groovy/scala sets produce non-empty sourceDirs and symbol counts.
- Around line 385-407: The current depPattern Regex in Srcx.kt only matches
Kotlin-DSL dependency declarations like implementation("g:a:v"), so Groovy-style
declarations in build.gradle (e.g. implementation 'g:a:v' or implementation
"g:a:v") are ignored; update depPattern (or add a second pattern) used in the
parsing loop that reads buildFile.readLines() to also match Groovy forms by
accepting single-quoted and bare-double-quoted argument syntax and the same
scopes (api/implementation/compileOnly/runtimeOnly/testImplementation), then
ensure the match handling that constructs DependencyEntry (group ->
ArtifactGroup, artifact -> ArtifactName, version -> ArtifactVersion, scope)
works for either regex match so Groovy dependencies are included in results.
- Around line 600-605: The rootProject task registration uses bare strings so
subprojects calling wireAutoGenerate() end up finalizing a relative task name;
capture the TaskProvider when registering the root tasks (the TASK_CONTEXT and
TASK_CLEAN registrations that create ContextTask/CleanTask) and then use that
TaskProvider when wiring finalizers inside wireAutoGenerate() (i.e., call
task.finalizedBy(rootContextTaskProvider) instead of
task.finalizedBy(TASK_CONTEXT)); update the registration site to keep the
provider (e.g., contextTaskProvider) and reference that provider in
wireAutoGenerate() so the finalizer points to the absolute root task.
---
Duplicate comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt`:
- Around line 28-35: The CleanTask.clean method reads project.rootProject and
rootProject.gradle.includedBuilds at execution time, breaking
configuration-cache safety; change CleanTask to expose task inputs (e.g., a
DirectoryProperty for rootProjectDir and a
ListProperty/ConfigurableFileCollection for includedBuildDirs) and stop
referencing project inside the `@TaskAction`; populate those properties from
registerTasks(...) when tasks are registered (wire root dir and included build
dirs into the task), update `@TaskAction` clean to iterate those injected
properties and call cleanSafe(...) only on them, and add appropriate
`@InputFiles/`@PathSensitive annotations so Gradle knows the inputs.
- Around line 37-42: The current escape check in cleanSafe uses string prefix
matching and can be fooled; update cleanSafe (in CleanTask.kt) to compare Path
objects instead: obtain basePath = baseDir.canonicalFile.toPath() and targetPath
= File(baseDir, outputDir.get()).canonicalFile.toPath(), normalize them and
verify targetPath.startsWith(basePath) (Path.startsWith uses path segments, not
string prefixes); if the check fails, throw the same error message and do not
call Srcx.cleanOutputDir(dir). This ensures ../ escapes are detected reliably.
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 61-64: The code in ContextTask.kt building includedBuildRefs uses
build.projectDir.relativeTo(rootProject.projectDir) which can throw for builds
on different filesystem roots; change the mapping in the includedBuilds ->
includedBuildRefs logic to use relativeToOrNull(rootProject.projectDir) and, if
it returns null, fall back to build.projectDir.path (or
build.projectDir.absolutePath) so the task won't abort on non-relativizable
included builds.
- Around line 45-84: The generate() `@TaskAction` in ContextTask reads the live
Gradle model (project.rootProject, includedBuilds, project instances) which
breaks configuration-cache compatibility; move all Gradle API reads into task
registration and expose them as Providers/Files for the action. Specifically,
during task registration capture values via providers (e.g., rootProject
reference, includedBuilds list, buildPairs, relPath calculations, and summaries)
and wire them into task inputs like outputDir and a Provider<List<...>> or
RegularFile/DirectoryProperty; then change generate() to only consume those
Providers (call get() on them) and call Srcx.collectProjects /
Srcx.generateIncludedBuildReports / Srcx.extractProjectSummary /
Srcx.computeBuildEdges / Srcx.generateClassDiagram via precomputed provider
values or lazy mapped providers rather than directly calling project.rootProject
or iterating includedBuilds inside the `@TaskAction`; ensure task inputs are
annotated as `@Input/`@InputFiles/@Classpath as appropriate and remove direct
Gradle model access from generate().
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 87756fac-3249-481f-98a3-8051692e0ab4
📒 Files selected for processing (13)
src/main/kotlin/zone/clanker/gradle/srcx/Srcx.ktsrc/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.ktsrc/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.ktsrc/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.ktsrc/slopTest/kotlin/zone/clanker/gradle/srcx/TaskAnnotationTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/BuildEdgesTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/IncludedBuildReportTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/IncludedBuildTraversalTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SourceSetDiscoveryTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt
✅ Files skipped from review due to trivial changes (4)
- src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
- src/slopTest/kotlin/zone/clanker/gradle/srcx/TaskAnnotationTest.kt
- src/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.kt
- src/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
- src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt
3d9ecaf to
b89047a
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt (1)
398-404:⚠️ Potential issue | 🟡 MinorDuplicate test case.
Lines 402-404 duplicate the test on lines 398-400. This appears to be a copy-paste error—both test
TASK_CONTEXTinstead of one testingTASK_CLEAN.Suggested fix
then("TASK_CONTEXT is srcx-context") { Srcx.TASK_CONTEXT shouldBe "srcx-context" } - then("TASK_CONTEXT is srcx-context") { - Srcx.TASK_CONTEXT shouldBe "srcx-context" + then("TASK_CLEAN is srcx-clean") { + Srcx.TASK_CLEAN shouldBe "srcx-clean" }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt` around lines 398 - 404, The second duplicate test block in SrcxSettingsPluginTest repeats the TASK_CONTEXT assertion; replace the duplicate then(...) that references TASK_CONTEXT with a test for TASK_CLEAN: update the test description to something like "TASK_CLEAN is srcx-clean" and assert Srcx.TASK_CLEAN shouldBe "srcx-clean" so the class-level constants are both covered (use the existing then(...) block that currently duplicates TASK_CONTEXT and change its description and assertion to reference Srcx.TASK_CLEAN and the expected "srcx-clean" value).
🧹 Nitpick comments (2)
src/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.kt (2)
37-44: Unify duplicated.gitignorewriting helpers.
writeGitignoreandwriteGitignoreAthave the same body. Keeping one helper reduces drift risk.Also applies to: 173-176
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.kt` around lines 37 - 44, The two functions writeGitignore and writeGitignoreAt in ReportWriter.kt contain identical bodies; consolidate them by removing one and having the remaining helper accept both calling patterns (e.g., keep writeGitignoreAt(rootProjectDir: File, outputDir: String) or keep writeGitignore(rootProjectDir: File, outputDir: String)) and update callers to use the unified function; ensure the unified function creates the directory (dir.mkdirs()) and writes ".gitignore" with "*\n" as currently implemented and remove the duplicate implementation to avoid drift.
47-99: Extract included-build summary collection into one shared path.
generateIncludedBuildReportsandcollectIncludedBuildSummariesboth scan included builds and extract summaries. This duplicates heavy I/O/analysis work and can slow larger builds. Consider one helper that returns summaries and reuse it in both flows.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.kt` around lines 47 - 99, Extract the included-build scanning + summary extraction into a single helper and reuse it from both functions: create or reuse a method that takes builds: Collection<IncludedBuild> and returns Map<String, List<ProjectSummary>> (you already have collectIncludedBuildSummaries — keep or adapt it as the single source of truth), then modify generateIncludedBuildReports to call that helper to get each build's summaries (instead of calling ProjectScanner.discoverIncludedBuildProjects and SymbolExtractor.extractStandaloneProjectSummary inline), and update any local variable names accordingly so generateIncludedBuildReports uses the returned summaries to render per-project symbols.md and the build context.md; ensure behavior (sanitized projectPath handling, reportDir creation, writeGitignoreAt) stays the same.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.kt`:
- Around line 130-142: The runCatching block in ReportWriter.kt currently
swallows all exceptions and returns "" silently; change the final
.getOrDefault("") to .getOrElse { e -> /* log and fallback */ } and emit a
warning that includes the exception and context before returning the fallback
string. Locate the block that calls scanSources, classifyAll,
buildDependencyGraph and generateDependencyDiagram and replace the silent
fallback with a logger.warn or logger.warn("[context] failed to generate class
diagram", e) (use the ReportWriter's existing logger/LOGGER instance) so the
root cause is recorded while still returning the empty string as a last resort.
- Around line 154-167: The Executor lifecycle in ReportWriter.kt is not
failure-safe: ensure the fixed thread pool (pool created with
Executors.newFixedThreadPool(THREAD_POOL_SIZE)) is always shut down even if
futures.map { it.get() } is interrupted or hangs; wrap the submission/results
collection in a try/finally where finally calls pool.shutdownNow() (or
shutdown() then awaitTermination) to guarantee termination, and replace plain
Future.get() with get(timeout, unit) to avoid indefinite blocking, catching
InterruptedException/ExecutionException/TimeoutException and converting them
into sensible result strings for each project (use the same project identifier
from work(project)).
- Around line 146-170: runParallel currently spins up a custom thread pool and
accesses Gradle Project instances on worker threads
(Executors.newFixedThreadPool, pool.submit, futures, runParallel, work), which
violates Gradle's threading model; replace this with the Gradle Worker API by
capturing immutable project data (project.path, project.projectDir,
project.name, etc.) on the task thread, implement a Worker class with
appropriate WorkParameters to accept that immutable data and the work payload,
submit work via WorkerExecutor.noIsolation() or processIsolation() as
appropriate, and migrate the error/result handling so failures are returned from
the worker (or thrown back to the task) instead of building futures from
Executors; if any shared mutable state is required, expose it via a thread-safe
BuildService.
In `@src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt`:
- Around line 63-97: Symbol extraction currently only skips lines starting with
'//' for properties but not for classes or functions; update the logic in
SymbolExtractor (where classPattern, functionPattern, propertyPattern are used
and results.add(SymbolEntry(...)) is called) to perform the same comment check
for all symbol kinds—i.e., if line.trimStart().startsWith("//") skip matching
entirely before running classPattern.find(), functionPattern.find(), or
propertyPattern.find(); optionally factor this into a small helper like
isCommentLine(line) and call it before all three pattern checks to keep the code
DRY.
---
Outside diff comments:
In `@src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt`:
- Around line 398-404: The second duplicate test block in SrcxSettingsPluginTest
repeats the TASK_CONTEXT assertion; replace the duplicate then(...) that
references TASK_CONTEXT with a test for TASK_CLEAN: update the test description
to something like "TASK_CLEAN is srcx-clean" and assert Srcx.TASK_CLEAN shouldBe
"srcx-clean" so the class-level constants are both covered (use the existing
then(...) block that currently duplicates TASK_CONTEXT and change its
description and assertion to reference Srcx.TASK_CLEAN and the expected
"srcx-clean" value).
---
Nitpick comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.kt`:
- Around line 37-44: The two functions writeGitignore and writeGitignoreAt in
ReportWriter.kt contain identical bodies; consolidate them by removing one and
having the remaining helper accept both calling patterns (e.g., keep
writeGitignoreAt(rootProjectDir: File, outputDir: String) or keep
writeGitignore(rootProjectDir: File, outputDir: String)) and update callers to
use the unified function; ensure the unified function creates the directory
(dir.mkdirs()) and writes ".gitignore" with "*\n" as currently implemented and
remove the duplicate implementation to avoid drift.
- Around line 47-99: Extract the included-build scanning + summary extraction
into a single helper and reuse it from both functions: create or reuse a method
that takes builds: Collection<IncludedBuild> and returns Map<String,
List<ProjectSummary>> (you already have collectIncludedBuildSummaries — keep or
adapt it as the single source of truth), then modify
generateIncludedBuildReports to call that helper to get each build's summaries
(instead of calling ProjectScanner.discoverIncludedBuildProjects and
SymbolExtractor.extractStandaloneProjectSummary inline), and update any local
variable names accordingly so generateIncludedBuildReports uses the returned
summaries to render per-project symbols.md and the build context.md; ensure
behavior (sanitized projectPath handling, reportDir creation, writeGitignoreAt)
stays the same.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3d169502-f41a-4cfe-8e0c-83958f430fa4
📒 Files selected for processing (17)
src/main/kotlin/zone/clanker/gradle/srcx/Srcx.ktsrc/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.ktsrc/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.ktsrc/main/kotlin/zone/clanker/gradle/srcx/scan/ProjectScanner.ktsrc/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.ktsrc/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.ktsrc/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.ktsrc/slopTest/kotlin/zone/clanker/gradle/srcx/PackageBoundaryTest.ktsrc/slopTest/kotlin/zone/clanker/gradle/srcx/TaskAnnotationTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/BuildEdgesTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/IncludedBuildReportTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/IncludedBuildTraversalTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SourceSetDiscoveryTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.ktsrc/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt
✅ Files skipped from review due to trivial changes (3)
- src/slopTest/kotlin/zone/clanker/gradle/srcx/TaskAnnotationTest.kt
- src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
- src/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.kt
🚧 Files skipped from review as they are similar to previous changes (5)
- src/test/kotlin/zone/clanker/gradle/srcx/SourceSetDiscoveryTest.kt
- src/test/kotlin/zone/clanker/gradle/srcx/IncludedBuildTraversalTest.kt
- src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt
- src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt
- src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
| internal fun runParallel( | ||
| projects: List<Project>, | ||
| work: (Project) -> String, | ||
| ) { | ||
| if (projects.isEmpty()) { | ||
| println("srcx: No projects to process.") | ||
| return | ||
| } | ||
| val pool = Executors.newFixedThreadPool(THREAD_POOL_SIZE) | ||
| val futures = | ||
| projects.map { project -> | ||
| pool.submit( | ||
| Callable { | ||
| runCatching { work(project) } | ||
| .getOrElse { e -> "FAIL ${project.path}: ${e.message}" } | ||
| }, | ||
| ) | ||
| } | ||
| val results = futures.map { it.get() } | ||
| pool.shutdown() | ||
| pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS) | ||
| results.forEach { println(it) } | ||
| val failed = results.count { it.startsWith("FAIL") } | ||
| println("srcx: symbols complete -- ${projects.size} projects, $failed failed") | ||
| if (failed > 0) error("srcx: $failed project(s) failed during generation") |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Is org.gradle.api.Project safe to access from custom executor threads inside a task action, and what does Gradle recommend for parallel work?
💡 Result:
No—org.gradle.api.Project (and most Gradle model/configuration state reachable from it, like Configuration resolution) should not be accessed from custom executor threads you create inside a task action. Gradle can treat such access as “not … in a safe manner” when it happens from a thread “not managed by Gradle,” and it’s increasingly incompatible with newer execution models/features (e.g., configuration cache / project isolation expectations). [3], [4]
What Gradle recommends for parallel work instead
- Use the Worker API (
WorkerExecutor) to run parallel work inside a task in a Gradle-managed way. It is explicitly intended for “safe, concurrent execution of work items” and parallel execution within a single task. [2] - In Worker API
noIsolation()mode, Gradle explicitly constrains work items so they cannot change project state (i.e., don’t touchProject/mutable Gradle state from the worker). Capture everything you need fromProjecton the task thread and pass it asWorkParameters. [1] - For shared mutable resources across parallel work, use Build Services (which “must be thread-safe”) and let Gradle coordinate/constrain concurrent usage (e.g., via
maxParallelUsages). [5]
Sources: Gradle Worker API docs [1], WorkerExecutor Javadoc [2], configuration-cache guidance about Project use at execution time [3], Gradle forum warning explaining “thread not managed by Gradle” [4], Build Services docs [5].
Use WorkerExecutor instead of custom thread pools for parallel work on Gradle Projects.
Gradle Project instances must not be accessed from custom executor threads created inside a task action. This violates Gradle's thread-safety model and will become increasingly incompatible with newer features (e.g., configuration cache, project isolation). Instead, use the Worker API (WorkerExecutor), which is explicitly designed for safe parallel execution within tasks. Capture immutable data from Project (e.g., path, projectDir, name) on the task thread and pass it as WorkParameters to your workers. If workers need shared mutable state, use Gradle Build Services, which are required to be thread-safe.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.kt` around lines
146 - 170, runParallel currently spins up a custom thread pool and accesses
Gradle Project instances on worker threads (Executors.newFixedThreadPool,
pool.submit, futures, runParallel, work), which violates Gradle's threading
model; replace this with the Gradle Worker API by capturing immutable project
data (project.path, project.projectDir, project.name, etc.) on the task thread,
implement a Worker class with appropriate WorkParameters to accept that
immutable data and the work payload, submit work via
WorkerExecutor.noIsolation() or processIsolation() as appropriate, and migrate
the error/result handling so failures are returned from the worker (or thrown
back to the task) instead of building futures from Executors; if any shared
mutable state is required, expose it via a thread-safe BuildService.
eb8e8fd to
f46acc1
Compare
- SettingsExtension: abstract class with @Inject, Property<String>, Property<Boolean> - Conventions set after creation (outputDir, autoGenerate) - Extract ContextTask and CleanTask into task/ package - SettingsPlugin is now thin — just registers tasks - Matches wrkx architecture pattern
f46acc1 to
12886f9
Compare
…tal builds - Hub classes rendered as trees showing each dependent's file path and line number - Added HubDependent/HubDependentRef with file path + declaration line for both hubs and dependents - Super node label for hubs with 50+ dependents (collapsed instead of listing all) - Added declarationLine to SourceFileMetadata via PSI textOffset - Rewrote IncludedBuildRenderer with full context: overview, symbols, hub trees, deps, findings - Renamed symbols.md → context.md for per-project reports - Added @InputFiles/@OutputDirectory to ContextTask for Gradle up-to-date checking - Wired srcx-clean to lifecycle clean task via LifecycleBasePlugin - Renamed HubClass.dependents → dependentCount, dependentNames → dependents (List<HubDependentRef>)
- Use relativeToOrNull with fallback for included builds outside root dir - Wire autoGenerate finalizer via TaskProvider instead of bare string - Add timeout and safe shutdown to executor pool in runParallel
- ContextTask: all Gradle model data (project dirs, paths, deps, included builds) captured at configuration time via task properties. Task action operates only on pre-computed data and files. - CleanTask: base directories captured via ListProperty<File> instead of accessing project.rootProject at execution time. - Added extractProjectSummaryFromData to SymbolExtractor (no Project param) - Added writeProjectReportToDir, generateIncludedBuildReportsFromData, runParallelMapped to ReportWriter (no Project/IncludedBuild params) - Created IncludedBuildInfo data class for serializable included build state - Pre-computed dependencies via extractDependenciesFromProject at config time - Added collectIncludedBuildInfos to capture included build data at config time
- Fix duplicate TASK_CONTEXT test to test TASK_CLEAN instead - Unify writeGitignore/writeGitignoreAt into single implementation - Remove old generateIncludedBuildReports and collectIncludedBuildSummaries that took IncludedBuild; only keep the FromData variant - Extract writeBuildReports helper to deduplicate report writing
- Srcx: lifecycle logger for clean output - ReportWriter: lifecycle for progress, warn for diagram failures - ContextTask: task logger (inherited from DefaultTask) - SymbolIndex: warn for parse failures
* Use proper Gradle Property injection + extract tasks into classes - SettingsExtension: abstract class with @Inject, Property<String>, Property<Boolean> - Conventions set after creation (outputDir, autoGenerate) - Extract ContextTask and CleanTask into task/ package - SettingsPlugin is now thin — just registers tasks - Matches wrkx architecture pattern * Hub class trees with file+line, rich included build context, incremental builds - Hub classes rendered as trees showing each dependent's file path and line number - Added HubDependent/HubDependentRef with file path + declaration line for both hubs and dependents - Super node label for hubs with 50+ dependents (collapsed instead of listing all) - Added declarationLine to SourceFileMetadata via PSI textOffset - Rewrote IncludedBuildRenderer with full context: overview, symbols, hub trees, deps, findings - Renamed symbols.md → context.md for per-project reports - Added @InputFiles/@OutputDirectory to ContextTask for Gradle up-to-date checking - Wired srcx-clean to lifecycle clean task via LifecycleBasePlugin - Renamed HubClass.dependents → dependentCount, dependentNames → dependents (List<HubDependentRef>) * Address CodeRabbit review comments - Use relativeToOrNull with fallback for included builds outside root dir - Wire autoGenerate finalizer via TaskProvider instead of bare string - Add timeout and safe shutdown to executor pool in runParallel * Configuration-cache safe tasks: no Project access at execution time - ContextTask: all Gradle model data (project dirs, paths, deps, included builds) captured at configuration time via task properties. Task action operates only on pre-computed data and files. - CleanTask: base directories captured via ListProperty<File> instead of accessing project.rootProject at execution time. - Added extractProjectSummaryFromData to SymbolExtractor (no Project param) - Added writeProjectReportToDir, generateIncludedBuildReportsFromData, runParallelMapped to ReportWriter (no Project/IncludedBuild params) - Created IncludedBuildInfo data class for serializable included build state - Pre-computed dependencies via extractDependenciesFromProject at config time - Added collectIncludedBuildInfos to capture included build data at config time * Address latest CodeRabbit review - Fix duplicate TASK_CONTEXT test to test TASK_CLEAN instead - Unify writeGitignore/writeGitignoreAt into single implementation - Remove old generateIncludedBuildReports and collectIncludedBuildSummaries that took IncludedBuild; only keep the FromData variant - Extract writeBuildReports helper to deduplicate report writing * Replace println/System.err with Gradle logger - Srcx: lifecycle logger for clean output - ReportWriter: lifecycle for progress, warn for diagram failures - ContextTask: task logger (inherited from DefaultTask) - SymbolIndex: warn for parse failures
Summary
vartoabstract val Property<T>with@InjectconstructordoLasttask logic intotask/ContextTask.ktandtask/CleanTask.ktChanges
Srcx.SettingsExtension:var outputDir: String→abstract val outputDir: Property<String>Srcx.SettingsExtension:var autoGenerate: Boolean→abstract val autoGenerate: Property<Boolean>task/ContextTask.kt— extracted from inlinedoLasttask/CleanTask.kt— extracted from inlinedoLast.get()and.set()Test plan
Summary by CodeRabbit
New Features
Bug Fixes / Safety
Refactor
Documentation
Tests