Skip to content

Commit

Permalink
Put sample and integration-tests into the delegate build
Browse files Browse the repository at this point in the history
  • Loading branch information
RBusarow committed Dec 1, 2023
1 parent 49eaedd commit 65704b8
Show file tree
Hide file tree
Showing 23 changed files with 241 additions and 226 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,10 @@ jobs :
check-latest : true

- name : Run integration tests
run : ./gradlew -p integration-tests test --no-build-cache --no-daemon --stacktrace -Doverride_kotlin=${{ matrix.kotlin-version }} -Doverride_config-generateDaggerFactoriesWithAnvil=false
run : ./gradlew -p :delegate:integration-tests test --no-build-cache --no-daemon --stacktrace -Doverride_kotlin=${{ matrix.kotlin-version }} -Doverride_config-generateDaggerFactoriesWithAnvil=false

- name : Build the sample
run : ./gradlew :sample:app:assembleDebug --no-build-cache --no-daemon --stacktrace -Doverride_kotlin=${{ matrix.kotlin-version }} -Doverride_config-generateDaggerFactoriesWithAnvil=false
run : ./gradlew :delegate:sample:app:assembleDebug --no-build-cache --no-daemon --stacktrace -Doverride_kotlin=${{ matrix.kotlin-version }} -Doverride_config-generateDaggerFactoriesWithAnvil=false

- name : Upload Test Results
uses : actions/upload-artifact@v3
Expand Down Expand Up @@ -262,7 +262,7 @@ jobs :
check-latest : true

- name : Gradle integration tests
run : ./gradlew integrationTest --stacktrace
run : ./gradlew gradleTest --stacktrace

- name : Upload Test Results
uses : actions/upload-artifact@v3
Expand Down
7 changes: 3 additions & 4 deletions annotations-optional/build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
plugins {
id 'conventions.library'
id 'conventions.publish'
}

publish {
configurePom(
artifactId: 'annotations-optional',
pomName: 'Anvil Optional Annotations',
pomDescription: 'Optional annotations that we\'ve found to be helpful with managing larger dependency graphs'
artifactId: 'annotations-optional',
pomName: 'Anvil Optional Annotations',
pomDescription: 'Optional annotations that we\'ve found to be helpful with managing larger dependency graphs'
)
}

Expand Down
7 changes: 3 additions & 4 deletions annotations/build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
plugins {
id 'conventions.library'
id 'conventions.publish'
}

publish {
configurePom(
artifactId: 'annotations',
pomName: 'Anvil Annotations',
pomDescription: 'Annotations used to mark classes and methods for code generation in Anvil'
artifactId: 'annotations',
pomName: 'Anvil Annotations',
pomDescription: 'Annotations used to mark classes and methods for code generation in Anvil'
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
package com.squareup.anvil.conventions

import com.rickbusarow.kgx.extras
import com.rickbusarow.kgx.javaExtension
import com.rickbusarow.kgx.withAny
import com.squareup.anvil.conventions.utils.allDependenciesSequence
import com.squareup.anvil.conventions.utils.isInMainAnvilBuild
import com.squareup.anvil.conventions.utils.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.testing.Test
import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
import org.jetbrains.kotlin.gradle.internal.KaptGenerateStubsTask
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.util.Locale
import java.util.Properties

abstract class BasePlugin : Plugin<Project> {

Expand All @@ -26,6 +25,10 @@ abstract class BasePlugin : Plugin<Project> {

final override fun apply(target: Project) {

if (!target.isInMainAnvilBuild()) {
target.copyRootProjectGradleProperties()
}

beforeApply(target)

target.plugins.apply("base")
Expand All @@ -34,16 +37,13 @@ abstract class BasePlugin : Plugin<Project> {

configureGradleProperties(target)

target.plugins.withAny(
"org.jetbrains.kotlin.android",
"org.jetbrains.kotlin.jvm",
"org.jetbrains.kotlin.multiplatform",
) {
configureJava(target)

target.plugins.withType(KotlinBasePluginWrapper::class.java) {
configureKotlin(target)
}
configureTestLogging(target)

configureJava(target)
configureTests(target)

target.configurations.configureEach { config ->
config.resolutionStrategy {
Expand All @@ -54,6 +54,20 @@ abstract class BasePlugin : Plugin<Project> {
afterApply(target)
}

/**
* Included builds need the `GROUP` and `VERSION_NAME` values from the main build's
* `gradle.properties`. We can't just use a symlink because Windows exists.
* See https://github.com/square/anvil/pull/763#discussion_r1379563691
*/
private fun Project.copyRootProjectGradleProperties() {
rootProject.file("../../gradle.properties")
.inputStream()
.use { Properties().apply { load(it) } }
.forEach { key, value ->
extras.set(key.toString(), value.toString())
}
}

private fun configureGradleProperties(target: Project) {
target.version = target.property("VERSION_NAME") as String
target.group = target.property("GROUP") as String
Expand All @@ -62,13 +76,12 @@ abstract class BasePlugin : Plugin<Project> {
private fun configureKotlin(target: Project) {
target.tasks.withType(KotlinCompile::class.java).configureEach { task ->
task.compilerOptions {

allWarningsAsErrors.set(target.libs.versions.config.warningsAsErrors.get().toBoolean())

val hasIt = task.hasAnnotationsDependency()

if (hasIt) {
optIn.add("com.squareup.anvil.annotations.ExperimentalAnvilApi")
// Only add the experimental opt-in if the project has the `annotations` dependency,
// otherwise the compiler will throw a warning and fail in CI.
if (target.hasAnnotationDependency()) {
freeCompilerArgs.add("-opt-in=com.squareup.anvil.annotations.ExperimentalAnvilApi")
}

val fromInt = when (val targetInt = target.jvmTargetInt()) {
Expand All @@ -80,14 +93,31 @@ abstract class BasePlugin : Plugin<Project> {
}
}

private fun KotlinCompile.hasAnnotationsDependency(): Boolean {
return compileClasspathConfiguration()
?.allDependenciesSequence { resolved ->
resolved.moduleVersion?.group == "com.squareup.anvil"
/**
* This is an imperfect but pretty good heuristic
* to determine if the receiver has the `annotations` dependency,
* without actually resolving the dependency graph.
*/
private fun Project.hasAnnotationDependency(): Boolean {
val seed = sequenceOf("compileClasspath", "debugCompileClasspath")
.mapNotNull { configurations.findByName(it) }

val configs = generateSequence(seed) { configs ->
configs.flatMap { it.extendsFrom }
.mapNotNull { configurations.findByName(it.name) }
.takeIf { it.iterator().hasNext() }
}
.flatten()
.distinct()

// The -api and -utils projects declare the annotations as an `api` dependency.
val providingProjects = setOf("annotations", "compiler-api", "compiler-utils")

return configs.any { cfg ->
cfg.dependencies.any { dep ->
dep.group == "com.squareup.anvil" && dep.name in providingProjects
}
?.any { moduleVersion ->
moduleVersion.module.toString() == "com.squareup.anvil:annotations"
} ?: false
}
}

private fun configureJava(target: Project) {
Expand All @@ -101,7 +131,7 @@ abstract class BasePlugin : Plugin<Project> {
}
}

private fun configureTestLogging(target: Project) {
private fun configureTests(target: Project) {
target.tasks.withType(Test::class.java).configureEach { task ->

task.maxParallelForks = Runtime.getRuntime().availableProcessors()
Expand All @@ -116,23 +146,4 @@ abstract class BasePlugin : Plugin<Project> {
}
}
}

private fun KotlinCompile.compileClasspathConfiguration(): Configuration? {

if (this is KaptGenerateStubsTask) return null

// The task's `sourceSetName` property is set late in Android projects,
// but we can just parse the name out of the task's name.
val ssName = name
.substringAfter("compile")
.substringBefore("Kotlin")
.replaceFirstChar { it.lowercase(Locale.US) }

val compileClasspathName = when {
ssName.isEmpty() -> "compileClasspath"
else -> "${ssName}CompileClasspath"
}

return project.configurations.getByName(compileClasspathName)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.squareup.anvil.conventions

import com.rickbusarow.kgx.checkProjectIsRoot
import com.squareup.anvil.conventions.utils.namedOrNull
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.composite.internal.DefaultIncludedBuild
import org.gradle.composite.internal.DefaultIncludedBuild.IncludedBuildImpl
import org.gradle.internal.DefaultTaskExecutionRequest

class CompositePlugin : Plugin<Project> {

override fun apply(target: Project) {

target.checkProjectIsRoot()
require(target.gradle.includedBuilds.isNotEmpty()) {
"Only apply the 'composite' plugin to a root project with included builds. " +
"This project has no included builds, " +
"so the plugin would just waste time searching the task graph."
}

target.gradle.projectsEvaluated { gradle ->

val oldRequests = gradle.startParameter.taskRequests

val newRequests = oldRequests.map { request ->

val originalSplit = request.args
.splitInclusive { !it.startsWith('-') }

val taskPaths = originalSplit.mapTo(mutableSetOf()) { it.first() }

val includedProjects = gradle.includedBuilds
.asSequence()
.filterIsInstance<IncludedBuildImpl>()
.flatMap { impl ->

val defaultIncludedBuild = impl.target as DefaultIncludedBuild

defaultIncludedBuild.mutableModel.projectRegistry.allProjects
}

val newSplit = originalSplit.flatMap { taskWithArgs ->

val taskName = taskWithArgs.first()

if (taskName.startsWith(':')) {
// qualified task names aren't propagated to included builds
return@flatMap listOf(taskWithArgs)
}

val resolvedInRootBuild = target.tasks.namedOrNull(taskName)

// Don't propagate help tasks
if (taskName == "help") return@flatMap listOf(taskWithArgs)

val inRoot = resolvedInRootBuild != null

val included = includedProjects.mapNotNull { includedProject ->

val includedPath = "${includedProject.identityPath}:$taskName"

// Don't include tasks that are already in the task graph
if (taskPaths.contains(includedPath)) return@mapNotNull null

includedProject.tasks.namedOrNull(taskName) ?: return@mapNotNull null

target.logger.quiet("The task $taskName will delegate to $includedPath")

buildList<String> {
add(includedPath)
addAll(taskWithArgs.subList(1, taskWithArgs.size))
}
}

buildList {
if (inRoot) {
add(taskWithArgs)
}
addAll(included)
}
}

DefaultTaskExecutionRequest.of(newSplit.flatten(), request.projectPath, request.rootDir)
}

gradle.startParameter.setTaskRequests(newRequests)
}
}

private inline fun <E> List<E>.splitInclusive(predicate: (E) -> Boolean): List<List<E>> {

val toSplit = this@splitInclusive

if (toSplit.isEmpty()) return emptyList()

return toSplit.subList(1, toSplit.size)
.fold(mutableListOf(mutableListOf(toSplit[0]))) { acc: MutableList<MutableList<E>>, e ->
if (predicate(e)) {
acc.add(mutableListOf(e))
} else {
acc.last().add(e)
}
acc
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ open class KtlintConventionPlugin : Plugin<Project> {
*/
private fun propagateToIncludedBuilds(target: Project) {
target.gradle.includedBuilds
// Skip the delegate build since all its code is also in the main build.
.filter { it.name != "delegate" }
.forEach { build ->
target.tasks.named("ktlintCheck") { task ->
task.dependsOn(build.task(":ktlintCheck"))
Expand Down
Loading

0 comments on commit 65704b8

Please sign in to comment.