diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eefdff..fd56f8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfiguration.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfiguration.kt index 6aa9b46..33e3a9e 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfiguration.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfiguration.kt @@ -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, ) 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 5d19f9a..1f67d30 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 @@ -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 @@ -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, + ) + } } } } diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskDependencyDumpTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskDependencyDumpTest.kt index cdad1fa..7f61895 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskDependencyDumpTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskDependencyDumpTest.kt @@ -64,7 +64,7 @@ class TaskDependencyDumpTest { graph.addInternalDependency( module = inputModule, dependency = secondDependency, - configurationId = DependencyConfiguration.TEST + configurationId = DependencyConfiguration.TEST_COMPILE ) // when @@ -82,7 +82,7 @@ class TaskDependencyDumpTest { dependencies = listOf(DependencyReferenceDump(firstDependency, false)) ), ConfigurationDependencies( - id = DependencyConfiguration.TEST, + id = DependencyConfiguration.TEST_COMPILE, dependencies = listOf(DependencyReferenceDump(secondDependency, false)) ) ) diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfigurationTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfigurationTest.kt index d765a22..ccf0a1e 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfigurationTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfigurationTest.kt @@ -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() + } + } \ No newline at end of file diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderAndroidTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderAndroidTest.kt new file mode 100644 index 0000000..c8ef66e --- /dev/null +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderAndroidTest.kt @@ -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()!!.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") + } + +} 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 fd7ec9a..aa843bb 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,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 @@ -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) } @@ -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") } @@ -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") } @@ -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> { - val output = mutableMapOf>() - 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) diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphTest.kt index 4dcac99..907d85c 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphTest.kt @@ -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 ) @@ -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 ) @@ -184,7 +184,7 @@ class DependencyGraphTest { dependency = consumerDependency ) graph.addInternalDependency( - configurationId = DependencyConfiguration.TEST, + configurationId = DependencyConfiguration.TEST_COMPILE, module = consumerDependency, dependency = dependencyOfConsumerDependency ) @@ -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 ) diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/ProjectExtensions.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/ProjectExtensions.kt new file mode 100644 index 0000000..578820e --- /dev/null +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/ProjectExtensions.kt @@ -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> { + val output = mutableMapOf>() + project.configurations.forEach { config -> + if (config.isCanBeResolved) { + output[config.name] = config.incoming.resolutionResult.rootComponent + } + } + return output +} + +internal fun Set?.toIds(): List { + return this?.map { it.id } ?: emptyList() +} diff --git a/sample/android/build.gradle.kts b/sample/android/build.gradle.kts index cf0a28b..8d1f279 100644 --- a/sample/android/build.gradle.kts +++ b/sample/android/build.gradle.kts @@ -21,5 +21,6 @@ android { dependencies { implementation(project(":domain:a")) testImplementation(libs.junit) + testImplementation(libs.mockk) androidTestImplementation(project(":legacy:a")) } \ No newline at end of file diff --git a/sample/projectguard-baseline.yml b/sample/projectguard-baseline.yml index 343d542..db86580 100644 --- a/sample/projectguard-baseline.yml +++ b/sample/projectguard-baseline.yml @@ -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"