Skip to content

Commit

Permalink
update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
martinbonnin committed Feb 19, 2024
1 parent 3bf79b7 commit 0497c9a
Showing 1 changed file with 29 additions and 22 deletions.
51 changes: 29 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Gratatouille is an opinionated framework to build Gradle plugins. Write pure Kotlin functions and the Gratatouille KSP processor generates tasks, workers and wiring code for you.

Gratatouille enforces a clear separation between your plugin logic (**implementation**) and your plugin wiring (**gradle-plugin**) making your plugin immune to classloader issues πŸ›‘οΈ.
Gratatouille enforces a clear separation between your plugin logic (**implementation**) and your plugin wiring (**gradle-plugin**) making your plugin immune to classloader issues πŸ›‘οΈ

**Features**:
* [Pure functions](#pure-functions)
Expand Down Expand Up @@ -39,7 +39,7 @@ dependencies {
}
```

Write your plugin task action as a top-level Kotlin pure function annotated with `@GTaskAction`:
Write your task action as a pure top-level Kotlin function annotated with `@GTaskAction`:

```kotlin
@GTaskAction
Expand All @@ -60,29 +60,31 @@ internal data class Ingredients(
)
```

Gratatouille automatically maps function parameters to Gradle inputs and the return value to a Gradle output.
Gratatouille automatically maps function parameters to Gradle inputs and the return value to a Gradle output (more on outputs [below](#non-overlapping-task-outputs-by-default)).

Gratatouille generates entry points, task, workers and Gradle wiring code that can be used from your plugin.
Gratatouille generates entry points, tasks, workers and Gradle wiring code that you can then use to cook your plugin.

## Step 2/2 `com.gradleup.gratatouille.plugin`

To use the generated code in your plugin, create a `gradle-plugin` module next to your `implementation` module.

By using two different modules, Gratatouille ensures that Gradle classes do not leak in your plugin implementation and vice-versa.
> [!TIP]
> By using two different modules, Gratatouille ensures that Gradle classes do not leak in your plugin implementation and vice-versa.
The `gradle-plugin` module should depend on the Gradle API and apply the `com.gradleup.gratatouille.plugin`:
Apply the `com.gradleup.gratatouille.plugin` plugin in your `gradle-plugin` module:

```kotlin
// implementation/build.gradle.kts
// gradle-plugin/build.gradle.kts
plugins {
id("java-gradle-plugin")
id("com.gradleup.gratatouille.plugin").version("0.0.1-SNAPSHOT")
}

dependencies {
// Add your implementation module to the "gratatouille" configuration.
// This adds the wiring code to the main source set. No dependency is pulled
// in the plugin classpath.
// This does not add `:implementation` to your plugin classpath.
// Instead, the generated code uses reflection and a separate classloader to run
// your implementation
gratatouille(project(":implementation"))
}

Expand Down Expand Up @@ -116,11 +118,13 @@ override fun apply(project: Project) {

## Pure functions

No need to deal with stateful task properties and Gradle annotations. Your plugin code is a side-effect free function.
Your task code is a side-effect-free function, making it easier to [parallelize](#parallel-task-execution-by-default) and reason about.

Nullable parameters are generated as optional task properties. Calls to `Provider.get()` or `Provider.orNull` are automated.

## Built-in kotlinx.serialization support

Gratatouille has builtin support for [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization). You don't need to deal with serialization, the models are serialized and deserialized as needed.
Gratatouille has builtin support for [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization). Models are serialized and deserialized as needed.

## Supported input and output types

Expand All @@ -139,7 +143,7 @@ Outputs:

## Non-overlapping task outputs by default

Gratatouille allocate paths for output files and directory automatically. Each output gets a dedicated filesystem location at `"build/gtask/${taskName}/${outputName}"`.
Gratatouille allocate paths for output files and directories automatically. Each output gets a dedicated filesystem location at `"build/gtask/${taskName}/${outputName}"`.

This way:
* you don't have to set inputs.
Expand All @@ -149,7 +153,7 @@ If your function has a single return value, Gratatouille uses `outputFile` as ou

If your function needs multiple return values, wrap them in a non-serializable class.

If you need to control the output location of an output, you can do so using `@GManuallyWired`.
If you need to control the output location of an output, you can do so using `@GManuallyWired` and using `GOutputFile`/`GOutputDirectory` as parameters.

In your implementation:

Expand All @@ -158,10 +162,12 @@ In your implementation:
internal fun cook(
recipe: GInputFile,
ingredients: Ingredients,
// outputFile will be exposed in registerCookTask(outputFile) so you can configure it
@GManuallyWired outputFile: GOutputFile
// ratatouille is exposed in registerCookTask(outputFile) so you can configure it
@GManuallyWired ratatouille: GOutputFile,
// leftovers is set to "build/gtask/cook/leftovers"
leftovers: GOutputFile,
) {
outputFile.writeText(// cook here!)
ratatouille.writeText(/* cook here! */)
}
```

Expand All @@ -172,23 +178,24 @@ project.registerCookTask(
recipe = extension.recipe,
ingredients = prepareIngredients.flatMap { it.outputFile },
// Set outputFile location explicitly
outputFile = project.layout.buildDirectory.file("outputFile")
ratatouille = project.layout.buildDirectory.file("ratatouille")
// No need to set lefovers
)
```

## Classloader isolation by default

Gratatouille creates a separate classloader for each task and calls your pure function using reflection.
Gratatouille creates a separate classloader for each task and calls your pure functions using reflection.

This means your plugin can depend on popular dependencies such as the Kotlin stdlib, KotlinPoet or ASM without risking conflicts with other plugins or the Gradle classpath itself.

## Build cache by default

`@CacheableTask` is enabled by default. All input files use `PathSensitivity.RELATIVE` by default making your tasks easily relocatable.
`@CacheableTask` is added by default. All input files use `PathSensitivity.RELATIVE` making your tasks build relocatable.

## Easy documentation

`@GTaskAction` takes a `taskDescription` and a `taskGroup` argument making it easy to document your tasks:
`@GTaskAction` takes a `taskDescription` and a `taskGroup` argument making it easy to colocate your documentation with your implementation:

```kotlin
@GTaskAction(
Expand All @@ -208,13 +215,13 @@ internal fun cook(

Even with `org.gradle.parallel=true`, [Gradle tasks execute serially in a single module](https://docs.gradle.org/current/userguide/performance.html#parallel_execution).

Because your task actions are pure Kotlin function, no state is shared, making them perfect candidates for parallelisation.
Because your task actions are pure Kotlin function, no state is shared, making them perfect candidates for parallelization.

Gratatouille uses the [Worker API](https://docs.gradle.org/current/userguide/worker_api.html) to allow parallel execution making you build faster in general. Use `org.gradle.workers.max` to control the maximum number of workers.

## Compile time task wiring

Finally, Gratatouille encourages exposing extensions to users instead of task classes directly. This makes it easier to have some inputs user configurable while some others are an implementation details and more generally makes it easier to evolve the public API of your plugin.
Finally, Gratatouille encourages exposing extensions to users instead of task classes directly. All generated code is generated as `internal`. This makes it easier to have some inputs user configurable while some others are an implementation details and more generally makes it easier to evolve the public API of your plugin.

When a task has a high number of inputs, it can become hard to track which ones have been wired and which ones haven't. By using a central registration point, Gratatouille enforces at build time that all inputs/outputs have been properly wired.

0 comments on commit 0497c9a

Please sign in to comment.