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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@

# CHANGELOG

## 1.0.0-rc03 (2026-03-19)

- Fixed transitive library dependencies being included in the dependency graph verifications after `1.0.0-rc02`.
In the future, there will be an option to enable this behavior, but for now, there is backwards compatibility with the existing baselines.

## 1.0.0-rc02 (2026-03-16)

- Fixed issue during dependency resolution when configuration cache is enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ package com.rubensousa.projectguard.plugin.internal
internal object DependencyConfiguration {

const val COMPILE = "compileClasspath"
const val TEST = "testCompileClasspath"
const val TEST_COMPILE = "testCompileClasspath"
const val TEST_FIXTURE = "testFixturesCompileClasspath"

private val supportedConfigurations = mutableSetOf(
"androidTestUtil",
COMPILE,
TEST,
TEST_COMPILE,
TEST_FIXTURE,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ 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.ComponentSelectionCause
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.internal.artifacts.result.DefaultUnresolvedDependencyResult
Expand Down Expand Up @@ -80,11 +81,18 @@ internal class DependencyGraphBuilder {
}

is ModuleComponentIdentifier -> {
graph.addLibraryDependency(
module = moduleId,
dependency = "${projectId.group}:${projectId.module}",
configurationId = configurationId,
)
// Only include library dependencies that are not transitive
val isDirect = selected.selectionReason.descriptions.any {
it.cause == ComponentSelectionCause.REQUESTED
}
if (isDirect) {
val dependency = "${projectId.group}:${projectId.module}"
graph.addLibraryDependency(
module = moduleId,
dependency = dependency,
configurationId = configurationId,
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class TaskDependencyDumpTest {
graph.addInternalDependency(
module = inputModule,
dependency = secondDependency,
configurationId = DependencyConfiguration.TEST
configurationId = DependencyConfiguration.TEST_COMPILE
)

// when
Expand All @@ -82,7 +82,7 @@ class TaskDependencyDumpTest {
dependencies = listOf(DependencyReferenceDump(firstDependency, false))
),
ConfigurationDependencies(
id = DependencyConfiguration.TEST,
id = DependencyConfiguration.TEST_COMPILE,
dependencies = listOf(DependencyReferenceDump(secondDependency, false))
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,14 @@ class DependencyConfigurationTest {
assertThat(DependencyConfiguration.isReleaseConfiguration("testFixturesCompileClasspath")).isFalse()
}

@Test
fun `debug unit test compile is supported`() {
assertThat(DependencyConfiguration.isConfigurationSupported("debugUnitTestCompileClasspath")).isTrue()
}

@Test
fun `debug unit test runtime is not supported`() {
assertThat(DependencyConfiguration.isConfigurationSupported("debugUnitTestRuntimeClasspath")).isFalse()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2026 Rúben Sousa
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.rubensousa.projectguard.plugin.internal

import com.android.build.gradle.LibraryExtension
import com.google.common.truth.Truth.assertThat
import org.gradle.api.Project
import org.gradle.kotlin.dsl.findByType
import org.gradle.testfixtures.ProjectBuilder
import org.junit.Before
import kotlin.test.Test

class DependencyGraphBuilderAndroidTest {

private val graphBuilder = DependencyGraphBuilder()
private lateinit var rootProject: Project
private lateinit var consumerProject: Project

@Before
fun setup() {
rootProject = ProjectBuilder.builder()
.withName("root")
.build()
consumerProject = ProjectBuilder.builder()
.withName("consumer")
.withParent(rootProject)
.build()
consumerProject.plugins.apply("android-library")
consumerProject.extensions.findByType<LibraryExtension>()!!.apply {
namespace = "test.library"
compileSdk = 34
}
consumerProject.repositories.apply {
mavenCentral()
}
rootProject.evaluationDependsOnChildren()
}

@Test
fun `transitive dependencies of a library dependency are not included`() {
// given
consumerProject.dependencies.add("testImplementation", "io.mockk:mockk:1.14.9")

// when
val graph = graphBuilder.buildFromComponents(consumerProject.getResolvedConfigurations())

// then
val testConfiguration = graph.getConfigurations().find { it.id == "debugUnitTestCompileClasspath" }
assertThat(testConfiguration?.getDependencies(consumerProject.path).toIds()).containsExactly("io.mockk:mockk")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ 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
Expand Down Expand Up @@ -60,7 +58,7 @@ class DependencyGraphBuilderTest {

// then
val compileConfiguration = graph.getConfigurations().find { it.id == DependencyConfiguration.COMPILE }!!
assertThat(compileConfiguration.getDependencies(consumerProject.path).map { it.id })
assertThat(compileConfiguration.getDependencies(consumerProject.path).toIds())
.containsExactly(legacyProjectA.path, legacyProjectB.path)
}

Expand All @@ -75,7 +73,7 @@ class DependencyGraphBuilderTest {

// then
val compileConfiguration = graph.getConfigurations().find { it.id == DependencyConfiguration.COMPILE }!!
assertThat(compileConfiguration.getDependencies(consumerProject.path).map { it.id })
assertThat(compileConfiguration.getDependencies(consumerProject.path).toIds())
.containsExactly("com.google.truth:truth")
}

Expand All @@ -93,7 +91,7 @@ class DependencyGraphBuilderTest {

// then
val compileConfiguration = graph.getConfigurations().find { it.id == DependencyConfiguration.COMPILE }!!
assertThat(compileConfiguration.getDependencies(consumerProject.path).map { it.id })
assertThat(compileConfiguration.getDependencies(consumerProject.path).toIds())
.containsExactly("com.google.truth:truth")
}

Expand All @@ -107,21 +105,11 @@ class DependencyGraphBuilderTest {
val graph = graphBuilder.buildFromComponents(consumerProject.getResolvedConfigurations())

// then
val testConfiguration = graph.getConfigurations().find { it.id == DependencyConfiguration.TEST }!!
assertThat(testConfiguration.getDependencies(consumerProject.path).map { it.id })
val testConfiguration = graph.getConfigurations().find { it.id == DependencyConfiguration.TEST_COMPILE }!!
assertThat(testConfiguration.getDependencies(consumerProject.path).toIds())
.containsExactly(legacyProjectA.path, legacyProjectC.path)
}

private fun Project.getResolvedConfigurations(): Map<String, Provider<ResolvedComponentResult>> {
val output = mutableMapOf<String, Provider<ResolvedComponentResult>>()
project.configurations.forEach { config ->
if (config.isCanBeResolved) {
output[config.name] = config.incoming.resolutionResult.rootComponent
}
}
return output
}

private fun Project.addLegacyDependency(dependency: String): Project {
val legacyProject = createLegacySubProject(dependency)
dependencies.add("implementation", legacyProject)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class DependencyGraphTest {
val consumerDependency = "dependencyA"
val dependencyOfConsumerDependency = "dependencyB"
graph.addInternalDependency(
configurationId = DependencyConfiguration.TEST,
configurationId = DependencyConfiguration.TEST_COMPILE,
module = consumer,
dependency = consumerDependency
)
Expand Down Expand Up @@ -151,12 +151,12 @@ class DependencyGraphTest {
val consumerDependency = "dependencyA"
val dependencyOfConsumerDependency = "dependencyB"
graph.addInternalDependency(
configurationId = DependencyConfiguration.TEST,
configurationId = DependencyConfiguration.TEST_COMPILE,
module = consumer,
dependency = consumerDependency
)
graph.addInternalDependency(
configurationId = DependencyConfiguration.TEST,
configurationId = DependencyConfiguration.TEST_COMPILE,
module = consumerDependency,
dependency = dependencyOfConsumerDependency
)
Expand Down Expand Up @@ -184,7 +184,7 @@ class DependencyGraphTest {
dependency = consumerDependency
)
graph.addInternalDependency(
configurationId = DependencyConfiguration.TEST,
configurationId = DependencyConfiguration.TEST_COMPILE,
module = consumerDependency,
dependency = dependencyOfConsumerDependency
)
Expand Down Expand Up @@ -225,7 +225,7 @@ class DependencyGraphTest {
val consumer = "consumer"
val consumerDependency = "dependencyA"
graph.addLibraryDependency(
configurationId = DependencyConfiguration.TEST,
configurationId = DependencyConfiguration.TEST_COMPILE,
module = consumer,
dependency = consumerDependency
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2026 Rúben Sousa
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.rubensousa.projectguard.plugin.internal

import org.gradle.api.Project
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.provider.Provider

internal fun Project.getResolvedConfigurations(): Map<String, Provider<ResolvedComponentResult>> {
val output = mutableMapOf<String, Provider<ResolvedComponentResult>>()
project.configurations.forEach { config ->
if (config.isCanBeResolved) {
output[config.name] = config.incoming.resolutionResult.rootComponent
}
}
return output
}

internal fun Set<DirectDependency>?.toIds(): List<String> {
return this?.map { it.id } ?: emptyList()
}
1 change: 1 addition & 0 deletions sample/android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ android {
dependencies {
implementation(project(":domain:a"))
testImplementation(libs.junit)
testImplementation(libs.mockk)
androidTestImplementation(project(":legacy:a"))
}
2 changes: 2 additions & 0 deletions sample/projectguard-baseline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ suppressions:
reason: "Suppressed from baseline"
- dependency: ":legacy:b"
reason: "Suppressed from baseline"
- dependency: "io.mockk:mockk"
reason: "Suppressed from baseline"
:data:a:
- dependency: ":legacy:a"
reason: "Suppressed from baseline"
Expand Down
Loading