{"id":25287516,"url":"https://github.com/gradleup/gratatouille","last_synced_at":"2026-01-16T10:57:01.695Z","repository":{"id":223210598,"uuid":"759445743","full_name":"GradleUp/gratatouille","owner":"GradleUp","description":"Cook yourself delicious Gradle plugins 🐘🤝🐭👉🧑‍🍳","archived":false,"fork":false,"pushed_at":"2025-01-14T10:11:58.000Z","size":614,"stargazers_count":154,"open_issues_count":5,"forks_count":5,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-03-29T03:03:30.416Z","etag":null,"topics":["gradle","hacktoberfest","kotlin"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GradleUp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-18T16:07:40.000Z","updated_at":"2025-03-07T16:22:06.000Z","dependencies_parsed_at":"2024-02-19T02:25:23.311Z","dependency_job_id":"4f252877-af4a-470f-80a6-0bc2e56d6108","html_url":"https://github.com/GradleUp/gratatouille","commit_stats":null,"previous_names":["gradleup/gratatouille"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GradleUp%2Fgratatouille","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GradleUp%2Fgratatouille/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GradleUp%2Fgratatouille/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GradleUp%2Fgratatouille/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GradleUp","download_url":"https://codeload.github.com/GradleUp/gratatouille/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247284943,"owners_count":20913704,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gradle","hacktoberfest","kotlin"],"created_at":"2025-02-12T22:39:52.594Z","updated_at":"2026-01-16T10:57:01.682Z","avatar_url":"https://github.com/GradleUp.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Gratatouille 🐘🤝🐭👉🧑‍🍳 \n\nGratatouille is an opinionated framework to build Gradle plugins. Write Kotlin functions and the Gratatouille KSP processor generates tasks, workers, and wiring code for you.\n\nWhen used in classloader isolation mode, Gratatouille enforces a clear separation between your plugin implementation (**tasks**) and your plugin glue (**wiring**) making your tasks immune to [classloader](https://github.com/square/kotlinpoet/issues/1730#issuecomment-1819118527) [issues](https://github.com/gradle/gradle/issues/1370) 🛡️ \n\n**Key Features**:\n\n* [Tasks Generation](#functional-programming-style)\n* [Comprehensive input/output types](#supported-input-and-output-types)\n* [Non overlapping task outputs by default](#non-overlapping-task-outputs-by-default)\n* [Build cache by default](#build-cache-by-default)\n* [Easy documentation](#easy-documentation)\n* [Parallel execution by default](#parallel-task-execution-by-default)\n* [Compile-time task wiring](#compile-time-task-wiring)\n* [Plugin descriptors and markers without java-gradle-plugin](#descriptors-and-markers)\n* [Classloader isolation](#classloader-isolation-optional) (optional)\n* [kotlinx serialization support](#experimental-kotlinxserialization-support) (experimental)\n\nCheck out the [Apollo Faker Gradle Plugin](https://github.com/apollographql/apollo-kotlin-faker/blob/main/gradle-plugin/build.gradle.kts) for a real-life example or [test-app](test-app) for integration tests.\n\nGratatouille also uses Gratatouille to build its plugin, check [gratatouille-gradle-plugin/build.gradle.kts](gratatouille-gradle-plugin/build.gradle.kts) for more details. \n\n## Quick Start\n\nApply the `com.gradleup.gratatouille` plugin:\n\n```kotlin\nplugins {\n  id(\"org.jetbrains.kotlin.jvm\")\n  // KSP is required for code generation\n  id(\"com.google.devtools.ksp\")\n  // No need to add the 'java-gradle-plugin' plugin.\n  // Add the Gratatouille plugin\n  id(\"com.gradleup.gratatouille\").version(\"0.2.2\")\n}\n\ngratatouille {\n  // Configure the plugin marker\n  // This is only required for publication. \n  // If you're using GradleUp/Librarian, you can skip this step.\n  pluginMarker(\"com.example.myplugin\")\n}\n```\n\nDefine your task action using `@GTask`:\n\n```kotlin\n@GTask\ninternal fun prepareIngredients(persons: Int, ingredients: GOutputFile) {\n  ingrediens.writeText(\"\"\"\n  {\n    \"tomatoes\": ${persons * 0.75}.roundToInt(),\n    \"zucchinis\": ${persons * 0.3}.roundToInt(),\n    \"eggplants\": ${persons * 0.3}.roundToInt()\n  }\n  \"\"\".trimIndent())  \n}\n```\nGratatouille automatically maps function parameters to Gradle inputs and outputs(more on outputs [below](#non-overlapping-task-outputs-by-default)).\n\nGratatouille generates entry points, tasks, workers and [the rest of the owl](https://ibin.co/8lrGHjKW7mig.png)!\n\n\u003cdetails\u003e\n\u003csummary\u003eGenerated code\u003c/summary\u003e\n\n```kotlin\ninternal fun Project.registerPrepareIngredientsTask(\n  taskName: String = \"prepareIngredients\",\n  taskDescription: String? = null,\n  taskGroup: String? = null,\n  persons: Provider\u003cInt\u003e,\n): TaskProvider\u003cPrepareIngredientsTask\u003e {\n  val configuration = this@registerPrepareIngredientsTask.configurations.detachedConfiguration()\n  configuration.dependencies.add(dependencies.create(\"sample-plugin:implementation:0.0.1\"))\n  return tasks.register(taskName,PrepareIngredientsTask::class.java) {\n    it.description = taskDescription\n    it.group = taskGroup\n    it.classpath.from(configuration)\n    // infrastructure\n    // inputs\n    it.persons.set(persons)\n    // outputs\n    it.outputFile.set(this@registerPrepareIngredientsTask.layout.buildDirectory.file(\"gtask/${taskName}/ingredients\"))\n  }\n}\n\n@CacheableTask\ninternal abstract class PrepareIngredientsTask : DefaultTask() {\n  @get:Classpath\n  public abstract val classpath: ConfigurableFileCollection\n\n  @get:Input\n  public abstract val persons: Property\u003cInt\u003e\n\n  @get:OutputFile\n  public abstract val outputFile: RegularFileProperty\n\n  @Inject\n  public abstract fun getWorkerExecutor(): WorkerExecutor\n\n  private fun \u003cT\u003e T.isolate(): T {\n    @kotlin.Suppress(\"UNCHECKED_CAST\")\n    when (this) {\n        is Set\u003c*\u003e -\u003e {\n            return this.map { it.isolate() }.toSet() as T\n        }\n\n        is List\u003c*\u003e -\u003e {\n            return this.map { it.isolate() } as T\n        }\n\n        is Map\u003c*, *\u003e -\u003e {\n            return entries.map { it.key.isolate() to it.value.isolate() }.toMap() as T\n        }\n\n        else -\u003e {\n            return this\n        }\n    }\n  }\n\n  @TaskAction\n  public fun taskAction() {\n    getWorkerExecutor().noIsolation().submit(PrepareIngredientsWorkAction::class.java) {\n      it.classpath = classpath.files.isolate()\n      it.persons = persons.get().isolate()\n      it.ingredients = ingredients.asFile.get().isolate()\n    }\n  }\n}\n\nprivate interface PrepareIngredientsWorkParameters : WorkParameters {\n  public var classpath: Set\u003cFile\u003e\n\n  public var persons: Int\n\n  public var ingredients: File\n}\n\nprivate abstract class PrepareIngredientsWorkAction : WorkAction\u003cPrepareIngredientsWorkParameters\u003e {\n  override fun execute() {\n    with(parameters) {\n      URLClassLoader(\n        classpath.map { it.toURI().toURL() }.toTypedArray(),\n        ClassLoader.getPlatformClassLoader()\n      ).loadClass(\"recipes.PrepareIngredientsEntryPoint\")\n      .declaredMethods.single()\n      .invoke(\n        null,\n        persons,\n        ingredients,\n      )\n    }\n  }\n}\n\npublic class PrepareIngredientsEntryPoint {\n  public companion object {\n    @JvmStatic\n    public fun run(persons: Int, ingredients: File) {\n      prepareIngredients(\n        persons = persons,\n        ingredients = ingredients,\n      )\n    }\n  }\n}\n```\n\u003c/details\u003e \n\nUse `@GPlugin` to create a plugin and call `Project.register${TaskAction}Task()` to register the task:\n\n```kotlin\n// GPlugin generates a plugin and descriptor automatically\n@GPlugin(id = \"com.example.myplugin\")\nfun myPlugin(project: Project) {\n    val extension = project.extensions.create(\"recipes\", RecipesExtension::class.java)\n\n    // Register your \"PrepareIngredients\" task\n    val prepareIngredients = project.registerPrepareIngredientsTask(\n        persons = extension.persons\n        // no need to set the outputs, they are configured automatically \n    )\n    \n    // Register other tasks\n    project.registerCookTask(\n        recipe = extension.recipe,\n        // Wire tasks together\n        ingredients = prepareIngredients.flatMap { it.ingredients }\n    )\n}\n```\n\nNo need to implement `DefaultTask`, no risk of forgetting `@Cacheable`, etc... Gratatouille provides good defaults, making it easier to write plugins.\n\n## Features\n\n### Functional programming style\n\nYour code is modeled as functions taking inputs and generating outputs. \n\nNo need for stateful properties or classes. Nullable parameters are generated as optional task properties. Calls to `Provider.get()` or `Provider.orNull` are automated.\n\n### Supported input and output types\n\nInputs:\n* Any type annotated with `@Serializable` (serialized to a File)\n* Kotlin `Int`, `Boolean`, `Float`, `Double`, `String`\n* Kotlin `Set`, `List`, `Map`\n* Single File using the `GInputFile` typealias\n* FileCollection using the `GInputFiles` typealias\n* Directory using the `GInputDirectory` typealias\n\nOutputs:\n* Single File using the `GOutputFile` typealias\n* Directory using the `GOutputDirectory` typealias\n\nNo need to dive into [the details of `@InputDirectoy`](https://mbonnin.net/2025-01-22_input_directory_is_a_lie/) or [`PathSensitivity`](https://www.linen.dev/s/gradle-community/t/29202922/hey-i-have-a-simple-question-famous-last-words-i-think-it-is#892fd930-9a5d-4471-bd9a-acae3e61e518), Gratatouille input and output types support a wide range of use case with a clear and restricted API.\n\n### Non-overlapping task outputs by default\n\nGratatouille allocates paths for output files and directories automatically. Each output gets a dedicated filesystem location at `\"build/gtask/${taskName}/${outputName}\"`. \n\nThis way:\n* you don't have to think about what path to use.\n* the outputs are consistent and discoverable.\n* issues like [#26091](https://github.com/gradle/gradle/issues/26091) are avoided by construction.\n\nIf your function has a single return value, Gratatouille uses `outputFile` as output name.\n\nIf your function needs multiple return values, wrap them in a non-serializable class.\n\nIf you need to control the output location of an output, you can do so using `@GManuallyWired` and using `GOutputFile`/`GOutputDirectory` as parameters.\n\nIn your implementation:\n\n```kotlin\n@GTask\ninternal fun cook(\n    recipe: GInputFile,\n    ingredients: Ingredients,\n    // ratatouille is exposed in registerCookTask(outputFile) so you can configure it \n    @GManuallyWired ratatouille: GOutputFile,\n    // leftovers is set to \"build/gtask/cook/leftovers\" \n    leftovers: GOutputFile,\n) {\n    ratatouille.writeText(/* cook here! */)\n}\n```\n\nIn your plugin:\n\n```kotlin\nproject.registerCookTask(\n    recipe = extension.recipe,\n    ingredients = prepareIngredients.flatMap { it.outputFile },\n    // Set outputFile location explicitly\n    ratatouille = project.layout.buildDirectory.file(\"ratatouille\")\n    // No need to set lefovers\n)\n```\n\n### Build cache by default\n\n`@CacheableTask` is added by default. All input files use `PathSensitivity.RELATIVE` making your tasks relocatable. \n\n### Easy documentation\n\n`@GTask` takes a `description` and a `group` argument making it easy to colocate your documentation with your implementation:\n\n```kotlin\n@GTask(\n    description = \"cooks the most delicious ratatouille with the help of the tiniest chef\",\n    group = \"recipes\"\n)\ninternal fun cook(\n    recipe: GInputFile,\n    ingredients: Ingredients,\n    outputFile: GOutputFile\n) { \n    TODO()\n}\n```\n\n### Parallel task execution by default\n\nBy default, [Gradle tasks execute serially in a single project](https://docs.gradle.org/current/userguide/performance.html#parallel_execution) (unless using the [configuration cache](https://docs.gradle.org/current/userguide/performance.html#additional_configuration_cache_benefits)).\n\nBecause your task actions are Kotlin functions, no state is shared, making them perfect candidates for parallelization. \n\nGratatouille uses the [Worker API](https://docs.gradle.org/current/userguide/worker_api.html) to allow parallel execution making your build faster overall. Use `org.gradle.workers.max` to control the maximum number of workers.\n\n### Compile time task wiring\n\nFinally, 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.\n\nWhen 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.\n\n## Descriptors and markers\n\nIn order to map a plugin id to a jar file and a specific implementation class, Gradle uses [plugin markers](https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers) and descriptors.\n\nThe markers and descriptors are typically added by the [`java-gradle-plugin`](https://docs.gradle.org/current/userguide/java_gradle_plugin.html) plugin. This plugin also adds the `gradleApi()` dependency as an `api` dependency to your project, which is rarely needed.\n\nFor simplicity, Gratatouille, generates plugin descriptors automatically from the `@GPlugin` and `GExtension` annotations. \n\nFor markers, Gratatouille exposes a simple function:\n\n```kotlin\ngratatouille {\n  // Configure the plugin marker\n  pluginMarker(\"com.example.myplugin\")\n}\n\ndependencies {\n  // You can add use the Gradle API of your choice here \n  compileOnly(\"dev.gradleplugins:gradle-api:8.0\")\n}\n```\n\n## Classloader isolation (optional)\n\nGradle uses [multiple classloaders](https://dev.to/autonomousapps/build-compile-run-a-crash-course-in-classpaths-f4g), and it's notoriously really hard to understand where a given class is loaded from.\n\nEspecially, `buildSrc`/`build-logic` dependencies [leak in the main classpath](https://github.com/gradle/gradle/issues/4741) and override any dependencies from other plugin without conflict resolution. There are multiple workarounds such as declaring all plugins in `buildSrc` or in the top level `build.gradle[.kts]` file but the situation is confusing to Gradle newcomers and hard to debug.\n\nTo guard against those issues, Gratatouille provides a classloader isolation mode where your tasks use a separate classloader.\n\nThis means your tasks can depend on popular dependencies such as the Kotlin stdlib, KotlinPoet or ASM without risking conflicts with other plugins or the Gradle classpath itself.\n\n\u003e [!TIP]\n\u003e As an added bonus, isolating your tasks makes your build generally more up-to-date. Changing the implementation of a task doesn't invalidate your whole build anymore. Enjoy faster CI times and more granular task invalidation!\n\nFor classloader isolation to work, your plugin needs 2 projects:\n* The **tasks** project is where the task actions are defined and the work is done. This project can add dependencies. \n* The **plugin** project contains the glue code and Gradle API that calls the **tasks** project through reflection. This project must not add dependencies besides the compileOnly Gradle API.\n\n### Step 1/2: gradle-tasks\n\nCreate a `gradle-tasks` project for your plugin tasks and apply the `com.gradleup.gratatouille.tasks` plugin:\n\n```kotlin\n// implementation/build.gradle.kts\nplugins {\n  id(\"org.jetbrains.kotlin.jvm\")\n  id(\"com.google.devtools.ksp\")\n  id(\"com.gradleup.gratatouille.tasks\").version(\"0.2.2\")\n}\n\ndependencies {\n    // Add dependencies needed to do your task work\n    implementation(\"com.squareup:kotlinpoet:1.14.2\")\n    implementation(\"org.ow2.asm:asm-commons:9.6\")\n    // do **not** add gradle-api here\n}\n\ngratatouille {\n  // You may specify additional configuration options here\n  enableKotlinxSerialization.set(true)\n}\n```\n\nWrite your task actions as top-level Kotlin functions annotated with `@GTask`:\n\n```kotlin\n@GTask\ninternal fun prepareIngredients(persons: Int, ingredients: GOutputFile) {\n  ingrediens.writeText(\"\"\"\n  {\n    \"tomatoes\": ${persons * 0.75}.roundToInt(),\n    \"zucchinis\": ${persons * 0.3}.roundToInt(),\n    \"eggplants\": ${persons * 0.3}.roundToInt()\n  }\n  \"\"\".trimIndent())\n}\n```\n\nWhen using this mode, the plugin wiring code is generated as resources that are automatically included by the `gradle-plugin` project. \n\n### Step 2/2 gradle-plugin\n\nTo use the generated code in your plugin, create an `gradle-plugin` project next to your `gradle-tasks` project.\n\n\u003e [!IMPORTANT]\n\u003e By using two different projects, Gratatouille ensures that Gradle classes do not leak in your plugin implementation and vice-versa.\n\nApply the `com.gradleup.gratatouille` plugin in your `gradle-plugin` project:\n\n```kotlin\n// gradle-plugin/build.gradle.kts\nplugins {\n    id(\"com.gradleup.gratatouille\").version(\"0.2.2\")\n}\n\ngratatouille {\n  // Configure the plugin marker\n  pluginMarker(\"com.example.myplugin\")\n  // Optional: you may still use code generation for `@GTask` and `GExtension` helpers\n  codeGeneration()\n}\n\ndependencies {\n  // Add your implementation project to the \"gratatouille\" configuration.\n  // This does not add `:implementation` to your plugin classpath.\n  // Instead, the generated code uses reflection and a separate classloader to run\n  // your implementation\n  gratatouille(project(\":implementation\"))\n  \n  // Add the version of Gradle you want to compile against \n  compileOnly(\"dev.gradleplugins:gradle-api:8.0\")\n}\n\n```\n\nIn your plugin code, use `Project.register${TaskAction}Task()` to register the task\n\n### Experimental kotlinx.serialization support\n\nGratatouille has builtin support for [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization). Models are serialized and deserialized as needed.\n\nTo opt-in support for kotlinx.serialization, add `enableKotlinxSerialization.set(true)` to your configuration:\n\n```kotlin\ngratatouille {\n  enableKotlinxSerialization.set(true)\n  codeGeneration()\n  // ...\n}\n```\n\nWith `kotlinx.serialization` support, you can write your funtions as pure functions and the output will be serialized on the fly:\n\n```kotlin\n@GTask\ninternal fun prepareIngredients(persons: Int): Ingredients {\n    return Ingredients(\n        tomatoes = (persons * 0.75).roundToInt(),\n        zucchinis = (persons * 0.3).roundToInt(),\n        eggplants = (persons * 0.3).roundToInt(),\n    )\n}\n\n// kotlinx.serialization is supported out of the box\n@Serializable\ninternal data class Ingredients(\n    val tomatoes: Int,\n    val zucchinis: Int,\n    val eggplants: Int,\n)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgradleup%2Fgratatouille","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgradleup%2Fgratatouille","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgradleup%2Fgratatouille/lists"}