Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
5 changes: 4 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ coverage:
project:
default:
target: 80% # the required coverage value
threshold: 1% # the leniency in hitting the target
threshold: 1% # the leniency in hitting the target
patch:
default:
target: 80%
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -45,6 +46,7 @@ abstract class ProjectGuardExtension @Inject constructor(
private val dependencyRestrictionSpecs = objects.listProperty<DependencyRestrictionSpec>()
private val reportSpec = objects.property<ReportSpec>().convention(ReportSpec(showLibrariesInGraph = false))
private val options = objects.property<PluginOptions>().convention(PluginOptions(lifecycleTask = null))
private val provider = DefaultProvider { getSpec() }

override fun restrictModule(modulePath: String, action: Action<ModuleRestrictionScope>) {
val scope = ModuleRestrictionScopeImpl()
Expand Down Expand Up @@ -126,13 +128,25 @@ abstract class ProjectGuardExtension @Inject constructor(
override fun report(action: Action<ReportScope>) {
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<OptionScope>) {
val scope = OptionScopeImpl()
action.execute(scope)
options.set(PluginOptions(lifecycleTask = scope.lifecycleTask))
options.set(
options.get().copy(
lifecycleTask = scope.lifecycleTask,
)
)
}

internal fun getSpecProvider(): Provider<ProjectGuardSpec> {
return provider
}

internal fun getSpec(): ProjectGuardSpec {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -53,7 +53,6 @@ class ProjectGuardPlugin : Plugin<Project> {
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",
Expand Down Expand Up @@ -292,7 +291,7 @@ class ProjectGuardPlugin : Plugin<Project> {
) {
group = "verification"
description = "Verifies if there are any dependency restrictions"
pluginSpec.set(project.provider { extension.getSpec() })
pluginSpec.set(extension.getSpecProvider())
outputs.upToDateWhen { false }
}
}
Expand All @@ -307,7 +306,7 @@ class ProjectGuardPlugin : Plugin<Project> {
) {
group = "verification"
description = "Verifies if there are any dependency restrictions"
pluginSpec.set(project.provider { extension.getSpec() })
pluginSpec.set(extension.getSpecProvider())
outputs.upToDateWhen { false }
}
}
Expand All @@ -323,7 +322,7 @@ class ProjectGuardPlugin : Plugin<Project> {
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)
)
Expand All @@ -340,7 +339,14 @@ class ProjectGuardPlugin : Plugin<Project> {
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 && DependencyConfiguration.isConfigurationSupported(config.name)) {
components.put(
config.name,
config.incoming.resolutionResult.rootComponent
)
}
}
outputFile.set(
project.layout.buildDirectory.file(dependenciesFilePath)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@
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.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
import org.gradle.api.provider.Provider

internal class DependencyGraphBuilder {

Expand All @@ -42,35 +46,53 @@ internal class DependencyGraphBuilder {
return graph
}

fun buildFromProject(project: Project): DependencyGraph {
fun buildFromComponents(results: Map<String, Provider<ResolvedComponentResult>>): 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
)
}
}

is ExternalModuleDependency -> {
results.forEach { (configurationId, resultProvider) ->
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 = "${dependency.group}:${dependency.name}",
configurationId = config.name
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(
module = moduleId,
dependency = "${projectId.group}:${projectId.module}",
configurationId = configurationId,
)
}
}
}
}
}
}
}
return graph
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,42 +17,46 @@
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.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

@CacheableTask
@DisableCachingByDefault(because = "Component result is not serializable")
internal abstract class TaskDependencyDump : DefaultTask() {

@get:Input
internal abstract val projectPath: Property<String>

@get:Input
internal abstract val dependencyGraph: Property<DependencyGraph>
internal abstract val components: MapProperty<String, Provider<ResolvedComponentResult>>

@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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -36,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 {
Expand Down Expand Up @@ -67,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")
Expand All @@ -80,7 +81,7 @@ class PluginCacheTest {
val result = pluginRunner.runTask(libraryDependencyTask)

// then
assertThat(result).isEqualTo(TaskOutcome.FROM_CACHE)
assertThat(result).isEqualTo(TaskOutcome.SUCCESS)
}

@Test
Expand Down Expand Up @@ -118,24 +119,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
Expand Down Expand Up @@ -170,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")
Expand All @@ -183,7 +166,7 @@ class PluginCacheTest {
val result = pluginRunner.runTask(task)

// then
assertThat(result).isEqualTo(TaskOutcome.FROM_CACHE)
assertThat(result).isEqualTo(TaskOutcome.SUCCESS)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading