Skip to content

Commit ab7394c

Browse files
committed
Address Issue dropbox#258: Configuration predicate to control dependency graph creation
Adds a new configuration option to allow the consumer to provision of a `Predicate<Configuration>` instance to customize what `Configuration` instances should be considered when building the dependency graph.
1 parent cd343a9 commit ab7394c

11 files changed

+366
-26
lines changed

Diff for: README.md

+6
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ affectedModuleDetector {
104104
"Run static analysis tool without auto-correction by Impact analysis"
105105
)
106106
]
107+
configurationPredicate.set(new Predicate<Configuration>() {
108+
boolean test(Configuration configuration) {
109+
return !configuration.name.contains("somethingToExclude")
110+
}
111+
})
107112
}
108113
```
109114

@@ -124,6 +129,7 @@ affectedModuleDetector {
124129
- `includeUncommitted`: If uncommitted files should be considered affected
125130
- `top`: The top of the git log to use. Must be used in combination with configuration `includeUncommitted = false`
126131
- `customTasks`: set of [CustomTask](https://github.com/dropbox/AffectedModuleDetector/blob/main/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleConfiguration.kt)
132+
- `configurationPredicate`: A predicate to filter configurations that should be considered for the dependency graph. By default, all configurations are considered.
127133

128134
By default, the Detector will look for `assembleAndroidDebugTest`, `connectedAndroidDebugTest`, and `testDebug`. Modules can specify a configuration block to specify which variant tests to run:
129135
```groovy

Diff for: affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleConfiguration.kt

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.dropbox.affectedmoduledetector
22

33
import com.dropbox.affectedmoduledetector.util.toOsSpecificPath
4+
import org.gradle.api.artifacts.Configuration
5+
import org.gradle.api.model.ObjectFactory
6+
import org.gradle.api.provider.Property
47
import java.io.File
8+
import java.util.function.Predicate
59

6-
class AffectedModuleConfiguration {
10+
class AffectedModuleConfiguration(objectFactory: ObjectFactory) {
711

812
/**
913
* Implementation of [AffectedModuleTaskType] for easy adding of custom gradle task to
@@ -44,6 +48,16 @@ class AffectedModuleConfiguration {
4448
*/
4549
var customTasks = emptySet<CustomTask>()
4650

51+
/**
52+
* Predicate to determine if a configuration should be considered or ignored. This predicate
53+
* will be called for every configuration defined by each project module. By default,
54+
* all configurations are considered.
55+
*/
56+
@Suppress("UNCHECKED_CAST") // Erasure in the API results in: Property<Predicate<*>>
57+
val configurationPredicate: Property<Predicate<Configuration>> =
58+
(objectFactory.property(Predicate::class.java) as Property<Predicate<Configuration>>)
59+
.convention(AlwaysConfigurationPredicate())
60+
4761
/**
4862
* Folder to place the log in
4963
*/

Diff for: affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class AffectedModuleDetectorPlugin : Plugin<Project> {
6262
private fun registerMainConfiguration(project: Project) {
6363
project.extensions.add(
6464
AffectedModuleConfiguration.name,
65-
AffectedModuleConfiguration()
65+
AffectedModuleConfiguration(project.objects)
6666
)
6767
}
6868

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.dropbox.affectedmoduledetector
2+
3+
import org.gradle.api.artifacts.Configuration
4+
import java.util.function.Predicate
5+
6+
/**
7+
* Default implementation of a [Configuration] [Predicate] that always returns true, indicating
8+
* that all configurations should be considered.
9+
*/
10+
internal class AlwaysConfigurationPredicate : Predicate<Configuration> {
11+
override fun test(t: Configuration): Boolean {
12+
return true
13+
}
14+
}

Diff for: affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/DependencyTracker.kt

+20-14
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,30 @@ class DependencyTracker constructor(
3232
private val rootProject: Project,
3333
private val logger: Logger?
3434
) {
35+
private val configuration: AffectedModuleConfiguration by lazy {
36+
rootProject.extensions.getByType(AffectedModuleConfiguration::class.java)
37+
}
38+
3539
private val dependentList: Map<Project, Set<Project>> by lazy {
3640
val result = mutableMapOf<Project, MutableSet<Project>>()
3741
rootProject.subprojects.forEach { project ->
3842
logger?.info("checking ${project.path} for dependencies")
39-
project.configurations.forEach { config ->
40-
logger?.info("checking config ${project.path}/$config for dependencies")
41-
config
42-
.dependencies
43-
.filterIsInstance(ProjectDependency::class.java)
44-
.forEach {
45-
logger?.info(
46-
"there is a dependency from ${project.path} to " +
47-
it.dependencyProject.path
48-
)
49-
result.getOrPut(it.dependencyProject) { mutableSetOf() }
50-
.add(project)
51-
}
52-
}
43+
project.configurations
44+
.filter(configuration.configurationPredicate.get()::test)
45+
.forEach { config ->
46+
logger?.info("checking config ${project.path}/$config for dependencies")
47+
config
48+
.dependencies
49+
.filterIsInstance(ProjectDependency::class.java)
50+
.forEach {
51+
logger?.info(
52+
"there is a dependency from ${project.path} to " +
53+
it.dependencyProject.path
54+
)
55+
result.getOrPut(it.dependencyProject) { mutableSetOf() }
56+
.add(project)
57+
}
58+
}
5359
}
5460
result
5561
}

Diff for: affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleConfigurationTest.kt

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.dropbox.affectedmoduledetector
22

3+
import com.dropbox.affectedmoduledetector.mocks.MockObjectFactory
34
import com.google.common.truth.Truth.assertThat
5+
import org.gradle.api.artifacts.Configuration
46
import org.junit.Assert.fail
57
import org.junit.Before
68
import org.junit.Rule
@@ -9,6 +11,7 @@ import org.junit.rules.TemporaryFolder
911
import org.junit.runner.RunWith
1012
import org.junit.runners.JUnit4
1113
import java.io.File
14+
import java.util.function.Predicate
1215

1316
@RunWith(JUnit4::class)
1417
class AffectedModuleConfigurationTest {
@@ -26,7 +29,7 @@ class AffectedModuleConfigurationTest {
2629

2730
@Before
2831
fun setup() {
29-
config = AffectedModuleConfiguration()
32+
config = AffectedModuleConfiguration(MockObjectFactory())
3033
}
3134

3235
@Test
@@ -324,4 +327,28 @@ class AffectedModuleConfigurationTest {
324327

325328
assert(actual.first().taskDescription == "Description of fake task")
326329
}
330+
331+
@Test
332+
fun `GIVEN AffectedModuleConfiguration WHEN configuration predicate is set THEN is configuration predicate`() {
333+
// GIVEN
334+
val expected = Predicate<Configuration> { false }
335+
config.configurationPredicate.set(expected)
336+
337+
// WHEN
338+
val predicate = config.configurationPredicate.get()
339+
340+
// THEN
341+
assertThat(predicate).isSameInstanceAs(expected)
342+
}
343+
344+
@Test
345+
fun `GIVEN AffectedModuleConfiguration WHEN configuration predicate is not set THEN is default`() {
346+
// GIVEN default configuration
347+
348+
// WHEN
349+
val predicate = config.configurationPredicate.get()
350+
351+
// THEN
352+
assertThat(predicate).isInstanceOf(AlwaysConfigurationPredicate::class.java)
353+
}
327354
}

Diff for: affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorImplTest.kt

+56-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.dropbox.affectedmoduledetector
22

3+
import com.dropbox.affectedmoduledetector.mocks.MockObjectFactory
34
import com.google.common.truth.Truth
45
import org.gradle.api.Project
56
import org.gradle.api.plugins.ExtraPropertiesExtension
@@ -220,10 +221,13 @@ class AffectedModuleDetectorImplTest {
220221
val p19config = p19.configurations.create("p19config")
221222
p19config.dependencies.add(p19.dependencies.project(mutableMapOf("path" to ":p18")))
222223

223-
affectedModuleConfiguration = AffectedModuleConfiguration().also {
224+
affectedModuleConfiguration = AffectedModuleConfiguration(MockObjectFactory()).also {
224225
it.baseDir = tmpDir.absolutePath
225226
it.pathsAffectingAllModules = pathsAffectingAllModules
226227
}
228+
listOf(root, root2, root3).forEach { rootProject ->
229+
rootProject.extensions.add(AffectedModuleConfiguration.name, affectedModuleConfiguration)
230+
}
227231
}
228232

229233
@Test
@@ -1329,7 +1333,7 @@ class AffectedModuleDetectorImplTest {
13291333
@Test
13301334
fun `GIVEN affected module configuration WHEN invalid path THEN throw exception`() {
13311335
// GIVEN
1332-
val config = AffectedModuleConfiguration().also {
1336+
val config = AffectedModuleConfiguration(MockObjectFactory()).also {
13331337
it.baseDir = tmpFolder.root.absolutePath
13341338
}
13351339

@@ -1350,7 +1354,7 @@ class AffectedModuleDetectorImplTest {
13501354
@Test
13511355
fun `GIVEN affected module configuration WHEN valid paths THEN return paths`() {
13521356
// GIVEN
1353-
val config = AffectedModuleConfiguration().also {
1357+
val config = AffectedModuleConfiguration(MockObjectFactory()).also {
13541358
it.baseDir = tmpFolder.root.absolutePath
13551359
}
13561360

@@ -1522,6 +1526,55 @@ class AffectedModuleDetectorImplTest {
15221526
)
15231527
}
15241528

1529+
@Test
1530+
fun `GIVEN upward configuration reference from p2 to p6 WHEN no predicate is supplied THEN p2 is affected`() {
1531+
p2.configurations.create("p2-upward-p6") { config ->
1532+
config.dependencies.add(p2.dependencies.project(mapOf("path" to p6.path)))
1533+
}
1534+
val detector = AffectedModuleDetectorImpl(
1535+
rootProject = root,
1536+
logger = logger,
1537+
ignoreUnknownProjects = false,
1538+
projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS,
1539+
modules = null,
1540+
injectedGitClient = MockGitClient(
1541+
changedFiles = listOf(
1542+
convertToFilePath("d1/d3/d6", "foo.java")
1543+
),
1544+
tmpFolder = tmpFolder.root
1545+
),
1546+
config = affectedModuleConfiguration
1547+
)
1548+
Truth.assertThat(detector.shouldInclude(p2)).isTrue()
1549+
Truth.assertThat(detector.shouldInclude(p6)).isTrue()
1550+
}
1551+
1552+
@Test
1553+
fun `GIVEN upward configuration reference from p2 to p6 WHEN predicate filtered THEN p2 is unaffected`() {
1554+
p2.configurations.create("p2-upward-p6") { config ->
1555+
config.dependencies.add(p2.dependencies.project(mapOf("path" to p6.path)))
1556+
}
1557+
affectedModuleConfiguration.configurationPredicate.set { configuration ->
1558+
!configuration.name.contains("-upward-")
1559+
}
1560+
val detector = AffectedModuleDetectorImpl(
1561+
rootProject = root,
1562+
logger = logger,
1563+
ignoreUnknownProjects = false,
1564+
projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS,
1565+
modules = null,
1566+
injectedGitClient = MockGitClient(
1567+
changedFiles = listOf(
1568+
convertToFilePath("d1/d3/d6", "foo.java")
1569+
),
1570+
tmpFolder = tmpFolder.root
1571+
),
1572+
config = affectedModuleConfiguration
1573+
)
1574+
Truth.assertThat(detector.shouldInclude(p2)).isFalse()
1575+
Truth.assertThat(detector.shouldInclude(p6)).isTrue()
1576+
}
1577+
15251578
// For both Linux/Windows
15261579
fun convertToFilePath(vararg list: String): String {
15271580
return list.toList().joinToString(File.separator)

Diff for: affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPluginTest.kt

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.dropbox.affectedmoduledetector
22

3+
import com.dropbox.affectedmoduledetector.mocks.MockObjectFactory
34
import com.google.common.truth.Truth.assertThat
45
import org.gradle.api.Project
56
import org.gradle.api.internal.plugins.PluginApplicationException
@@ -10,7 +11,6 @@ import org.junit.Test
1011
import org.junit.rules.TemporaryFolder
1112
import org.junit.runner.RunWith
1213
import org.junit.runners.JUnit4
13-
import java.lang.IllegalStateException
1414

1515
@RunWith(JUnit4::class)
1616
class AffectedModuleDetectorPluginTest {
@@ -102,7 +102,7 @@ class AffectedModuleDetectorPluginTest {
102102
@Test
103103
fun `GIVEN affected module detector plugin WHEN register_custom_task is called AND AffectedModuleConfiguration customTask is not empty THEN task is added`() {
104104
// GIVEN
105-
val configuration = AffectedModuleConfiguration()
105+
val configuration = AffectedModuleConfiguration(MockObjectFactory())
106106
configuration.customTasks = setOf(fakeTask)
107107
rootProject.extensions.add(AffectedModuleConfiguration.name, configuration)
108108

@@ -122,7 +122,7 @@ class AffectedModuleDetectorPluginTest {
122122
@Test
123123
fun `GIVEN affected module detector plugin WHEN registerCustomTasks is called AND AffectedModuleConfiguration customTask is empty THEN task isn't added`() {
124124
// GIVEN
125-
val configuration = AffectedModuleConfiguration()
125+
val configuration = AffectedModuleConfiguration(MockObjectFactory())
126126
rootProject.extensions.add(AffectedModuleConfiguration.name, configuration)
127127
val plugin = AffectedModuleDetectorPlugin()
128128

@@ -144,7 +144,7 @@ class AffectedModuleDetectorPluginTest {
144144
@Test
145145
fun `GIVEN affected module detector plugin WHEN registerTestTasks THEN task all task added`() {
146146
// GIVEN
147-
val configuration = AffectedModuleConfiguration()
147+
val configuration = AffectedModuleConfiguration(MockObjectFactory())
148148
rootProject.extensions.add(AffectedModuleConfiguration.name, configuration)
149149
val plugin = AffectedModuleDetectorPlugin()
150150

@@ -168,7 +168,7 @@ class AffectedModuleDetectorPluginTest {
168168
@Test
169169
fun `GIVEN affected module detector plugin WHEN registerTestTasks called THEN added all tasks from InternalTaskType`() {
170170
// GIVEN
171-
val configuration = AffectedModuleConfiguration()
171+
val configuration = AffectedModuleConfiguration(MockObjectFactory())
172172
rootProject.extensions.add(AffectedModuleConfiguration.name, configuration)
173173
val plugin = AffectedModuleDetectorPlugin()
174174
val availableTaskVariants = 3 // runAffectedAndroidTests, assembleAffectedAndroidTests and runAffectedUnitTests
@@ -187,7 +187,7 @@ class AffectedModuleDetectorPluginTest {
187187
fun `GIVEN affected module detector plugin WHEN registerCustomTasks called THEN added all tasks from FakeTaskType`() {
188188
// GIVEN
189189
val givenCustomTasks = setOf(fakeTask, fakeTask.copy(commandByImpact = "otherCommand"))
190-
val configuration = AffectedModuleConfiguration()
190+
val configuration = AffectedModuleConfiguration(MockObjectFactory())
191191
configuration.customTasks = givenCustomTasks
192192
rootProject.extensions.add(AffectedModuleConfiguration.name, configuration)
193193
val plugin = AffectedModuleDetectorPlugin()

0 commit comments

Comments
 (0)