diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index df5ec18..9925165 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,9 +33,9 @@ jobs: - name: Setup gradle uses: gradle/gradle-build-action@v2 - name: Build and test tooling - run: ./gradlew --no-daemon --profile --stacktrace -p tooling build test + run: ./gradlew --no-daemon --profile --stacktrace -PRELEASE_SIGNING_ENABLED=false -p tooling publishToMavenLocal build test - name: Build and test affected-paths - run: ./gradlew --no-daemon --profile --stacktrace -p affected-paths build test + run: ./gradlew --no-daemon --profile --stacktrace -PRELEASE_SIGNING_ENABLED=false -p affected-paths publishToMavenLocal build test env: GRADLE_OPTS: -Dorg.gradle.parallel=true -Dorg.gradle.caching=true diff --git a/CHANGELOG.md b/CHANGELOG.md index 798f642..fb115dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## Unreleased +## v0.1.2 +- `affected-paths-core`, `tooling-support-*`: Add in support for composite builds being analyzed +- `affected-paths-core`: Remove filter of root project +- `affected-paths-core`: Fix improper project mapping for file changes in nested projects +- `affected-paths-core`: Allow a custom Gradle installation path to be passed in to the Gradle Tooling API + ## v0.1.1 - `tooling-support`: Fix crash from `SquareProjectModelBuilder` when used on a non-Java/Android project - `affected-paths-core`: Adds `autoInjectPlugin` flag to `CoreOptions`, which auto-injects the "com.squareup.tooling" plugin to the build diff --git a/README.md b/README.md index 18b51e8..8bfa509 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Affected-Paths +![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/square/affected-paths/test.yml) +![Maven Central](https://img.shields.io/maven-central/v/com.squareup.affected.paths/affected-paths-core) +![GitHub](https://img.shields.io/github/license/square/affected-paths) + Affected-Paths is a Java library that utilizes the Gradle Tooling API to parse Gradle based projects and identifies all modules affected (directly and indirectly) given the file changes from git. @@ -48,12 +52,12 @@ The affected-paths library can be found on [MavenCentral][1]: ### Gradle Groovy ```groovy -implementation 'com.squareup.affected.paths:affected-paths-core:0.1.1' +implementation 'com.squareup.affected.paths:affected-paths-core:<>' ``` Kotlin ```kotlin -implementation("com.squareup.affected.paths:affected-paths-core:0.1.1") +implementation("com.squareup.affected.paths:affected-paths-core:<>") ``` ### Maven @@ -61,7 +65,7 @@ implementation("com.squareup.affected.paths:affected-paths-core:0.1.1") com.squareup.affected.paths affected-paths-core - 0.1.1 + *latest* ``` @@ -123,7 +127,7 @@ If the auto-inject flag is disabled, the tooling plugin will have to be applied Gradle DSL ```groovy plugins { - id 'com.squareup.tooling' version '0.1.1' + id 'com.squareup.tooling' version '<>' } ``` @@ -131,7 +135,7 @@ Legacy ```groovy buildscript { dependencies { - classpath "com.squareup.affected.paths:tooling-support:0.1.1" + classpath "com.squareup.affected.paths:tooling-support:<>" } } 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 efbfb4d..a48ed79 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 @@ -112,4 +112,18 @@ internal class BaseConfigurationOptions { ) var autoInject: Boolean = false internal set + + @Option( + names = ["--changed-files"], + description = ["List of changed files to use instead of the Git diff"] + ) + var changedFiles: List = emptyList() + internal set + + @Option( + names = ["--gradle-installation-path"], + description = ["Use a custom Gradle installation"] + ) + var gradleInstallationPath: Path? = null + internal set } diff --git a/affected-paths/app/src/main/kotlin/com/squareup/affected/paths/app/utils/CoreOptionsUtils.kt b/affected-paths/app/src/main/kotlin/com/squareup/affected/paths/app/utils/CoreOptionsUtils.kt index b0eb080..8d56b57 100644 --- a/affected-paths/app/src/main/kotlin/com/squareup/affected/paths/app/utils/CoreOptionsUtils.kt +++ b/affected-paths/app/src/main/kotlin/com/squareup/affected/paths/app/utils/CoreOptionsUtils.kt @@ -31,6 +31,8 @@ internal fun BaseConfigurationOptions.toCoreOptions(): CoreOptions { maxGradleMemory = maxGradleMemory, customJvmFlags = listOf("-XX:-MaxFDLimit"), customGradleFlags = listOf("--stacktrace"), - autoInjectPlugin = autoInject + autoInjectPlugin = autoInject, + changedFiles = changedFiles, + gradleInstallationPath = gradleInstallationPath, ) } diff --git a/affected-paths/core/build.gradle b/affected-paths/core/build.gradle index d879f80..5619ce1 100644 --- a/affected-paths/core/build.gradle +++ b/affected-paths/core/build.gradle @@ -13,4 +13,5 @@ dependencies { implementation(libs.slf4j.api) testImplementation(libs.kotlin.test.junit5) + testImplementation(libs.kotlinx.coroutines.test) } 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 e6ffe5f..45cef9e 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 @@ -77,14 +77,24 @@ public class CoreAnalyzer @JvmOverloads constructor(private val coreOptions: Cor val projectsDeferred = projects?.let { CompletableDeferred(it) } ?: async(Dispatchers.IO) { ensureActive() // In case this is cancelled before start - val projectConnector = affectedPathsApplication.koin.get() - .forProjectDirectory(rootDir.toFile()) - .useBuildDistribution() - .connect() + val projectConnector = with(affectedPathsApplication.koin.get()) { + forProjectDirectory(rootDir.toFile()) + if (coreOptions.gradleInstallationPath != null) { + useInstallation(coreOptions.gradleInstallationPath.toFile()) + } else { + useBuildDistribution() + } + return@with connect() + } ensureActive() - val actionExecutor = projectConnector.action(SquareBuildAction(coreOptions.allowGradleParallel)) + val actionExecutor = projectConnector.action( + SquareBuildAction( + coreOptions.allowGradleParallel, + coreOptions.useIncludeBuild + ) + ) actionExecutor.withCancellationToken(cancellationTokenSource.token()) actionExecutor.addArguments(coreOptions.gradleArgs) actionExecutor.addJvmArguments(coreOptions.jvmArgs) @@ -101,7 +111,7 @@ public class CoreAnalyzer @JvmOverloads constructor(private val coreOptions: Cor } val affectedResultsDeferred = async(Dispatchers.Default) { - projectsDeferred.await().findAffectedPaths(changedFilesDeferred.await()) + findAffectedPaths(projectsDeferred.await(), changedFilesDeferred.await()) } // Cancel the Gradle build if the coroutine was cancelled 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 5cb5e83..efd6910 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 @@ -65,7 +65,13 @@ public data class CoreOptions @JvmOverloads constructor( val changedFiles: List = emptyList(), /** Auto-injects the "com.squareup.tooling" plugin to all projects in the build */ - val autoInjectPlugin: Boolean = true + val autoInjectPlugin: Boolean = true, + + /** Include any "includeBuild" builds from the current build */ + val useIncludeBuild: Boolean = true, + + /** Pass in a custom Gradle installation, instead of using the build distribution */ + val gradleInstallationPath: Path? = null, ) { init { @@ -97,10 +103,11 @@ public data class CoreOptions @JvmOverloads constructor( beforeProject { project -> project.buildscript { repositories { + mavenLocal() mavenCentral() } dependencies { - classpath "com.squareup.affected.paths:tooling-support:0.1.1" + classpath "com.squareup.affected.paths:tooling-support:0.1.2" } } } 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 702c977..3e4567a 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 @@ -29,16 +29,26 @@ import kotlinx.coroutines.launch /* * Finds all the affected paths of the list of Square Projects with the given list of changed files */ -internal suspend fun List.findAffectedPaths( +internal suspend fun findAffectedPaths( + projectList: List, changedFiles: List ): List { return coroutineScope { - val slices = async(Dispatchers.Default) { getReverseDependencies() } - val filesToDocs = async(Dispatchers.Default) { - filesToProjects(changedFiles, associateBy { it.pathToProject }) + // Separate the projects to their distinct builds + val projectsMappedToBuilds = buildMap> { + projectList.forEach { + val list = getOrPut(it.namespace) { arrayListOf() } + list.add(it) + } } - return@coroutineScope findAffectedAddresses(slices.await(), filesToDocs.await()) + 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()) + } } } @@ -107,10 +117,11 @@ private fun filesToProjects( val fileToDocsMap = hashMapOf>() val list = changedFiles.mapNotNull { file -> val modulePath = arrayListOf() - val doc = file.trim('/').split('/').firstNotNullOfOrNull { element -> + val docSet = file.trim('/').split('/').mapNotNull docSet@ { element -> modulePath.add(element) - return@firstNotNullOfOrNull projects[modulePath.joinToString("/")] - } ?: return@mapNotNull null + return@docSet projects[modulePath.joinToString("/")] + } + val doc = docSet.lastOrNull() ?: return@mapNotNull null return@mapNotNull file to doc } 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 a7f4931..609c651 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 @@ -36,19 +36,36 @@ 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) - // The "BuildModel" is the Gradle build after evaluating the "settings.gradle" file - val actions = controller.buildModel - .projects // All projects included in the "settings.gradle" file - .asSequence() - .filter { it.path != ":" } // Filter out the root project - .map { project -> - return@map ProjectBuildAction(project) - }.toList() + val actions = buildList { + // 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) + } + ) + } + } + + // 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() return if (allowParallelConfiguration && canRunParallel) { 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 new file mode 100644 index 0000000..bd0d43d --- /dev/null +++ b/affected-paths/core/src/test/kotlin/com/squareup/affected/paths/core/git/AffectedPathsTest.kt @@ -0,0 +1,376 @@ +package com.squareup.affected.paths.core.git + +import com.squareup.affected.paths.core.CoreAnalyzer +import com.squareup.affected.paths.core.CoreOptions +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.io.TempDir +import java.nio.file.Path +import kotlin.io.path.createDirectories +import kotlin.io.path.writeText +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.time.Duration.Companion.minutes + +class AffectedPathsTest { + + @TempDir + lateinit var root: Path + + @Test + fun `Can find affected project with single file change`() = runTest(timeout = 3.minutes) { + // Prep + createSettingsFile( + rootDir = root, + contents = """ + rootProject.name = 'blah' + include 'app' + include 'library' + """.trimIndent() + ) + + createModule( + rootDir = root, + name = "app", + contents = + """ + plugins { + id 'application' + } + + dependencies { + implementation(':library') + } + """.trimIndent() + ) + + createModule( + rootDir = root, + name = "library", + contents = + """ + plugins { + id 'java' + } + """.trimIndent() + ) + + // Test + val analyzer = CoreAnalyzer( + CoreOptions( + directory = root, + changedFiles = listOf("app/build.gradle") + ) + ) + + val result = analyzer.analyze() + + // Results + assertContentEquals(listOf("app", "library"), result.projectMap.keys) + assertContentEquals(listOf("app"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) + + } + + @Test + fun `Can find projects from included builds by default`() = 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(':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") + ) + ) + + val result = analyzer.analyze() + + assertContentEquals(listOf("build2/foobar", "app", "library"), result.projectMap.keys) + assertContentEquals(listOf("library"), 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(':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) + assertContentEquals(listOf("library"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) + } + + @Test + fun `Can find affected projects owned by included builds given a changed file`() = 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') { + dependencySubstitution { + substitute module('com.squareup:blah') using project(':foobar') + } + } + include 'app' + include 'library' + """.trimIndent() + ) + + createModule( + rootDir = build1, + name = "app", + contents = + """ + plugins { + id 'application' + } + + dependencies { + implementation(':library') + implementation('com.squareup:blah:0.0.1') + } + """.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("build2/foobar/build.gradle") + ) + ) + + val result = analyzer.analyze() + + assertContentEquals(listOf("build2/foobar", "app", "library"), result.projectMap.keys) + assertContentEquals(listOf("build2/foobar"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) + } + + @Test + fun `Proper project is mapped for nested projects`() = runTest(timeout = 3.minutes) { + // Prep + val build = root.resolve("build").createDirectories() + createSettingsFile( + rootDir = build, + contents = """ + rootProject.name = 'blah' + include ':app' + include ':library' + include ':library:foobar' + """.trimIndent() + ) + + createModule( + rootDir = build, + name = "app", + contents = + """ + plugins { + id 'application' + } + + dependencies { + implementation(':library:foobar') + implementation('com.squareup:blah:0.0.1') + } + """.trimIndent() + ) + + createModule( + rootDir = build, + name = "library", + contents = + """ + plugins { + id 'java' + } + """.trimIndent() + ) + + createModule( + rootDir = build, + name = "library/foobar", + contents = + """ + plugins { + id 'java' + } + """.trimIndent() + ) + + val analyzer = CoreAnalyzer( + CoreOptions( + directory = build, + changedFiles = listOf("library/foobar/build.gradle") + ) + ) + + val result = analyzer.analyze() + + assertContentEquals(listOf("app", "library", "library/foobar"), result.projectMap.keys) + assertContentEquals(listOf("library/foobar"), result.affectedResults.flatMap { it.affectedProjectPaths }.distinct()) + } + + private fun createSettingsFile(rootDir: Path, contents: String) { + rootDir.resolve("settings.gradle").apply { + writeText(contents) + } + } + + private fun createModule(rootDir: Path, name: String, contents: String): Path { + return rootDir.resolve(name).createDirectories().apply { + resolve("build.gradle").apply { + writeText(contents) + } + } + } +} diff --git a/gradle.properties b/gradle.properties index 3fb4133..398970d 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.1 +VERSION_NAME=0.1.2 POM_URL=https://github.com/squareup/affected-paths POM_SCM_URL=https://github.com/squareup/affected-paths diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c5a6bfb..b46285e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,7 @@ kotlin-dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", vers kotlin-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" } kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx" } ## Gradle gradle-tooling-api = { module = "org.gradle:gradle-tooling-api", version.ref = "gradleTAPI" } 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 683bd34..e1dd724 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 @@ -21,6 +21,8 @@ import com.android.build.gradle.AppExtension import com.android.build.gradle.LibraryExtension import com.squareup.tooling.models.SquareProject import com.squareup.tooling.models.SquareTestConfiguration +import com.squareup.tooling.support.core.extractors.relativePathToRootBuild +import com.squareup.tooling.support.core.extractors.relativePathToRootProject import com.squareup.tooling.support.core.models.SquareProject import com.squareup.tooling.support.core.models.SquareVariantConfiguration import org.gradle.api.Project @@ -38,8 +40,8 @@ internal fun Project.extractAppModuleProject(): SquareProject { return SquareProject( name = name, pluginUsed = "android-app", - namespace = group.toString(), - pathToProject = projectDir.toRelativeString(rootDir), + namespace = rootProject.name, + pathToProject = relativePathToRootBuild() ?: relativePathToRootProject(), // Variants and configurations are different things. Should really be split. variants = appExtension.applicationVariants.associate { variant -> val (srcs, deps) = variant.extractSquareVariantConfigurationParams(this, sourceIndex) @@ -71,8 +73,8 @@ internal fun Project.extractLibraryModuleProject(): SquareProject { return SquareProject( name = name, pluginUsed = "android-library", - namespace = group.toString(), - pathToProject = projectDir.toRelativeString(rootDir), + namespace = rootProject.name, + pathToProject = relativePathToRootBuild() ?: relativePathToRootProject(), variants = libraryExtension.libraryVariants.associate { variant -> val (srcs, deps) = variant.extractSquareVariantConfigurationParams(this, sourceIndex) val tests = buildMap { 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 1624d92..884f1d9 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 @@ -51,7 +51,7 @@ class SquareProjectModelBuilderTest { val result = projectExtractor.extractSquareProject(appProject) val expected = SquareProject( name = "app", - namespace = "com.squareup.test", + namespace = "com.squareup", pathToProject = "test/app", pluginUsed = "android-app", variants = emptyMap() @@ -85,7 +85,7 @@ class SquareProjectModelBuilderTest { val result = projectExtractor.extractSquareProject(libraryProject) val expected = SquareProject( name = "lib", - namespace = "com.squareup.test", + namespace = "com.squareup", pathToProject = "test/lib", pluginUsed = "android-library", variants = emptyMap() diff --git a/tooling/support/core/src/main/kotlin/com/squareup/tooling/support/core/extractors/RootGradleUtil.kt b/tooling/support/core/src/main/kotlin/com/squareup/tooling/support/core/extractors/RootGradleUtil.kt new file mode 100644 index 0000000..b517407 --- /dev/null +++ b/tooling/support/core/src/main/kotlin/com/squareup/tooling/support/core/extractors/RootGradleUtil.kt @@ -0,0 +1,28 @@ +package com.squareup.tooling.support.core.extractors + +import org.gradle.api.Project +import org.gradle.api.invocation.Gradle +import org.gradle.invocation.DefaultGradle + +/** + * Gets the root Gradle build, if one exists. + */ +public fun Gradle.getRootGradle(): Gradle { + return parent?.getRootGradle() ?: this +} + +/** + * Finds the relative path of the project to the root build directory, if there is a relative root. + * Otherwise, returns `null`. + */ +public fun Project.relativePathToRootBuild(): String? { + val buildRootFile = (gradle.getRootGradle() as DefaultGradle).owner.buildRootDir + return projectDir.relativeToOrNull(buildRootFile)?.path +} + +/** + * Finds the relative path of the project to the repo root. + */ +public fun Project.relativePathToRootProject(): String { + return projectDir.toRelativeString(rootDir) +} diff --git a/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/JavaModuleDocumentExtractors.kt b/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/JavaModuleDocumentExtractors.kt index 6ed29b6..1607cfb 100644 --- a/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/JavaModuleDocumentExtractors.kt +++ b/tooling/support/jvm/src/main/kotlin/com/squareup/tooling/support/jvm/JavaModuleDocumentExtractors.kt @@ -20,6 +20,8 @@ package com.squareup.tooling.support.jvm import com.squareup.tooling.models.SquareDependency import com.squareup.tooling.models.SquareProject import com.squareup.tooling.models.SquareTestConfiguration +import com.squareup.tooling.support.core.extractors.relativePathToRootBuild +import com.squareup.tooling.support.core.extractors.relativePathToRootProject import com.squareup.tooling.support.core.models.SquareProject import com.squareup.tooling.support.core.models.SquareVariantConfiguration import org.gradle.api.Project @@ -58,8 +60,8 @@ internal fun Project.extractJavaModuleProject(): SquareProject { return SquareProject( name = name, pluginUsed = "jvm", - namespace = group.toString(), - pathToProject = projectDir.toRelativeString(rootDir), + namespace = rootProject.name, + pathToProject = relativePathToRootBuild() ?: relativePathToRootProject(), variants = variants.mapValues { (key, pair) -> val (srcs, deps) = pair SquareVariantConfiguration( diff --git a/tooling/support/jvm/src/test/kotlin/com/squareup/tooling/support/jvm/SquareProjectModelBuilderTest.kt b/tooling/support/jvm/src/test/kotlin/com/squareup/tooling/support/jvm/SquareProjectModelBuilderTest.kt index 3efcbac..c1c07a6 100644 --- a/tooling/support/jvm/src/test/kotlin/com/squareup/tooling/support/jvm/SquareProjectModelBuilderTest.kt +++ b/tooling/support/jvm/src/test/kotlin/com/squareup/tooling/support/jvm/SquareProjectModelBuilderTest.kt @@ -56,7 +56,7 @@ class SquareProjectModelBuilderTest { val result = projectExtractor.extractSquareProject(appProject) val expected = SquareProject( name = "app", - namespace = "com.squareup.test", + namespace = "com.squareup", pathToProject = "test/app", pluginUsed = "jvm", variants = mapOf(