diff --git a/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/CoreAnalyzer.kt b/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/CoreAnalyzer.kt index 5ee71ab..336c099 100644 --- a/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/CoreAnalyzer.kt +++ b/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/CoreAnalyzer.kt @@ -101,10 +101,7 @@ public class CoreAnalyzer @JvmOverloads constructor(private val coreOptions: Cor ensureActive() val actionExecutor = projectConnection.action( - SquareBuildAction( - coreOptions.allowGradleParallel, - coreOptions.useIncludeBuild - ) + SquareBuildAction(coreOptions.allowGradleParallel) ) actionExecutor.withCancellationToken(cancellationTokenSource.token()) if (coreOptions.useBuildScan) { diff --git a/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/CoreOptions.kt b/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/CoreOptions.kt index f7b0c22..223eea2 100644 --- a/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/CoreOptions.kt +++ b/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/CoreOptions.kt @@ -67,9 +67,6 @@ public data class CoreOptions @JvmOverloads constructor( /** Auto-injects the "com.squareup.tooling" plugin to all projects in the build */ val autoInjectPlugin: Boolean = true, - /** Include any "includeBuild" builds from the current build */ - val useIncludeBuild: Boolean = true, - /** Gradle distribution file to use */ val gradleDistributionPath: Path? = null, diff --git a/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/utils/SquareToolingApi.kt b/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/utils/SquareToolingApi.kt index 609c651..9657629 100644 --- a/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/utils/SquareToolingApi.kt +++ b/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/utils/SquareToolingApi.kt @@ -35,36 +35,24 @@ private class ProjectBuildAction(private val project: Model) : BuildAction> { override fun execute(controller: BuildController): List { // Run the ProjectBuildAction in parallel, if we can val canRunParallel = controller.getCanQueryProjectModelInParallel(SquareProject::class.java) val actions = buildList { + addAll(controller.buildModel.projects.asSequence().map { project -> ProjectBuildAction(project) }) // Include any builds along with the root build - if (useIncludeBuild) { - controller.buildModel.includedBuilds.forEach { build -> - addAll( - build.projects // All projects included in the "settings.gradle" file of all builds - .asSequence() - .map { project -> - return@map ProjectBuildAction(project) - } - ) - } + controller.buildModel.includedBuilds.forEach { build -> + addAll( + build.projects // All projects included in the "settings.gradle" file of all builds + .asSequence() + .map { project -> + return@map ProjectBuildAction(project) + } + ) } - - // The "BuildModel" is the Gradle build after evaluating the "settings.gradle" file - addAll( - controller.buildModel - .projects // All projects included in the "settings.gradle" file - .asSequence() - .map { project -> - return@map ProjectBuildAction(project) - }.toList() - ) } if (actions.isEmpty()) return emptyList() diff --git a/affected-paths/core/src/test/kotlin/com/squareup/affected/paths/core/git/AffectedPathsTest.kt b/affected-paths/core/src/test/kotlin/com/squareup/affected/paths/core/git/AffectedPathsTest.kt index 0ffcfca..8d71845 100644 --- a/affected-paths/core/src/test/kotlin/com/squareup/affected/paths/core/git/AffectedPathsTest.kt +++ b/affected-paths/core/src/test/kotlin/com/squareup/affected/paths/core/git/AffectedPathsTest.kt @@ -9,6 +9,7 @@ import kotlin.io.path.createDirectories import kotlin.io.path.writeText import kotlin.test.Test import kotlin.test.assertContentEquals +import kotlin.test.assertEquals import kotlin.time.Duration.Companion.minutes class AffectedPathsTest { @@ -139,81 +140,7 @@ class AffectedPathsTest { val result = analyzer.analyze() - assertContentEquals(listOf("build2/foobar", "app", "library"), result.projectMap.keys) - assertContentEquals(listOf("app", "app:debug:debugUnitTest", "library", "app:release:releaseUnitTest"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) - } - - @Test - fun `Ignores projects in included builds when useIncludeBuild is false`() = runTest(timeout = 3.minutes) { - // Prep - val build1 = root.resolve("build1").createDirectories() - val build2 = build1.resolve("build2").createDirectories() - createSettingsFile( - rootDir = build1, - contents = """ - rootProject.name = 'blah' - includeBuild 'build2' - include 'app' - include 'library' - """.trimIndent() - ) - - createModule( - rootDir = build1, - name = "app", - contents = - """ - plugins { - id 'application' - } - - dependencies { - implementation project(':library') - } - """.trimIndent() - ) - - createModule( - rootDir = build1, - name = "library", - contents = - """ - plugins { - id 'java' - } - """.trimIndent() - ) - - createSettingsFile( - rootDir = build2, - contents = """ - rootProject.name = 'blah2' - include 'foobar' - """.trimIndent() - ) - - createModule( - rootDir = build2, - name = "foobar", - contents = - """ - plugins { - id 'java' - } - """.trimIndent() - ) - - val analyzer = CoreAnalyzer( - CoreOptions( - directory = build1, - changedFiles = listOf("library/build.gradle"), - useIncludeBuild = false - ) - ) - - val result = analyzer.analyze() - - assertContentEquals(listOf("app", "library"), result.projectMap.keys) + assertEquals(setOf("build2/foobar", "app", "library"), result.projectMap.keys) assertContentEquals(listOf("app", "app:debug:debugUnitTest", "library", "app:release:releaseUnitTest"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) } @@ -291,8 +218,11 @@ class AffectedPathsTest { val result = analyzer.analyze() - assertContentEquals(listOf("build2/foobar", "app", "library"), result.projectMap.keys) - assertContentEquals(listOf("build2/foobar"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) + assertEquals(setOf("build2/foobar", "app", "library"), result.projectMap.keys) + assertContentEquals( + listOf("app", "app:debug:debugUnitTest", "build2/foobar", "app:release:releaseUnitTest"), + result.affectedResults.flatMap { it.affectedProjectPaths }.distinct() + ) } @Test diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b46285e..6d83a49 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ logback = "1.4.5" koin = "3.3.0" -jgit = "6.4.0.202211300538-r" +jgit = "6.10.0.202406032230-r" vanniktech = "0.25.3" diff --git a/tooling/models/build.gradle b/tooling/models/build.gradle index 63eea31..bc84605 100644 --- a/tooling/models/build.gradle +++ b/tooling/models/build.gradle @@ -2,7 +2,3 @@ plugins { id 'square-lib' id 'square-publishing' } - -dependencies { - compileOnly(gradleApi()) -} diff --git a/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/TestExtractors.kt b/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/TestExtractors.kt index 6ab9cec..10503a6 100644 --- a/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/TestExtractors.kt +++ b/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/TestExtractors.kt @@ -21,7 +21,7 @@ import com.android.build.gradle.api.BaseVariant import com.android.build.gradle.api.TestVariant import com.android.build.gradle.api.UnitTestVariant import com.squareup.tooling.models.SquareTestConfiguration -import com.squareup.tooling.support.core.extractors.extractSquareDependency +import com.squareup.tooling.support.core.extractors.extractResolvedProjectDependencies import com.squareup.tooling.support.core.models.SquareTestConfiguration import org.gradle.api.Project @@ -55,6 +55,6 @@ private fun BaseVariant.extractSquareTestConfiguration(project: Project): Square }.map { it.toRelativeString(project.projectDir) } return SquareTestConfiguration( srcs = dirs.toSet(), - deps = compileConfiguration.allDependencies.map { it.extractSquareDependency(project) }.toSet() + deps = compileConfiguration.extractResolvedProjectDependencies(project).toSet() ) } diff --git a/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/VariantExtractor.kt b/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/VariantExtractor.kt index 438269a..fa8851c 100644 --- a/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/VariantExtractor.kt +++ b/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/VariantExtractor.kt @@ -19,8 +19,7 @@ package com.squareup.tooling.support.android import com.android.build.gradle.api.BaseVariant import com.squareup.tooling.models.SquareDependency -import com.squareup.tooling.support.core.extractors.extractDependencies -import com.squareup.tooling.support.core.extractors.extractSquareDependency +import com.squareup.tooling.support.core.extractors.extractSquareDependencies import org.gradle.api.Project import java.io.File @@ -77,8 +76,7 @@ internal fun BaseVariant.extractSquareVariantConfigurationParams( addAll(compileConfiguration.extendsFrom.map { it.name }) }.toTypedArray() - val deps = project.configurations.extractDependencies(*configNames) - .map { it.extractSquareDependency(project) } + val deps = project.configurations.extractSquareDependencies(project, *configNames) return srcs to deps.toSet() } diff --git a/tooling/support/android/src/test/kotlin/com/squareup/tooling/support/android/TestExtractorsTest.kt b/tooling/support/android/src/test/kotlin/com/squareup/tooling/support/android/TestExtractorsTest.kt index 4d5432c..d5a72be 100644 --- a/tooling/support/android/src/test/kotlin/com/squareup/tooling/support/android/TestExtractorsTest.kt +++ b/tooling/support/android/src/test/kotlin/com/squareup/tooling/support/android/TestExtractorsTest.kt @@ -54,8 +54,8 @@ class TestExtractorsTest { val squareUnitTestConfiguration = unitTestVariant .extractSquareTestConfiguration(appProject) - assertEquals(1, squareUnitTestConfiguration.deps.size) - assertTrue { squareUnitTestConfiguration.deps.all { it.target == "/app" } } + assertEquals(2, squareUnitTestConfiguration.deps.size) + assertTrue { squareUnitTestConfiguration.deps.any { it.target == "/app" } } // Test the TestVariant val testVariant = appProject.extensions.getByType(AppExtension::class.java).testVariants.first() @@ -66,7 +66,7 @@ class TestExtractorsTest { squareTestConfiguration.srcs.all { it.startsWith("src/") }, "All source paths must be relative to the project dir" ) - assertEquals(1, squareTestConfiguration.deps.size) - assertTrue { squareTestConfiguration.deps.all { it.target == "/app" } } + assertEquals(2, squareTestConfiguration.deps.size) + assertTrue { squareTestConfiguration.deps.any { it.target == "/app" } } } } diff --git a/tooling/support/android/src/test/kotlin/com/squareup/tooling/support/android/VariantExtractorsTest.kt b/tooling/support/android/src/test/kotlin/com/squareup/tooling/support/android/VariantExtractorsTest.kt index 5cd55a0..28f1e2a 100644 --- a/tooling/support/android/src/test/kotlin/com/squareup/tooling/support/android/VariantExtractorsTest.kt +++ b/tooling/support/android/src/test/kotlin/com/squareup/tooling/support/android/VariantExtractorsTest.kt @@ -53,7 +53,11 @@ class VariantExtractorsTest { "All source paths must be relative to the project dir" ) assertEquals(24, srcs.size, "Sources were missing") - assertTrue("No dependencies should be listed") { deps.isEmpty() } + // Filter out the kotlin-stdlib-jdk8 dependency + val filteredDeps = deps.filterNot { + it.target.contains("kotlin-stdlib") + } + assertTrue("No dependencies should be listed") { filteredDeps.isEmpty() } } @Test diff --git a/tooling/support/core/src/main/kotlin/com/squareup/tooling/support/core/extractors/DependencyExtractors.kt b/tooling/support/core/src/main/kotlin/com/squareup/tooling/support/core/extractors/DependencyExtractors.kt index c703ea2..c1a39ff 100644 --- a/tooling/support/core/src/main/kotlin/com/squareup/tooling/support/core/extractors/DependencyExtractors.kt +++ b/tooling/support/core/src/main/kotlin/com/squareup/tooling/support/core/extractors/DependencyExtractors.kt @@ -20,27 +20,121 @@ package com.squareup.tooling.support.core.extractors import com.squareup.tooling.models.SquareDependency import com.squareup.tooling.support.core.models.SquareDependency import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.ConfigurationContainer import org.gradle.api.artifacts.Dependency 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.component.ProjectComponentSelector +import org.gradle.api.artifacts.result.ResolvedDependencyResult +import org.gradle.api.artifacts.result.UnresolvedDependencyResult import org.gradle.api.internal.artifacts.dependencies.AbstractExternalModuleDependency import org.gradle.api.internal.artifacts.dependencies.AbstractModuleDependency +import org.gradle.util.GradleVersion -// Extracts Gradle Dependency objects from the given configurations -public fun ConfigurationContainer.extractDependencies( +/** + * Extracts SquareDependency objects from the given configurations + */ +public fun ConfigurationContainer.extractSquareDependencies( + project: Project, vararg configurationNames: String -): Sequence { - return configurationNames.asSequence().flatMap { configurationName -> - getByName(configurationName).allDependencies.asSequence() - } +): Sequence { + return configurationNames.asSequence() + .map { configurationName -> getByName(configurationName) } + .flatMap { configuration -> + configuration.extractResolvedProjectDependencies(project) + } +} + +/** + * Extracts Gradle Dependency objects from the given configurations + */ +public fun Configuration.extractDependencies(): Sequence { + return allDependencies.asSequence() +} + +/** + * Extracts dependencies from the resolved artifacts of the configuration. + * + * If configuration is not resolvable, it will extract dependencies from the configuration itself. + */ +public fun Configuration.extractResolvedProjectDependencies(project: Project): Sequence { + if (!isCanBeResolved) return extractDependencies().map { it.extractSquareDependency(project) } + + val resolutionResult = incoming.resolutionResult + val allDependencies = resolutionResult.allDependencies + val directDependencies = resolutionResult.root.dependencies.toSet() + + return allDependencies.asSequence() + .map { dependencyResult -> + when (dependencyResult) { + is ResolvedDependencyResult -> { + return@map when (val id = dependencyResult.selected.id) { + is ProjectComponentIdentifier -> { + val path = gradlePathToFilePath(id.identityPath) + val isTransitive = dependencyResult !in directDependencies + SquareDependency( + target = path, + tags = if (isTransitive) setOf("transitive") else emptySet() + ) + } + + is ModuleComponentIdentifier -> { + @Suppress("UselessCallOnNotNull") + SquareDependency( + target = "@maven://${id.moduleIdentifier.group.orEmpty().ifBlank { "undefined" }}:${id.moduleIdentifier.name}" + ) + } + + else -> { + println("WARNING: Unknown dep type $javaClass") + SquareDependency(target = id.displayName) + } + } + } + + is UnresolvedDependencyResult -> { + return@map when (val id = dependencyResult.requested) { + is ProjectComponentSelector -> { + val path = gradlePathToFilePath(id.identityPath) + val isTransitive = dependencyResult !in directDependencies + SquareDependency( + target = path, + tags = if (isTransitive) setOf("transitive") else emptySet() + ) + } + + is ModuleComponentSelector -> { + @Suppress("UselessCallOnNotNull") + SquareDependency( + target = "@maven://${id.moduleIdentifier.group.orEmpty().ifBlank { "undefined" }}:${id.moduleIdentifier.name}" + ) + } + + else -> { + println("WARNING: Unknown dep type $javaClass") + SquareDependency(target = id.displayName) + } + } + } + + else -> { + return@map SquareDependency("unknown") + } + } + } } + // Converts the given Gradle Dependency into a SquareDependency use to construct the model project -public fun Dependency.extractSquareDependency(project: Project): SquareDependency { +private fun Dependency.extractSquareDependency(project: Project): SquareDependency { return when (this) { // Used by maven dependencies. is AbstractExternalModuleDependency -> { - SquareDependency(target = "@maven://${group ?: "undefined"}:$name") + @Suppress("UselessCallOnNotNull") + SquareDependency(target = "@maven://${group.orEmpty().ifBlank { "undefined" }}:$name") } // Meant to capture non-project dependencies, but in reality captures project dependencies. @@ -71,7 +165,7 @@ public fun Dependency.extractSquareDependency(project: Project): SquareDependenc // Meant to de-duplicate project name from target string. private fun Dependency.keyRelativeTo(relative: String = ""): String { if (this is ProjectDependency) { - return dependencyProject.path.replace(':', '/') + return gradlePathToFilePath(dependencyProject.path) } val s = group?.split(".", ":", "/") ?: emptyList() val result = when (s.firstOrNull()) { @@ -80,3 +174,26 @@ private fun Dependency.keyRelativeTo(relative: String = ""): String { } return result.plus(name).joinToString("") { "/$it" } } + +private fun gradlePathToFilePath(path: String): String { + val filePath = path.replace(':', '/') + return if (filePath.startsWith('/')) filePath else "/$filePath" +} + +private val ProjectComponentIdentifier.identityPath: String + get() { + return if (GradleVersion.current() >= GradleVersion.version("8.2")) { + if (projectPath.startsWith(build.buildPath)) projectPath else "${build.buildPath}$projectPath" + } else { + if (projectPath.startsWith(build.name)) projectPath else "${build.name}$projectPath" + } + } + +private val ProjectComponentSelector.identityPath: String + get() { + return if (GradleVersion.current() >= GradleVersion.version("8.2")) { + if (projectPath.startsWith(buildPath)) projectPath else "$buildPath$projectPath" + } else { + if (projectPath.startsWith(buildName)) projectPath else "$buildName$projectPath" + } + } diff --git a/tooling/support/core/src/test/kotlin/com/squareup/tooling/support/core/DependencyExtractorsTests.kt b/tooling/support/core/src/test/kotlin/com/squareup/tooling/support/core/DependencyExtractorsTests.kt index 841f283..f31cade 100644 --- a/tooling/support/core/src/test/kotlin/com/squareup/tooling/support/core/DependencyExtractorsTests.kt +++ b/tooling/support/core/src/test/kotlin/com/squareup/tooling/support/core/DependencyExtractorsTests.kt @@ -18,7 +18,8 @@ package com.squareup.tooling.support.core import com.squareup.tooling.support.core.extractors.extractDependencies -import com.squareup.tooling.support.core.extractors.extractSquareDependency +import com.squareup.tooling.support.core.extractors.extractResolvedProjectDependencies +import com.squareup.tooling.support.core.models.SquareDependency import org.gradle.testfixtures.ProjectBuilder import org.junit.jupiter.api.Test import kotlin.test.assertEquals @@ -34,7 +35,7 @@ class DependencyExtractorsTests { project.dependencies.add("testConfig", "com.squareup:foo") project.dependencies.add("testConfig", "com.squareup:bar") - val result = project.configurations.extractDependencies("testConfig").toList() + val result = project.configurations.getByName("testConfig").extractDependencies().toList() // Test assertEquals(2, result.size) @@ -51,7 +52,7 @@ class DependencyExtractorsTests { // Test val configuration = project.configurations.getByName("testConfig") - val result = configuration.dependencies.map { it.extractSquareDependency(project) }.first() + val result = configuration.extractResolvedProjectDependencies(project).first() assertTrue("AbstractExternalModuleDependency should not set tags") { return@assertTrue result.tags.isEmpty() @@ -72,12 +73,11 @@ class DependencyExtractorsTests { // Test val configuration = project.configurations.getByName("testConfig") - val result = configuration.dependencies.map { it.extractSquareDependency(project) }.first() + val result = configuration.extractResolvedProjectDependencies(project).first() assertTrue("AbstractExternalModuleDependency should not set tags") { return@assertTrue result.tags.isEmpty() } - println(result) assertTrue("AbstractExternalModuleDependency target incorrect") { return@assertTrue result.target == "@maven://undefined:foo" } @@ -94,16 +94,47 @@ class DependencyExtractorsTests { // Test val configuration = project.configurations.getByName("testConfig") - val result = configuration.dependencies.map { it.extractSquareDependency(project) }.first() + val result = configuration.extractResolvedProjectDependencies(project).first() assertEquals( - expected = 1, + expected = 0, actual = result.tags.size, - message = "Transitive tag not applied" + message = "Transitive tag applied" ) println(result) assertTrue("AbstractModuleDependency target incorrect") { return@assertTrue result.target == "/squareTest" } } + + @Test + fun `test extractResolvedProjectDependencies() with empty project dependencies`() { + // Setup + val project = ProjectBuilder.builder().build() + project.configurations.create("testConfig") + + // Test + val configuration = project.configurations.getByName("testConfig") + + val result = configuration.extractResolvedProjectDependencies(project) + assertTrue(result.none(), "The result should be an empty sequence") + } + + @Test + fun `test extractResolvedProjectDependencies() with resolved project dependencies`() { + // Setup + val project = ProjectBuilder.builder().build() + project.configurations.create("testConfig") + val projectDependency = ProjectBuilder.builder().withName("squareTest").withParent(project).build() + projectDependency.configurations.create("default") + project.dependencies.add("testConfig", projectDependency) + + // Test + val configuration = project.configurations.getByName("testConfig") + + val result = configuration.extractResolvedProjectDependencies(project) + assertEquals(1, result.count()) + assertTrue(result.contains(SquareDependency(target = "/squareTest"))) + } + } diff --git a/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/KotlinExtractorSupport.kt b/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/KotlinExtractorSupport.kt index 566cda6..e33121e 100644 --- a/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/KotlinExtractorSupport.kt +++ b/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/KotlinExtractorSupport.kt @@ -19,8 +19,7 @@ package com.squareup.tooling.support.jvm import com.squareup.tooling.models.SquareDependency import com.squareup.tooling.models.SquareTestConfiguration -import com.squareup.tooling.support.core.extractors.extractDependencies -import com.squareup.tooling.support.core.extractors.extractSquareDependency +import com.squareup.tooling.support.core.extractors.extractSquareDependencies import com.squareup.tooling.support.core.models.SquareTestConfiguration import org.gradle.api.Project import org.gradle.api.tasks.SourceSet @@ -33,10 +32,11 @@ internal fun KotlinSourceSet.extractSquareTestConfiguration( ): SquareTestConfiguration { return SquareTestConfiguration( srcs = kotlin.sourceDirectories.map { it.toRelativeString(project.projectDir) }.toSet(), - deps = project.configurations.extractDependencies( + deps = project.configurations.extractSquareDependencies( + project, implementationMetadataConfigurationName, compileOnlyMetadataConfigurationName - ).map { it.extractSquareDependency(project) }.toSet() + ).toSet() ) } @@ -78,8 +78,7 @@ internal fun KotlinSourceSet.extractSquareVariantConfigurationParams( ) }.toTypedArray() - val result = project.configurations.extractDependencies(*configNames) - .map { it.extractSquareDependency(project) }.toList() + val result = project.configurations.extractSquareDependencies(project,*configNames).toList() deps.addAll(result) return srcs to deps diff --git a/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/TestVariantExtractor.kt b/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/TestVariantExtractor.kt index b53f78c..0cd7924 100644 --- a/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/TestVariantExtractor.kt +++ b/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/TestVariantExtractor.kt @@ -18,8 +18,7 @@ package com.squareup.tooling.support.jvm import com.squareup.tooling.models.SquareTestConfiguration -import com.squareup.tooling.support.core.extractors.extractDependencies -import com.squareup.tooling.support.core.extractors.extractSquareDependency +import com.squareup.tooling.support.core.extractors.extractSquareDependencies import com.squareup.tooling.support.core.models.SquareTestConfiguration import org.gradle.api.Project import org.gradle.api.tasks.SourceSet @@ -28,7 +27,9 @@ import org.gradle.api.tasks.SourceSet internal fun SourceSet.extractSquareTestConfiguration(project: Project): SquareTestConfiguration { return SquareTestConfiguration( srcs = allSource.sourceDirectories.map { it.toRelativeString(project.projectDir) }.toSet(), - deps = project.configurations.extractDependencies(compileClasspathConfigurationName) - .map { it.extractSquareDependency(project) }.toSet() + deps = project.configurations.extractSquareDependencies( + project, + compileClasspathConfigurationName + ).toSet() ) } diff --git a/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/VariantExtractor.kt b/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/VariantExtractor.kt index ef61e0a..030703a 100644 --- a/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/VariantExtractor.kt +++ b/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/VariantExtractor.kt @@ -18,8 +18,7 @@ package com.squareup.tooling.support.jvm import com.squareup.tooling.models.SquareDependency -import com.squareup.tooling.support.core.extractors.extractDependencies -import com.squareup.tooling.support.core.extractors.extractSquareDependency +import com.squareup.tooling.support.core.extractors.extractSquareDependencies import org.gradle.api.Project import org.gradle.api.tasks.SourceSet @@ -55,6 +54,7 @@ internal fun SourceSet.extractSquareVariantConfigurationParams( } } + @Suppress("UselessCallOnNotNull", "RemoveExplicitTypeArguments") val configNames = buildList { add(compileClasspathConfigurationName) addAll( @@ -67,10 +67,7 @@ internal fun SourceSet.extractSquareVariantConfigurationParams( ) }.toTypedArray() - deps.addAll( - project.configurations.extractDependencies(*configNames) - .map { it.extractSquareDependency(project) } - ) + deps.addAll(project.configurations.extractSquareDependencies(project, *configNames)) return srcs to deps } diff --git a/tooling/support/jvm/src/test/kotlin/com/squareup/tooling/support/jvm/VariantExtractorsTest.kt b/tooling/support/jvm/src/test/kotlin/com/squareup/tooling/support/jvm/VariantExtractorsTest.kt index b88bd11..0574b89 100644 --- a/tooling/support/jvm/src/test/kotlin/com/squareup/tooling/support/jvm/VariantExtractorsTest.kt +++ b/tooling/support/jvm/src/test/kotlin/com/squareup/tooling/support/jvm/VariantExtractorsTest.kt @@ -106,7 +106,11 @@ class VariantExtractorsTest { val (srcs, deps) = kotlinSourceSet.extractSquareVariantConfigurationParams(appProject, "main") assertTrue(srcs.containsAll(listOf("src/main/java", "src/main/kotlin"))) - assertTrue("No dependencies should be listed") { deps.isEmpty() } + // Filter out the kotlin-stdlib-jdk8 dependency + val filteredDeps = deps.filterNot { + it.target.contains("kotlin-stdlib") + } + assertTrue("No dependencies should be listed") { filteredDeps.isEmpty() } } @Test diff --git a/tooling/support/src/main/kotlin/com/squareup/tooling/support/builder/SquareProjectModelBuilder.kt b/tooling/support/src/main/kotlin/com/squareup/tooling/support/builder/SquareProjectModelBuilder.kt index a0d00e5..6def00a 100644 --- a/tooling/support/src/main/kotlin/com/squareup/tooling/support/builder/SquareProjectModelBuilder.kt +++ b/tooling/support/src/main/kotlin/com/squareup/tooling/support/builder/SquareProjectModelBuilder.kt @@ -42,6 +42,7 @@ private class SquareProjectModelBuilderImpl : SquareProjectModelBuilder { return modelName == SquareProject::class.java.name } + // The `Any?` return type is valid. Ignore the error. override fun buildAll(modelName: String, project: Project): Any? { if (modelName == SquareProject::class.java.name) { return extractors.firstNotNullOfOrNull { it.extractSquareProject(project) } diff --git a/tooling/support/src/test/kotlin/com/squareup/tooling/support/builder/SquareProjectModelBuilderTest.kt b/tooling/support/src/test/kotlin/com/squareup/tooling/support/builder/SquareProjectModelBuilderTest.kt index fc76962..049ae6c 100644 --- a/tooling/support/src/test/kotlin/com/squareup/tooling/support/builder/SquareProjectModelBuilderTest.kt +++ b/tooling/support/src/test/kotlin/com/squareup/tooling/support/builder/SquareProjectModelBuilderTest.kt @@ -86,7 +86,7 @@ class SquareProjectModelBuilderTest { .withParent(rootProject) .build() - val expectedTestDependency = SquareDependency("/app", setOf("transitive")) + val expectedTestDependency = SquareDependency("/app") appProject.forceEvaluate() @@ -107,7 +107,7 @@ class SquareProjectModelBuilderTest { ANDROID_SRC_DIRECTORY_PATHS.map { "src/main/$it" } ) } - assertTrue(debugVariant.deps.isEmpty()) + assertTrue(debugVariant.deps.filterNot { it.target.contains("kotlin-stdlib") }.isEmpty()) val releaseVariant = requireNotNull(result.variants["release"]) assertTrue { @@ -116,7 +116,7 @@ class SquareProjectModelBuilderTest { ANDROID_SRC_DIRECTORY_PATHS.map { "src/main/$it" } ) } - assertTrue(releaseVariant.deps.isEmpty()) + assertTrue(releaseVariant.deps.filterNot { it.target.contains("kotlin-stdlib") }.isEmpty()) // Check test variant properties val debugTestVariants = debugVariant.tests @@ -165,7 +165,7 @@ class SquareProjectModelBuilderTest { .withParent(rootProject) .build() - val expectedTestDependency = SquareDependency("/lib", setOf("transitive")) + val expectedTestDependency = SquareDependency("/lib") libProject.forceEvaluate() @@ -186,7 +186,7 @@ class SquareProjectModelBuilderTest { ANDROID_SRC_DIRECTORY_PATHS.map { "src/main/$it" } ) } - assertTrue(debugVariant.deps.isEmpty()) + assertTrue(debugVariant.deps.filterNot { it.target.contains("kotlin-stdlib") }.isEmpty()) val releaseVariant = requireNotNull(result.variants["release"]) assertTrue { @@ -195,7 +195,7 @@ class SquareProjectModelBuilderTest { ANDROID_SRC_DIRECTORY_PATHS.map { "src/main/$it" } ) } - assertTrue(releaseVariant.deps.isEmpty()) + assertTrue(releaseVariant.deps.filterNot { it.target.contains("kotlin-stdlib") }.isEmpty()) // Check test variant properties val debugTestVariants = debugVariant.tests @@ -341,6 +341,64 @@ class SquareProjectModelBuilderTest { assertTrue(debugTestVariants.keys.isEmpty()) } + @Test + fun `Ensure dependency substitution rules are accounted for during model building`() { + val projectModelBuilder = SquareProjectModelBuilder() + + val rootProject = ProjectBuilder + .builder() + .withProjectDir(temporaryFolder) + .withName("com.squareup.test") + .build() + + ProjectBuilder + .builder() + .withName("test-lib") + .withProjectDir(generateTestBuild(File(rootProject.projectDir, "test-lib"))) + .withParent(rootProject) + .build() + + // Add in the ":app" project + val app = ProjectBuilder + .builder() + .withName("app") + .withProjectDir(generateApplicationBuild(File(rootProject.projectDir, "app"))) + .withParent(rootProject) + .build() + + app.buildFile.appendText(""" + + configurations.all { config -> + config.resolutionStrategy.dependencySubstitution { substitution -> + substitution.substitute(substitution.module("org.blah:blah")) + .using(substitution.project(":test-lib")) + } + } + + dependencies { + implementation 'org.blah:blah:' + } + """.trimIndent()) + + app.forceEvaluate() + + val result = projectModelBuilder.buildAll(SquareProject::class.java.name, app) as SquareProject + + // Check SquareProject properties + assertEquals("app", result.name) + assertEquals("com.squareup.test", result.namespace) + assertEquals("app", result.pathToProject) + assertEquals("android-app", result.pluginUsed) + + assertTrue("Dependencies were not substituted") { + result.variants.values.any { configuration -> + configuration.deps.any { dep -> + dep.target == "/test-lib" + } + } + } + } + @Test fun `Do not throw exception if a non-Java or non-Android plugin is used`() { val projectModelBuilder = SquareProjectModelBuilder()