Skip to content

Commit

Permalink
Add aggregate *All tasks and stub * tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
Virtlink committed Jul 22, 2024
1 parent a7ab30a commit b83509b
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 101 deletions.
Original file line number Diff line number Diff line change
@@ -1,43 +1,26 @@
package org.metaborg.convention

import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.provider.Property
import org.gradle.api.publish.plugins.PublishingPlugin
import org.gradle.language.base.plugins.LifecycleBasePlugin
import javax.inject.Inject

/** Configuration for the root project convention. */
open class RootProjectConventionExtension @Inject constructor(
/** The Gradle object factory. */
objects: ObjectFactory,
) {
/** Suffix for all task names. */
val taskNameSuffix: Property<String> = objects.property(String::class.java)
.convention("")

/** The name of the `assemble` task. */
val assembleTaskName: Property<String> = objects.property(String::class.java)
.convention(taskNameSuffix.map { "${LifecycleBasePlugin.ASSEMBLE_TASK_NAME}$it"} )
/** The name of the `build` task. */
val buildTaskName: Property<String> = objects.property(String::class.java)
.convention(taskNameSuffix.map { "${LifecycleBasePlugin.BUILD_TASK_NAME}$it"} )
/** The name of the `clean` task. */
val cleanTaskName: Property<String> = objects.property(String::class.java)
.convention(taskNameSuffix.map { "${LifecycleBasePlugin.CLEAN_TASK_NAME}$it"} )
/** The name of the `publish` task. */
val publishTaskName: Property<String> = objects.property(String::class.java)
.convention(taskNameSuffix.map { "${PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME}$it"} )
/** The name of the `publishToMavenLocal` task. */
val publishToMavenLocalTaskName: Property<String> = objects.property(String::class.java)
.convention(taskNameSuffix.map { "${PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME}${it}ToMavenLocal"} )
/** The name of the `check` task. */
val checkTaskName: Property<String> = objects.property(String::class.java)
.convention(taskNameSuffix.map { "${LifecycleBasePlugin.CHECK_TASK_NAME}$it"} )
/** The name of the `test` task. */
val testTaskName: Property<String> = objects.property(String::class.java)
.convention(taskNameSuffix.map { "${JavaPlugin.TEST_TASK_NAME}$it"} )
/** The name of the `tasks` task. */
val tasksTaskName: Property<String> = objects.property(String::class.java)
.convention("allTasks")
/** Whether to add the aggregate `assembleAll`, `buildAll`, `checkAll`, and `cleanAll` lifecycle tasks. */
val addAggregateLifecycleTasks: Property<Boolean> = objects.property(Boolean::class.java)
.convention(true)
/** Whether to add the aggregate `publishAll`, `publishAllToMavenLocal` lifecycle tasks. */
val addAggregatePublishTasks: Property<Boolean> = objects.property(Boolean::class.java)
.convention(false)

/** Whether to add stub `assemble`, `build`, `check`, and `clean` lifecycle tasks that depend on their `*All` counterparts, if not already defined. */
val addStubLifecycleTasks: Property<Boolean> = objects.property(Boolean::class.java)
.convention(true)
/** Whether to add stub `publish` and `publishToMavenLocal` tasks that depend on their `*All` counterparts, if not already defined. */
val addStubPublishTasks: Property<Boolean> = objects.property(Boolean::class.java)
.convention(false)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,138 @@ package org.metaborg.convention

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.Task
import org.gradle.api.publish.plugins.PublishingPlugin
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.create
import org.gradle.language.base.plugins.LifecycleBasePlugin

/**
* Configures a root project of a multi-project build, by adding explicit tasks
* Configures a root project of a multi-project/composite build, by adding explicit lifecycle tasks
* that call tasks in the sub-builds and subprojects.
*
* Normally, when you execute a task such as `test` in a multi-project build, you will execute
* all `:test` tasks in all projects. In contrast, when you specifically execute `:test`
* (prefixed with a colon), you execute the `:test` task only in the root project.
* Normally, when you execute a task such as `check` in a multi-project build, you will execute
* all `:check` tasks in all projects. In contrast, when you specifically execute `:check`
* (prefixed with a colon), you execute the `:check` task only in the root project.
* Now, we would like to create a task in this composite build that executes basically
* the equivalent of `test` in each of the included multi-project builds, which would execute
* `:test` in each of the projects. However, this seems to be impossible to write down. Instead,
* we call the root `:test` task in the included build, and in each included build's multi-project
* root project we'll extend the `test` task to depend on the `:test` tasks of the subprojects.
* the equivalent of `check` in each of the included multi-project builds, which would execute
* `:check` in each of the projects. However, this seems to be impossible to write down. Instead,
* we call the root `:check` task in the included build, and in each included build's multi-project
* root project we'll extend the `check` task to depend on the `:check` tasks of the subprojects.
*/
class RootProjectConventionPlugin: Plugin<Project> {
override fun apply(project: Project): Unit = with(project) {
// Add the configuration extension
val extension = extensions.create<RootProjectConventionExtension>("rootProjectConvention")

afterEvaluate {
// Build tasks
tasks.register(extension.assembleTaskName.get()) {
group = LifecycleBasePlugin.BUILD_GROUP
description = "Assembles the outputs of the subprojects and included builds."
dependsOn(gradle.includedBuilds.map { it.task(":${LifecycleBasePlugin.ASSEMBLE_TASK_NAME}") })
dependsOn(project.subprojects.mapNotNull { it.tasks.findByName(LifecycleBasePlugin.ASSEMBLE_TASK_NAME) })
}
tasks.register(extension.buildTaskName.get()) {
group = LifecycleBasePlugin.BUILD_GROUP
description = "Assembles and tests the subprojects and included builds."
dependsOn(gradle.includedBuilds.map { it.task(":${LifecycleBasePlugin.BUILD_TASK_NAME}") })
dependsOn(project.subprojects.mapNotNull { it.tasks.findByName(LifecycleBasePlugin.BUILD_TASK_NAME) })
}
tasks.register(extension.cleanTaskName.get()) {
group = LifecycleBasePlugin.BUILD_GROUP
description = "Cleans the outputs of the subprojects and included builds."
dependsOn(gradle.includedBuilds.map { it.task(":${LifecycleBasePlugin.CLEAN_TASK_NAME}") })
dependsOn(project.subprojects.mapNotNull { it.tasks.findByName(LifecycleBasePlugin.CLEAN_TASK_NAME) })
}
// Lifecycle tasks
registerRootTasks(
"${LifecycleBasePlugin.ASSEMBLE_TASK_NAME}All",
LifecycleBasePlugin.ASSEMBLE_TASK_NAME,
LifecycleBasePlugin.BUILD_GROUP,
"Assembles the outputs of this project, subprojects, and included builds.",
extension.addAggregateLifecycleTasks.get(),
extension.addStubLifecycleTasks.get(),
)
registerRootTasks(
"${LifecycleBasePlugin.BUILD_TASK_NAME}All",
LifecycleBasePlugin.BUILD_TASK_NAME,
LifecycleBasePlugin.BUILD_GROUP,
"Assembles and tests this project, subprojects, and included builds.",
extension.addAggregateLifecycleTasks.get(),
extension.addStubLifecycleTasks.get(),
)
registerRootTasks(
"${LifecycleBasePlugin.CLEAN_TASK_NAME}All",
LifecycleBasePlugin.CLEAN_TASK_NAME,
LifecycleBasePlugin.BUILD_GROUP,
"Cleans the outputs of this project, subprojects, and included builds.",
extension.addAggregateLifecycleTasks.get(),
extension.addStubLifecycleTasks.get(),
)
registerRootTasks(
"${LifecycleBasePlugin.CHECK_TASK_NAME}All",
LifecycleBasePlugin.CHECK_TASK_NAME,
LifecycleBasePlugin.VERIFICATION_GROUP,
"Runs all checks on this project, subprojects, and included builds.",
extension.addAggregateLifecycleTasks.get(),
extension.addStubLifecycleTasks.get(),
)

// Publishing tasks
tasks.register(extension.publishTaskName.get()) {
group = PublishingPlugin.PUBLISH_TASK_GROUP
description = "Publishes all subprojects and included builds to a remote Maven repository."
dependsOn(gradle.includedBuilds.map { it.task(":${PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME}") })
dependsOn(project.subprojects.mapNotNull { it.tasks.findByName(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME) })
}
tasks.register(extension.publishToMavenLocalTaskName.get()) {
group = PublishingPlugin.PUBLISH_TASK_GROUP
description = "Publishes all subprojects and included builds to the local Maven repository."
dependsOn(gradle.includedBuilds.map { it.task(":${PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME}ToMavenLocal") })
dependsOn(project.subprojects.mapNotNull { it.tasks.findByName("${PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME}ToMavenLocal") })
}
// Publish tasks
registerRootTasks(
"${PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME}All",
PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME,
PublishingPlugin.PUBLISH_TASK_GROUP,
"Publishes all publications produced by this project, subprojects, and included builds to remote Maven repositories.",
extension.addAggregatePublishTasks.get(),
extension.addStubPublishTasks.get(),
)
registerRootTasks(
"${PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME}AllToMavenLocal",
"${PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME}ToMavenLocal",
PublishingPlugin.PUBLISH_TASK_GROUP,
"Publishes all Maven publications produced by this project, subprojects, and included builds to the local Maven cache.",
extension.addAggregatePublishTasks.get(),
extension.addStubPublishTasks.get(),
)
}
}

// Verification tasks
tasks.register(extension.checkTaskName.get()) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Runs all checks on the subprojects and included builds."
dependsOn(gradle.includedBuilds.map { it.task(":${LifecycleBasePlugin.CHECK_TASK_NAME}") })
dependsOn(project.subprojects.mapNotNull { it.tasks.findByName(LifecycleBasePlugin.CHECK_TASK_NAME) })
}
tasks.register(extension.testTaskName.get()) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Runs all unit tests on the subprojects and included builds."
dependsOn(gradle.includedBuilds.map { it.task(":${JavaPlugin.TEST_TASK_NAME}") })
dependsOn(project.subprojects.mapNotNull { it.tasks.findByName(JavaPlugin.TEST_TASK_NAME) })
/**
* Registers the tasks in the root project.
*
* This registers a `buildAll` task (if [addAggregateLifecycleTask] is `true`) that depends on
* the existing `build` task in the root project, the `build` tasks in the subprojects,
* and the `build` tasks in the included builds.
*
* This also registers a `build` task (if [addStubLifecycleTask] is `true`) that depends on
* the `buildAll` task (if defined).
*
* @param allTaskName The name of the `*All` task, for example `buildAll`.
* @param taskName The name of the task, for example `build`.
* @param group The group of the tasks.
* @param description The description of the tasks.
* @param addAggregateLifecycleTask Whether to add the aggregate `*All` lifecycle task.
* @param addStubLifecycleTask Whether to add the stub `*` lifecycle task.
*/
private fun Project.registerRootTasks(
allTaskName: String,
taskName: String,
group: String,
description: String,
addAggregateLifecycleTask: Boolean,
addStubLifecycleTask: Boolean,
) {
// NOTE: The order is important.
// First, we check if there is already a `build` lifecycle task.
// Then we add the `buildAll` lifecycle task, which may depend on the existing `build` task, if any.
// To prevent cycles, only after that do we add the `build` stub lifecycle task itself,
// which depends on the `buildAll` task (if defined).

val existingTask = project.tasks.findByName(taskName)

val allTask: TaskProvider<Task>? = if (addAggregateLifecycleTask) {
tasks.register(allTaskName) {
this.group = group
this.description = description
// Depend on the normal lifecycle task, if any
existingTask?.let { dependsOn(it) }
// Depend on the normal lifecycle tasks of the subprojects, if present
dependsOn(project.subprojects.mapNotNull { it.tasks.findByName(taskName) })
// Depend on the normal lifecycle tasks in included builds
// NOTE: This may cause an error if the build doesn't define the specified task.
dependsOn(gradle.includedBuilds.map { it.task(":$taskName") })
}
} else null

// Help tasks
tasks.register(extension.tasksTaskName.get()) {
group = "Help"
description = "Displays all tasks of subprojects and included builds."
dependsOn(gradle.includedBuilds.map { it.task(":tasks") })
dependsOn(project.subprojects.mapNotNull { it.tasks.findByName("tasks") })
if (addStubLifecycleTask && existingTask == null) {
tasks.register(taskName) {
this.group = group
this.description = description
// Depend on the aggregate lifecycle task, if any
allTask?.let { dependsOn(it) }
}
}
}
Expand Down
19 changes: 7 additions & 12 deletions docs/content/conventions/rootproject.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "Root Project Convention"
---
# Root Project Convention Plugin
The Root Project convention plugin adds tasks that invoke the corresponding tasks on the sub-builds and subprojects.
The Root Project convention plugin adds `buildAll`, `assembleAll`, `cleanAll`, and `checkAll` tasks that invoke the corresponding tasks on the sub-builds and subprojects. Optionally, it can add `publishAll` and `publishAllToMavenLocal` tasks as well, but this is generally not recommended as not all included builds may have the `maven-publish` plugin applied.

```kotlin title="build.gradle.kts"
plugins {
Expand All @@ -15,15 +15,10 @@ The plugin can be configured using the `rootProjectConvention` extension:

```kotlin title="build.gradle.kts"
rootProjectConvention {
// The suffix for tasks that invoke tasks in subprojects and included builds
taskNameSuffix.set("All")
assembleTaskName.set("assembleAll")
buildTaskName.set("buildAll")
cleanTaskName.set("cleanAll")
publishTaskName.set("publishAll")
publishToMavenLocalTaskName.set("publishAllToMavenLocal")
checkTaskName.set("checkAll")
testTaskName.set("testAll")
tasksTaskName.set("allTasks")
addAggregateLifecycleTasks.set(true)
addAggregatePublishTasks.set(false)

addStubLifecycleTasks.set(true)
addStubPublishTasks.set(false)
}
```
```

0 comments on commit b83509b

Please sign in to comment.