diff --git a/.gitignore b/.gitignore index ce181e5..4e67eeb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,27 @@ -.idea +.kotlin +.fleet .gradle -build \ No newline at end of file + +# Build outputs +build + +# Idea +**/.idea/* +!**/.idea/codeStyles +!**/.idea/icon.png +!**/.idea/runConfigurations +!**/.idea/scopes +!**/.idea/dictionaries +*.iml + +# Place where the Android SDK path is set +local.properties + +# XCode +xcuserdata +project.xcworkspace + +# Mac OS Finder +.DS_Store +Thumbs.db + diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..aaa4877 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index c937633..514c0f1 100644 --- a/README.md +++ b/README.md @@ -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](https://github.com/square/kotlinpoet/issues/1730#issuecomment-1819118527) 🛡️ +When used in classloader isolation mode, Gratatouille enforces a clear separation between your plugin logic (**implementation**) and your plugin wiring (**api**) making your plugin immune to [classloader issues](https://github.com/square/kotlinpoet/issues/1730#issuecomment-1819118527) 🛡️ **Key Features**: @@ -10,37 +10,26 @@ Gratatouille enforces a clear separation between your plugin logic (**implementa * [Kotlinx serialization support](#built-in-kotlinxserialization-support) * [Comprehensive input/output types](#supported-input-and-output-types) * [Non overlapping task outputs](#non-overlapping-task-outputs-by-default) -* [Classloader isolation](#classloader-isolation-by-default) * [Build cache](#build-cache-by-default) * [Documentation](#easy-documentation) * [Parallel execution](#parallel-task-execution-by-default) * [Compile-time task wiring](#compile-time-task-wiring) +* [Classloader isolation](#classloader-isolation-optional) (optional) Check out the [sample-plugin](sample-plugin) and [sample-app](sample-app). ## Quick Start -### Step 1/2: `com.gradleup.gratatouille.implementation` - -Create an `implementation` module for your plugin implementation and apply the `com.gradleup.gratatouille.implementation` plugin: +Apply the `com.gradleup.gratatouille` plugin: ```kotlin -// implementation/build.gradle.kts plugins { - id("com.gradleup.gratatouille.implementation").version("0.0.1") -} - -dependencies { - // Add the gratatouille annotations - implementation("com.gradleup.gratatouille:gratatouille-core:0.0.1") - // Add other dependencies - implementation("com.squareup:kotlinpoet:1.14.2") - implementation("org.ow2.asm:asm-commons:9.6") - // do **not** add gradleApi() here + id("java-gradle-plugin") + id("com.gradleup.gratatouille").version("0.0.2") } ``` -Write your task action as a pure top-level Kotlin function annotated with `@GTaskAction`: +Define your task action using `@GTaskAction`: ```kotlin @GTaskAction @@ -65,35 +54,113 @@ Gratatouille automatically maps function parameters to Gradle inputs and the ret 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` +
+Generated code -To use the generated code in your plugin, create a `gradle-plugin` module next to your `implementation` module. +```kotlin +internal fun Project.registerPrepareIngredientsTask( + taskName: String = "prepareIngredients", + taskDescription: String? = null, + taskGroup: String? = null, + persons: Provider, +): TaskProvider { + val configuration = this@registerPrepareIngredientsTask.configurations.detachedConfiguration() + configuration.dependencies.add(dependencies.create("sample-plugin:implementation:0.0.1")) + return tasks.register(taskName,PrepareIngredientsTask::class.java) { + it.description = taskDescription + it.group = taskGroup + it.classpath.from(configuration) + // infrastructure + // inputs + it.persons.set(persons) + // outputs + it.outputFile.set(this@registerPrepareIngredientsTask.layout.buildDirectory.file("gtask/${taskName}/outputFile")) + } +} -> [!IMPORTANT] -> By using two different modules, Gratatouille ensures that Gradle classes do not leak in your plugin implementation and vice-versa. +@CacheableTask +internal abstract class PrepareIngredientsTask : DefaultTask() { + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + public abstract val classpath: ConfigurableFileCollection + + @get:Input + public abstract val persons: Property + + @get:OutputFile + public abstract val outputFile: RegularFileProperty + + @Inject + public abstract fun getWorkerExecutor(): WorkerExecutor + + private fun T.isolate(): T { + @kotlin.Suppress("UNCHECKED_CAST") + when (this) { + is Set<*> -> { + return this.map { it.isolate() }.toSet() as T + } + + is List<*> -> { + return this.map { it.isolate() } as T + } + + is Map<*, *> -> { + return entries.map { it.key.isolate() to it.value.isolate() }.toMap() as T + } + + else -> { + return this + } + } + } + + @TaskAction + public fun taskAction() { + getWorkerExecutor().noIsolation().submit(PrepareIngredientsWorkAction::class.java) { + it.classpath = classpath.files.isolate() + it.persons = persons.get().isolate() + it.outputFile = outputFile.asFile.get().isolate() + } + } +} -Apply the `com.gradleup.gratatouille.plugin` plugin in your `gradle-plugin` module: +private interface PrepareIngredientsWorkParameters : WorkParameters { + public var classpath: Set -```kotlin -// gradle-plugin/build.gradle.kts -plugins { - id("java-gradle-plugin") - id("com.gradleup.gratatouille.plugin").version("0.0.1") + public var persons: Int + + public var outputFile: File } -dependencies { - // Add your implementation module to the "gratatouille" configuration. - // 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")) +private abstract class PrepareIngredientsWorkAction : WorkAction { + override fun execute() { + with(parameters) { + URLClassLoader( + classpath.map { it.toURI().toURL() }.toTypedArray(), + ClassLoader.getPlatformClassLoader() + ).loadClass("recipes.PrepareIngredientsEntryPoint") + .declaredMethods.single() + .invoke( + null, + persons, + outputFile, + ) + } + } } -// Create your plugin as usual, see https://docs.gradle.org/current/userguide/java_gradle_plugin.html -gradlePlugin { - // ... +public class PrepareIngredientsEntryPoint { + public companion object { + @JvmStatic + public fun run(persons: Int, outputFile: File) { + prepareIngredients( + persons = persons, + ).encodeJsonTo(outputFile) + } + } } ``` +
In your plugin code, use `Project.register${TaskAction}Task()` to register the task: @@ -185,12 +252,6 @@ project.registerCookTask( ) ``` -### Classloader isolation by default - -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 added by default. All input files use `PathSensitivity.RELATIVE` making your tasks relocatable. @@ -227,3 +288,89 @@ Finally, Gratatouille encourages exposing extensions to users instead of task cl 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. +## Classloader isolation (optional) + +Gradle 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. + +Especially, `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. + +To guard against those issues, Gratatouille provides a "classloader isolation" mode where your task actions use a separate classloader. + +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. + +For classloader isolation to work, your plugin needs 2 modules: +* The **implementation** module is where the task actions are defined and the work is done. This module can add dependencies. +* The **api** module contains the glue code and Gradle API that calls the **implementation** module through reflection. This module must not add dependencies. + +### Step 1/2: `com.gradleup.gratatouille.implementation` + +Create an `implementation` module for your plugin implementation and apply the `com.gradleup.gratatouille.implementation` plugin: + +```kotlin +// implementation/build.gradle.kts +plugins { + id("com.gradleup.gratatouille.implementation").version("0.0.1") +} + +dependencies { + // Add other dependencies + implementation("com.squareup:kotlinpoet:1.14.2") + implementation("org.ow2.asm:asm-commons:9.6") + // do **not** add gradleApi() here +} +``` + +Write your task action as a pure top-level Kotlin function annotated with `@GTaskAction`: + +```kotlin +@GTaskAction +internal fun prepareIngredients(persons: Int): Ingredients { + return Ingredients( + tomatoes = (persons * 0.75).roundToInt(), + zucchinis = (persons * 0.3).roundToInt(), + eggplants = (persons * 0.3).roundToInt(), + ) +} + +// kotlinx.serialization is supported out of the box +@Serializable +internal data class Ingredients( + val tomatoes: Int, + val zucchinis: Int, + val eggplants: Int, +) +``` + +When using this mode, the plugin wiring code is generated as resources that are included by the `com.gradleup.gratatouille.api` plugin. + +### Step 2/2 `com.gradleup.gratatouille.plugin` + +To use the generated code in your plugin, create an `api` module next to your `implementation` module. + +> [!IMPORTANT] +> By using two different modules, Gratatouille ensures that Gradle classes do not leak in your plugin implementation and vice-versa. + +Apply the `com.gradleup.gratatouille.api` plugin in your `api` module: + +```kotlin +// gradle-plugin/build.gradle.kts +plugins { + id("java-gradle-plugin") + id("com.gradleup.gratatouille.api").version("0.0.1") +} + +dependencies { + // Add your implementation module to the "gratatouille" configuration. + // 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")) +} + +// Create your plugin as usual, see https://docs.gradle.org/current/userguide/java_gradle_plugin.html +gradlePlugin { + // ... +} +``` + +In your plugin code, use `Project.register${TaskAction}Task()` to register the task diff --git a/build-logic/.idea/codeStyles b/build-logic/.idea/codeStyles new file mode 120000 index 0000000..70d0521 --- /dev/null +++ b/build-logic/.idea/codeStyles @@ -0,0 +1 @@ +../../.idea/codeStyles \ No newline at end of file diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index ebaee29..47f371c 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -1,5 +1,8 @@ +import com.gradleup.librarian.gradle.configureJavaCompatibility + plugins { `embedded-kotlin` + alias(libs.plugins.librarian).apply(false) } dependencies { @@ -13,3 +16,9 @@ dependencies { } group = "build-logic" + +/** + * Ideally would use Runtime.version().feature() but the current Gradle still ships with Kotlin 1.9 + * that doesn't know about Java 22 🤷‍♂️ + */ +configureJavaCompatibility(17) diff --git a/build-logic/gradle/wrapper b/build-logic/gradle/wrapper new file mode 120000 index 0000000..3232fe4 --- /dev/null +++ b/build-logic/gradle/wrapper @@ -0,0 +1 @@ +../../gradle/wrapper \ No newline at end of file diff --git a/build-logic/gradle/wrapper/gradle-wrapper.jar b/build-logic/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 7f93135..0000000 Binary files a/build-logic/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/build-logic/gradle/wrapper/gradle-wrapper.properties b/build-logic/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 3fa8f86..0000000 --- a/build-logic/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/build-logic/gradlew b/build-logic/gradlew deleted file mode 100755 index 1aa94a4..0000000 --- a/build-logic/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/build-logic/gradlew b/build-logic/gradlew new file mode 120000 index 0000000..502f5a2 --- /dev/null +++ b/build-logic/gradlew @@ -0,0 +1 @@ +../gradlew \ No newline at end of file diff --git a/build-logic/gradlew.bat b/build-logic/gradlew.bat deleted file mode 100644 index 6689b85..0000000 --- a/build-logic/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/build-logic/gradlew.bat b/build-logic/gradlew.bat new file mode 120000 index 0000000..2840132 --- /dev/null +++ b/build-logic/gradlew.bat @@ -0,0 +1 @@ +../gradlew.bat \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22c..9355b41 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gratatouille-gradle-plugin/build.gradle.kts b/gratatouille-gradle-plugin/build.gradle.kts index 05d91bd..de9835e 100644 --- a/gratatouille-gradle-plugin/build.gradle.kts +++ b/gratatouille-gradle-plugin/build.gradle.kts @@ -28,9 +28,13 @@ gradlePlugin { this.implementationClass = "gratatouille.gradle.GratatouilleImplementationPlugin" this.id = "com.gradleup.gratatouille.implementation" } - create("com.gradleup.gratatouille.plugin") { - this.implementationClass = "gratatouille.gradle.GratatouilleGradlePluginPlugin" - this.id = "com.gradleup.gratatouille.plugin" + create("com.gradleup.gratatouille.api") { + this.implementationClass = "gratatouille.gradle.GratatouilleApiPlugin" + this.id = "com.gradleup.gratatouille.api" + } + create("com.gradleup.gratatouille") { + this.implementationClass = "gratatouille.gradle.GratatouillePlugin" + this.id = "com.gradleup.gratatouille" } } -} \ No newline at end of file +} diff --git a/gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouilleGradlePluginPlugin.kt b/gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouilleApiPlugin.kt similarity index 96% rename from gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouilleGradlePluginPlugin.kt rename to gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouilleApiPlugin.kt index 5da6138..dad49af 100644 --- a/gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouilleGradlePluginPlugin.kt +++ b/gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouilleApiPlugin.kt @@ -7,7 +7,7 @@ import org.gradle.api.tasks.Copy import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension import javax.inject.Inject -class GratatouilleGradlePluginPlugin : Plugin { +class GratatouilleApiPlugin : Plugin { override fun apply(target: Project) { target.configurations.create("gratatouille") { it.isTransitive = false diff --git a/gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouilleImplementationPlugin.kt b/gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouilleImplementationPlugin.kt index 0d08fbf..b937da3 100644 --- a/gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouilleImplementationPlugin.kt +++ b/gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouilleImplementationPlugin.kt @@ -19,7 +19,7 @@ class GratatouilleImplementationPlugin : Plugin { target.afterEvaluate { target.extensions.getByName("ksp").apply { this as KspExtension - this.arg("gratatouilleCoordinates", "${target.group}:${target.name}:${target.version}") + this.arg("implementationCoordinates", "${target.group}:${target.name}:${target.version}") } } } diff --git a/gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouillePlugin.kt b/gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouillePlugin.kt new file mode 100644 index 0000000..ebd9b35 --- /dev/null +++ b/gratatouille-gradle-plugin/src/main/kotlin/gratatouille/gradle/GratatouillePlugin.kt @@ -0,0 +1,19 @@ +package gratatouille.gradle + +import com.gradleup.gratatouille.gradle.BuildConfig +import org.gradle.api.Plugin +import org.gradle.api.Project + +class GratatouillePlugin : Plugin { + override fun apply(target: Project) { + target.withPlugins("org.jetbrains.kotlin.jvm", "com.google.devtools.ksp") { + target.configurations.getByName("ksp").dependencies.add( + target.dependencies.create("${BuildConfig.group}:gratatouille-processor:${BuildConfig.version}") + ) + target.configurations.getByName("implementation").dependencies.add( + target.dependencies.create("${BuildConfig.group}:gratatouille-core:${BuildConfig.version}") + ) + } + } +} + diff --git a/gratatouille-processor/src/main/kotlin/gratatouille/processor/GraskAction.kt b/gratatouille-processor/src/main/kotlin/gratatouille/processor/GTaskAction.kt similarity index 91% rename from gratatouille-processor/src/main/kotlin/gratatouille/processor/GraskAction.kt rename to gratatouille-processor/src/main/kotlin/gratatouille/processor/GTaskAction.kt index 692b4df..29396f9 100644 --- a/gratatouille-processor/src/main/kotlin/gratatouille/processor/GraskAction.kt +++ b/gratatouille-processor/src/main/kotlin/gratatouille/processor/GTaskAction.kt @@ -19,7 +19,12 @@ internal class GTaskAction( val description: String?, val group: String?, val parameters: List, - val returnValues: List + val returnValues: List, + /** + * The coordinates where to find the implementation for this action + * May be null if the plugin is not isolated + */ + val implementationCoordinates: String? ) internal sealed interface Type @@ -42,10 +47,10 @@ internal class Property( ) -internal fun KSFunctionDeclaration.toGTaskAction(): GTaskAction { +internal fun KSFunctionDeclaration.toGTaskAction(implementationCoordinates: String?): GTaskAction { val parameters = mutableListOf() val returnValues = returnType.toReturnValues() - val reservedNames = setOf(taskName, taskDescription, taskGroup, classpath, extraClasspath, workerExecutor) + val reservedNames = setOf(taskName, taskDescription, taskGroup, classpath, workerExecutor) val returnValuesNames = returnValues.map { it.name }.toSet() this.parameters.forEach { valueParameter -> @@ -116,12 +121,12 @@ internal fun KSFunctionDeclaration.toGTaskAction(): GTaskAction { ) } - val GTaskActionAnnotation = annotations.first { it.shortName.asString() == "GTaskAction" } - val name = GTaskActionAnnotation.arguments.firstOrNull { it.name?.asString() == "name" } + val gTaskActionAnnotation = annotations.first { it.shortName.asString() == "GTaskAction" } + val name = gTaskActionAnnotation.arguments.firstOrNull { it.name?.asString() == "name" } ?.takeIf { it.origin != Origin.SYNTHETIC }?.value?.toString() - val description = GTaskActionAnnotation.arguments.firstOrNull { it.name?.asString() == "description" } + val description = gTaskActionAnnotation.arguments.firstOrNull { it.name?.asString() == "description" } ?.takeIf { it.origin != Origin.SYNTHETIC }?.value?.toString() - val group = GTaskActionAnnotation.arguments.firstOrNull { it.name?.asString() == "group" } + val group = gTaskActionAnnotation.arguments.firstOrNull { it.name?.asString() == "group" } ?.takeIf { it.origin != Origin.SYNTHETIC }?.value?.toString() return GTaskAction( @@ -131,7 +136,8 @@ internal fun KSFunctionDeclaration.toGTaskAction(): GTaskAction { returnValues = returnValues, annotationName = name, description = description, - group = group + group = group, + implementationCoordinates = implementationCoordinates ) } diff --git a/gratatouille-processor/src/main/kotlin/gratatouille/processor/GratatouilleProcessor.kt b/gratatouille-processor/src/main/kotlin/gratatouille/processor/GratatouilleProcessor.kt index b656c92..be2b3d4 100644 --- a/gratatouille-processor/src/main/kotlin/gratatouille/processor/GratatouilleProcessor.kt +++ b/gratatouille-processor/src/main/kotlin/gratatouille/processor/GratatouilleProcessor.kt @@ -6,50 +6,59 @@ import com.google.devtools.ksp.symbol.KSFunctionDeclaration import com.squareup.kotlinpoet.ksp.writeTo class GratatouilleProcessor( - private val codeGenerator: CodeGenerator, - private val logger: KSPLogger, - private val options: Map, + private val codeGenerator: CodeGenerator, + private val logger: KSPLogger, + private val options: Map, ) : SymbolProcessor { - override fun process(resolver: Resolver): List { - val symbols = resolver.getSymbolsWithAnnotation("gratatouille.GTaskAction") + override fun process(resolver: Resolver): List { + val symbols = resolver.getSymbolsWithAnnotation("gratatouille.GTaskAction") - val coordinates = options.get("gratatouilleCoordinates") ?: error("Gratatouille requires coordinates of the implementation module") - symbols.forEach { - when (it) { - is KSFunctionDeclaration -> { - it.toGTaskAction().apply { - entryPoint().writeTo(codeGenerator, Dependencies.ALL_FILES) + val coordinates = options.get("implementationCoordinates") + symbols.forEach { + when (it) { + is KSFunctionDeclaration -> { + it.toGTaskAction(coordinates).apply { + entryPoint().writeTo(codeGenerator, Dependencies.ALL_FILES) - taskFile(coordinates).let { fileSpec -> - codeGenerator.createNewFile( - Dependencies.ALL_FILES, - "", - "META-INF/gratatouille/${fileSpec.packageName.replace(".", "/")}/${fileSpec.name}.kt", - "" - ).writer().use { - fileSpec.writeTo(it) - } - } - } - } - - else -> error("@GTaskAction is only valid on functions") + taskFile().let { fileSpec -> + val file = if (implementationCoordinates != null) { + codeGenerator.createNewFile( + Dependencies.ALL_FILES, + "", + "META-INF/gratatouille/${fileSpec.packageName.replace(".", "/")}/${fileSpec.name}.kt", + "" + ) + } else { + codeGenerator.createNewFile( + Dependencies.ALL_FILES, + fileSpec.packageName, + fileSpec.name, + ) + } + file.writer().use { + fileSpec.writeTo(it) + } } + } } - return emptyList() + + else -> error("@GTaskAction is only valid on functions") + } } + return emptyList() + } } class GratatouilleProcessorProvider : SymbolProcessorProvider { - override fun create( - environment: SymbolProcessorEnvironment - ): SymbolProcessor { - return GratatouilleProcessor( - environment.codeGenerator, - environment.logger, - environment.options - ) - } + override fun create( + environment: SymbolProcessorEnvironment + ): SymbolProcessor { + return GratatouilleProcessor( + environment.codeGenerator, + environment.logger, + environment.options + ) + } } internal val classpathProperty = Property(InputFiles, classpath, false, false, false) \ No newline at end of file diff --git a/gratatouille-processor/src/main/kotlin/gratatouille/processor/Task.kt b/gratatouille-processor/src/main/kotlin/gratatouille/processor/Task.kt index e6d8531..115407b 100644 --- a/gratatouille-processor/src/main/kotlin/gratatouille/processor/Task.kt +++ b/gratatouille-processor/src/main/kotlin/gratatouille/processor/Task.kt @@ -1,199 +1,172 @@ package gratatouille.processor -import com.squareup.kotlinpoet.AnnotationSpec -import com.squareup.kotlinpoet.ClassName -import com.squareup.kotlinpoet.CodeBlock -import com.squareup.kotlinpoet.FileSpec -import com.squareup.kotlinpoet.FunSpec -import com.squareup.kotlinpoet.KModifier -import com.squareup.kotlinpoet.ParameterSpec -import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.* import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import com.squareup.kotlinpoet.PropertySpec -import com.squareup.kotlinpoet.TypeName -import com.squareup.kotlinpoet.TypeSpec -import com.squareup.kotlinpoet.TypeVariableName - -internal fun GTaskAction.taskFile(coordinates: String): FileSpec { - val className = taskClassName() - - val fileSpec = FileSpec.builder(className) - .addFunction(register(coordinates)) - .addType(task()) - .addType(workParameters()) - .addType(workAction()) - .build() - return fileSpec +internal fun GTaskAction.taskFile(): FileSpec { + val className = taskClassName() + + val fileSpec = FileSpec.builder(className) + .addFunction(register()) + .addType(task()) + .addType(workParameters()) + .addType(workAction()) + .build() + + return fileSpec } -private fun GTaskAction.register(coordinates: String): FunSpec { - val defaultTaskName = annotationName ?: taskName() - return FunSpec.builder(registerName()) - .addModifiers(KModifier.INTERNAL) - .receiver(ClassName("org.gradle.api", "Project")) - .returns(ClassName("org.gradle.api.tasks", "TaskProvider").parameterizedBy(taskClassName())) - .addParameter( - ParameterSpec.builder(taskName, ClassName("kotlin", "String")) - .defaultValue(defaultTaskName.toCodeBlock()) - .build() - ) - .addParameter( - ParameterSpec.builder(taskDescription, ClassName("kotlin", "String").copy(nullable = true)) - .defaultValue(description.toCodeBlock()) - .build() - ) - .addParameter( - ParameterSpec.builder(taskGroup, ClassName("kotlin", "String").copy(nullable = true)) - .defaultValue(group.toCodeBlock()) - .build() +private fun GTaskAction.register(): FunSpec { + val defaultTaskName = annotationName ?: taskName() + return FunSpec.builder(registerName()) + .addModifiers(KModifier.INTERNAL) + .receiver(ClassName("org.gradle.api", "Project")) + .returns(ClassName("org.gradle.api.tasks", "TaskProvider").parameterizedBy(taskClassName())) + .addParameter( + ParameterSpec.builder(taskName, ClassName("kotlin", "String")) + .defaultValue(defaultTaskName.toCodeBlock()) + .build() + ) + .addParameter( + ParameterSpec.builder(taskDescription, ClassName("kotlin", "String").copy(nullable = true)) + .defaultValue(description.toCodeBlock()) + .build() + ) + .addParameter( + ParameterSpec.builder(taskGroup, ClassName("kotlin", "String").copy(nullable = true)) + .defaultValue(group.toCodeBlock()) + .build() + ) + .apply { + this@register.parameters.filter { it.type.isInput() }.forEach { + + addParameter( + ParameterSpec.builder(it.name, it.type.toProviderType()) + .build() ) - .addParameter( - ParameterSpec.builder( - extraClasspath, - ClassName("org.gradle.api.file", "FileCollection").copy(nullable = true) - ) - .defaultValue(CodeBlock.of("null")) - .build() + } + + (this@register.parameters.filter { !it.type.isInput() } + this@register.returnValues).forEach { + if (it.manuallyWired) { + addParameter( + ParameterSpec.builder(it.name, it.type.toProviderType()) + .build() + ) + } + } + } + .addCode( + buildCodeBlock { + add( + "val configuration = this@%L.configurations.detachedConfiguration()\n", + registerName(), ) - .apply { - this@register.parameters.filter { it.type.isInput() }.forEach { - - addParameter( - ParameterSpec.builder(it.name, it.type.toProviderType()) - .build() - ) + if (implementationCoordinates != null) { + add("configuration.dependencies.add(dependencies.create(%S))\n", implementationCoordinates) + } + add("return tasks.register($taskName,%T::class.java) {\n", taskClassName()) + withIndent { + add("it.description = $taskDescription\n") + add("it.group = $taskGroup\n") + add("it.$classpath.from(configuration)\n") + add("// infrastructure\n") + add("// inputs\n") + this@register.parameters.filter { it.type.isInput() }.forEach { + when (it.type) { + is InputFiles -> { + add("it.%L.from(%L)\n", it.name, it.name) + } + + else -> { + add("it.%L.set(%L)\n", it.name, it.name) + } } - - (this@register.parameters.filter { !it.type.isInput() } + this@register.returnValues).forEach { - if (it.manuallyWired) { - addParameter( - ParameterSpec.builder(it.name, it.type.toProviderType()) - .build() - ) - } + } + + add("// outputs\n") + (this@register.parameters.filter { !it.type.isInput() } + this@register.returnValues).forEach { + if (it.manuallyWired) { + add("it.%L.set(%L)\n", it.name, it.name) + } else { + val method = when (it.type) { + is OutputDirectory -> "dir" + is KotlinxSerializableOutput, is OutputFile -> "file" + else -> error("Gratatouille: invalid output type for '${it.name}': ${it.type}") + } + add( + "it.%L.set(this@%L.layout.buildDirectory.$method(%L))\n", + it.name, + registerName(), + "\"gtask/\${$taskName}/${it.name}\"" + ) } + } } - .addCode( - CodeBlock.builder() - .add( - "val configuration = this@%L.configurations.detachedConfiguration(dependencies.create(%S))\n", - registerName(), - coordinates - ) - .add("return tasks.register($taskName,%T::class.java) {\n", taskClassName()) - .indent() - .add("it.description = $taskDescription\n") - .add("it.group = $taskGroup\n") - .add("it.$classpath.from(configuration)\n") - .apply { - add("// infrastructure\n") - add("if ($extraClasspath != null) {\n") - indent() - add("it.$classpath.from($extraClasspath)\n") - unindent() - add("}\n") - } - .apply { - add("// inputs\n") - this@register.parameters.filter { it.type.isInput() }.forEach { - when (it.type) { - is InputFiles -> { - add("it.%L.from(%L)\n", it.name, it.name) - } - - else -> { - add("it.%L.set(%L)\n", it.name, it.name) - } - } - } - } - .apply { - add("// outputs\n") - (this@register.parameters.filter { !it.type.isInput() } + this@register.returnValues).forEach { - if (it.manuallyWired) { - add("it.%L.set(%L)\n", it.name, it.name) - } else { - val method = when (it.type) { - is OutputDirectory -> "dir" - is KotlinxSerializableOutput, is OutputFile -> "file" - else -> error("Gratatouille: invalid output type for '${it.name}': ${it.type}") - } - add( - "it.%L.set(this@%L.layout.buildDirectory.$method(%L))\n", - it.name, - registerName(), - "\"gtask/\${$taskName}/${it.name}\"" - ) - } - } - } - .unindent() - .add("}\n") - .build() - ) - .build() + add("}\n") + } + ) + .build() } private fun Type.isInput(): Boolean { - return when (this) { - is OutputDirectory, is OutputFile -> false - else -> true - } + return when (this) { + is OutputDirectory, is OutputFile -> false + else -> true + } } private fun Type.toProviderType(): TypeName { - return when (this) { - is OutputDirectory, InputDirectory -> ClassName("org.gradle.api.provider", "Provider") - .parameterizedBy(ClassName("org.gradle.api.file", "Directory")) - - is KotlinxSerializableInput, is KotlinxSerializableOutput, is OutputFile, InputFile -> ClassName( - "org.gradle.api.provider", - "Provider" - ) - .parameterizedBy(ClassName("org.gradle.api.file", "RegularFile")) - - InputFiles -> ClassName("org.gradle.api.file", "FileCollection") - is JvmType -> typename.toGradleProvider() - } + return when (this) { + is OutputDirectory, InputDirectory -> ClassName("org.gradle.api.provider", "Provider") + .parameterizedBy(ClassName("org.gradle.api.file", "Directory")) + + is KotlinxSerializableInput, is KotlinxSerializableOutput, is OutputFile, InputFile -> ClassName( + "org.gradle.api.provider", + "Provider" + ) + .parameterizedBy(ClassName("org.gradle.api.file", "RegularFile")) + + InputFiles -> ClassName("org.gradle.api.file", "FileCollection") + is JvmType -> typename.toGradleProvider() + } } private fun GTaskAction.task(): TypeSpec { - return TypeSpec.classBuilder(taskClassName().simpleName) - .addAnnotation( - AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "CacheableTask")) - .build() + return TypeSpec.classBuilder(taskClassName().simpleName) + .addAnnotation( + AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "CacheableTask")) + .build() + ) + .addModifiers(KModifier.ABSTRACT, KModifier.INTERNAL) + .superclass(ClassName("org.gradle.api", "DefaultTask")) + .apply { + (listOf(classpathProperty) + parameters + returnValues).forEach { + addProperty( + it.toPropertySpec() ) - .addModifiers(KModifier.ABSTRACT, KModifier.INTERNAL) - .superclass(ClassName("org.gradle.api", "DefaultTask")) - .apply { - (listOf(classpathProperty) + parameters + returnValues).forEach { - addProperty( - it.toPropertySpec() - ) - } - } - .addFunction( - FunSpec.builder(workerExecutor) - .addModifiers(KModifier.ABSTRACT) - .addAnnotation( - AnnotationSpec.builder(ClassName("javax.inject", "Inject")).build() - ) - .returns(ClassName("org.gradle.workers", "WorkerExecutor")) - .build() + } + } + .addFunction( + FunSpec.builder(workerExecutor) + .addModifiers(KModifier.ABSTRACT) + .addAnnotation( + AnnotationSpec.builder(ClassName("javax.inject", "Inject")).build() ) - .addFunction(isolate()) - .addFunction(taskAction()) + .returns(ClassName("org.gradle.workers", "WorkerExecutor")) .build() + ) + .addFunction(isolate()) + .addFunction(taskAction()) + .build() } private fun isolate(): FunSpec { - return FunSpec.builder("isolate") - .addTypeVariable(TypeVariableName("T")) - .receiver(TypeVariableName("T")) - .returns(TypeVariableName("T")) - .addCode( - """ + return FunSpec.builder("isolate") + .addTypeVariable(TypeVariableName("T")) + .receiver(TypeVariableName("T")) + .returns(TypeVariableName("T")) + .addCode( + """ @kotlin.Suppress("UNCHECKED_CAST") when (this) { is Set<*> -> { @@ -213,254 +186,263 @@ private fun isolate(): FunSpec { } } """.trimIndent() - ) - .addModifiers(KModifier.PRIVATE) - .build() + ) + .addModifiers(KModifier.PRIVATE) + .build() } private fun GTaskAction.taskAction(): FunSpec { - return FunSpec.builder("taskAction") - .addAnnotation( - AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "TaskAction")) - .build() - ) - .addCode( - CodeBlock.builder() - .add("$workerExecutor().noIsolation().submit(%T::class.java) {\n", workActionClassName()) - .indent() - .apply { - (listOf(classpathProperty) + parameters + returnValues).forEach { - val extra = CodeBlock.builder() - .apply { - when (it.type) { - InputDirectory, - InputFile, - is KotlinxSerializableInput, - is KotlinxSerializableOutput, - is OutputDirectory, - is OutputFile - -> { - add(".asFile") - } - - InputFiles -> { - add(".files") - } - - else -> Unit - } - if (it.type !is InputFiles) { - if (it.optional) { - add(".orNull?") - } else { - add(".get()") - } - } - add(".isolate()") - } - .build() - add("it.%L = %L%L\n", it.name, it.name, extra) - } + return FunSpec.builder("taskAction") + .addAnnotation( + AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "TaskAction")) + .build() + ) + .addCode( + CodeBlock.builder() + .add("$workerExecutor().noIsolation().submit(%T::class.java) {\n", workActionClassName()) + .indent() + .apply { + (listOf(classpathProperty) + parameters + returnValues).forEach { + val extra = CodeBlock.builder() + .apply { + when (it.type) { + InputDirectory, + InputFile, + is KotlinxSerializableInput, + is KotlinxSerializableOutput, + is OutputDirectory, + is OutputFile + -> { + add(".asFile") + } + + InputFiles -> { + add(".files") + } + + else -> Unit } - .unindent() - .add("}\n") - .build() - ) + if (it.type !is InputFiles) { + if (it.optional) { + add(".orNull?") + } else { + add(".get()") + } + } + add(".isolate()") + } + .build() + add("it.%L = %L%L\n", it.name, it.name, extra) + } + } + .unindent() + .add("}\n") .build() + ) + .build() } private fun PropertySpec.Builder.annotateInput( - packageName: String, - simpleName: String, - internal: Boolean, - optional: Boolean + packageName: String, + simpleName: String, + internal: Boolean, + optional: Boolean ) = apply { - if (internal) { - addAnnotation( - AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "Internal")) - .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) - .build() - ) - } else { - if (optional) { - addAnnotation( - AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "Optional")) - .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) - .build() - ) - } - addAnnotation( - AnnotationSpec.builder(ClassName(packageName, simpleName)) - .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) - .build() - ) + if (internal) { + addAnnotation( + AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "Internal")) + .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) + .build() + ) + } else { + if (optional) { + addAnnotation( + AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "Optional")) + .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) + .build() + ) } + addAnnotation( + AnnotationSpec.builder(ClassName(packageName, simpleName)) + .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) + .build() + ) + } } private fun Property.toPropertySpec(): PropertySpec { - val builder = when (type) { - is InputDirectory -> { - PropertySpec.builder(name, ClassName("org.gradle.api.file", "DirectoryProperty")) - .annotateInput("org.gradle.api.tasks", "InputDirectory", internal, optional) - .addAnnotation( - AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "PathSensitive")) - .addMember(CodeBlock.of("%T.RELATIVE", ClassName("org.gradle.api.tasks", "PathSensitivity"))) - .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) - .build() - ) - } + val builder = when (type) { + is InputDirectory -> { + PropertySpec.builder(name, ClassName("org.gradle.api.file", "DirectoryProperty")) + .annotateInput("org.gradle.api.tasks", "InputDirectory", internal, optional) + .addAnnotation( + AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "PathSensitive")) + .addMember(CodeBlock.of("%T.RELATIVE", ClassName("org.gradle.api.tasks", "PathSensitivity"))) + .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) + .build() + ) + } - is KotlinxSerializableInput, is InputFile -> { - PropertySpec.builder(name, ClassName("org.gradle.api.file", "RegularFileProperty")) - .annotateInput("org.gradle.api.tasks", "InputFile", internal, optional) - .addAnnotation( - AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "PathSensitive")) - .addMember(CodeBlock.of("%T.RELATIVE", ClassName("org.gradle.api.tasks", "PathSensitivity"))) - .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) - .build() - ) - } + is KotlinxSerializableInput, is InputFile -> { + PropertySpec.builder(name, ClassName("org.gradle.api.file", "RegularFileProperty")) + .annotateInput("org.gradle.api.tasks", "InputFile", internal, optional) + .addAnnotation( + AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "PathSensitive")) + .addMember(CodeBlock.of("%T.RELATIVE", ClassName("org.gradle.api.tasks", "PathSensitivity"))) + .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) + .build() + ) + } - is InputFiles -> { - PropertySpec.builder(name, ClassName("org.gradle.api.file", "ConfigurableFileCollection")) - .annotateInput("org.gradle.api.tasks", "InputFiles", internal, optional) - .addAnnotation( - AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "PathSensitive")) - .addMember(CodeBlock.of("%T.RELATIVE", ClassName("org.gradle.api.tasks", "PathSensitivity"))) - .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) - .build() - ) - } + is InputFiles -> { + PropertySpec.builder(name, ClassName("org.gradle.api.file", "ConfigurableFileCollection")) + .annotateInput("org.gradle.api.tasks", "InputFiles", internal, optional) + .addAnnotation( + AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "PathSensitive")) + .addMember(CodeBlock.of("%T.RELATIVE", ClassName("org.gradle.api.tasks", "PathSensitivity"))) + .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) + .build() + ) + } - is JvmType -> { - PropertySpec.builder(name, type.typename.toGradleProperty()) - .annotateInput("org.gradle.api.tasks", "Input", internal, optional) - } + is JvmType -> { + PropertySpec.builder(name, type.typename.toGradleProperty()) + .annotateInput("org.gradle.api.tasks", "Input", internal, optional) + } - is OutputDirectory -> { - PropertySpec.builder(name, ClassName("org.gradle.api.file", "DirectoryProperty")) - .addAnnotation( - AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "OutputDirectory")) - .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) - .build() - ) - } + is OutputDirectory -> { + PropertySpec.builder(name, ClassName("org.gradle.api.file", "DirectoryProperty")) + .addAnnotation( + AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "OutputDirectory")) + .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) + .build() + ) + } - is KotlinxSerializableOutput, is OutputFile -> { - PropertySpec.builder(name, ClassName("org.gradle.api.file", "RegularFileProperty")) - .addAnnotation( - AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "OutputFile")) - .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) - .build() - ) - } + is KotlinxSerializableOutput, is OutputFile -> { + PropertySpec.builder(name, ClassName("org.gradle.api.file", "RegularFileProperty")) + .addAnnotation( + AnnotationSpec.builder(ClassName("org.gradle.api.tasks", "OutputFile")) + .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) + .build() + ) } + } - return builder.addModifiers(KModifier.ABSTRACT).build() + return builder.addModifiers(KModifier.ABSTRACT).build() } private fun TypeName.toGradleProperty(): TypeName { - return when { - this is ParameterizedTypeName && this.rawType == ClassName("kotlin.collections", "Map") -> { - ClassName("org.gradle.api.provider", "MapProperty").parameterizedBy(this.typeArguments) - } - - this is ParameterizedTypeName && this.rawType == ClassName("kotlin.collections", "List") -> { - ClassName("org.gradle.api.provider", "ListProperty").parameterizedBy(this.typeArguments) - } + return when { + this is ParameterizedTypeName && this.rawType == ClassName("kotlin.collections", "Map") -> { + ClassName("org.gradle.api.provider", "MapProperty").parameterizedBy(this.typeArguments) + } - this is ParameterizedTypeName && this.rawType == ClassName("kotlin.collections", "Set") -> { - ClassName("org.gradle.api.provider", "SetProperty").parameterizedBy(this.typeArguments) - } + this is ParameterizedTypeName && this.rawType == ClassName("kotlin.collections", "List") -> { + ClassName("org.gradle.api.provider", "ListProperty").parameterizedBy(this.typeArguments) + } - this is ClassName -> ClassName("org.gradle.api.provider", "Property").parameterizedBy(this) - else -> error("Gratatouille: cannot convert '$this' to a Gradle property") + this is ParameterizedTypeName && this.rawType == ClassName("kotlin.collections", "Set") -> { + ClassName("org.gradle.api.provider", "SetProperty").parameterizedBy(this.typeArguments) } + + this is ClassName -> ClassName("org.gradle.api.provider", "Property").parameterizedBy(this) + else -> error("Gratatouille: cannot convert '$this' to a Gradle property") + } } private fun TypeName.toGradleProvider(): TypeName { - return ClassName("org.gradle.api.provider", "Provider").parameterizedBy(this) + return ClassName("org.gradle.api.provider", "Provider").parameterizedBy(this) } private fun GTaskAction.workAction(): TypeSpec { - return TypeSpec.classBuilder(workActionClassName().simpleName) - .addModifiers(KModifier.PRIVATE, KModifier.ABSTRACT) - .addSuperinterface(ClassName("org.gradle.workers", "WorkAction").parameterizedBy(workParametersClassName())) - .addFunction(workActionExecute()) + return TypeSpec.classBuilder(workActionClassName().simpleName) + .addModifiers(KModifier.PRIVATE, KModifier.ABSTRACT) + .addSuperinterface(ClassName("org.gradle.workers", "WorkAction").parameterizedBy(workParametersClassName())) + .addFunction(workActionExecute()) - .build() + .build() } private fun GTaskAction.workActionExecute(): FunSpec { - return FunSpec.builder("execute") - .addModifiers(KModifier.OVERRIDE) - .addCode(CodeBlock.builder() - .add("with(parameters) {\n") - .indent() - .add("%T(\n", ClassName("java.net", "URLClassLoader")) - .indent() - .add("$classpath.map { it.toURI().toURL() }.toTypedArray(),\n") - .add("%T.getPlatformClassLoader()\n", ClassName("java.lang", "ClassLoader")) - .unindent() - .add(")") - .add(".loadClass(%S)\n", entryPointClassName().canonicalName) - .add(".declaredMethods.single()\n") - .add(".invoke(\n") - .indent() - .add("null,\n") - .apply { - (parameters + returnValues).forEach { - add("%L,\n", it.name) - } - } - .unindent() - .add(")\n") - .unindent() - .add("}\n") - .build()) - .build() + return FunSpec.builder("execute") + .addModifiers(KModifier.OVERRIDE) + .addCode( + buildCodeBlock { + add("with(parameters) {\n") + withIndent { + if (implementationCoordinates != null) { + add("%T(\n", ClassName("java.net", "URLClassLoader")) + withIndent { + add("$classpath.map { it.toURI().toURL() }.toTypedArray(),\n") + add("%T.getPlatformClassLoader()\n", ClassName("java.lang", "ClassLoader")) + } + add(")") + add(".loadClass(%S)\n", entryPointClassName().canonicalName) + add(".declaredMethods.single()\n") + add(".invoke(\n") + withIndent { + add("null,\n") + (parameters + returnValues).forEach { + add("%L,\n", it.name) + } + } + add(")\n") + } else { + add("%T.run(\n", entryPointClassName()) + withIndent { + (parameters + returnValues).forEach { + add("%L,\n", it.name) + } + } + add(")\n") + } + } + add("}\n") + }) + .build() } private fun GTaskAction.workParameters(): TypeSpec { - return TypeSpec.interfaceBuilder(workParametersClassName().simpleName) - .addModifiers(KModifier.PRIVATE) - .addSuperinterface(ClassName("org.gradle.workers", "WorkParameters")) - .apply { - addProperty( - PropertySpec.builder( - classpath, - ClassName("kotlin.collections", "Set") - .parameterizedBy(ClassName("java.io", "File")) - ).mutable(true).build() - ) - (parameters + returnValues).forEach { - addProperty(PropertySpec.builder(it.name, it.toTypeName()).mutable(true).build()) - } - } - .build() + return TypeSpec.interfaceBuilder(workParametersClassName().simpleName) + .addModifiers(KModifier.PRIVATE) + .addSuperinterface(ClassName("org.gradle.workers", "WorkParameters")) + .apply { + addProperty( + PropertySpec.builder( + classpath, + ClassName("kotlin.collections", "Set") + .parameterizedBy(ClassName("java.io", "File")) + ).mutable(true).build() + ) + (parameters + returnValues).forEach { + addProperty(PropertySpec.builder(it.name, it.toTypeName()).mutable(true).build()) + } + } + .build() } private fun GTaskAction.registerName(): String { - return "register" + this.functionName.capitalizeFirstLetter() + "Task" + return "register" + this.functionName.capitalizeFirstLetter() + "Task" } private fun GTaskAction.taskName(): String { - return this.functionName.decapitalizeFirstLetter() + return this.functionName.decapitalizeFirstLetter() } private fun GTaskAction.taskClassName(): ClassName { - val simpleName = this.functionName.capitalizeFirstLetter() + "Task" - return ClassName(this.packageName, simpleName) + val simpleName = this.functionName.capitalizeFirstLetter() + "Task" + return ClassName(this.packageName, simpleName) } internal fun GTaskAction.workParametersClassName(): ClassName { - val simpleName = this.functionName.capitalizeFirstLetter() + "WorkParameters" - return ClassName(this.packageName, simpleName) + val simpleName = this.functionName.capitalizeFirstLetter() + "WorkParameters" + return ClassName(this.packageName, simpleName) } private fun GTaskAction.workActionClassName(): ClassName { - val simpleName = this.functionName.capitalizeFirstLetter() + "WorkAction" - return ClassName(this.packageName, simpleName) + val simpleName = this.functionName.capitalizeFirstLetter() + "WorkAction" + return ClassName(this.packageName, simpleName) } diff --git a/gratatouille-processor/src/main/kotlin/gratatouille/processor/names.kt b/gratatouille-processor/src/main/kotlin/gratatouille/processor/names.kt index 3fc77bc..203d0f5 100644 --- a/gratatouille-processor/src/main/kotlin/gratatouille/processor/names.kt +++ b/gratatouille-processor/src/main/kotlin/gratatouille/processor/names.kt @@ -5,5 +5,4 @@ internal val taskDescription = "taskDescription" internal val taskGroup = "taskGroup" internal val outputFile = "outputFile" internal val classpath = "classpath" -internal val extraClasspath = "extraClasspath" internal val workerExecutor = "getWorkerExecutor" diff --git a/libs.versions.toml b/libs.versions.toml index 5c9d1b8..e9fa635 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -1,8 +1,9 @@ [versions] -ksp = "1.9.22-1.0.16" -kotlin = "1.9.22" -coroutines = "1.8.0-RC2" +ksp = "2.0.20-1.0.24" +kotlin = "2.0.20" +coroutines = "1.8.0" kotlinpoet = "1.14.2" +librarian = "0.0.6" [libraries] ksp-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } @@ -17,3 +18,8 @@ gradle-api = { group = "dev.gradleplugins", name = "gradle-api", version = "8.0" kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version = "1.6.2" } kotlinpoet = { group = "com.squareup", name = "kotlinpoet", version.ref = "kotlinpoet" } kotlinpoet-ksp = { group = "com.squareup", name = "kotlinpoet-ksp", version.ref = "kotlinpoet" } +librarian = { module = "com.gradleup.librarian:librarian-gradle-plugin", version.ref = "librarian" } +okio = "com.squareup.okio:okio:3.9.0" + +[plugins] +librarian = { id = "com.gradleup.librarian", version.ref = "librarian"} \ No newline at end of file diff --git a/sample-app/gradle/wrapper b/sample-app/gradle/wrapper new file mode 120000 index 0000000..3232fe4 --- /dev/null +++ b/sample-app/gradle/wrapper @@ -0,0 +1 @@ +../../gradle/wrapper \ No newline at end of file diff --git a/sample-app/gradlew b/sample-app/gradlew deleted file mode 100644 index 1aa94a4..0000000 --- a/sample-app/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/sample-app/gradlew b/sample-app/gradlew new file mode 120000 index 0000000..502f5a2 --- /dev/null +++ b/sample-app/gradlew @@ -0,0 +1 @@ +../gradlew \ No newline at end of file diff --git a/sample-app/gradlew.bat b/sample-app/gradlew.bat deleted file mode 100644 index 7101f8e..0000000 --- a/sample-app/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/sample-app/gradlew.bat b/sample-app/gradlew.bat new file mode 120000 index 0000000..2840132 --- /dev/null +++ b/sample-app/gradlew.bat @@ -0,0 +1 @@ +../gradlew.bat \ No newline at end of file diff --git a/sample-plugin/gradle-plugin/build.gradle.kts b/sample-plugin/gradle-plugin/build.gradle.kts index 9162d8e..d188378 100644 --- a/sample-plugin/gradle-plugin/build.gradle.kts +++ b/sample-plugin/gradle-plugin/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("org.jetbrains.kotlin.jvm") id("java-gradle-plugin") - id("com.gradleup.gratatouille.plugin") + id("com.gradleup.gratatouille.api") } version = rootProject.version diff --git a/test-app/.idea/codeStyles b/test-app/.idea/codeStyles new file mode 120000 index 0000000..70d0521 --- /dev/null +++ b/test-app/.idea/codeStyles @@ -0,0 +1 @@ +../../.idea/codeStyles \ No newline at end of file diff --git a/test-app/build.gradle.kts b/test-app/build.gradle.kts index e7cff27..bbe3260 100644 --- a/test-app/build.gradle.kts +++ b/test-app/build.gradle.kts @@ -1,3 +1,44 @@ +import kotlin.test.assertFailsWith + plugins { - id("testplugin") + id("testplugin") + id("testplugin.isolated") +} + +testExtension { + stringInput.set("input") + internalInput.set("internalInput") + //optionalInput.set() + + setInput.add(42) + listInput.add(43) + mapInput.put("key", "value") + + filesInput.from(fileTree("inputs/fileInputs")) + //optionalFileInput.set() + fileInput.set(file("inputs/fileInput")) + directoryInput.set(layout.projectDirectory.dir("inputs/directoryInput")) + serializableInput.set(file("inputs/serializableInput")) + + stringInput2.set("input2") + stringInput3.set("input3") + + fileOutput3.set(file("build/output")) +} + +testExtensionIsolated { + stringInput.set("world") +} + +tasks.register("build") { + dependsOn("taskAction2", "taskAction3", "taskActionIsolated") + + doLast { + check(file("build/gtask/taskActionIsolated/outputFile").readText() == "hello world - okio.Buffer") + + // Make sure we don't have okio in the classpath + assertFailsWith { + Class.forName("okio.Buffer") + } + } } \ No newline at end of file diff --git a/test-app/gradle/wrapper b/test-app/gradle/wrapper new file mode 120000 index 0000000..3232fe4 --- /dev/null +++ b/test-app/gradle/wrapper @@ -0,0 +1 @@ +../../gradle/wrapper \ No newline at end of file diff --git a/test-app/gradle/wrapper/gradle-wrapper.jar b/test-app/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index d64cd49..0000000 Binary files a/test-app/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/test-app/gradle/wrapper/gradle-wrapper.properties b/test-app/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index a80b22c..0000000 --- a/test-app/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/test-app/gradlew b/test-app/gradlew deleted file mode 100644 index 1aa94a4..0000000 --- a/test-app/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/test-app/gradlew b/test-app/gradlew new file mode 120000 index 0000000..502f5a2 --- /dev/null +++ b/test-app/gradlew @@ -0,0 +1 @@ +../gradlew \ No newline at end of file diff --git a/test-app/gradlew.bat b/test-app/gradlew.bat deleted file mode 100644 index 7101f8e..0000000 --- a/test-app/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/test-app/gradlew.bat b/test-app/gradlew.bat new file mode 120000 index 0000000..2840132 --- /dev/null +++ b/test-app/gradlew.bat @@ -0,0 +1 @@ +../gradlew.bat \ No newline at end of file diff --git a/test-app/settings.gradle.kts b/test-app/settings.gradle.kts index 3cfdf09..ed9b107 100644 --- a/test-app/settings.gradle.kts +++ b/test-app/settings.gradle.kts @@ -16,3 +16,4 @@ dependencyResolutionManagement { } includeBuild("../test-plugin") +includeBuild("../test-plugin-isolated") diff --git a/test-plugin-isolated/api/build.gradle.kts b/test-plugin-isolated/api/build.gradle.kts new file mode 100644 index 0000000..ff8d5de --- /dev/null +++ b/test-plugin-isolated/api/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("org.jetbrains.kotlin.jvm") + id("org.jetbrains.kotlin.plugin.serialization") + id("com.google.devtools.ksp") + id("com.gradleup.gratatouille.api") + id("java-gradle-plugin") +} + +dependencies { + gratatouille(project(":implementation")) +} + +gradlePlugin { + plugins { + create("testplugin.isolated") { + this.implementationClass = "testplugin.isolated.TestPluginIsolated" + this.id = "testplugin.isolated" + } + } +} \ No newline at end of file diff --git a/test-plugin-isolated/api/src/main/kotlin/testplugin/isolated/TestPluginIsolated.kt b/test-plugin-isolated/api/src/main/kotlin/testplugin/isolated/TestPluginIsolated.kt new file mode 100644 index 0000000..88343b6 --- /dev/null +++ b/test-plugin-isolated/api/src/main/kotlin/testplugin/isolated/TestPluginIsolated.kt @@ -0,0 +1,20 @@ +package testplugin.isolated + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.provider.Property +import testplugin.isolated.registerTaskActionIsolatedTask + +class TestPluginIsolated : Plugin { + override fun apply(target: Project) { + val extension = target.extensions.create("testExtensionIsolated", TestExtensionIsolated::class.java) + + val task1 = target.registerTaskActionIsolatedTask( + stringInput = extension.stringInput, + ) + } +} + +abstract class TestExtensionIsolated { + abstract val stringInput: Property +} \ No newline at end of file diff --git a/test-plugin-isolated/build.gradle.kts b/test-plugin-isolated/build.gradle.kts new file mode 100644 index 0000000..561988b --- /dev/null +++ b/test-plugin-isolated/build.gradle.kts @@ -0,0 +1,10 @@ +buildscript { + repositories { + mavenCentral() + gradlePluginPortal() + } + dependencies { + classpath("build-logic:build-logic") + classpath("com.gradleup.gratatouille:gratatouille-gradle-plugin") + } +} diff --git a/sample-app/gradle/wrapper/gradle-wrapper.jar b/test-plugin-isolated/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from sample-app/gradle/wrapper/gradle-wrapper.jar rename to test-plugin-isolated/gradle/wrapper/gradle-wrapper.jar diff --git a/sample-app/gradle/wrapper/gradle-wrapper.properties b/test-plugin-isolated/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from sample-app/gradle/wrapper/gradle-wrapper.properties rename to test-plugin-isolated/gradle/wrapper/gradle-wrapper.properties diff --git a/test-plugin-isolated/gradlew b/test-plugin-isolated/gradlew new file mode 100644 index 0000000..1aa94a4 --- /dev/null +++ b/test-plugin-isolated/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/test-plugin-isolated/gradlew.bat b/test-plugin-isolated/gradlew.bat new file mode 100644 index 0000000..7101f8e --- /dev/null +++ b/test-plugin-isolated/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/test-plugin/implementation/build.gradle.kts b/test-plugin-isolated/implementation/build.gradle.kts similarity index 78% rename from test-plugin/implementation/build.gradle.kts rename to test-plugin-isolated/implementation/build.gradle.kts index da27000..f886b6b 100644 --- a/test-plugin/implementation/build.gradle.kts +++ b/test-plugin-isolated/implementation/build.gradle.kts @@ -6,6 +6,5 @@ plugins { } dependencies { - implementation(libs.kotlinx.serialization.json) -} - + implementation(libs.okio) +} \ No newline at end of file diff --git a/test-plugin-isolated/implementation/src/main/kotlin/testplugin/isolated/tasks.kt b/test-plugin-isolated/implementation/src/main/kotlin/testplugin/isolated/tasks.kt new file mode 100644 index 0000000..3a45976 --- /dev/null +++ b/test-plugin-isolated/implementation/src/main/kotlin/testplugin/isolated/tasks.kt @@ -0,0 +1,15 @@ +package testplugin.isolated + +import gratatouille.* +import okio.buffer +import okio.sink + +@GTaskAction +internal fun taskActionIsolated( + stringInput: String, + outputFile: GOutputFile +) { + outputFile.sink().buffer().use { + it.writeUtf8("hello $stringInput - ${Class.forName("okio.Buffer").name}") + } +} diff --git a/test-plugin-isolated/settings.gradle.kts b/test-plugin-isolated/settings.gradle.kts new file mode 100644 index 0000000..4ac25dc --- /dev/null +++ b/test-plugin-isolated/settings.gradle.kts @@ -0,0 +1,21 @@ +pluginManagement { + listOf(repositories, dependencyResolutionManagement.repositories).forEach { + it.apply { + mavenCentral() + google() + gradlePluginPortal() + } + } +} + +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../libs.versions.toml")) + } + } +} + +includeBuild("..") +includeBuild("../build-logic") +include(":api", ":implementation") diff --git a/test-plugin/build.gradle.kts b/test-plugin/build.gradle.kts index d3f98ff..561988b 100644 --- a/test-plugin/build.gradle.kts +++ b/test-plugin/build.gradle.kts @@ -8,6 +8,3 @@ buildscript { classpath("com.gradleup.gratatouille:gratatouille-gradle-plugin") } } - - -group = "sample-plugin" \ No newline at end of file diff --git a/test-plugin/gradle-plugin/src/main/kotlin/testplugin/TestPlugin.kt b/test-plugin/gradle-plugin/src/main/kotlin/testplugin/TestPlugin.kt deleted file mode 100644 index 0092d73..0000000 --- a/test-plugin/gradle-plugin/src/main/kotlin/testplugin/TestPlugin.kt +++ /dev/null @@ -1,34 +0,0 @@ -package testplugin - -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.file.RegularFile -import org.gradle.api.file.RegularFileProperty - -class TestPlugin : Plugin { - override fun apply(target: Project) { - val task1 = target.registerTaskAction1Task( - stringInput = target.provider { "stringInput" }, - internalInput = target.provider { "internalInput" }, - optionalInput = target.objects.property(String::class.java), - setInput = target.provider { setOf(0) }, - listInput = target.provider { listOf(0) }, - mapInput = target.provider { mapOf("key" to "value") }, - filesInput = target.files("inputs/filesInput"), - optionalFileInput = target.objects.fileProperty(), - fileInput = target.provider { target.layout.projectDirectory.file("inputs/fileInput") }, - directoryInput = target.provider { target.layout.projectDirectory.dir("inputs/directoryInput") }, - serializableInput = target.provider { target.layout.projectDirectory.file("inputs/serializableInput") }, - ) - - target.registerTaskAction2Task( - input = target.provider { "input2" }, - myData = task1.flatMap { it.outputFile } - ) - - target.registerTaskAction3Task( - input = target.provider { "input3" }, - outputFile = target.layout.buildDirectory.file("output3") - ) - } -} \ No newline at end of file diff --git a/test-plugin/settings.gradle.kts b/test-plugin/settings.gradle.kts index f9b830b..2a1d566 100644 --- a/test-plugin/settings.gradle.kts +++ b/test-plugin/settings.gradle.kts @@ -18,4 +18,4 @@ dependencyResolutionManagement { includeBuild("..") includeBuild("../build-logic") -include(":implementation", ":gradle-plugin") +include(":test-plugin") diff --git a/test-plugin/gradle-plugin/build.gradle.kts b/test-plugin/test-plugin/build.gradle.kts similarity index 67% rename from test-plugin/gradle-plugin/build.gradle.kts rename to test-plugin/test-plugin/build.gradle.kts index 8188d56..1613f66 100644 --- a/test-plugin/gradle-plugin/build.gradle.kts +++ b/test-plugin/test-plugin/build.gradle.kts @@ -1,13 +1,11 @@ plugins { id("org.jetbrains.kotlin.jvm") - id("com.gradleup.gratatouille.plugin") + id("org.jetbrains.kotlin.plugin.serialization") + id("com.google.devtools.ksp") + id("com.gradleup.gratatouille") id("java-gradle-plugin") } -dependencies { - gratatouille(project(":implementation")) -} - gradlePlugin { plugins { create("testplugin") { diff --git a/test-plugin/test-plugin/src/main/kotlin/testplugin/TestPlugin.kt b/test-plugin/test-plugin/src/main/kotlin/testplugin/TestPlugin.kt new file mode 100644 index 0000000..413c406 --- /dev/null +++ b/test-plugin/test-plugin/src/main/kotlin/testplugin/TestPlugin.kt @@ -0,0 +1,62 @@ +package testplugin + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty + +class TestPlugin : Plugin { + override fun apply(target: Project) { + val extension = target.extensions.create("testExtension", TestExtension::class.java) + + val task1 = target.registerTaskAction1Task( + stringInput = extension.stringInput, + internalInput = extension.internalInput, + optionalInput = extension.optionalInput, + setInput = extension.setInput, + listInput = extension.listInput, + mapInput = extension.mapInput, + filesInput = extension.filesInput, + optionalFileInput = extension.optionalFileInput, + fileInput = extension.fileInput, + directoryInput = extension.directoryInput, + serializableInput = extension.serializableInput, + ) + + target.registerTaskAction2Task( + input = extension.stringInput2, + myData = task1.flatMap { it.outputFile } + ) + + target.registerTaskAction3Task( + input = extension.stringInput3, + outputFile = extension.fileOutput3 + ) + } +} + +abstract class TestExtension { + abstract val stringInput: Property + abstract val internalInput: Property + abstract val optionalInput: Property + + abstract val setInput: SetProperty + abstract val listInput: ListProperty + abstract val mapInput: MapProperty + + abstract val filesInput: ConfigurableFileCollection + abstract val optionalFileInput: RegularFileProperty + abstract val fileInput: RegularFileProperty + abstract val directoryInput: DirectoryProperty + abstract val serializableInput: RegularFileProperty + + abstract val stringInput2: Property + abstract val stringInput3: Property + + abstract val fileOutput3: RegularFileProperty +} \ No newline at end of file diff --git a/test-plugin/implementation/src/main/kotlin/testplugin/tasks.kt b/test-plugin/test-plugin/src/main/kotlin/testplugin/tasks.kt similarity index 100% rename from test-plugin/implementation/src/main/kotlin/testplugin/tasks.kt rename to test-plugin/test-plugin/src/main/kotlin/testplugin/tasks.kt