diff --git a/CHANGELOG.md b/CHANGELOG.md index fb115dd..39a108d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased +## v0.1.3 +- `affected-paths-core`: Fix custom Gradle flags not being properly set +- `tooling-support-android`: Evaluate projects with `com.android.test` plugin + ## v0.1.2 - `affected-paths-core`, `tooling-support-*`: Add in support for composite builds being analyzed - `affected-paths-core`: Remove filter of root project diff --git a/affected-paths/app/src/main/kotlin/com/squareup/affected/paths/app/options/BaseConfigurationOptions.kt b/affected-paths/app/src/main/kotlin/com/squareup/affected/paths/app/options/BaseConfigurationOptions.kt index a48ed79..f82007e 100644 --- a/affected-paths/app/src/main/kotlin/com/squareup/affected/paths/app/options/BaseConfigurationOptions.kt +++ b/affected-paths/app/src/main/kotlin/com/squareup/affected/paths/app/options/BaseConfigurationOptions.kt @@ -115,7 +115,8 @@ internal class BaseConfigurationOptions { @Option( names = ["--changed-files"], - description = ["List of changed files to use instead of the Git diff"] + description = ["List of changed files to use instead of the Git diff"], + split = " " ) var changedFiles: List = emptyList() internal set 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 efd6910..00fc629 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 @@ -94,6 +94,7 @@ public data class CoreOptions @JvmOverloads constructor( } internal val gradleArgs: List = buildList { + addAll(customGradleFlags) if (autoInjectPlugin) { add("-I") add( @@ -107,7 +108,7 @@ public data class CoreOptions @JvmOverloads constructor( mavenCentral() } dependencies { - classpath "com.squareup.affected.paths:tooling-support:0.1.2" + classpath "com.squareup.affected.paths:tooling-support:0.1.3" } } } diff --git a/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/utils/DependencyUtils.kt b/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/utils/DependencyUtils.kt index 3e4567a..dd4db2d 100644 --- a/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/utils/DependencyUtils.kt +++ b/affected-paths/core/src/main/kotlin/com/squareup/affected/paths/core/utils/DependencyUtils.kt @@ -34,21 +34,12 @@ internal suspend fun findAffectedPaths( changedFiles: List ): List { return coroutineScope { - // Separate the projects to their distinct builds - val projectsMappedToBuilds = buildMap> { - projectList.forEach { - val list = getOrPut(it.namespace) { arrayListOf() } - list.add(it) - } - } - return@coroutineScope projectsMappedToBuilds.flatMap { (_, projects) -> - val slices = async(Dispatchers.Default) { projects.getReverseDependencies() } - val filesToDocs = async(Dispatchers.Default) { - filesToProjects(changedFiles, projects.associateBy { it.pathToProject }) - } - return@flatMap findAffectedAddresses(slices.await(), filesToDocs.await()) + val slices = async(Dispatchers.Default) { projectList.getReverseDependencies() } + val filesToDocs = async(Dispatchers.Default) { + filesToProjects(changedFiles, projectList.associateBy { it.pathToProject }) } + return@coroutineScope findAffectedAddresses(slices.await(), filesToDocs.await()) } } 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 bd0d43d..0ffcfca 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 @@ -38,7 +38,7 @@ class AffectedPathsTest { } dependencies { - implementation(':library') + implementation project(':library') } """.trimIndent() ) @@ -95,7 +95,7 @@ class AffectedPathsTest { } dependencies { - implementation(':library') + implementation project(':library') } """.trimIndent() ) @@ -140,7 +140,7 @@ class AffectedPathsTest { val result = analyzer.analyze() assertContentEquals(listOf("build2/foobar", "app", "library"), result.projectMap.keys) - assertContentEquals(listOf("library"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) + assertContentEquals(listOf("app", "app:debug:debugUnitTest", "library", "app:release:releaseUnitTest"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) } @Test @@ -168,7 +168,7 @@ class AffectedPathsTest { } dependencies { - implementation(':library') + implementation project(':library') } """.trimIndent() ) @@ -214,7 +214,7 @@ class AffectedPathsTest { val result = analyzer.analyze() assertContentEquals(listOf("app", "library"), result.projectMap.keys) - assertContentEquals(listOf("library"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) + assertContentEquals(listOf("app", "app:debug:debugUnitTest", "library", "app:release:releaseUnitTest"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) } @Test @@ -246,7 +246,7 @@ class AffectedPathsTest { } dependencies { - implementation(':library') + implementation project(':library') implementation('com.squareup:blah:0.0.1') } """.trimIndent() @@ -319,7 +319,7 @@ class AffectedPathsTest { } dependencies { - implementation(':library:foobar') + implementation project(':library:foobar') implementation('com.squareup:blah:0.0.1') } """.trimIndent() @@ -357,7 +357,7 @@ class AffectedPathsTest { val result = analyzer.analyze() assertContentEquals(listOf("app", "library", "library/foobar"), result.projectMap.keys) - assertContentEquals(listOf("library/foobar"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) + assertContentEquals(listOf("app", "app:debug:debugUnitTest", "library/foobar", "app:release:releaseUnitTest"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) } private fun createSettingsFile(rootDir: Path, contents: String) { diff --git a/gradle.properties b/gradle.properties index 398970d..4237d4f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 kotlin.code.style=official GROUP=com.squareup.affected.paths -VERSION_NAME=0.1.2 +VERSION_NAME=0.1.3 POM_URL=https://github.com/squareup/affected-paths POM_SCM_URL=https://github.com/squareup/affected-paths diff --git a/test-support/src/main/kotlin/com/squareup/test/support/AndroidTestUtils.kt b/test-support/src/main/kotlin/com/squareup/test/support/AndroidTestUtils.kt index f7dbdfc..0b1495f 100644 --- a/test-support/src/main/kotlin/com/squareup/test/support/AndroidTestUtils.kt +++ b/test-support/src/main/kotlin/com/squareup/test/support/AndroidTestUtils.kt @@ -63,3 +63,25 @@ public fun generateLibraryBuild(temporaryFolder: File): File { } return buildDirectory } + +// Helper function for generating a bare-bone Android test project +public fun generateTestBuild(temporaryFolder: File): File { + val buildDirectory = temporaryFolder.canonicalFile.apply { mkdirs() } + File(buildDirectory, "build.gradle").apply { + writeText(""" + apply plugin: 'com.android.test' + apply plugin: 'kotlin-android' + + android { + targetProjectPath = ":app" + namespace 'com.squareup.test.library' + compileSdk 33 + defaultConfig { + targetSdk 17 + minSdk 17 + } + } + """.trimIndent()) + } + return buildDirectory +} diff --git a/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/AndroidProjectExtractor.kt b/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/AndroidProjectExtractor.kt index e1dd724..97e7418 100644 --- a/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/AndroidProjectExtractor.kt +++ b/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/AndroidProjectExtractor.kt @@ -19,6 +19,7 @@ package com.squareup.tooling.support.android import com.android.build.gradle.AppExtension import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.TestExtension import com.squareup.tooling.models.SquareProject import com.squareup.tooling.models.SquareTestConfiguration import com.squareup.tooling.support.core.extractors.relativePathToRootBuild @@ -93,3 +94,36 @@ internal fun Project.extractLibraryModuleProject(): SquareProject { } ) } + +/** + * Extracts a [SquareProject] using the Android [TestExtension]. + */ +internal fun Project.extractTestModuleProject(): SquareProject { + val testExtension = requireNotNull(extensions.findByType(TestExtension::class.java)) + + // Gets the sources defined in the extension + val sourceIndex = testExtension.sourceIndexExtractor() + + return SquareProject( + name = name, + pluginUsed = "android-test", + namespace = rootProject.name, + pathToProject = relativePathToRootBuild() ?: relativePathToRootProject(), + variants = testExtension.applicationVariants.associate { variant -> + val (srcs, deps) = variant.extractSquareVariantConfigurationParams(this, sourceIndex) + val tests = buildMap { + variant.testVariant?.let { + put(it.name, it.extractSquareTestConfiguration(this@extractTestModuleProject)) + } + variant.unitTestVariant?.let { + put(it.name, it.extractSquareTestConfiguration(this@extractTestModuleProject)) + } + } + variant.name to SquareVariantConfiguration( + srcs = srcs, + deps = deps, + tests = tests + ) + } + ) +} diff --git a/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/SquareProjectExtractorImpl.kt b/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/SquareProjectExtractorImpl.kt index f04e124..e2df400 100644 --- a/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/SquareProjectExtractorImpl.kt +++ b/tooling/support/android/src/main/kotlin/com/squareup/tooling/support/android/SquareProjectExtractorImpl.kt @@ -32,6 +32,9 @@ internal class SquareProjectExtractorImpl : SquareProjectExtractor { // Android library plugin logic project.plugins.hasPlugin("com.android.library") -> project.extractLibraryModuleProject() + // Android test plugin logic + project.plugins.hasPlugin("com.android.test") -> project.extractTestModuleProject() + else -> null } } diff --git a/tooling/support/android/src/test/kotlin/com/squareup/tooling/support/android/SquareProjectModelBuilderTest.kt b/tooling/support/android/src/test/kotlin/com/squareup/tooling/support/android/SquareProjectModelBuilderTest.kt index 884f1d9..fcec489 100644 --- a/tooling/support/android/src/test/kotlin/com/squareup/tooling/support/android/SquareProjectModelBuilderTest.kt +++ b/tooling/support/android/src/test/kotlin/com/squareup/tooling/support/android/SquareProjectModelBuilderTest.kt @@ -93,6 +93,40 @@ class SquareProjectModelBuilderTest { assertEquals(expected, result) } + @Test + fun `Ensure android test constructs SquareProject`() { + val projectExtractor = SquareProjectExtractorImpl() + + val rootProject = ProjectBuilder + .builder() + .withName("com.squareup") + .build() + + val childProject = ProjectBuilder + .builder() + .withName("test") + .withParent(rootProject) + .build() + + val testProject = ProjectBuilder + .builder() + .withName("lib") + .withParent(childProject) + .build() + + testProject.plugins.apply("com.android.test") + + val result = projectExtractor.extractSquareProject(testProject) + val expected = SquareProject( + name = "lib", + namespace = "com.squareup", + pathToProject = "test/lib", + pluginUsed = "android-test", + variants = emptyMap() + ) + assertEquals(expected, result) + } + @Test fun `Ensure no plugins returns null`() { val projectExtractor = SquareProjectExtractorImpl() 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 8df3706..fc76962 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 @@ -20,6 +20,7 @@ package com.squareup.tooling.support.builder import com.squareup.test.support.forceEvaluate import com.squareup.test.support.generateApplicationBuild import com.squareup.test.support.generateLibraryBuild +import com.squareup.test.support.generateTestBuild import com.squareup.tooling.models.SquareProject import com.squareup.tooling.support.core.models.SquareDependency import org.gradle.testfixtures.ProjectBuilder @@ -29,7 +30,6 @@ import org.junit.jupiter.api.io.TempDir import java.io.File import kotlin.test.assertContains import kotlin.test.assertEquals -import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -288,6 +288,59 @@ class SquareProjectModelBuilderTest { assertTrue(releaseUnitTest.deps.isEmpty()) } + @Test + fun `Ensure android test constructs SquareProject`() { + val projectModelBuilder = SquareProjectModelBuilder() + + val rootProject = ProjectBuilder + .builder() + .withProjectDir(temporaryFolder) + .withName("com.squareup.test") + .build() + + val libProject = ProjectBuilder + .builder() + .withName("test-lib") + .withProjectDir(generateTestBuild(File(rootProject.projectDir, "lib"))) + .withParent(rootProject) + .build() + + // Add in the ":app" project + ProjectBuilder + .builder() + .withName("app") + .withProjectDir(generateApplicationBuild(File(rootProject.projectDir, "app"))) + .withParent(rootProject) + .build() + + val expectedTestDependency = SquareDependency("/app", setOf("transitive")) + + libProject.forceEvaluate() + + val result = projectModelBuilder.buildAll(SquareProject::class.java.name, libProject) as SquareProject + + // Check SquareProject properties + assertEquals("test-lib", result.name) + assertEquals("com.squareup.test", result.namespace) + assertEquals("lib", result.pathToProject) + assertEquals("android-test", result.pluginUsed) + + // Check variant properties + assertTrue(result.variants.keys.containsAll(listOf("debug"))) + val debugVariant = requireNotNull(result.variants["debug"]) + assertTrue { + debugVariant.srcs.containsAll( + ANDROID_SRC_DIRECTORY_PATHS.map { "src/debug/$it" } + + ANDROID_SRC_DIRECTORY_PATHS.map { "src/main/$it" } + ) + } + assertTrue(debugVariant.deps.contains(expectedTestDependency)) + + // Check test variant properties + val debugTestVariants = debugVariant.tests + assertTrue(debugTestVariants.keys.isEmpty()) + } + @Test fun `Do not throw exception if a non-Java or non-Android plugin is used`() { val projectModelBuilder = SquareProjectModelBuilder()