From e3ad9a5c63c9abebf1fa645836ea667238b66474 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Mon, 16 Mar 2026 10:15:14 +0100 Subject: [PATCH 01/12] Expose spec via provider --- .../plugin/ProjectGuardExtension.kt | 18 ++++++++++++++++-- .../projectguard/plugin/ProjectGuardPlugin.kt | 6 +++--- sample/gradle.properties | 1 + 3 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 sample/gradle.properties diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardExtension.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardExtension.kt index 7580420..989953d 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardExtension.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardExtension.kt @@ -30,6 +30,7 @@ import com.rubensousa.projectguard.plugin.internal.ReportSpec import com.rubensousa.projectguard.plugin.internal.getDependencyPath import org.gradle.api.Action import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.internal.provider.DefaultProvider import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.listProperty @@ -45,6 +46,7 @@ abstract class ProjectGuardExtension @Inject constructor( private val dependencyRestrictionSpecs = objects.listProperty() private val reportSpec = objects.property().convention(ReportSpec(showLibrariesInGraph = false)) private val options = objects.property().convention(PluginOptions(lifecycleTask = null)) + private val provider = DefaultProvider { getSpec() } override fun restrictModule(modulePath: String, action: Action) { val scope = ModuleRestrictionScopeImpl() @@ -126,13 +128,25 @@ abstract class ProjectGuardExtension @Inject constructor( override fun report(action: Action) { val scope = ReportScopeImpl() action.execute(scope) - reportSpec.set(ReportSpec(showLibrariesInGraph = scope.showLibrariesInGraph)) + reportSpec.set( + reportSpec.get().copy( + showLibrariesInGraph = scope.showLibrariesInGraph + ) + ) } override fun options(action: Action) { val scope = OptionScopeImpl() action.execute(scope) - options.set(PluginOptions(lifecycleTask = scope.lifecycleTask)) + options.set( + options.get().copy( + lifecycleTask = scope.lifecycleTask, + ) + ) + } + + internal fun getSpecProvider(): Provider { + return provider } internal fun getSpec(): ProjectGuardSpec { diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt index 2b2f413..952973c 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt @@ -292,7 +292,7 @@ class ProjectGuardPlugin : Plugin { ) { group = "verification" description = "Verifies if there are any dependency restrictions" - pluginSpec.set(project.provider { extension.getSpec() }) + pluginSpec.set(extension.getSpecProvider()) outputs.upToDateWhen { false } } } @@ -307,7 +307,7 @@ class ProjectGuardPlugin : Plugin { ) { group = "verification" description = "Verifies if there are any dependency restrictions" - pluginSpec.set(project.provider { extension.getSpec() }) + pluginSpec.set(extension.getSpecProvider()) outputs.upToDateWhen { false } } } @@ -323,7 +323,7 @@ class ProjectGuardPlugin : Plugin { group = "other" description = "Generates a JSON report of all dependency restrictions for this module." projectPath.set(project.path) - specProperty.set(project.provider { extension.getSpec() }) + specProperty.set(extension.getSpecProvider()) outputFile.set( project.layout.buildDirectory.file(jsonReportFilePath) ) diff --git a/sample/gradle.properties b/sample/gradle.properties new file mode 100644 index 0000000..2d8d1e4 --- /dev/null +++ b/sample/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true \ No newline at end of file From dd18c80196ecd50c7fc30f38ec3f1a30286e0764 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Mon, 16 Mar 2026 11:14:18 +0100 Subject: [PATCH 02/12] Add support for configuration cache when fetching dependencies --- .../projectguard/plugin/ProjectGuardPlugin.kt | 9 ++- .../plugin/internal/DependencyGraphBuilder.kt | 63 +++++++++++-------- .../internal/task/TaskDependencyDump.kt | 9 ++- .../projectguard/plugin/PluginCacheTest.kt | 19 ------ .../internal/DependencyGraphBuilderTest.kt | 15 ++++- 5 files changed, 63 insertions(+), 52 deletions(-) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt index 952973c..fb7f570 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt @@ -340,7 +340,14 @@ class ProjectGuardPlugin : Plugin { group = "other" description = "Generates a JSON containing the dependencies of this module." projectPath.set(project.path) - dependencyGraph.set(project.provider { graphBuilder.buildFromProject(project) }) + project.configurations.forEach { config -> + if (config.isCanBeResolved) { + components.put( + config.name, + config.incoming.resolutionResult.rootComponent + ) + } + } outputFile.set( project.layout.buildDirectory.file(dependenciesFilePath) ) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt index 3f264c7..5b41ebc 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt @@ -17,9 +17,10 @@ package com.rubensousa.projectguard.plugin.internal import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphDump -import org.gradle.api.Project -import org.gradle.api.artifacts.ExternalModuleDependency -import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.artifacts.component.ModuleComponentIdentifier +import org.gradle.api.artifacts.component.ProjectComponentIdentifier +import org.gradle.api.artifacts.result.ResolvedComponentResult +import org.gradle.api.artifacts.result.ResolvedDependencyResult internal class DependencyGraphBuilder { @@ -42,35 +43,43 @@ internal class DependencyGraphBuilder { return graph } - fun buildFromProject(project: Project): DependencyGraph { + fun buildFromComponents(results: Map): DependencyGraph { val graph = DependencyGraph() - project.configurations - .filter { config -> config.isCanBeResolved && DependencyConfiguration.isConfigurationSupported(config.name) } - .forEach { config -> - val moduleId = project.path - config.incoming.dependencies - .forEach { dependency -> - when (dependency) { - is ProjectDependency -> { - if (dependency.path != moduleId) { - graph.addInternalDependency( - module = moduleId, - dependency = dependency.path, - configurationId = config.name - ) - } - } + results.forEach { (configurationId, result) -> + if (DependencyConfiguration.isConfigurationSupported(configurationId)) { + val resultId = result.id + if (resultId is ProjectComponentIdentifier) { + val moduleId = resultId.projectPath + result.dependencies.forEach { dependencyResult -> + when (dependencyResult) { + is ResolvedDependencyResult -> { + val selected = dependencyResult.selected + when (val projectId = selected.id) { + is ProjectComponentIdentifier -> { + if (projectId.projectPath != moduleId) { + graph.addInternalDependency( + module = moduleId, + dependency = projectId.projectPath, + configurationId = configurationId, + ) + } + } - is ExternalModuleDependency -> { - graph.addLibraryDependency( - module = moduleId, - dependency = "${dependency.group}:${dependency.name}", - configurationId = config.name - ) + is ModuleComponentIdentifier -> { + graph.addLibraryDependency( + module = moduleId, + dependency = "${projectId.group}:${projectId.module}", + configurationId = configurationId, + ) + } + } } } } + } } + } return graph } -} \ No newline at end of file + +} diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt index 2708682..d8adeea 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt @@ -17,13 +17,16 @@ package com.rubensousa.projectguard.plugin.internal.task import com.rubensousa.projectguard.plugin.internal.DependencyGraph +import com.rubensousa.projectguard.plugin.internal.DependencyGraphBuilder import com.rubensousa.projectguard.plugin.internal.report.ConfigurationDependencies import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphDump import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphModuleDump import com.rubensousa.projectguard.plugin.internal.report.DependencyReferenceDump import com.rubensousa.projectguard.plugin.internal.report.JsonFileWriter import org.gradle.api.DefaultTask +import org.gradle.api.artifacts.result.ResolvedComponentResult import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input @@ -38,21 +41,21 @@ internal abstract class TaskDependencyDump : DefaultTask() { internal abstract val projectPath: Property @get:Input - internal abstract val dependencyGraph: Property + internal abstract val components: MapProperty @get:OutputFile internal abstract val outputFile: RegularFileProperty @TaskAction fun projectGuardDependencyDump() { + val graph = DependencyGraphBuilder().buildFromComponents(components.get()) val executor = DependencyDumpExecutor( moduleId = projectPath.get(), outputFile = outputFile.get().asFile, - dependencyGraph = dependencyGraph.get() + dependencyGraph = graph, ) executor.execute() } - } internal class DependencyDumpExecutor( diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginCacheTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginCacheTest.kt index af14397..27d8bd6 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginCacheTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginCacheTest.kt @@ -19,7 +19,6 @@ package com.rubensousa.projectguard.plugin import com.google.common.truth.Truth.assertThat import org.gradle.testkit.runner.TaskOutcome import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -118,24 +117,6 @@ class PluginCacheTest { assertThat(result).isEqualTo(TaskOutcome.UP_TO_DATE) } - @Ignore("Not working for now") - @Test - fun `outputs from projectGuardAggregateDependencyDump are re-used from cache`() { - // given - pluginRunner.createModule("a") - pluginRunner.createModule("b") - pluginRunner.addDependency(from = "a", to = "b") - val task = ":projectGuardAggregateDependencyDump" - pluginRunner.runTask(task) - - // when - pluginRunner.deleteBuildDirs() - val result = pluginRunner.runTask(task) - - // then - assertThat(result).isEqualTo(TaskOutcome.FROM_CACHE) - } - @Test fun `outputs from projectGuardAggregateDependencyDump are not re-used if dependencies changed`() { // given diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt index ac90eea..22dfa6d 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt @@ -18,6 +18,7 @@ package com.rubensousa.projectguard.plugin.internal import com.google.common.truth.Truth.assertThat import org.gradle.api.Project +import org.gradle.api.artifacts.result.ResolvedComponentResult import org.gradle.testfixtures.ProjectBuilder import org.junit.Before import kotlin.test.Test @@ -53,7 +54,7 @@ class DependencyGraphBuilderTest { val legacyProjectB = consumerProject.addLegacyDependency("b") // when - val graph = graphBuilder.buildFromProject(consumerProject) + val graph = graphBuilder.buildFromComponents(consumerProject.getResolvedConfigurations()) // then val compileConfiguration = graph.getConfigurations().find { it.id == DependencyConfiguration.COMPILE }!! @@ -68,7 +69,7 @@ class DependencyGraphBuilderTest { val legacyProjectC = consumerProject.addLegacyTestDependency("c") // when - val graph = graphBuilder.buildFromProject(consumerProject) + val graph = graphBuilder.buildFromComponents(consumerProject.getResolvedConfigurations()) // then val testConfiguration = graph.getConfigurations().find { it.id == DependencyConfiguration.TEST }!! @@ -76,6 +77,16 @@ class DependencyGraphBuilderTest { .containsExactly(legacyProjectA.path, legacyProjectC.path) } + private fun Project.getResolvedConfigurations(): Map { + val output = mutableMapOf() + project.configurations.forEach { config -> + if(config.isCanBeResolved) { + output[config.name] = config.incoming.resolutionResult.rootComponent.get() + } + } + return output + } + private fun Project.addLegacyDependency(dependency: String): Project { val legacyProject = createLegacySubProject(dependency) dependencies.add("implementation", legacyProject) From f19416fe74c9a9251549af29264eed82cfd65640 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Mon, 16 Mar 2026 11:22:10 +0100 Subject: [PATCH 03/12] Enable configuration cache in integration tests --- .../plugin/internal/task/TaskRestrictionDump.kt | 2 -- .../com/rubensousa/projectguard/plugin/PluginCacheTest.kt | 6 ++++-- .../rubensousa/projectguard/plugin/PluginIntegrationTest.kt | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskRestrictionDump.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskRestrictionDump.kt index 8de6cc8..492cb54 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskRestrictionDump.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskRestrictionDump.kt @@ -30,7 +30,6 @@ import kotlinx.serialization.json.Json import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.OutputFile @@ -39,7 +38,6 @@ import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import java.io.File -@CacheableTask internal abstract class TaskRestrictionDump : DefaultTask() { @get:Input diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginCacheTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginCacheTest.kt index 27d8bd6..6fe0bd0 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginCacheTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginCacheTest.kt @@ -35,6 +35,8 @@ class PluginCacheTest { @Before fun setup() { rootBuildFile = temporaryFolder.newFile("build.gradle.kts") + val propertiesFile = temporaryFolder.newFile("gradle.properties") + propertiesFile.writeText("org.gradle.configuration-cache=true") rootBuildFile.writeText( """ plugins { @@ -151,7 +153,7 @@ class PluginCacheTest { } @Test - fun `outputs from projectGuardRestrictionDump are cached`() { + fun `outputs from projectGuardRestrictionDump are not cached`() { // given pluginRunner.createModule("a") pluginRunner.createModule("b") @@ -164,7 +166,7 @@ class PluginCacheTest { val result = pluginRunner.runTask(task) // then - assertThat(result).isEqualTo(TaskOutcome.FROM_CACHE) + assertThat(result).isEqualTo(TaskOutcome.SUCCESS) } @Test diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginIntegrationTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginIntegrationTest.kt index 10a6496..27da4c0 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginIntegrationTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginIntegrationTest.kt @@ -32,6 +32,8 @@ class PluginIntegrationTest { @Before fun setup() { + val propertiesFile = temporaryFolder.newFile("gradle.properties") + propertiesFile.writeText("org.gradle.configuration-cache=true") rootBuildFile = temporaryFolder.newFile("build.gradle.kts") rootBuildFile.writeText( """ From 5bb3f6d0a324f6fde3f06ddce5c33846c824480e Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Mon, 16 Mar 2026 11:22:17 +0100 Subject: [PATCH 04/12] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68ff115..9eefdff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # CHANGELOG +## 1.0.0-rc02 (2026-03-16) + +- Fixed issue during dependency resolution when configuration cache is enabled + ## 1.0.0-rc01 (2026-03-14) - Fixed NPE caused by empty module suppressions when the baseline is manually edited From 17371d35ae2f3e540123da574bdfffefcb717edf Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Mon, 16 Mar 2026 11:48:27 +0100 Subject: [PATCH 05/12] Cover extension provider --- .../projectguard/plugin/ProjectGuardExtensionTest.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardExtensionTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardExtensionTest.kt index 7ee47ae..36a77ea 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardExtensionTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardExtensionTest.kt @@ -42,6 +42,18 @@ class ProjectGuardExtensionTest { assertThat(guards.first().denied.first().modulePath).isEqualTo(":legacy") } + @Test + fun `extension provider returns spec`() { + // given + val extension = createExtension() + + // when + val spec = extension.getSpec() + + // then + assertThat(extension.getSpecProvider().get()).isEqualTo(spec) + } + @Test fun `extension correctly configures module restriction`() { // given From 6ddea8b769296d2552f5fd876c8ef5a8b0ddb1c6 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Mon, 16 Mar 2026 12:01:33 +0100 Subject: [PATCH 06/12] Include dependencies not resolved --- .../plugin/internal/DependencyGraphBuilder.kt | 13 +++++++ .../internal/DependencyGraphBuilderTest.kt | 34 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt index 5b41ebc..1974efe 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt @@ -18,9 +18,11 @@ package com.rubensousa.projectguard.plugin.internal import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphDump import org.gradle.api.artifacts.component.ModuleComponentIdentifier +import org.gradle.api.artifacts.component.ModuleComponentSelector import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.artifacts.result.ResolvedComponentResult import org.gradle.api.artifacts.result.ResolvedDependencyResult +import org.gradle.api.internal.artifacts.result.DefaultUnresolvedDependencyResult internal class DependencyGraphBuilder { @@ -52,6 +54,17 @@ internal class DependencyGraphBuilder { val moduleId = resultId.projectPath result.dependencies.forEach { dependencyResult -> when (dependencyResult) { + is DefaultUnresolvedDependencyResult -> { + val requested = dependencyResult.requested + if (requested is ModuleComponentSelector) { + graph.addLibraryDependency( + module = moduleId, + dependency = "${requested.group}:${requested.module}", + configurationId = configurationId, + ) + } + } + is ResolvedDependencyResult -> { val selected = dependencyResult.selected when (val projectId = selected.id) { diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt index 22dfa6d..64f1f3e 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt @@ -19,6 +19,7 @@ package com.rubensousa.projectguard.plugin.internal import com.google.common.truth.Truth.assertThat import org.gradle.api.Project import org.gradle.api.artifacts.result.ResolvedComponentResult +import org.gradle.kotlin.dsl.repositories import org.gradle.testfixtures.ProjectBuilder import org.junit.Before import kotlin.test.Test @@ -62,6 +63,39 @@ class DependencyGraphBuilderTest { .containsExactly(legacyProjectA.path, legacyProjectB.path) } + @Test + fun `graph is built correctly when library is included but not resolved`() { + // given + val library = "com.google.truth:truth:1.4.5" + consumerProject.dependencies.add("implementation", library) + + // when + val graph = graphBuilder.buildFromComponents(consumerProject.getResolvedConfigurations()) + + // then + val compileConfiguration = graph.getConfigurations().find { it.id == DependencyConfiguration.COMPILE }!! + assertThat(compileConfiguration.getDependencies(consumerProject.path).map { it.id }) + .containsExactly("com.google.truth:truth") + } + + @Test + fun `graph is built correctly when library is included and resolved`() { + // given + val library = "com.google.truth:truth:1.4.5" + consumerProject.repositories { + mavenCentral() + } + consumerProject.dependencies.add("implementation", library) + + // when + val graph = graphBuilder.buildFromComponents(consumerProject.getResolvedConfigurations()) + + // then + val compileConfiguration = graph.getConfigurations().find { it.id == DependencyConfiguration.COMPILE }!! + assertThat(compileConfiguration.getDependencies(consumerProject.path).map { it.id }) + .containsExactly("com.google.truth:truth") + } + @Test fun `graph is built correctly for testImplementation configuration`() { // given From ade53d81f63d28ec4cc8647c4547ae860ee348a0 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Mon, 16 Mar 2026 12:02:32 +0100 Subject: [PATCH 07/12] Disable build cache for dependency dumps --- .../projectguard/plugin/internal/task/TaskDependencyDump.kt | 2 -- .../com/rubensousa/projectguard/plugin/PluginCacheTest.kt | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt index d8adeea..028ba98 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt @@ -28,13 +28,11 @@ import org.gradle.api.artifacts.result.ResolvedComponentResult import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import java.io.File -@CacheableTask internal abstract class TaskDependencyDump : DefaultTask() { @get:Input diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginCacheTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginCacheTest.kt index 6fe0bd0..930641f 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginCacheTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/PluginCacheTest.kt @@ -68,7 +68,7 @@ class PluginCacheTest { } @Test - fun `outputs from projectGuardDependencyDump are re-used from cache`() { + fun `outputs from projectGuardDependencyDump are not re-used from cache`() { // given pluginRunner.createModule("a") pluginRunner.createModule("b") @@ -81,7 +81,7 @@ class PluginCacheTest { val result = pluginRunner.runTask(libraryDependencyTask) // then - assertThat(result).isEqualTo(TaskOutcome.FROM_CACHE) + assertThat(result).isEqualTo(TaskOutcome.SUCCESS) } @Test From a0158ddfaaf048648d96c748c46873b703fb6488 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Mon, 16 Mar 2026 12:09:15 +0100 Subject: [PATCH 08/12] Update patch coverage target --- codecov.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index ab540e5..c490ce0 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,4 +3,7 @@ coverage: project: default: target: 80% # the required coverage value - threshold: 1% # the leniency in hitting the target \ No newline at end of file + threshold: 1% # the leniency in hitting the target + patch: + default: + target: 80% \ No newline at end of file From 5b130d4f2699a42d946d8f6e5aa4e104acec30dc Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Mon, 16 Mar 2026 12:20:08 +0100 Subject: [PATCH 09/12] Resolve all configurations --- .../projectguard/plugin/ProjectGuardPlugin.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt index fb7f570..40c776c 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt @@ -339,12 +339,12 @@ class ProjectGuardPlugin : Plugin { ) { group = "other" description = "Generates a JSON containing the dependencies of this module." - projectPath.set(project.path) - project.configurations.forEach { config -> - if (config.isCanBeResolved) { + projectPath.set(targetProject.path) + targetProject.configurations.all { + if (isCanBeResolved) { components.put( - config.name, - config.incoming.resolutionResult.rootComponent + this.name, + this.incoming.resolutionResult.rootComponent ) } } From 1cae4727ae5e19de175210817ac8600b5ed6a27d Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Mon, 16 Mar 2026 12:34:42 +0100 Subject: [PATCH 10/12] Revert due to ConcurrentModificationException --- .../rubensousa/projectguard/plugin/ProjectGuardPlugin.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt index 40c776c..95c4eb4 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt @@ -53,7 +53,6 @@ class ProjectGuardPlugin : Plugin { private val htmlAggregateReportFilePath = "reports/$pluginId" private val dependenciesFilePath = "reports/$pluginId/dependencies.json" private val jsonReportFilePath = "reports/$pluginId/report.json" - private val graphBuilder = DependencyGraphBuilder() private val androidPluginIds = listOf( "com.android.test", "com.android.application", @@ -340,11 +339,11 @@ class ProjectGuardPlugin : Plugin { group = "other" description = "Generates a JSON containing the dependencies of this module." projectPath.set(targetProject.path) - targetProject.configurations.all { - if (isCanBeResolved) { + targetProject.configurations.forEach { config -> + if (config.isCanBeResolved) { components.put( - this.name, - this.incoming.resolutionResult.rootComponent + config.name, + config.incoming.resolutionResult.rootComponent ) } } From 033106c147c14aa6e5ecddb81afb6e8e68b0cba4 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Mon, 16 Mar 2026 14:17:45 +0100 Subject: [PATCH 11/12] Use provider of ResolvedComponentResult as input --- .../plugin/internal/DependencyGraphBuilder.kt | 6 ++++-- .../plugin/internal/task/TaskDependencyDump.kt | 5 ++++- .../plugin/internal/DependencyGraphBuilderTest.kt | 9 +++++---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt index 1974efe..f2f4621 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt @@ -23,6 +23,7 @@ import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.artifacts.result.ResolvedComponentResult import org.gradle.api.artifacts.result.ResolvedDependencyResult import org.gradle.api.internal.artifacts.result.DefaultUnresolvedDependencyResult +import org.gradle.api.provider.Provider internal class DependencyGraphBuilder { @@ -45,10 +46,11 @@ internal class DependencyGraphBuilder { return graph } - fun buildFromComponents(results: Map): DependencyGraph { + fun buildFromComponents(results: Map>): DependencyGraph { val graph = DependencyGraph() - results.forEach { (configurationId, result) -> + results.forEach { (configurationId, resultProvider) -> if (DependencyConfiguration.isConfigurationSupported(configurationId)) { + val result = resultProvider.get() val resultId = result.id if (resultId is ProjectComponentIdentifier) { val moduleId = resultId.projectPath diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt index 028ba98..0bb36c6 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt @@ -28,18 +28,21 @@ import org.gradle.api.artifacts.result.ResolvedComponentResult import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction +import org.gradle.work.DisableCachingByDefault import java.io.File +@DisableCachingByDefault(because = "Component result is not serializable") internal abstract class TaskDependencyDump : DefaultTask() { @get:Input internal abstract val projectPath: Property @get:Input - internal abstract val components: MapProperty + internal abstract val components: MapProperty> @get:OutputFile internal abstract val outputFile: RegularFileProperty diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt index 64f1f3e..fd7ec9a 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt @@ -19,6 +19,7 @@ package com.rubensousa.projectguard.plugin.internal import com.google.common.truth.Truth.assertThat import org.gradle.api.Project import org.gradle.api.artifacts.result.ResolvedComponentResult +import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.repositories import org.gradle.testfixtures.ProjectBuilder import org.junit.Before @@ -111,11 +112,11 @@ class DependencyGraphBuilderTest { .containsExactly(legacyProjectA.path, legacyProjectC.path) } - private fun Project.getResolvedConfigurations(): Map { - val output = mutableMapOf() + private fun Project.getResolvedConfigurations(): Map> { + val output = mutableMapOf>() project.configurations.forEach { config -> - if(config.isCanBeResolved) { - output[config.name] = config.incoming.resolutionResult.rootComponent.get() + if (config.isCanBeResolved) { + output[config.name] = config.incoming.resolutionResult.rootComponent } } return output From c9fbded966c13d0d30102a3a878700dc61b5dc72 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Mon, 16 Mar 2026 21:16:36 +0100 Subject: [PATCH 12/12] Filter out configurations before assigning them to the task --- .../projectguard/plugin/ProjectGuardPlugin.kt | 8 +-- .../plugin/internal/DependencyGraphBuilder.kt | 62 +++++++++---------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt index 95c4eb4..52ab989 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt @@ -17,7 +17,7 @@ package com.rubensousa.projectguard.plugin import com.android.build.api.variant.AndroidComponentsExtension -import com.rubensousa.projectguard.plugin.internal.DependencyGraphBuilder +import com.rubensousa.projectguard.plugin.internal.DependencyConfiguration import com.rubensousa.projectguard.plugin.internal.task.TaskAggregateDependencyDump import com.rubensousa.projectguard.plugin.internal.task.TaskAggregateRestrictionDump import com.rubensousa.projectguard.plugin.internal.task.TaskBaseline @@ -338,9 +338,9 @@ class ProjectGuardPlugin : Plugin { ) { group = "other" description = "Generates a JSON containing the dependencies of this module." - projectPath.set(targetProject.path) - targetProject.configurations.forEach { config -> - if (config.isCanBeResolved) { + projectPath.set(project.path) + project.configurations.forEach { config -> + if (config.isCanBeResolved && DependencyConfiguration.isConfigurationSupported(config.name)) { components.put( config.name, config.incoming.resolutionResult.rootComponent diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt index f2f4621..5d19f9a 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt @@ -49,45 +49,43 @@ internal class DependencyGraphBuilder { fun buildFromComponents(results: Map>): DependencyGraph { val graph = DependencyGraph() results.forEach { (configurationId, resultProvider) -> - if (DependencyConfiguration.isConfigurationSupported(configurationId)) { - val result = resultProvider.get() - val resultId = result.id - if (resultId is ProjectComponentIdentifier) { - val moduleId = resultId.projectPath - result.dependencies.forEach { dependencyResult -> - when (dependencyResult) { - is DefaultUnresolvedDependencyResult -> { - val requested = dependencyResult.requested - if (requested is ModuleComponentSelector) { - graph.addLibraryDependency( - module = moduleId, - dependency = "${requested.group}:${requested.module}", - configurationId = configurationId, - ) - } + val result = resultProvider.get() + val resultId = result.id + if (resultId is ProjectComponentIdentifier) { + val moduleId = resultId.projectPath + result.dependencies.forEach { dependencyResult -> + when (dependencyResult) { + is DefaultUnresolvedDependencyResult -> { + val requested = dependencyResult.requested + if (requested is ModuleComponentSelector) { + graph.addLibraryDependency( + module = moduleId, + dependency = "${requested.group}:${requested.module}", + configurationId = configurationId, + ) } + } - is ResolvedDependencyResult -> { - val selected = dependencyResult.selected - when (val projectId = selected.id) { - is ProjectComponentIdentifier -> { - if (projectId.projectPath != moduleId) { - graph.addInternalDependency( - module = moduleId, - dependency = projectId.projectPath, - configurationId = configurationId, - ) - } - } - - is ModuleComponentIdentifier -> { - graph.addLibraryDependency( + is ResolvedDependencyResult -> { + val selected = dependencyResult.selected + when (val projectId = selected.id) { + is ProjectComponentIdentifier -> { + if (projectId.projectPath != moduleId) { + graph.addInternalDependency( module = moduleId, - dependency = "${projectId.group}:${projectId.module}", + dependency = projectId.projectPath, configurationId = configurationId, ) } } + + is ModuleComponentIdentifier -> { + graph.addLibraryDependency( + module = moduleId, + dependency = "${projectId.group}:${projectId.module}", + configurationId = configurationId, + ) + } } } }